Stingray is object-oriented, functional general purpose programming language supporting:
- Variables
- Simple Classes
- Type Ascription
- Recursion
- Arrays
Stingray source files have the extension of .stingray
or .sg
Stingray allows for some of the following basic types:
- Integer (i32)
- Double (f64)
- String (i8[])
- Nothing
Program consists of import statements and declarations:
Program ::= {Import} {Declaration}
Import ::= 'import' identifier {'.' identifier} 'as' identifier
Declaration ::= 'declare' identifier as TypeReference
We can compose 3 types of declarations in Stingray:
- Type alias
declare MyIntArray as Integer[]
- Class/Interface declaration
declare MyClass as class {...}
- Function declaration
declare MyFunc as Int x -> Int {...}
Any Type Reference has the following syntax:
Function Declaration has the folowing syntax:
Stingray allows for top-level function declarations with the following syntax rules:
FunctionDeclarationType ::= FunctionRangeDomain '->' FunctionRangeDomain
FunctionDefinitionType ::= NamedFunctionRange '->' FunctionRangeDomain Body
FunctionRangeDomain ::= TypeReference | '(' [TypeReference {',' TypeReference}] ')'
NamedFunctionRange ::= TypeReference identifier | '(' [TypeReference identifier {',' TypeReference identifier}] ')'
Class declarations in Stingray have the following syntax:
ClassType ::= 'class' [ExtendsList] [StaticsBody] "{" [ClassBody] "}"
ExtendsList ::= '<' [identifier] '>';
StaticsBody ::= '(' FieldDefinition {FieldDefinition} ')'
ClassBody ::= {FieldDefinition}
FieldDefinition ::= identifier [':' TypeReference] [Value]
Value ::= '<-' Expression | '{' Expression '}'
Body ::= '{' {Statement} '}'
Bodies of all the functions are sequences of statements:
Statement ::= identifier [':' TypeReference] [Value]
| IfStatement
| 'while' Expression Body
| 'for' identifier 'in' RangeExpr Body
| 'return' Expression
| FunctionCall
IfStatement ::= 'if' Expression Body ['else' (IfStatement | Body)]
FunctionCall ::= Term '(' [Expression {',' Expression}] ')'
LambdaExpr ::= '(' {identifier} ')' Body
MethodRef ::= identifier '::' identifier
Expression ::= Term [Binop Term] | LambdaExpr | MethodRef
ArrayRef ::= Term '[' Expression ']'
FunctionCall ::= Term '(' [Expression {',' Expression}] ')'
FieldRef ::= Term '.' identifier
Term ::= '(' Expression ')'
| ArrayRef
| FunctionCall
| FieldRef
| ('true' | 'false' | 'empty')
| (numeric | string | identifier)
| '[' [Expression {',' Expression}] ']'
| RangeExpr
RangeOpening ::= '[' | '('
RangeClosing ::= ']' | ')'
RangeExpr ::= RangeOpening Expression ';' Expression RangeClosing [':' Expression]
Unop ::= '#' | '+' | '-' | 'not'
Binop ::= '=' | '!=' | '<' | '>' | '<=' | '>=' | '+' | '-' | '*' | '/' | '%' | '^' | 'and' | 'xor' | 'or' | '=>' | '<=>' | 'is'
Range expressions examples are:
[0; 5] <=> [0, 1, 2, 3, 4, 5]
[0; 5) <=> [0, 1, 2, 3, 4]
(0; 5) <=> [1, 2, 3, 4]
(0; 5] <=> [1, 2, 3, 4, 5]
[0; 5]:2 <=> [0, 2, 4]
[0; 5):2 <=> [0, 2, 4]
(0; 5):2 <=> [1, 3]
(0; 5]:2 <=> [1, 3, 5]
Semantics for operators =>
and <=>
:
a => b
is equivalent tonot a or b
a <=> b
is equivalent toa and b or not a and not b
If arguments in arithmetic expression have different types, then one is converted to another: from smallest to largest. E. g. Integer -> Double
and then typing rules are applied.
Arithmetic:
- Any Integer literal has type
Integer
. Any Floating-Point literal has typeDouble
. - if
a:T, b:T
then(a + b): T
, whereT in {Integer, Double}
- if
a:T, b:T
then(a - b): T
, whereT in {Integer, Double}
- if
a:T, b:T
then(a * b): T
, whereT in {Integer, Double}
- if
a:T, b:T
then(a / b): T
, whereT in {Integer, Double}
- if
a:T, b:T
then(a % b): T
, whereT in {Integer}
- if
a:T, b:T
then(a ^ b): T
, whereT in {Integer, Double}
Relational:
- if
a:T, b:T
then(a < b): Boolean
, whereT in {Integer, Double}
- if
a:T, b:T
then(a <= b): Boolean
, whereT in {Integer, Double}
- if
a:T, b:T
then(a > b): Boolean
, whereT in {Integer, Double}
- if
a:T, b:T
then(a >= b): Boolean
, whereT in {Integer, Double}
- if
a:T, b:T
then(a = b): Boolean
, whereT in {Integer, Double}
- if
a:T, b:T
then(a != b): Boolean
, whereT in {Integer, Double}
If any of the arguments have numeric type - Char, Integer, Long, Float, Double
, then it is converted to Boolean
using the following rule:
a: Num => a' := (a != 0): Boolean
, where Num in {Char, Integer, Long, Float, Double}
.
true: Boolean, false: Boolean
- if
a:Boolean, b:Boolean
then(a and b): Boolean
- if
a:Boolean, b:Boolean
then(a or b): Boolean
- if
a:Boolean, b:Boolean
then(a xor b): Boolean
- if
a:Boolean, b:Boolean
then(a => b): Boolean
- if
a:Boolean, b:Boolean
then(a <=> b): Boolean
declare sayHello as Nothing -> Nothing
declare SimpleClass as class (
foo : Integer <- 13
bar : Double
) {
classField <- 12
foox: Integer n -> Integer {
return n + 1 + foox(n-1) + classField - foo
}
}
declare ComplexClass as class <SimpleClass> {
foox: Integer n -> Double {
return n - 2
}
}
declare fact as Integer n -> Integer {
if (n = 0) {
return 1
}
return n * fact(n-1)
}
declare main as String[] args -> Nothing {
let foo: sayHello
let x : Double {empty}
let y <- 12.32
x <- 43
let z {x = y}
let f <- (12 + x < 45 + y) and (12 < 13) or "abc" <= "abcd" and empty
let arr <- [12, true, 33.3]
let arrElem <- arr[5]
let simpleVariable <- SimpleClass()
simpleVariable.foox(arr.length)
let bar <- SimpleClass.bar
if (f) {
for i in [1; 5] {
x <- i + x
}
} else if (z) {
f <- true
let x <- "Hello"
} else {
let bar <- 1212
return
}
}
Here are some operator preferences, from lowes to greatest (first line - lowest precedence, last line - highest)
- =>, <=>
- or, xor
- and
- not
- =, !=, <, >, <=, >=
- +, -
- *, /, %
- ^
- #, -
- [], ., ()
- Flex >= 2.6.4
- Bison >= 3.8.2
- BNFC >= 2.9.4
- Build BNFC syntax with
bnfc --cpp -m stingray.cf -o syntax/
being incompiler/
directory. - Compile project using cmake
- Clear syntax directory by calling
make distclean
being incompiler/syntax/
directory. - Make sure to run
clang-format
- Work in your own separate branch