Language Syntax Reference

Standard Library

Links

Gentee script programming language

Introduction

Gentee is a strongly typed procedural language. First of all, it is designed to write scripts to automate repetitive actions and processes on the computer. The Gentee language has a simple syntax and it is easy to learn and maintain.

Source and binary GitHub repository: https://github.com/gentee/gentee
Documentation GitHub repository: https://github.com/gentee/gentee.github.io
Development language: Go

Lexical elements

Source code is UTF-8 encoded. The syntax is specified using Extended Backus-Naur Form.

newline        = 0x0A
unicode_char = /* Unicode code point */
unicode_linechar  = /* Unicode code point except newline */
unicode_letter = /* a Unicode code point classified as "Letter" */
letter        = unicode_letter | "_"
decimal_digit = "0" … "9" 
octal_digit   = "0" … "7" 
hex_digit     = "0" … "9" | "A" … "F" | "a" … "f" 
decimals  = decimal_digit { decimal_digit }
exponent  = ( "e" | "E" ) [ "+" | "-" ] decimals

Comments and character substitution

There are the following types of comments and automatically substitution characters.

// Single-line comment is here
This single-line comment starts with the character sequence // and stops at the end of the line.

/* General comment is here */
General comments can appear anywhere. Such comment begins with a forward slash/asterisk combination /* and is terminated by “end comment” delimiter */.

# header
At the beginning of the script, you can specify the parameters for use in other programs. Such comments should must be listed one after the other in each line from the beginning of the script. If you do not want to specify ‘#’ at the beginning of each line, then insert ### before and after the text.

#!/usr/local/bin/gentee
# the first line can be used to run the script on Linux.
###
  desc = Description of the script
  result = ok
  var = value
###

;
The new line character is the separating character between expressions and statements. A semicolon is replaced with a new line character. So, you can use semicolons if you want to put several statements on one line.

:
A colon is replaced with an opening curly brace and a closing curly brace is added at the end of the current line.

// These examples are equivalent
if a == 10 : a = b + c; c = d + e 

if a == 10 
{
   a = b + c
   c = d + e
}

Identifiers

Identifiers are names that are used to refer to variables, types, functions, constants etc. An identifier is a sequence of letters and digits. The first character of the identifier must be a letter.

identifier = letter { letter | unicode_digit }
IdentifierList = identifier { identifier }

There are some predeclared identifiers and keywords. The following words are reserved and may not be used as identifiers.

Keywords

const elif else false for func go if in return run struct true while

Predeclared stdlib types

arr bool buf char error finfo float int map range str time trace thread

Literals

An integer literal is a sequence of digits representing an integer constant.

decimal = ( "1" … "9" ) { decimal_digit } 
octal = "0" { octal_digit } .
hex = "0" ( "x" | "X" ) hex_digit { hex_digit } 
integer = decimal | octal | hex
float = decimals "." [ decimals ] [ exponent ] | decimals exponent
0x34Fab
0722
19023862
0.123e+3
234.e-2
9.7732E-1
0.0177E+2
5e-2

A char literal represents an integer value identifying a Unicode code point. You can specify one character or a sequence of characters beginning with a backslash enclosed in single quotes. Multi-character sequences can be like these:

'\r',  '\n',  '\t', '\"', '\'', '\\' 
\xa5 \x2B  (\x + two hex_digit)
\u03B1  (\u + four hex_digit)
\0371  (\0 + three octal_digit)
byteVal  = octalStr | hexStr .
octalStr = `\` "0" octal_digit octal_digit octal_digit .
hexStr   = `\` "x" hex_digit hex_digit .
uShort   = `\` "u" hex_digit hex_digit hex_digit hex_digit .
uLong    = `\` "U" hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit hex_digit .
escapedChar     = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | `"` ) 
charLit         = "'" ( unicode_char | uShort | uLong | escapedChar | byteVal | `\'`) "'" .

