Sing

Личный сайт Go-разработчика из Казани

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.

1/* Multi- line comment. 2 /* It can be nested */ 3 Use it to remark-out part of the code. 4 It leaves no trace in the intermediate c++ code. 5 (sing translates into nice human readable c++) 6*/ 7 8// Single line comment, can be placed only before a statement or declaration... 9// ...or at the right of the first line of a statement or declaration. 10// single line comments are kept into c++. 11// 12// here we declare if we need to use public declarations from other files. 13// (in this case from files 'sio', 'sys') 14requires "sio"; 15requires "sys"; 16 17// 18// A sing function declaration. 19// All the declarations can be made public with the 'public' keyword. 20// All the declarations start with a keyword specifying the type of declaration 21// (in this case fn for function) then follows the name, the arguments and the 22// return type. 23// 24// Each argument starts with a direction qualifyer (in, out, io) which tells if 25// the argument is an input, an output or both... 26// ...then follows the argument name and the type. 27public fn singmain(in argv [*]string) i32 28{ 29 // print is from the sio file and sends a string to the console 30 sio.print("Hello World\n"); 31 32 // type conversions are allowed in the form of <newtype>(expression). 33 sio.print(string(sum(5, 10)) + "\n"); 34 35 // For clarity you can specify after an argument its name separated by ':'. 36 var result i32; 37 recursive_power(10:base, 3:exponent, result); 38 39 // referred here to avoid a 'not used' error. 40 learnTypes(); 41 42 // functions can only return a single value of some basic type. 43 return(0); 44} 45 46// You can have as many arguments as you want, comma separated. 47// You can also omit the 'in' direction qualifyer (it is the default). 48fn sum(arg1 i32, arg2 i32) i32 49{ 50 // as 'fn' declares a function, 'let' declares a constant. 51 // With constants, if you place an initializer, you can omit the type. 52 let the_sum = arg1 + arg2; 53 54 return(the_sum); 55} 56 57// Arguments are passed by reference, which means that in the function body you 58// use the argument names to refer to the passed variables. 59// Example: all the functions in the recursion stack access the same 'result' 60// variable, supplied by the singmain function. 61fn recursive_power(base i32, exponent i32, out result i32) void 62{ 63 if (exponent == 0) { 64 result = 1; 65 } else { 66 recursive_power(base, exponent - 1, result); 67 result *= base; 68 } 69} 70 71//********************************************************** 72// 73// TYPES 74// 75//********************************************************** 76fn learnTypes() void 77{ 78 // the var keyword declares mutable variables 79 // in this case an UTF-8 encoded string 80 var my_name string; 81 82 // ints of 8..64 bits size 83 var int0 i8; 84 var int1 i16; 85 var int2 i32; 86 var int3 i64; 87 88 // uints 89 var uint0 u8; 90 var uint1 u16; 91 var uint2 u32; 92 var uint3 u64; 93 94 // floats 95 var float0 f32; 96 var float1 f64; 97 98 // complex 99 var cmplx0 c64; 100 var cmplx1 c128; 101 102 cmplx0 = 0; 103 cmplx1 = 0; 104 105 // and of course... 106 var bool0 bool; 107 108 // type inference: by default constants are i32, f32, c64 109 let an_int32 = 15; 110 let a_float32 = 15.0; 111 let a_complex = 15.0 + 3i; 112 let a_string = "Hello !"; 113 let a_bool = false; 114 115 // To create constant of different types use a conversion-like syntax: 116 // NOTE: this is NOT a conversion. Just a type specification 117 let a_float64 = f64(5.6); 118 119 // in a type definition [] reads as "array of" 120 // in the example []i32 => array of i32. 121 var intarray []i32 = {1, 2, 3}; 122 123 // You can specify a length, else the length is given by the initializer 124 // the last initializer is replicated on the extra items 125 var sizedarray [10]i32 = {1, 2, 3}; 126 127 // Specify * as the size to get a dynamic array (can change its length) 128 var dyna_array [*]i32; 129 130 // you can append items to a vector invoking a method-like function on it. 131 dyna_array.push_back(an_int32); 132 133 // getting the size of the array. sys.validate() is like assert in c 134 sys.validate(dyna_array.size() == 1); 135 136 // a map that associates a number to a string. 137 // "map(x)..." reads "map with key of type x and value of type..." 138 var a_map map(string)i32; 139 140 a_map.insert("one", 1); 141 a_map.insert("two", 2); 142 a_map.insert("three", 3); 143 let key = "two"; 144 145 // note: the second argument of get_safe is the value to be returned 146 // when the key is not found. 147 sio.print("\nAnd the value is...: " + string(a_map.get_safe(key, -1))); 148 149 // string concatenation 150 my_name = "a" + "b"; 151} 152 153// an enum type can only have a value from a discrete set. 154// can't be converted to/from int ! 155enum Stages {first, second, last} 156 157// you can refer to enum values (to assign/compare them) 158// specifying both the typename and tagname separated with the '.' operator 159var current_stage = Stages.first; 160 161 162//********************************************************** 163// 164// POINTERS 165// 166//********************************************************** 167 168// This is a factory for a dynamic vector. 169// In a type declaration '*' reads 'pointer to..' 170// so the return type is 'pointer to a vector of i32' 171fn vectorFactory(first i32, last i32) *[*]i32 172{ 173 var buffer [*]i32; 174 175 // fill 176 for (value in first : last) { 177 buffer.push_back(value); 178 } 179 180 // The & operator returns the address of the buffer. 181 // You can only use & on local variables 182 // As you use & on a variable, that variable is allocated on the HEAP. 183 return(&buffer); 184} 185 186fn usePointers() void 187{ 188 var bufferptr = vectorFactory(0, 100); 189 190 // you don't need to use the factory pattern to use pointers. 191 var another_buffer [*]i32; 192 var another_bufferptr = &another_buffer; 193 194 // you can dereference a pointer with the * operator 195 // sys.validate is an assertion (causes a signal if the argument is false) 196 sys.validate((*bufferptr)[0] == 0); 197 198 /* 199 // as all the pointers to a variable exit their scope the variable is 200 // no more accessible and is deleted (freed) 201 */ 202} 203 204//********************************************************** 205// 206// CLASSES 207// 208//********************************************************** 209 210// This is a Class. The member variables can be directly initialized here 211class AClass { 212public: 213 var public_var = 100; // same as any other variable declaration 214 fn is_ready() bool; // same as any other function declaration 215 fn mut finalize() void; // destructor (called on object deletion) 216private: 217 var private_var string; 218 219 // Changes the member variables and must be marked as 'mut' (mutable) 220 fn mut private_fun(errmsg string) void; 221} 222 223// How to declare a member function 224fn AClass.is_ready() bool 225{ 226 // inside a member function, members can be accessed through the 227 // 'this' keyword and the field selector '.' 228 return(this.public_var > 10); 229} 230 231fn AClass.private_fun(errmsg string) void 232{ 233 this.private_var = errmsg; 234} 235 236// using a class 237fn useAClass() void 238{ 239 // in this way you create a variable of type AClass. 240 var instance AClass; 241 242 // then you can access its members through the '.' operator. 243 if (instance.is_ready()) { 244 instance.public_var = 0; 245 } 246} 247 248//********************************************************** 249// 250// INTERFACES 251// 252//********************************************************** 253 254// You can use polymorphism in sing defining an interface... 255interface ExampleInterface { 256 fn mut eraseAll() void; 257 fn identify_myself() void; 258} 259 260// and then creating classes which implement the interface 261// NOTE: you don't need (and cannot) re-declare the interface functions 262class Implementer1 : ExampleInterface { 263private: 264 var to_be_erased i32 = 3; 265public: 266 var only_on_impl1 = 0; 267} 268 269class Implementer2 : ExampleInterface { 270private: 271 var to_be_erased f32 = 3; 272} 273 274fn Implementer1.eraseAll() void 275{ 276 this.to_be_erased = 0; 277} 278 279fn Implementer1.identify_myself() void 280{ 281 sio.print("\nI'm the terrible int eraser !!\n"); 282} 283 284fn Implementer2.eraseAll() void 285{ 286 this.to_be_erased = 0; 287} 288 289fn Implementer2.identify_myself() void 290{ 291 sio.print("\nI'm the terrible float eraser !!\n"); 292} 293 294fn interface_casting() i32 295{ 296 // upcasting is automatic (es: *Implementer1 to *ExampleInterface) 297 var concrete Implementer1; 298 var if_ptr *ExampleInterface = &concrete; 299 300 // you can access interface members with (guess what ?) '.' 301 if_ptr.identify_myself(); 302 303 // downcasting requires a special construct 304 // (see also below the conditional structures) 305 typeswitch(ref = if_ptr) { 306 case *Implementer1: return(ref.only_on_impl1); 307 case *Implementer2: {} 308 default: return(0); 309 } 310 311 return(1); 312} 313 314// All the loop types 315fn loops() void 316{ 317 // while: the condition must be strictly of boolean type 318 var idx = 0; 319 while (idx < 10) { 320 ++idx; 321 } 322 323 // for in an integer range. The last value is excluded 324 // 'it' is local to the loop and must not be previously declared 325 for (it in 0 : 10) { 326 } 327 328 // reverse direction 329 for (it in 10 : 0) { 330 } 331 332 // configurable step. The loop stops when it's >= the final value 333 for (it in 0 : 100 step 3) { 334 } 335 336 // with an auxiliary counter. 337 // The counter start always at 0 and increments by one at each iteration 338 for (counter, it in 3450 : 100 step -22) { 339 } 340 341 // value assumes in turn all the values from array 342 var array [*]i32 = {0, 10, 100, 1000}; 343 for (value in array) { 344 } 345 346 // as before with auxiliary counter 347 for (counter, value in array) { 348 } 349} 350 351// All the conditional structures 352interface intface {} 353class c0_test : intface {public: fn c0stuff() void;} 354class delegating : intface {} 355 356fn conditionals(in object intface, in objptr *intface) void 357{ 358 let condition1 = true; 359 let condition2 = true; 360 let condition3 = true; 361 var value = 30; 362 363 // condition1 must be a boolean. 364 if (condition1) { 365 ++value; // conditioned statement 366 } 367 368 // you can chain conditions with else if 369 if (condition1) { 370 ++value; 371 } else if (condition2) { 372 --value; 373 } 374 375 // a final else runs if any other condition is false 376 if (condition1) { 377 ++value; 378 } else if (condition2) { 379 --value; 380 } else { 381 value = 0; 382 } 383 384 // based on the switch value selects a case statement 385 switch (value) { 386 case 0: sio.print("value is zero"); // a single statement ! 387 case 1: {} // do nothing 388 case 2: // falls through 389 case 3: sio.print("value is more than one"); 390 case 4: { // a block is a single statement ! 391 value = 0; 392 sio.print("how big !!"); 393 } 394 default: return; // if no one else matches 395 } 396 397 // similar to a switch but selects a case based on argument type. 398 // - object must be a function argument of type interface. 399 // - the case types must be classes implementing the object interface. 400 // - in each case statement, ref assumes the class type of that case. 401 typeswitch(ref = object) { 402 case c0_test: ref.c0stuff(); 403 case delegating: {} 404 default: return; 405 } 406 407 // - object must be an interface pointer. 408 // - the case types must be pointers to classes implementing the objptr interface. 409 // - in each case statement, ref assumes the class pointer type of that case. 410 typeswitch(ref = objptr) { 411 case *c0_test: { 412 ref.c0stuff(); 413 return; 414 } 415 case *delegating: {} 416 default: sio.print("unknown pointer type !!"); 417 } 418}

Further Reading

official Sing web site.

If you want to play with sing you are recommended to download the vscode plugin. Please follow the instructions at Getting Started