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 ¶
If you want to play with sing you are recommended to download the vscode plugin. Please follow the instructions at Getting Started