There are two types of string literals.

  1. A string in backquotes can contain any characters. If you want to specify a backquote, then you need to double it.
  2. A double-quoted string can also contain any characters (including a line break), but it has a backslash control character. You can specify the following characters after a backslash:
    \a   U+0007 alert or bell  
    \b   U+0008 backspace  
    \f   U+000C form feed  
    \n   U+000A newline  
    \r   U+000D carriage return  
    \t   U+0009 horizontal tab  
    \v   U+000b vertical tab  
    \\   U+005c backslash  
    \"   U+0022 double quote  
    
stringLit         = stringBackQuote | stringDoubleQuote
stringBackQuote   = "`" { unicode_char | "%{" Expression "}" | "${" identifier "}" } "`"
stringDoubleQuote = `"` { unicode_char | uShort | uLong | escapedChar | byteVal | "\{" Expression "}" } `"`

You can insert expressions into any type of string. In this case, the result type of expression can be any, if there is a corresponding function that can convert this value to a string. Expressions must be enclosed in curly brackets with the preceding % sign (for backquotes) or a backslash (for double quotes).

`10+20 equals %{10 + 20}. User name is "%{USERNAME}"`
"This is the first line.\r\nThis is \{ `the` + `second`} line."

Types

A type determines a set of values that have the same operations and functions specific for those values. A type is denoted by a type name. A map is a group of elements of one type, indexed by a set of unique string keys. By default, arr and map arrays consist of strings, but you can specify any nested types by separating them with a dot. Note that variables of types arr, map, buf and types that has been defined with struct, unlike other types, are passed by reference, not by value. This means that if you change the value of this parameter inside the function, you will change the original variable.

TypeName  = identifier { "." identifier }

The Gentee language predeclares the following types.

Name Description Values Initial value
int 64-bit integer type -9223372036854775808 .. 9223372036854775807 0
float 64-bit double 2.22 E–308 .. 1.79 E+308 0.0
bool boolean type true or false false
str string a sequence of bytes empty string
char Unicode character Unicode code point int32 space
arr array array of elements empty array of strings
map associative array associative array of elements empty associative array of strings
buf array of bytes array of uint8 empty array
arr.map.int a
map.arr.str b   // the same as map.arr b
map.bool c
arr.int  d

Type casting

There is no automatic type casting in Gentee . For basic types, there are functions for converting from one type to another, their names are the same as the name of the resulting type.

  int bool str char float
int   int(false) int(“-23”) int(‘A’) int(3.24)
bool bool(1)   bool(“0”)   bool(1.1)
str str(20) str(false)   str(‘z’) str(5.662)
char          
float float(10)   float(“-2E-34”)    
int(false) // = 0           
int(true) // = 1    
bool(0) // = false  
bool(0.) // = false  
bool(integer except zero) // = true    
bool("")  bool("0") bool("false") //=false
bool("not empty, zero or false string")   //=true

Type definition

You can define a structure type by using the struct keyword. Specify a type name after the keyword, and list the field types and names inside the curly braces. All fields in a variable of a structured type are automatically initialized. All variables of these types are passed by reference, rather than by value, when passed to functions. To assign or get a field value, specify its name after the dot.

structDecl = "struct" identifier "{" FieldDecl { newline  FieldDecl } "}"
FieldDecl = TypeName identifier
FieldExpr = PrimaryExpr "." identifier
struct my : int ID; str name
struct myStruct {
      int ID
      my myval
      arr st_arr
      map.int st_map
}
run int {
    myStruct ms
    ms.ID = 20
    return ms.ID * 2
}

Function type

The Gentee language allows you to work with function identifiers. You can get the ID of the function, pass it as a parameter and call the corresponding function. To work with function identifiers, you must define a function type using the fn keyword and specify the types of parameters and return value. To get the function ID, specify &function_name.fn_type. The function identifier can be passed in parameters or assigned to a variable of the corresponding fn type. To call a function by its identifier, it is sufficient to specify the variable name and parentheses with parameters, as when calling a function by name.

