diff options
author | mdegirolami <mdegirolami@gmail.com> | 2021-11-02 13:26:19 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-02 13:26:19 +0100 |
commit | 74ccc905616e648b443f68fe1e96c5a64449290c (patch) | |
tree | 78001a90aa12d53eb63a2da3b46f31c9a32455c2 /sing.html.markdown | |
parent | 9552f2a1fee55280b3bdda18624058aa14c1a369 (diff) |
[sing/en] Added sing language (#4256)
* Added sing language
* Update sing.html.markdown
Co-authored-by: Andre Polykanine <ap@oire.me>
* Update sing.html.markdown
Co-authored-by: Andre Polykanine <ap@oire.me>
* Update sing.html.markdown
Co-authored-by: Andre Polykanine <ap@oire.me>
* Update sing.html.markdown
Co-authored-by: Andre Polykanine <ap@oire.me>
* Update sing.html.markdown
Co-authored-by: Andre Polykanine <ap@oire.me>
* Update sing.html.markdown
Co-authored-by: Andre Polykanine <ap@oire.me>
* Update sing.html.markdown
Co-authored-by: Andre Polykanine <ap@oire.me>
* Update sing.html.markdown
Co-authored-by: Andre Polykanine <ap@oire.me>
* Update sing.html.markdown
Co-authored-by: Andre Polykanine <ap@oire.me>
* Update sing.html.markdown
Co-authored-by: Andre Polykanine <ap@oire.me>
* Fixed a comment
Co-authored-by: De Girolami <maurizio.degirolami@datalogic.com>
Co-authored-by: Andre Polykanine <ap@oire.me>
Diffstat (limited to 'sing.html.markdown')
-rw-r--r-- | sing.html.markdown | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/sing.html.markdown b/sing.html.markdown new file mode 100644 index 00000000..2e439800 --- /dev/null +++ b/sing.html.markdown @@ -0,0 +1,446 @@ +--- +name: Sing +category: language +language: Sing +filename: learnsing.sing +contributors: + - ["Maurizio De Girolami", "https://github.com/mdegirolami"] +--- + +The purpose of sing is to provide a simple, safe, fast language that +can be a good replacement for c++ for high performance applications. + +Sing is an easy choice because it compiles to human-quality readable c++. + +Because of that, if you work for a while with Sing and, at any time, you discover you don't like Sing anymore, you lose nothing of your work +because you are left with nice and clean c++ code. + +In some way you can also think Sing as a tool to write c++ in a way that enforces some best practices. + +```go +/* Multi- line comment. + /* It can be nested */ + Use it to remark-out part of the code. + It leaves no trace in the intermediate c++ code. + (sing translates into nice human readable c++) +*/ + +// Single line comment, can be placed only before a statement or declaration... +// ...or at the right of the first line of a statement or declaration. +// single line comments are kept into c++. +// +// here we declare if we need to use public declarations from other files. +// (in this case from files 'sio', 'sys') +requires "sio"; +requires "sys"; + +// +// A sing function declaration. +// All the declarations can be made public with the 'public' keyword. +// All the declarations start with a keyword specifying the type of declaration +// (in this case fn for function) then follows the name, the arguments and the +// return type. +// +// Each argument starts with a direction qualifyer (in, out, io) which tells if +// the argument is an input, an output or both... +// ...then follows the argument name and the type. +public fn singmain(in argv [*]string) i32 +{ + // print is from the sio file and sends a string to the console + sio.print("Hello World\n"); + + // type conversions are allowed in the form of <newtype>(expression). + sio.print(string(sum(5, 10)) + "\n"); + + // For clarity you can specify after an argument its name separated by ':'. + var result i32; + recursive_power(10:base, 3:exponent, result); + + // referred here to avoid a 'not used' error. + learnTypes(); + + // functions can only return a single value of some basic type. + return(0); +} + +// You can have as many arguments as you want, comma separated. +// You can also omit the 'in' direction qualifyer (it is the default). +fn sum(arg1 i32, arg2 i32) i32 +{ + // as 'fn' declares a function, 'let' declares a constant. + // With constants, if you place an initializer, you can omit the type. + let the_sum = arg1 + arg2; + + return(the_sum); +} + +// Arguments are passed by reference, which means that in the function body you +// use the argument names to refer to the passed variables. +// Example: all the functions in the recursion stack access the same 'result' +// variable, supplied by the singmain function. +fn recursive_power(base i32, exponent i32, out result i32) void +{ + if (exponent == 0) { + result = 1; + } else { + recursive_power(base, exponent - 1, result); + result *= base; + } +} + +//********************************************************** +// +// TYPES +// +//********************************************************** +fn learnTypes() void +{ + // the var keyword declares mutable variables + // in this case an UTF-8 encoded string + var my_name string; + + // ints of 8..64 bits size + var int0 i8; + var int1 i16; + var int2 i32; + var int3 i64; + + // uints + var uint0 u8; + var uint1 u16; + var uint2 u32; + var uint3 u64; + + // floats + var float0 f32; + var float1 f64; + + // complex + var cmplx0 c64; + var cmplx1 c128; + + cmplx0 = 0; + cmplx1 = 0; + + // and of course... + var bool0 bool; + + // type inference: by default constants are i32, f32, c64 + let an_int32 = 15; + let a_float32 = 15.0; + let a_complex = 15.0 + 3i; + let a_string = "Hello !"; + let a_bool = false; + + // To create constant of different types use a conversion-like syntax: + // NOTE: this is NOT a conversion. Just a type specification + let a_float64 = f64(5.6); + + // in a type definition [] reads as "array of" + // in the example []i32 => array of i32. + var intarray []i32 = {1, 2, 3}; + + // You can specify a length, else the length is given by the initializer + // the last initializer is replicated on the extra items + var sizedarray [10]i32 = {1, 2, 3}; + + // Specify * as the size to get a dynamic array (can change its length) + var dyna_array [*]i32; + + // you can append items to a vector invoking a method-like function on it. + dyna_array.push_back(an_int32); + + // getting the size of the array. sys.validate() is like assert in c + sys.validate(dyna_array.size() == 1); + + // a map that associates a number to a string. + // "map(x)..." reads "map with key of type x and vaue of type..." + var a_map map(string)i32; + + a_map.insert("one", 1); + a_map.insert("two", 2); + a_map.insert("three", 3); + let key = "two"; + + // note: the second argument of get_safe is the value to be returned + // when the key is not found. + sio.print("\nAnd the value is...: " + string(a_map.get_safe(key, -1))); + + // string concatenation + my_name = "a" + "b"; +} + +// an enum type can only have a value from a discrete set. +// can't be converted to/from int ! +enum Stages {first, second, last} + +// you can refer to enum values (to assign/compare them) +// specifying both the typename and tagname separated with the '.' operator +var current_stage = Stages.first; + + +//********************************************************** +// +// POINTERS +// +//********************************************************** + +// This is a factory for a dynamic vector. +// In a type declaration '*' reads 'pointer to..' +// so the return type is 'pointer to a vector of i32' +fn vectorFactory(first i32, last i32) *[*]i32 +{ + var buffer [*]i32; + + // fill + for (value in first : last) { + buffer.push_back(value); + } + + // The & operator returns the address of the buffer. + // You can only use & on local variables + // As you use & on a variable, that variable is allocated on the HEAP. + return(&buffer); +} + +fn usePointers() void +{ + var bufferptr = vectorFactory(0, 100); + + // you don't need to use the factory pattern to use pointers. + var another_buffer [*]i32; + var another_bufferptr = &another_buffer; + + // you can dereference a pointer with the * operator + // sys.validate is an assertion (causes a signal if the argument is false) + sys.validate((*bufferptr)[0] == 0); + + /* + // as all the pointers to a variable exit their scope the variable is + // no more accessible and is deleted (freed) + */ +} + +//********************************************************** +// +// CLASSES +// +//********************************************************** + +// This is a Class. The member variables can be directly initialized here +class AClass { +public: + var public_var = 100; // same as any other variable declaration + fn is_ready() bool; // same as any other function declaration + fn mut finalize() void; // destructor (called on object deletion) +private: + var private_var string; + + // Changes the member variables and must be marked as 'mut' (mutable) + fn mut private_fun(errmsg string) void; +} + +// How to declare a member function +fn AClass.is_ready() bool +{ + // inside a member function, members can be accessed thrugh the + // this keyword and the field selector '.' + return(this.public_var > 10); +} + +fn AClass.private_fun(errmsg string) void +{ + this.private_var = errmsg; +} + +// using a class +fn useAClass() void +{ + // in this way you create a variable of type AClass. + var instance AClass; + + // then you can access its members through the '.' operator. + if (instance.is_ready()) { + instance.public_var = 0; + } +} + +//********************************************************** +// +// INTERFACES +// +//********************************************************** + +// You can use polymorphism in sing defining an interface... +interface ExampleInterface { + fn mut eraseAll() void; + fn identify_myself() void; +} + +// and then creating classes which implement the interface +// NOTE: you don't need (and cannot) re-declare the interface functions +class Implementer1 : ExampleInterface { +private: + var to_be_erased i32 = 3; +public: + var only_on_impl1 = 0; +} + +class Implementer2 : ExampleInterface { +private: + var to_be_erased f32 = 3; +} + +fn Implementer1.eraseAll() void +{ + this.to_be_erased = 0; +} + +fn Implementer1.identify_myself() void +{ + sio.print("\nI'm the terrible int eraser !!\n"); +} + +fn Implementer2.eraseAll() void +{ + this.to_be_erased = 0; +} + +fn Implementer2.identify_myself() void +{ + sio.print("\nI'm the terrible float eraser !!\n"); +} + +fn interface_casting() i32 +{ + // upcasting is automatic (es: *Implementer1 to *ExampleInterface) + var concrete Implementer1; + var if_ptr *ExampleInterface = &concrete; + + // you can access interface members with (guess what ?) '.' + if_ptr.identify_myself(); + + // downcasting requires a special construct + // (see also below the conditional structures) + typeswitch(ref = if_ptr) { + case *Implementer1: return(ref.only_on_impl1); + case *Implementer2: {} + default: return(0); + } + + return(1); +} + +// All the loop types +fn loops() void +{ + // while: the condition must be strictly of boolean type + var idx = 0; + while (idx < 10) { + ++idx; + } + + // for in an integer range. The last value is excluded + // 'it' is local to the loop and must not be previously declared + for (it in 0 : 10) { + } + + // reverse direction + for (it in 10 : 0) { + } + + // configurable step. The loop stops when it's >= the final value + for (it in 0 : 100 step 3) { + } + + // with an auxiliary counter. + // The counter start always at 0 and increments by one at each iteration + for (counter, it in 3450 : 100 step -22) { + } + + // value assumes in turn all the values from array + var array [*]i32 = {0, 10, 100, 1000}; + for (value in array) { + } + + // as before with auxiliary counter + for (counter, value in array) { + } +} + +// All the conditional structures +interface intface {} +class c0_test : intface {public: fn c0stuff() void;} +class delegating : intface {} + +fn conditionals(in object intface, in objptr *intface) void +{ + let condition1 = true; + let condition2 = true; + let condition3 = true; + var value = 30; + + // condition1 must be a boolean. + if (condition1) { + ++value; // conditioned statement + } + + // you can chain conditions with else if + if (condition1) { + ++value; + } else if (condition2) { + --value; + } + + // a final else runs if any other condition is false + if (condition1) { + ++value; + } else if (condition2) { + --value; + } else { + value = 0; + } + + // based on the switch value selects a case statement + switch (value) { + case 0: sio.print("value is zero"); // a single statement ! + case 1: {} // do nothing + case 2: // falls through + case 3: sio.print("value is more than one"); + case 4: { // a block is a single statement ! + value = 0; + sio.print("how big !!"); + } + default: return; // if no one else matches + } + + // similar to a switch but selects a case based on argument type. + // - object must be a function argument of type interface. + // - the case types must be classes implementing the object interface. + // - in each case statement, ref assumes the class type of that case. + typeswitch(ref = object) { + case c0_test: ref.c0stuff(); + case delegating: {} + default: return; + } + + // - object must be an interface pointer. + // - the case types must be pointers to classes implementing the objptr interface. + // - in each case statement, ref assumes the class pointer type of that case. + typeswitch(ref = objptr) { + case *c0_test: { + ref.c0stuff(); + return; + } + case *delegating: {} + default: sio.print("unknown pointer type !!"); + } +} +``` + +## Further Reading + +[official Sing web site](https://mdegirolami.wixsite.com/singlang). + +If you want to play with sing you are recommended to download the vscode plugin. Please +follow the instructions at [Getting Started](https://mdegirolami.wixsite.com/singlang/copy-of-interfacing-sing-and-c-2) |