Описание языка

Стандартная библиотека

Скриптовый язык программирования Gentee

Введение

В настоящий момент, Gentee находится в стадии разработки и эта документация описывает только реализованные возможности.

Gentee является строго-типизированным процедурным языком. В первую очередь, он предназначен для написания скриптов с целью автоматизации повторяющихся действий и процессов на компьютере. Язык имеет простой синтаксис, лёгок в изучении и сопровождении.

GitHub репозитарий исходников и запускаемых файлов: https://github.com/gentee/gentee
GitHub репозитарий документации: https://github.com/gentee/gentee.github.io
Язык разработки: Go

Лексические элементы

Исходный код должен быть в кодировке. Синтаксис описан с использованием расширенной формы Бэкуса-Наура.

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" 

Комментарии и замена символов

Имеются следующие типы комментариев и автоматически заменяемых символов

// Однострочный комментарий
Однострочный комментарий начинается с двойного слэша // и заканчивается символом перевода строки newline.

/* Общий комментарий */
Общий комментарий начинается с комбинации /* и заканчивается */. Такие комментарии могут вставляться где угодно.

# Заголовок
В начале скрипта можно указать данные для использования в других программах. Такие комментарии должны идти подряд в каждой строке от начала скрипта. Можно не указывать ‘#’ в начале каждой строки, а вставить ### перед и после текста.

#!/usr/local/bin/gentee
# первая строка может использоваться для запуска скрипта в Linux.
###
  desc = Description of the script
  result = ok
  var = value
###

;
Символ перевода строки служит разделителем между выражениями и управляющими конструкциями. Точка с запятой заменяется на перевод строки. Таким образом, вы можете использовать точку с запятой, если вы хотите разместить несколько выражений на одной строке.

:
Двоеточие заменяется на открывающую фигурную скобку и вставляется закрывающая фигурная скобка в конце текущей строки.

// эти примеры эквивалентны
if a == 10 : a = b + c; c = d + e 

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

Идентификаторы

Идентификаторы - это имена, которые используются для обозначения переменных, типов, констант, функций и т.д.. Идентификатор определяется с помощью последовательности букв и цифр, но начинаться идентифкатор должен с буквы.

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

Имеется несколько предопределённых идентификаторов и ключевых слов. Следующие слова зарезервированы и не могут быть использованы в качестве идентификаторов.

arr bool char const elif else false for func if in int map range return run str true while

Литералы

Целочисленная литера - это последовательность цифр представляющая целочисленное число (константу).

decimal = ( "1" … "9" ) { decimal_digit } 
octal = "0" { octal_digit } .
hex = "0" ( "x" | "X" ) hex_digit { hex_digit } 
integer = decimal | octal | hex

Символьный char литерал служит для идентификации Unicode символа. Вы можете указать один конкретный символ или последовательность символов начинающуюся с обратного слэша заключенные в одинарные кавычки. Последовательность символов с обратным слэшем может иметь несколько форматов:

'\r',  '\n',  '\t', '\"', '\'', '\\' 
\xa5 \x2B  (\x + two hex digits)
\u03B1  (\u + four hex digits)
\0371  (\three octet digits)
byteVal  = octalStr | hexStr .
octalStr = `\` 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 | `\'`) "'" .

Имеется два типа строковых литералов.

  1. Строка в обратных кавычках может содержать любые символы. Если нужно указать обратную кавычку, то нужно удвоить её.
  2. Строка в двойных кавычках также может содержать любые символы (в том числе перенос строки), но у неё имеется управляющий символ в виде обратной косой черты. Вы можете указывать после обратной косой черты следующие символы
\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 "}" } "`"
stringDoubleQuote = `"` { unicode_char | uShort | uLong | escapedChar | byteVal | "\{" Expression "}" } `"`

В любой тип строки можно вставлять выражения. При этом тип выражения может быть любым, если имеется соответствующая функция приведения этого типа к строке. Выражения должны быть заключены в фигурные скобки со предшествующим знаком % (для обратных кавычек) или обратной косой чертой (в случае двойных кавычек).

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

Типы

