-
Notifications
You must be signed in to change notification settings - Fork 3
5 The Language
CSpydr is free-from, meaning that spaces and comments are ignored, except in strings, in characters and as delimiters between tokens. Spaces are recognized as whitespaces, carriage returns and tabs. Only ASCII characters are accepted.
Identifiers consist of a series of alphanumeric characters or underscores, not starting with a digit. Next to that, CSpydr is case-sensitive. meaning that myid and myID are two different identifiers. When compiled with the C backend identifiers may not start with __csp_
.
CSpydr has a series of keywords, which cannot be used as identifiers
Keywords:
true false nil let fn
loop while for if else
ret match type struct union
enum import const extern macro
namespace sizeof typeof alignof break
continue noop len asm using
with
Symbols:
++ += + -- -= - *= *
%= % /= / &= && & ^
^= <<= >>= << >> || |= |>
| == => = != ! >= <=
<- < > ( ) { } [
] ~ , ; _ :: ... ²
³ $ `
Strings & Characters:
String literals are delimited by double quotes ("
) and can contain any amount of any character except non-escaped double quotes.
Character literals are delimited by single quotes ('
) and can only contain one single character, or an escape sequence. These escape sequences include the following:
Literal characters | Interpreted character |
---|---|
\a | Bell (alert) |
\b | Backspace |
\f | Form Feed |
\n | Newline |
\r | Carriage return |
\t | Horizontal Tab |
\v | Vertical Tab |
\' | Single Quote |
\" | Double Quote |
\\ | Backslash |
\0 | Null character |
Numbers:
An integer constant can be written as decimal, hexadecimal, binary or octal. Floating-point constants can only be written in decimal and must include a radix point. All number literals can contain as many underscores _
as you like for better visual parsing. Examples of valid numerical constants are the following:
99 1 1_000_000
0xff 0xabcdef 0x3d
0o77 0o7234 0o0
0b11 0b10010 0b11011
1.2 3.1415 1_234.567
Comments:
Comments can be either single-line or blocks. Single-line comments are denoted by a hashtag #
and include the rest of the line, while block comments are denoted by #[
and go on until ]#
.
Variables:
As most programming languages, CSpydr supports variables, which are named storage areas that can be manipulated. Each variable has its own type and can be assigned or referenced anywhere in the same or a deeper scope after its declaration. The name of the variable must be an identifier that is not also a type (see 5.1 - Lexical Conventions).
Variables can be declared with or without an initial value and type using the let
or const
keyword:
let foo = 0; # initialization with value and implicit type
let bar: i64; # initialization without value and explicit type
const baz = 3; # initialization using `const`, making the variable immutable
Arguments:
Arguments are a type of local variables defined in a function or lambda declaration used for passing values to them:
fn foo(bar: i32, baz: &char) {}
Members:
Members are named memory-offsets in structs and unions:
struct {
foo: i32, # offset 0 bytes
bar: f64, # offset 4 bytes
baz: &char # offset 12 bytes
}
Like almost any procedural language, CSpydr has statements. Statements are constructs used for defining control flow and -behaviour.
These are all statements in CSpydr:
Blocks group multiple statements together while also creating a new scope for variables. Like in C, they start by {
and end with }
{
# statements here
}
CSpydr has three loop types: loop
for an endless loop, while
for loops with a single exit condition and for
-loops for more complex loop behaviours.
loop
loop {
# statement gets repeated endlessly
}
while
while condition {
# statement gets repeated until `condition` is false (0)
}
for
For-loops consist of 3 parts: the initializer statement, condition- and counter-expression seperated by semicolons ;
:
for init; condition; counter; {
}
The init
-statement gets run once at the beginning, whereas counter
after every iteration.
The loop iterates, as long as condition
is true (not 0).
CSpydr offers the following control statements:
if match ret break continue using with asm
These statements can change the flow of programs, create new branches or merge them. Statements, that don't end in }
require a semicolon ;
after them.
if:
If statements execute a statement if a given condition is true (not 0). If the statement is false (0), the if statement may execute the statement defined after else
. Defining else
is optional, however.
if condition {
# if condition is not 0, execute this
}
else {
# if condition is 0, execute this (not necessary)
}
match:
Match statements work similar to C's switch
statement. They are used when the program branches into many directions when different conditions are met. In the match statement, you define different "cases", each with their own value to compare. _
is the default case and gets executed if no other case matched the condition.
match condition {
0 => # if condition is 0, execute this
1 => # if condition is 1, execute this
_ => # if none of these conditions match, execute this
}
ret:
The return-statement (ret
) exits and returns a value from the current function or lambda. It can be written in two different ways using ret
or <-
ret 0; # return 0
<- 1; # return 1
break:
The break-statement breaks out of the current loop.
break;
continue:
The continue-statement starts the next loop iteration immediately.
continue;
using:
The using-statement does not change control flow. Rather, it can pull functions and variables from a given namespace into the current scope, making function names more precise.
using std::io;
puts("..."); # we can now use puts() defined in std::io, rather than std::io::puts()
with:
The with-statement can be used in conjunction with IO types for easy error handling and memory management. It assigns a given value to a given value, checking if the value is not 0. Finally, it executes a predefined function for freeing memory or similar. The exit-function for a given type can be set using the exit_fn
compiler directive: [exit_fn("your_exit_function": your_type)]
. Exactly as if-statements, the with-statement can be followed by else
, executed if the variable assignment was not successful.
with ptr = alloc(10) {
# do something with ptr, it will be automatically freed.
}
else {
# if memory allocation failed, execute this (not necessary)
}
asm:
The asm-statement can inject inline x86_64-bit GNU Assembly. This statement is mostly used for low-level functions like system calls and can be highly unsafe. The body of the assembly statement consists of strings, but also can have integer literals or variable identifiers. The values/addresses of which will be automatically substituted.
asm "mov %rax, " foo;
Some expressions can be used like statements. The expressions include:
- function calls
- assignments
-
++
and--
- pipes
Local variables can be declared in any block statement using the let
or const
keyword. Variables defined with const
are immutable.
let x = 0;
let y: i32 = 0;
let z: &char;
2.1 - Compatibility
2.2 - Obtaining CSpydr
2.3 - Dependencies
2.4 - Building
2.5 - Installation
2.6 - Usage
4.1 - Types and Values
4.2 - Type Modifiers
4.3 - Truthiness
4.4 - Naming Conventions
5.1 - Lexical Conventions
5.2 - Variables, Members and Arguments
5.3 - Statements
5.3.1 - Blocks
5.3.2 - Loops
5.3.3 - Control Flow
5.3.4 - Expression Statements
5.3.5 - Variable Declarations
5.4 - Expressions
5.4.1 - Literals
5.4.2 - Operator Precedence
5.4.3 - Binary Operators
5.4.4 - Unary Operators
5.4.5 - Postfix Operators