fnDecl = "fn" FnName [FnParameters] [ TypeName ]
FnName = identifier
FnParameters     = "(" [ FnParameterList ] ")"
FnParameterList  = TypeName { [","] TypeName }
FnIdent = "&" FuncName "." FnName
fn bin( int int ) int
func add( int i, int j ) int : return i + j
func sub( int i, int j ) int : return i - j
func mybin(int i j, bin f ) int : return j + f(i, j)

run int {
  bin isub = &sub.bin
  return mybin(1, 2, &add.bin) + mybin(3, 7, isub)
}

Declarations

Constant declarations

The constant name can not contain lowercase letters. Constants can be assigned to any expressions. The value of the constant is computed when the constant is first accessed, but the type of the constant is automatically determined at the compilation stage by the type of the assigned expression. Therefore, despite the fact that the type is not specified when defining a constant, type checking is in effect when a constant is used.

ConstDecl      = "const" ( ConstIota | ConstExp )
ConstIota = Expression "{" { IdentifierList newline } "}"
ConstExp = "{" { identifier "=" Expression newline } "}"

Constants can be defined in two ways.

  1. Specifying an initial value or an expression for each constant.
    const {
     MY_ID = 1
     MY_VAL = myFunc( MY_ID + 23)
     CHECK= MY_VAL < 32
    }
    
  2. Using a common expression with IOTA. Sometimes it is necessary to define a list of constants with values that are computed according to certain rules. In this case, after the const keyword, you need to specify one common expression that will be calculated for each constant in this definition. In this expression, you can use the special variable IOTA, which is equal to the ordinal index of the constant in the list, starting at zero. Constants should be separated by a space or a new line.
const 0x1 << IOTA {
      FIRST SECOND   // 0x1    0x2
      THIRD                   // 0x4
}
const (IOTA * 2) + 1 {
      MY1    // 1
      MY2   // 3
      MY3   // 5
}

Function declarations

The function declaration consists of two parts - a description of the parameters with the return type and the function body. When you define a function, you must specify the keyword “func”, the function name, the parameters to be passed, and the type of the return value. Only the function name is required.

FunctionDecl   = "func" FunctionName [Parameters] [ Result ] Block
FunctionName   = identifier 
Result         = TypeName 
Parameters     = "(" [ ParameterList ] ["..."] ")"
ParameterList  = VarList { "," VarList }
func VariadicExample(int i, int s...) int {
    int sum = i*2
    for v in s {
       sum += v
    }
    return sum
}
func MyFunc(int par1 par2) int { 
    int par3 = VariadicExample(3, par1, par2, 4, 5, par1+par2)
    return (par1+par2 +par3)/3 
}

The final incoming parameter in a function signature may have a ‘…’ suffix. A function with such a parameter is called variadic function and may be invoked with zero or more arguments for that parameter. You get this parameter as an array of arguments. For example, int pars… means that pars is really arr.int and you can get the i-th argument with pars[i].

Run declaration

The Gentee script must contain a special function without parameters, which is defined using the keyword “run”. The script starts by calling this function. The script must have only one definition of “run”.

RunDecl = "run" [FunctionName] [ Result ] Block
run int {
    int i ret
    while i < 10 {
       ret += myFunc(i++)
    }
    return ret
}

Statements

A block is a sequence of declarations and statements within curly braces. Blocks can be nested into each other.

Block = "{" StatementList "}" .
StatementList = { Statement newline } .
Statement = ReturnStmt | IfStmt | Expression | WhileStmt | VarDeclaration | ForStmt | 
            BreakStmt | ContinueStmt | SwitchStmt | GoStmt

Variable declarations

Any function variable must be described before it can be used. A variable declaration creates one or more variables and assign to each an initial value. A variable can be defined in any block. The scope of the variable extends to the block in which it is defined and to all nested blocks. You cannot create variables with the name of existing functions and “visible” variables. Also, the variable name must contain at least one lowercase letter, because uppercase names are used for constants. A variable declaration begins by specifying its type. There are two types of initialization - you can define a single variable with a value assigned to it or several variables of the same type with default initialization.