Тип описывает множество значений, которые имеют одинаковые операции и функции специально для этих значений. Тип определяется именем типа. Ассоциативный массив map - это группа элементов одного типа к которой можно обращаться по строковому индексу. Каждый элемент имеет соответствующий уникальный строковый ключ. По умолчанию, массивы arr и map состоят из строк, но вы можете указать любую вложенность типов, разделив их точкой. Следует заметить, что переменные типов arr и map, в отличии от прочих типов, передаются по ссылке, а не по значению. Это значит, что если внутри функции вы изменили значение такого параметра, то у вас изменится оригинальная переменная.

TypeName  = identifier  { "." identifier }

Язык Gentee следующие предопределенные типы.

Имя Описание Значения Начальное значение
int 64-bit целочисленный тип -9223372036854775808 .. 9223372036854775807 0
bool логический тип true or false false
str строка последовательность байт пустая строка
char Unicode символ Unicode символ int32 пробел
arr массив массив элементов пустой массив строк
map ассоциативный массив ассоциативный массив элементов пустой массив строк
arr.map.int a
map.arr.str b   // the same as map.arr b
map.bool c
arr.int  d

Приведение типов

В языке Gentee отсутствует автоматическое приведение типов. Для основных типов имеются функции конвертирования из одного типа в другой, их имена совпадают с именем результирующего типа.

  int bool str char
int   int(0) int(“-23”) int(‘A’)
bool bool(1)   bool(“0”)  
str str(20) str(false)   str(‘z’)
char        
int(false) // = 0           
int(true) // = 1    
bool(0) // = false  
bool(integer except zero) // = true    
bool("")  bool("0") bool("false") //=false
bool("not empty, zero or false string")   //=true

Описания

Описание констант

Имя константы не должно содержать букв в нижнем регистре и иметь как минимум одну букву в верхнем регистре. Константам можно присваивать любые выражения. Значение константы вычисляется при первом обращении к данной константе, но тип константы автоматически определяется на этапе компиляции по типу присваиваемого выражения. Поэтому, несмотря на то, что тип при определении константы не указывается, действует проверка типов при её использовании.

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

Константы можно определить двумя способами.

  1. Указывая начальное значение или выражение для каждой константы.
    const {
     MY_ID = 1
     MY_VAL = myFunc( MY_ID + 23)
     CHECK= MY_VAL < 32
    }
    
  2. Используя общее выражение с IOTA. Иногда возникает необходимость определить список констант со значениями, которые вычисляются по определенным правилам. В этом случае, после ключевого слова const необходимо указать одно общее выражение, которая будет вычисляться для каждой константы в данном определении. В этом выражении можно использовать специальную переменную IOTA, которая равна порядковому индексу константы в списке с нуля. Сами константы могут перечисляться через пробел или с новой строки.
    const 0x1 << IOTA {
       FIRST SECOND   // 0x1    0x2
       THIRD                   // 0x4
    }
    const (IOTA * 2) + 1 {
       MY1    // 1
       MY2   // 3
       MY3   // 5
    }
    

Описание функции

Определение функции состоит из двух частей - описание параметров с возвращаемым типом и тела функции. При определении функции вы должны указать ключевое слово “func”, имя функции, передаваемые параметры и тип возвращаемого значения. Только имя функции является обязательным элементом.

FunctionDecl   = "func" FunctionName [Parameters] [ Result ] Block
FunctionName   = identifier 
Result         = TypeName 
Parameters     = "(" [ ParameterList ] ")"
ParameterList  = VarList { "," VarList }
func Average(int par1 par2) int { 
    return (par1+par2)/2 
}

Описание функции запуска

Скрипт на языке Gentee должен содержать специальную функцию без параметров, которая определяется с помощью ключевого слова “run”. Выполнение скрипта начинается с вызова этой функции. Скрипт должен иметь только одно определение “run”.

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

Конструкции языка

Блок это последовательность определений и конструкций внутри фигурных скобок. Блоки могут вкладываться друг в друга.

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

Определение переменной

