Документация к языку Mash

Переменные и неявные указатели

В Mash каждая переменная хранит указатель на объект в памяти. При передаче переменных в методы, получении результата или же при простых манипуляциях с ними – работа идет с объектами в памяти по указателям на них.

Т.е. в следующем коде в метод p(arr) будет передан массив в виде указателя на уже созданный ранее объект.

proc p(arr):
   …
end

proc main():
   arr ?= [1, 2, 3, 4, 5]
   p(arr)
end

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

Временные переменные и сборщик мусора

В отличии от подобных ему языков программирования, в Mash полуавтоматический сборщик мусора, в основе которого лежит механизм меток временных значений, а не подсчета указателей.

Т.е. разработчик сам решает когда следует очистить память от мусора и также может делать это вручную, для определенных случаев.

Сборка мусора вызывается с помощью gc() и освобождает память из под всех временных объектов.

Многие простые действия с переменными сопровождаются созданием временных объектов в памяти.

Разработчик может явно объявлять выделение памяти, для дальнейшей работы с ней, т.е. объявлять переменные для длительного использования.

Примеры создания временной переменной:

x ?= 10

И переменных, не помеченных для сборщика мусора:

x ?= new(10)
var x2 = 20

Пример использования gc():

while a > b:
   doSomething(a, b)
   gc()
end

Память также можно освобождать вручную:

var a = 10, b = 20, c = 30

Free(a, b, c)

Зоны видимости переменных

Переменные в Mash могут быть объявлены как локально, так и глобально.

Глобальные переменные объявляются между методов, через оператор var. Локальные – внутри методов любым способом.

Типы данных

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

Примеры кода:

x ?= 10
x += 3.14
x *= “3”
x /= “2,45”

x ?= [1, 2, 3.33, func1(1, 2, 3), “String”, [“Enum type”, 1, 2, 3], “3,14”]

Интроспекция позволяет определять тип нужной переменной:

if typeof(x) == TypeInt:
   …
end

Массивы & перечисления

Редкие задачи не заставляют разработчика объявлять очередной массив в коде.
Mash поддерживает массивы, состоящие из произвольного количества уровней, + массивы могут быть чем-то вроде деревьев, т.е. размеры подуровней могут различаться на одном подуровне.

Примеры объявлений массивов и перечислений:

a ?= new[10][20]

var b = new[10][20]

c ?= [1, [], 3, 3.14, “Test”, [1, 2, 3, 4, 5], 7.7, a, b, [“77”, func1()]]

var d = [1, 2, 3]

Любые объекты, объявляемые через оператор new не помечаются для сборщика мусора.Массивы также как и обычные объекты в памяти освобождаются вызовом Free()

Такая работа с перечислениями довольно удобна.

Например, можно передавать в методы или возвращать из них множество значений одной переменной:

func doSomething(a, b, c):
   return [a+c, b+c]
end

Операторы присваивания

В Mash целых 3 оператора присваивания.

  • ?=
    Присваивает переменной указатель на какой-либо объект (если переменная объявлена но не хранит указатель на объект в памяти, то нужно использовать именно этот оператор, для присваивания ей значения).
  • =
    Обычное присваивание. Присваивает объекту по указателю в переменной значение другого объекта.
  • @=
    Присваивает значение объекту по явному указателю на этот объект (об этом будет сказано позже).

Математические и логические операции

Список поддерживаемых на данный момент математических и логических операций:

  • +, , *, /
    В комментариях не нуждаются.
  • \
    Деление нацело.
  • %
    Остаток от деления.
  • &
    Логическое “и”.
  • |
    Логическое “или”.
  • ^
    Логическое “исключающее или”.
  • ~
    Логическое “не”.
  • ++,
    Инкремент и декремент.
  • <<, >>
    Побитовые сдвиги влево и вправо.
  • ==, <>, >=, <=
    Логические операторы сравнения.
  • in
    Проверяет принадлежность объекта к перечислению или к массиву.
    Пример: if Flag in [1, 3, 5, 8]:

Явные указатели

Казалось бы, зачем они нужны, если есть не явные указатели? Например для проверки, хранят ли переменные A и B указатели на один и тот же объект в памяти.

  • @ – получить указатель на объект и положить его в переменную, как объект.
    Пример: a ?= @b
  • ? – получить объект по указателю из объекта в переменной.
    Пример: a ?= ?b

Процедуры и функции

Я решил сделать в языке разделение методов по возврату значений на процедуры и функции (как в Pascal).

Объявления методов производятся подобно следующим примерам:

proc SayHello(arg1, arg2, argN):
   println(“Hello, “, arg1, arg2, argN)
end

func SummIt(a, b):
   return a + b
end

Языковые конструкции

Пример конструкции if..else..end.

if <условие>:
   …
else:
   …
end

Цикл for.

for([инициализация]; <условие>; [операции с переменными]):
   …
end

While. Условие проверяется перед итерацией.

while <условие>:
   …
end

Whilst. Цикл, отличающийся от while тем, что проверка условия выполняется после итерации.

whilst <условие>:
   …
end

switch..case..end..else..end… – всем знакомая конструкция для создания логических ветвлений.

switch <переменная>:
   case <значение 1>:
      …
   end

   case <значение 2>:
      …
   end

   else:
      …
end

Классы и элементы ООП языка

В Mash реализована поддержка классов, наследования, динамической интроспекции и рефлексии, полиморфизм. Т.е. стандартный набор скриптового языка поддерживается.

Рассмотрим объявление простого класса:

class MyClass:
   var a, b
   proc Create, Free
   func SomeFunction
end

Объявление класса не содержит реализаций методов, которые в нем объявлены.
В качестве конструктора класса выступает метод Create. В качестве деструктора – Free.

После объявления класса можно описать реализацию его методов:

proc MyClass::Create(a, b):
   $a ?= new(a)
   $b ?= new(b)
end

proc MyClass::Free():
   Free($a, $b, $)
end

func MyClass::SomeFunction(x):
   return ($a + $b) / x
end

Вы могли заметить символ $ в некоторых местах кода – этим символом я просто сократил длинное this->. Т.е. код:

return ($a + $b) / x

Free($a, $b, $)

Эквивалентен этому коду:

return (this->a + this->b) / x

Free(this->a, this->b, this)

В this находится указатель на экземпляр класса, от лица которого вызывается метод этого класса.

Для того, чтобы унаследовать функционал какого-либо класса, нужно описать объявление нового класса таким вот образом:

class MySecondClass(MyClass):
   func SomeFunction
end

func MySecondClass::SomeFunction(x):
   return ($a – $b) / x
end

MySecondClass – будет иметь конструктор и деструктор от своего предка + функция SomeFunction, которая имеется у класса-предка перезаписывается функцией из нового класса.

Для создания экземпляров классов существует оператор new.

Примеры кода:

a ?= new MyClass

(Выделение памяти только под структуру класса)

b ?= new MyClass(10, 20)

(Выделение памяти под структуру класса и последующий вызов конструктора класса)

Тип экземпляра класса можно определить при создании этого экземпляра, соответственно в языке отсутствует приведение типов.

Интроспекция позволяет определить тип экземпляра класса, пример кода:

x ?= new MyClass(10, 20)

if x->type == MyClass:

end

Иногда нужно обратиться к классовой функции и перезаписать её новой. Пример кода:

func class::NewSomeFunction(x):
   return $x * $y * x
end

x ?= new MyClass(10, 20)
x->SomeFunction ?= class::NewSomeFunction

При вызове:

x->SomeFunction(33)

Вызывается NewSomeFunction, будто это оригинальная функция класса.