VarDeclaration = VarAssign | VarList
VarList = TypeName IdentifierList
VarAssign = TypeName identifier "=" | "&=" VarInit
int x y myVal
int z = myFunc(x) + y + 10
arr a &= b

If statement

The “if” statements begin with “if”, they can have one or more “elif” blocks and end with “else”. The statement sequentially evaluates the condition for each branch and if the condition is true, then the corresponding block executes. In this case, the remaining branches are skipped, the statement ends and the control is passed to the next command. If all branch conditions are false, then, if present, the “else” branch is executed.

IfStmt = "if" Expression Block [{ "elif" Expression Block }][ "else" Block ]
if a == 11 {
    b = 20
} else {
    c = a+b
}
if x > y && isOK { 
     x = 1 
} elif a > 1 {
   x++
} elif b < 10 {
    b = a
} else {x = 0}

While statement

The “while” statement is a simple loop. It specifies the repeated execution of a block as long as a boolean condition evaluates to true. If the condition is false the first time through the loop, the statements inside the loop are never executed.

WhileStmt = "while" Expression Block 
a = 0
while a < 5 {
   с += a
   a++
}

For statement

The “for” statement is used to iterate through all the elements of the specified object. The object must be of a type that supports indexing such as arr, map, str, buf, range of integers. For each entry for assigns iteration values to corresponding iteration variables if present and then executes the block. You must specify the name of the variable for iteration values and, optionally, the name of the variable for the index value.
If you want to iterate through a range of integer numbers, use the syntax from..to as the object, where from and to are values of the integer type. This will iterate through all the numbers between from and to, including the extreme values. The initial value can be greater than the final value, in this case, the iteration value will be decreased.

ForStmt = "for" identifier [, identifier] "in" Expression Block
str dest
for ch, i in `strΔ` {
   dest += "\{i}\{ch}"
}
int sum
for i in 0..100 : sum += i

Switch statement

The “switch” statement allows you to perform different operations in case an expression has different values. The switch keyword is followed by the initial expression that is calculated and stored as the switch value. The switch value can be of type int, float, char, str. Then you enumerate case statement in curly braces with all possible values and the source code that should be executed. One case can have several possible values separated with a comma in case of which it will be executed. After executing the case block with the matching value, the program goes to the statement coming after switch. The rest of case blocks are not checked.

If you want to execute some operations in case none of the case blocks is executed, insert the default construction at the end of switch. The default statement can appear only once and should come after all case statements.

SwitchStmt = "switch" Expression newline CaseStmt { CaseStmt } [ "default" Block ]
CaseStmt = "case" Expression {, Expression } Block
int i = 67
int j
switch i+3 
case 20,10,5 {
  i +=10
}
case j,20+50,80 {
  i -=10
}
default: i *= 2

Return statement

A “return” statement terminates the execution of the current function, and optionally provides a result value. If a function doesn’t have a result type, a “return” statement must not specify any result value. You can use “return” statement in any nested block.

ReturnStmt = "return" [ Expression ]
func mul2(int i) int { return i*2}

Break statement

The “break” statement terminates switch/case statement or the execution of the loop (for and while). break is likely to be located within nested blocks. If a program contains several nested loops, break will exit the current loop.

BreakStmt = "break"
while b > c {
   if !myfunc( b ) {
      break   
   }
   b++
}

Continue statements