Любая переменная функции должна быть описана перед её использованием. Определение переменной создает одну или несколько переменных и присваивает каждой начальные значения. Переменная может быть определена в любом блоке. Область видимости переменной распространяется на блок, в котором она определена и на все вложенные блоки. Нельзя создавать переменные с именем существующих функций и видимых переменных. Также, имя переменной должно содержать как минимум одну букву в нижнем регистре, так как имена в верхнем регистре используются для констант. Определение переменой начинается с указания её типа. Существует два типа инициализации - можно определить одну переменную с присваиванием ей значения или несколько переменных одного типа с инициализацией по умолчанию.

VarDeclaration = VarAssign | VarList
VarList = TypeName IdentifierList
VarAssign = TypeName identifier "=" Expression
int x y myVal
int z = myFunc(x) + y + 10

Конструкция If

Конструкции If начинается с “if”, они могут иметь один или несколько блоков “elif” и заканчиваться “else”. Команда последовательно вычисляет условие для каждой ветки и, если условие возвращает истину, то тогда происходит выполнение соответствующего блока. В этом случае, остальные ветки пропускаются и управление передается следующей команде. Если условия во всех ветках ложны, то выполняется блок “else”, если он существует.

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

Конструкция “while” является простым циклом. Данная конструкция выполняет блок до тех пор, пока логическое выражение равно истине. Если выражение ложно сразу в первый раз, то блок не будет выполнен ни разу.

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

Конструкция For

Конструкция For служит для перебора всех элементов указанного объекта. Объект должен иметь тип, который поддерживает обращение по индексу, например, arr, map, str, range of integers. Для каждого из его элементов выполняется код, который определен внутри конструкции. Вы должны указать имя переменной, которой будут присваиваться элементы и, опционально, имя переменной, которая будет равна текущему индексу.
Если вы хотите перебрать целочисленные значения в указанном диапазоне, то используйте в качестве объекта запись from..to, где from и to значения типа int. Такой цикл будет перебирать все числа от from до to, включая крайние значения. Начальное значение может быть больше конечного значения, в этом случае, значение на каждом цикле будет уменьшаться.

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

Конструкция return

Конструкция “return” прекращает выполнение текущей функции и может возвращать результирующее значение. Если у функции не указан результирующий тип, то конструкция “return” не должна возвращать значение. Вы можете использовать return в любом вложенном блоке.

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

Выражения

Выражение возвращает значения путем применения операторов и функций к операндам. Операндом может быть литерал, идентификатор определяющий константу, переменную, функцию или выражение в скобках. Для вызова функции необходимо указать её имя и в круглых скобках перечислить параметры через запятую. Параметры тоже могут быть выражениями.

Operand     = Literal | OperandName | "(" Expression ")"
Literal     = BasicLiteral
constLit    = "true" | "false"
BasicLiteral    = integer | stringLit | constLit | charLit
OperandName = identifier | EnvVariable
PrimaryExpr = Operand |	FuncName Arguments | IfOp
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 ")"

При вычислении логических операторов “&&” (И) и “||” (ИЛИ), правый операнд вычисляется опционально. Например, в случаях false && myFunc() и true || myFunc() функция myFunc не будет вызываться.

Оператор Тип Описание int bool str char
+ binary сложение int   str str
- binary вычитание int      
- unary смена знака int      
* binary умножение int      
* unary длина     int  
/ binary деление int      
% binary остаток int      
unary приращение int      
++ unary уменьшение int      
« binary сдвиг влево int      
>> binary сдвиг вправо int      
& binary двоичное И int      
| binary двоичное ИЛИ int      
^ binary двоичный XOR int      
~ unary двоичное NOT int      
! unary логическое NOT   bool    
&& binary логичееское И   bool    
| | binary логическое ИЛИ   bool    
== binary равно bool   bool bool
!= binary не равно bool   bool bool
< binary меньше bool   bool bool
<= binary меньше или равно bool   bool bool
> binary больше bool   bool bool
>= binary больше или равно bool   bool bool
= binary присваивание int bool str char
+= binary x = x + y int   str  
-= binary x = x - y int      
*= binary x = x * y int      
/= binary x = x / y int      
%= binary x = x % y int      
|= binary x = x | y int      
&= binary x = x & y int      
«= binary x = x « y int      
>>= binary x = x » y int      
^= binary x = x ^ y int      

Операторы присваивания являются бинарными операторами, которые возвращают присвоенное значение. Таким образом операторы присваивания могут использоваться внутри выражений.

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

Приоритеты операторов

Как правило все операторы выполняются слева направо, но имеется такое понятие как приоритет операторов. Если следующий оператор имеет более высокий приоритет, то в начале выполнится оператор с более высоким приоритетом. Например, умножение имеет более высокий приоритет и 4 + 5 * 2 равно 14, но если мы поставим круглые скобки то ( 4 + 5 ) * 2 равно 18.

Оператор Тип Ассоциативность
Высший приоритет    
(   )  [   ]   Слева направо
!   -   ~   *   ++   – Унарный префикс Справо налево
++   – Унарный постфикс Слева направо
/   %   * Бинарный Слева направо
+   - Бинарный Слева направо
«   » Бинарный Слева направо
& Бинарный Слева направо
^ Бинарный Слева направо
| Бинарный Слева направо
==   !=   <   <=   >   >= Бинарный Слева направо
|| Бинарный Слева направо
&& Бинарный Слева направо
=   +=   -=   *=   /=   %=   «=   »=   &=   ^=   |= Бинарный Справо налево
.. Бинарный Слева направо
Низший приоритет    

Круглые скобки () изменяют порядок вычисления частей выражения. Операции инкремента ++ и – могут быть как префиксными, так и постфиксными.

Условный оператор “?”

Условный оператор “?” аналогичен по своей работе конструкции “if”, но может использоваться внутри выражения. Он содержит три операнда-выражения. Операнды заключены в скобки и разделены запятыми, в начале вычисляется значение первого логического (целочисленного) выражения. Если значение истинно, то вычисляется второе выражение и полученное значение становиться результатом работы условного оператора. В противном случае вычисляется третий операнд и возвращается его значение.

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

Индексное выражение

Индексы позволяют вам получать или устанавливать определенный элемент переменной по его индексу. Индекс - это позиция определенного элемента внутри указанной переменной. Индексы в Gentee начинаются с нуля (для map индексы имеют строковый тип), первый элемент имеет индекс 0, второй индекс один и т.д. Следующие типы поддерживают индексы:

  • str. Индекс должен иметь тип int и быть меньше длины строки. Если индекс выходит за этот диапазон, то возникает ошибка выполнения. Результат имеет тип char.
  • arr. Индекс должен иметь тип int и быть меньше длины массив. Если индекс выходит за этот диапазон, то возникает ошибка выполнения. Результат имеет такой же тип, как тип элементов массива.
  • map. Индекс должен иметь тип str. В случае получения значения, элемент с таким индексом должен существовать в ассоциативном массиве. Если такой ключ отсутствует, то возникает ошибка выполнения. Результат имеет такой же тип, как тип элементов ассоциативного массива.
IndexExp = ident "[" Expression "]"
str temp = `0123`
temp[1] = temp[3]  // result `0323`
arr ain
ain += `test`
temp = ain[0]
map  mymap
mymap["mykey"] = "myvalue"

Запуск программ

В языке существует специальная команда $ для запуска приложений и команд операционной системы с указанными параметрами. Данная команда запускает весь следующий за ней текст до конца строки. Между символом $ и командной строкой должен присутствовать пробел. Если данная команда используется в выражении, то она перехватывает стандартный вывод и возвращает его в виде строки. В противном случае, стандартный вывод будет виден в консоли. Можно использовать подстановку выражений с помощью %{Expression} как в строке с обратными кавычками. Если какой-то параметр содержит пробел, то его нужно заключить в любые кавычки - “a b”, ‘c d’, `e f`. Если запускаемое приложение или команда завершилось с кодом ошибки, отличным от нуля, то скрипт также прекратит работу и возвратит ошибку.

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

Переменные окружения

Язык Gentee позволяет вам легко получать и присваивать значения переменных окружения. Для этого укажите знак $ перед именем переменной. Кроме этого, вы можете подставлять переменные окружения с помощью конструкции ${ENV_NAME} в командах запуска $ и строках с обратными кавычками. Это запись короче, чем %{ $ENV_NAME }. Переменные окружения всегда имеет строковый тип, но вы можете присваивать им значения типа str, int и bool.

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