The “continue” statement may occur within loops (for and while) and attempts to transfer control to the loop expression, which is used to increment or decrement the counter (for for or to the conditional expression for while; moreover, the execution of the loop body is not completely executed. The instruction executes only the most tightly enclosing loop, if this loop is nested.

ContinueStmt = "continue"
for i in 0..100 {
   if i > 10 && i < 20 {
      continue 
   }
   a += i // The given expression is not evaluated if i>10 and i<20
}

Expressions

The expression returns a value by applying operators and functions to operands. An operand may be a literal, an identifier denoting a constant, variable, or function, or a parenthesized expression. To call a function, specify its name and list the parameters in parentheses separated by comma. Parameters can also be expressions.

Operand     = Literal | OperandName | "(" Expression ")" | GoStmt
Literal     = BasicLiteral
constLit    = "true" | "false"
BasicLiteral    = float | integer | stringLit | constLit | charLit
OperandName = identifier | EnvVariable | FnIdent
PrimaryExpr = Operand |	FuncName Arguments | IfOp | IndexExp | FieldExpr
Arguments      = "(" [  ExpressionList [ "," ExpressionList ]  ] ")" 
ExpressionList = Expression { "," Expression } 
Expression = UnaryExpr | Expression binaryOp Expression | OperandName assignOp Expression
UnaryExpr  = PrimaryExpr | unaryOp UnaryExpr | incOp OperandName | OperandName incOp 
binaryOp  = "||" | "&&" | relOp | mathOp | assignOp | rangeOp
relOp     = "==" | "!=" | "<" | "<=" | ">" | ">=" 
mathOp     = "+" | "-" | "|" | "^" | "*" | "/" | "%" | "<<" | ">>" | "&" | 
unaryOp   = "-" | "!" | "^" | "*" | "#" | "##"
incOp = "++" | "--" 
rangeOp = ".."
assignOp = "=" | "+=" | "-=" | "|=" | "^=" | "*=" | "/=" | "%=" | "<<=" | ">>=" | "&=" | "#="
IfOp = "?" "(" Expression "," Expression "," Expression ")"

When calculating the logical operators “&&” (AND) and “||” (OR), the right operand is evaluated optionally. For example, in the cases of false && myFunc() and true || myFunc (), the myFunc function will not be called.

Assignment operators are binary operators that return an assigned value. Therefore, assignment operators can be used within expressions.

int i j k
i = j = 5+(k=60/5)*2
return (k+j)*2 + i   // 111

Operator precedence

As a rule, all statements are executed from left to right, but there is such a concept as statement priority. If the next statement has a higher priority, the statement with a higher priority is executed first. For example, multiplication has a higher priority and 4 + 5 * 2 is 14, but if we use parentheses, ( 4 + 5 ) * 2 is 18.

Operator Type Associativity
The highest priority    
(   )  [   ]   Left to right
!   -   ^   #   ##   *   ++   -- Unary prefix Right to left
++   -- Unary postfix Left to right
/   %   * Binary Left to right
+   - Binary Left to right
<<   >> Binary Left to right
& Binary Left to right
^ Binary Left to right
| Binary Left to right
==   !=   <   <=   >   >= Binary Left to right
|| Binary Left to right
&& Binary Left to right
#= Binary Left to right
=   +=   -=   *=   /=   %=   <<=   >>=   &=   ^=   |= Binary Right to left
.. Binary Left to right
The lowest priority    

Parentheses () change the order in which expressions are evaluated. Increment operations ++ and -- can be occurred either in the prefix or in the postfix notation.

Conditional operator “?”

Conditional operator “?” is similar in its work to the “if” statement, but can be used inside an expression. The conditional operator has three operand expressions. The operands are enclosed in parentheses and separated by commas, at the beginning the value of the first logical expression is evaluated. If the value is true, the second expression is evaluated and the resulting value is the result of the conditional operator. Otherwise, the third operand is evaluated and its value is returned.

if a >= ?( x, 0xFFF, ?( y < 5 && y > 2, y, 2*b )) + 2345
{
     r = ?( a == 10, a, a + b ) 
}

Arrays and structures initialization

When defining variables of the type arr, buf or map, you can immediately assign array elements. You can also specify the field values of structural type variables. Values are listed separated by commas or line breaks. You can specify expressions as a value. When initializing an associative array map, you must specify the key as a string and the value after a colon. If the elements of the array are other arrays, they are also initialized using braces. When initializing structure fields, you must specify the key as an identifier and a colon-separated value. A variable of buf type can be initialized by a combination of the values of the int, str, char, buf types.

DelimInit = "," | newline
ArrInit = "{" VarInit {DelimInit VarInit} "}"
BufInit = "{" Expression {DelimInit Expression} "}"
MapInit = "{" Expression ":" VarInit { DelimInit Expression ":" VarInit  }  "}"
StructInit = "{" identifier ":" VarInit { DelimInit identifier ":" VarInit  }  "}"
VarInit = ArrInit | BufInit | MapInit | StructInit | Expression
mystruct my = {ID: 20, name: "some text"}
buf a = {250+5, '1', 'A', "test", 0}
map.arr.int ret = {"key1": {0, 1 }, `key2`:{ 2, 3 } }
arr.map ret = { {"test": "value 1"}
                {`next`:"value 2"} }
arr.bool mb = : true, false, true
map  my = {"key1": GetVal(1), "key2": GetVal(2)}

Index Expression

Indexing allows you to get or set a specific element of a variable by its index. The index is a position of some element inside of the specified variable. Gentee has zero-based indexing (excepting map), meaning that the first element has the index zero, the second element has the index one, etc. The following types support indexing:

  • str. The index must be of the int type and less than the length of the string. If the index is out of range, a run-time error occurs. The type of the result is char.
  • buf. The index must be of the int type and less than the length of the array. If the index is out of range, a run-time error occurs. The type of the result is int but 0 <= result value <= 255.
  • arr. The index must be of the int type and less than the length of the array. If the index is out of range, a run-time error occurs. The type of the result is the same as the type of array’s elements.
  • map. The index must be of the string type. If a value is obtained, the map must have the element with this key. If there is not the such index, a run-time error occurs. The type of the result is the same as the type of map’s elements.

If array or map consists of arrays, then you can sequentially apply the index expression.

IndexExp = PrimaryExpr "[" Expression "]" { "[" Expression "]" } 
str temp = `0123`
temp[1] = temp[3]  // result `0323`
arr ain
ain += `test`
temp = ain[0]
map mymap
mymap["mykey"] = "myvalue"
arr.map amap
amap += mymap
amap[0]["mykey"] = "new value"

Assignment Expressions

There is an assignment operator = for all types in the Gentee language. When you use assignment for buf, arr, map, and all structure types, you will get copies of the data. Consider an example

arr a1 = {`A`, `B`, `C`}
arr a2 = a1
a2 += `D`
a1[0] = `Z`
//  a1 = `Z`, `B`, `C`
//  a2 = `A`, `B`, `C`, `D`

When assigning a2 = a1 we got a copy of the array a1. Actions on arrays will not affect each other in any way. Sometimes making copies of large objects can slow down program execution. For example, if a function returns some structure that you want to work with further, then there is no point in creating its copy. To resolve such situations, there is another assignment operator &= for types buf, arr, map and all structure types. This operator does not copy data into a variable, but creates a clone of this data. In this case, the data remains in a single copy.

arr a1 = {`A`, `B`, `C`}
arr a2 &= a1
a2 += `D`
a1[0] = `Z`
//  a1 = `Z`, `B`, `C`, `D`
//  a2 = `Z`, `B`, `C`, `D`
time t
t &= Now()  // it is better than t = Now()

Context

There are no global variables in Gentee. Instead, there is an associative array map.str, which is readable and writable from any functions and streams. In addition to storing data as a key-value, the context can also recursively replace keys in strings of the form “#key_name# #another_key_name#”. All context functions are described in the standard library. Here we consider only operators.

  • Unary operator ## str recursively replaces keys with their values in the passed string and returns the result.
  • Unary operator # key works similarly to the ## operator, but it needs to pass the name-identifier of the context key.
  • The key #= value operator writes the key key with the specified value into the context. If the value is of type int, bool, float instead of type str, it will be converted to a string.
func ooops() {
    AB #= `oops`
}
run str {
    AB #= `test`
    CD #= `#AB# - `    // don't replace #AB#.  CD = #AB# - 
    E #= #AB           // get #AB#. E = test
    ooops()            // change AB
    val #= 10
    return #CD + #E + ##` #val# == 10`
}
// Result:  oops - test 10 == 10

Running programs

The language has a special command $ to run applications and operating system commands with the specified parameters. The script takes the entire line up to the newline character and execute it. There must be a space between the $ and the command line. If this command is used in an expression, it captures the standard output and returns it as a string. Otherwise, the standard output will be visible in the console. You can specify expressions with %{Expression} as in a backquoted string. If any parameter contains a space, enclose that parameter in any quotes - “a b”, ‘c d’, `e f`. If the executable application or command terminates with an error code other than zero, the script will also stop working and return an error.

Command = "$ " { unicode_linechar | "%{" Expression "}" | "${" identifier "}" }
run str {
   $ dir
   str name = $ echo "John Smith"
   return $ echo My name is %{name}
}

Environment Variables

The Gentee language allows you to easily get environment variables and assign values to them. To do this, specify the $ character before the variable name. In addition, you can substitute the environment variables with the ${ENV_NAME} construct in the $ launch commands and back-quoted strings. This entry is shorter than %{ $ENV_NAME }. Environment variables are always of string type, but you can assign them values of str, int, and bool types.

EnvVariable = "$" identifier
run str {
    $MYVAR = `Go path: ${GOPATH}` + $GOROOT
    return $ echo ${MYVAR}
}

Multithreading

The Gentee language allows you to create multi-threaded scripts. In this case, part of the script can be performed in parallel, which reduces the time of its work. To execute some code in a separate thread, you must specify it in the go statement. The script will continue to perform the following constructions without waiting for the end of the code inside go, but immediately after the new thread is launched. The go statement returns the identifier of the created thread, which can be assigned to a variable of the thread type and then used in the multithreading functions. If the script has finished its work earlier than the children threads, then it will wait for all threads to finish working. If an error occurs in any of the threads while executing a multi-threaded script, all the threads are closed and the script returns this error.

GoStmt = "go"  Block
func myThread {
  for i in 0..50 {
    Print(`x` )
    if i % 3 == 0 : sleep(5)
  }
}

run {
  thread th = go {
    for i in 0..100 {
      Print(` `, i )
      if i % 7 == 0 : sleep(5)
    }
  }
  suspend( th )
  go : myThread()
  resume(th)
  Print( `OK`)
  wait(th)
  Print( `END`)
}

Include and import

Include declaration

The include declaration imports all types, functions and constants from the specified files and their included files.

The import declaration imports only public types, functions and constants from the specified files and their included files. Public objects are defined using the pub keyword.

stringConst         = "`" { unicode_char } "`" | stringDoubleConst
stringDoubleConst = `"` { unicode_char | uShort | uLong | escapedChar | byteVal } `"`
importDecl = "import" "{" {stringConst newline} "}"
includeDecl = "include" "{" {stringConst newline} "}"

Consider the visibility of objects as a table. Let there be two files.

// a.g can include or import b.g
func afunc(i int) : return i*2
pub func apubfunc(i int) : return i*3

// b.g 
func bfunc(i int) : return i*4
pub func bpubfunc(i int) : return i*5

Let the c.g file can import or include a.g file. You can see what functions are visible in c.g in different situations.

  include a
a includes b
include a
a imports b
import a
a includes b
import a
a imports b
afunc visible visible    
apubfunc visible visible visible visible
bfunc visible      
bpubfunc visible   visible  

Pub declaration

The pub declaration marks the next function, type or constants as public. They can be imported with import declaration.

pubDecl = "pub"
objects = [pubDecl] (structDecl | FnDecl | ConstDecl | FunctionDecl)

The pub keyword indicates that the next function, constants, or structure will be passed when the file is imported.

pub const IOTA {  // public constants. Visible when include or import
    MY1
    MY2
}

const IOTA*2 {  // private constants. Visible only when include
    MY3
    MY4
}