Tutorial
Nasal легко учить. После прочтения этого туториала, примерно за 15 минут, вы сможете полноценно использовать nasal.
none
— это тип ошибки, используемый для прерывания выполнения. Этот тип не создаётся пользовательской программой.
nil
— это нулевой тип. Как null
.
var spc = nil;
num
имеет 3 формата: dec
, hex
и oct
. Используется IEEE754 double
для хранения.
# этот язык использует '#' для написания заметок
var n = 2.71828; # dec
var n = 2.147e16; # dec
var n = 1e-10; # dec
var n = 0xAA55; # hex
var n = 0o170001; # oct
# внимание: true и false также полезны в nasal сейчас
var n = true; # на самом деле n теперь равен 1.0
var n = false; # на самом деле n теперь равен 0.0
str
имеет 3 формата. Третий используется для объявления символа.
var s = 'str';
var s = "another string";
var s = `c`;
# некоторые специальные символы разрешены в этом языке:
'\a'; '\b'; '\e'; '\f';
'\n'; '\r'; '\t'; '\v';
'\0'; '\\'; '\?'; '\'';
'\"';
vec
имеет неограниченную длину и может хранить все типы значений.
var vec = [];
var vec = [0, nil, {}, [], func(){return 0}];
append(vec, 0, 1, 2);
hash
— это хэш-карта (или как dict
в python
), которая хранит значения со строками/идентификаторами в качестве ключа.
var hash = {
member1: nil,
member2: "str",
"member3": "имя участника также может быть строковой константой",
funct: func() {
return me.member2~me.member3;
}
};
func
— это функциональный тип (на самом деле это lambda
).
var f = func(x, y, z) {
return nil;
}
# функция может быть объявлена без параметров и `(`, `)`
var f = func {
return 114514;
}
var f = func(x, y, z, deft = 1) {
return x+y+z+deft;
}
var f = func(args...) {
var sum = 0;
foreach(var i; args) {
sum += i;
}
return sum;
}
upval
используется для хранения апвайлов, используется в vm
, чтобы убедиться, что замыкание работает правильно.
ghost
используется для хранения других сложных типов данных C/C++
. Этот тип создаётся родной функцией nasal. Если хотите определить новый тип данных, посмотрите, как добавить родные функции, отредактировав код.
Nasal имеет основные математические операторы +
-
*
/
и специальный оператор ~
, который объединяет строки или векторы.
1+2-(1+3)*(2+4)/(16-9);
"str1"~"str2";
[0]~[1]; # должно быть [0, 1]
Для условных выражений операторы ==
!=
<
>
<=
>=
используются для сравнения двух значений. and
or
имеют ту же функцию, что и C/C++ &&
||
.
1+1 and (1<0 or 1>0);
1<=0 and 1>=0;
1==0 or 1!=0;
Унарные операторы -
!
имеют ту же функцию, что и в C/C++.
-1;
!0;
Побитовые операторы ~
|
&
^
имеют ту же функцию, что и в C/C++.
# эти операторы будут:
# 1. преобразовывать f64 в i32 (static_cast<int32_t>)
# 2. выполнять побитовую функцию
~0x80000000; # not 2147483647
0x8|0x1; # or
0x1&0x2; # and
0x8^0x1; # xor
Операторы =
+=
-=
*=
/=
~=
^=
&=
|=
используются в выражениях присваивания.
a = b = c = d = 1;
a += 1;
a -= 1;
a *= 1;
a /= 1;
a ~= "string";
a ^= 0xff;
a &= 0xca;
a |= 0xba;
a = [0];
a ~= [1]; # должно быть [0, 1]
Оператор ??
используется для проверки того, является ли значение слева nil
, и если нет, возвращает значение справа:
В данном тексте описывается язык программирования Nasal. В нём используются конструкции, которые могут быть знакомы читателю по другим языкам программирования.
* *Оператор ?.* используется для получения значения элемента хеша, если значение слева не равно nil. Если значение слева равно nil или не является хешем, то это вызовет ошибку и завершение программы.
Пример использования оператора ?.:
var a = nil; print(a?.try_get); # nil
var a = {try_get: "congrats!"}; print(a?.try_get); # "congrats!"
* Определяются переменные:
* одна переменная: `var a = 1;`;
* несколько переменных из вектора: `var (a, b, c) = [0, 1, 2];`;
* несколько переменных из кортежа: `var (a, b, c) = (0, 1, 2);`.
Примеры определения переменных:
```javascript
var a = 1; # define single variable
var (a, b, c) = [0, 1, 2]; # define multiple variables from a vector
var (a, b, c) = (0, 1, 2); # define multiple variables from a tuple
globals
— хеш-карта, включающая все глобальные символы и их значения;arg
— в глобальной области видимости arg — это аргументы командной строки, а в локальной области видимости — динамические аргументы этого вызова функции.Например:
var a = 1;
println(globals); # will print {a:1}
# nasal a b c
println(arg); # will print ["a", "b", "c"]
func() {
println(arg);
}(1, 2, 3); # will print [1, 2, 3]
(a, b[0], c.d) = [0, 1, 2];
(a, b[1], c.e) = (0, 1, 2);
(a, b) = (b, a);
elsif
, которое выполняет те же функции, что и else if
.Пример условного выражения:
if (1) {
;
} elsif (2) {
;
} else if (3) {
;
} else {
;
}
Пример цикла while:
while(condition) {
continue;
}
Пример цикла for:
for(var i = 0; i<10; i += 1) {
break;
}
Nasal также имеет два вида циклов, которые перебирают элементы вектора:
forindex
получает индекс вектора. Индекс будет равен от 0 до size(elem)-1.Пример цикла forindex
:
forindex(var i; elem) {
print(elem[i]);
}
foreach
получает элемент вектора. Элемент будет равен elem[0] до elem[size(elem)-1].Пример цикла foreach
:
foreach(var i; elem) {
print(i);
}
chr()
.Пример использования Subvec:
a[0];
a[-1, 1, 0:2, 0:, :3, :, nil:8, 3:nil, nil:nil];
"hello world"[0];
Это не очень эффективно, потому что хеш-карты используют строку в качестве ключа для сравнения. Но если это действительно полезно, эффективность может быть не так важна...
Пример специального вызова функции:
f(x:0, y:nil, z:[]);
Также функции имеют такое использование:
Пример лямбда-функций:
func(x, y) {
return x+y
}(0, 1);
func(x) {
return 1/(1+math.exp(-x));
}(0.5);
Существует интересный тестовый файл y-combinator.nas
, попробуйте его для развлечения:
var fib = func(f) {
return f(f);
}(
func(f) {
return func(x) {
if(x<2) return x;
return f(f)(x-1)+f(f)(x-2);
}
}
);
Пример замыкания:
var f = func() {
var a = 1;
return func() {return a;};
}
print(f()());
Замыкания упрощают ООП.
Пример ООП с использованием замыканий:
var student = func(n, a) {
var (name, age) = (n, a);
return {
print_info: func() {println(name, ' ', age);},
set_age: func(a) {age = a;},
get_age: func() {return age;},
set_name: func(n) {name = n;},
get_name: func() {return name;}
};
}
Ещё один способ ООП — это черта.
Когда хеш имеет член с именем parents
и значение типа вектор, то при попытке найти член, которого нет в этом хеше, виртуальная машина будет искать член в parents
. Если есть хеш, у которого есть этот член, вы получите значение члена.
Используя этот механизм, мы можем реализовать ООП. Перевод текста с английского на русский язык:
Подобным образом, результатом будет «114514»:
var trait = {
get: func {return me.val;},
set: func(x) {me.val = x;}
};
var class = {
new: func() {
return {
val: nil,
parents: [trait]
};
}
};
var a = class.new();
a.set(114514);
println(a.get());
Первая виртуальная машина не может найти член set
в хэше a
, но в a.parents
есть хэш trait
, который имеет член set
, поэтому мы получаем set
.
Переменная me
указывает на хэш a
, поэтому мы меняем a.val
.
И get
имеет тот же процесс.
И мы должны напомнить вам, что если вы сделаете это:
var trait = {
get: func {return me.val;},
set: func(x) {me.val = x;}
};
var class = {
new: func() {
return {
val: nil,
parents: [trait]
};
}
};
var a = class.new();
var b = class.new();
a.set(114);
b.set(514);
println(a.get());
println(b.get());
var c = a.get;
var d = b.get;
println(c());
println(c());
println(d());
println(d());
Вы получите такой результат сейчас:
114
514
514
514
Потому что a.get
установит me=a
в trait.get
. Затем b.get
сделает me=b
. Так что фактически c
тоже является b.get
после выполнения var d=b.get
.
Если вы хотите использовать этот трюк для повышения эффективности программы, вы должны знать этот специальный механизм.
Подробнее см. в namespace.md
Эта часть показывает, как мы добавляем собственные функции в этом интерпретаторе. Если вас интересует эта часть, это может вам помочь. И...
ОСТОРОЖНО: Если вы хотите добавить свои собственные функции без изменения исходного кода, см. раздел module
после этой части.
Если вы действительно хотите изменить исходный код, проверьте встроенные функции в lib.nas
и посмотрите пример ниже.
Определение:
// вы также можете использовать макрос для определения одного.
var builtin_print(context*, gc*);
Затем завершите эту функцию, используя C++:
var builtin_print(context* ctx, gc* ngc) {
// находим значение с индексом, начинающимся с 1
// потому что local[0] зарезервирован для значения 'me'
for(auto& i : ctx->localr[1].vec().elems) {
std::cout << i;
}
std::cout << std::flush;
// генерируем возвращаемое значение,
// используем ngc::alloc(type) для создания нового значения
// или используем зарезервированные ссылки nil/one/zero
return nil;
}
При запуске встроенной функции alloc
будет выполняться более одного раза, это может вызвать маркировку-очистку в gc::alloc
.
Полученное ранее значение будет собрано, но всё ещё используется во встроенной функции, это вызовет фатальную ошибку.
Поэтому используйте gc::temp
во встроенных функциях для временного хранения управляемого gc значения, которое вы хотите вернуть позже. Вот так:
var builtin_keys(context* ctx, gc* ngc) {
auto hash = ctx->localr[1];
if (hash.type!=vm_hash && hash.type!=vm_map) {
return nas_err("keys", "\"hash\" must be hash");
}
// используем gc.temp для хранения управляемого значением gc, чтобы избежать очистки
auto res = ngc->temp = ngc->alloc(vm_vec);
auto& vec = res.vec().elems;
if (hash.type==vm_hash) {
for(const auto& iter : hash.hash().elems) {
vec.push_back(ngc->newstr(iter.first));
}
} else {
for(const auto& iter : hash.map().mapper) {
vec.push_back(ngc->newstr(iter.first));
}
}
ngc->temp = nil;
return res;
}
После этого зарегистрируйте имя встроенной функции (в nasal) и указатель функции в этой таблице:
nasal_builtin_table builtin[] = {
{"__print", builtin_print},
{nullptr, nullptr}
};
Наконец, оберните __print
в носовой файл:
var print = func(elems...) {
return __print(elems);
};
На самом деле аргументы, которые использует __print
, не являются необходимыми.
Так что писать это так тоже правильно:
var print = func(elems...) {
return __print;
};
Если вы не обернёте встроенную функцию в обычную носовую функцию, эта собственная функция может вызвать segmentation fault при поиске аргументов.
Используйте import("filename.nas")
для получения носового файла, включая... Функции в Nasal: использование модулей C++
Если вы хотите использовать встроенные функции, то можете это сделать. Также есть ещё один способ импорта назальных файлов, оба способа имеют одинаковую функцию:
use dirname.dirname.filename;
import("./dirname/dirname/filename.nas");
Если существует только один способ добавить свои собственные функции в назальный код, это действительно неудобно. К счастью, мы разработали несколько полезных встроенных функций, которые помогут вам добавлять созданные вами модули.
Функции, используемые для загрузки динамических библиотек, добавляются в std/dylib.nas
:
var dlopen = func(libname) {
...
}
var dlclose = func(lib) {
...
}
var dlcall = func(ptr, args...) {
...
}
var limitcall = func(arg_size = 0) {
...
}
Как видите, эти функции используются для загрузки динамических библиотек в среду выполнения назального кода и их выполнения. Давайте посмотрим, как они работают.
Сначала напишите файл C++, который вы хотите преобразовать в динамическую библиотеку, используя fib.cpp
в качестве примера (примеры кодов находятся в папке ./module
):
// добавьте заголовочный файл nasal.h, чтобы получить API
#include "nasal.h"
double fibonaci(double x) {
if (x<=2) {
return x;
}
return fibonaci(x-1)+fibonaci(x-2);
}
// пример списка параметров функций модуля
var fib(var* args, usize size, gc* ngc) {
if (!size) {
return nas_err("fib", "не хватает аргументов");
}
// аргументы генерируются в vm_vec: args
// получите значения из вектора, которые должны быть использованы здесь
var num = args[0];
// если вы хотите, чтобы ваша функция была более безопасной, попробуйте это
// nas_err выведет информацию об ошибке на экран
// и вернёт vm_null для прерывания выполнения
if(num.type!=vm_num) {
return nas_err("extern_fib", "\"num\" должно быть числом");
}
// хорошо, вы должны знать, что vm_num теперь не управляется сборщиком мусора
// если хотите вернуть объект gc, используйте ngc->alloc(type)
// использование gc такое же, как добавление встроенной функции
return var::num(fibonaci(num.tonum()));
}
// затем поместите имя функции и адрес в эту таблицу
// убедитесь, что конец таблицы равен {nullptr,nullptr}
module_func_info func_tbl[] = {
{"fib", fib},
{nullptr, nullptr}
};
// необходимо написать эту функцию, она поможет назальному коду
// получить указатель на функцию по имени
// причина использования этого способа получения указателя на функцию
// заключается в том, что `var` имеет конструкторы, которые несовместимы с C
// поэтому "extern "C" var fib" может вызывать предупреждения компиляции
NASAL_EXTERN module_func_info* get() {
return func_tbl;
}
Затем скомпилируйте этот fib.cpp
в динамическую библиотеку.
Linux (*.so
):
clang++ -c -O3 fib.cpp -fPIC -o fib.o
clang++ -shared -o libfib.so fib.o
Mac (*.so
& *.dylib
): аналогично Linux.
Windows (*.dll
):
g++ -c -O3 fib.cpp -fPIC -o fib.o
g++ -shared -o libfib.dll fib.o
Затем мы пишем тестовый назальный файл для запуска этой функции fib
:
use std.dylib;
var dlhandle = dylib.dlopen("libfib");
var fib = dlhandle.fib;
for(var i = 1; i<30; i += 1)
println(dylib.dlcall(fib, i));
dylib.dlclose(dlhandle.lib);
Функция dylib.dlopen
используется для загрузки динамической библиотеки и получения адреса функции.
Функция dylib.dlcall
используется для вызова функции, первый аргумент — это адрес функции, убедитесь, что этот аргумент является vm_obj
и type=obj_extern
.
Функция dylib.dlclose
используется для выгрузки библиотеки, в момент вызова функции все адреса функций, полученные из неё, становятся недействительными.
Функция dylib.limitcall
используется для получения функции dlcall
, которая имеет ограниченный размер параметра, эта функция может повысить производительность вашего кода, поскольку она не использует vm_vec
для хранения аргументов, а вместо этого использует локальную область видимости для их хранения, так что это может избежать частого сбора мусора. И приведённый выше код также можно записать следующим образом:
use std.dylib;
var dlhandle = dylib.dlopen("libfib");
var fib = dlhandle.fib;
var invoke = dylib.limitcall(1); # это означает, что вызываемая функция имеет только один параметр
for(var i = 1; i<30; i += 1)
println(invoke(fib, i));
dylib.dlclose(dlhandle.lib);
Если у вас всё получилось, поздравляем!
./nasal a.nas
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
``` ## Ghost Type (для разработчиков lib)
Теперь довольно просто создать нового ghost самостоятельно.
Посмотрите на пример ниже:
```c++
const auto ghost_for_test = "ghost_for_test";
// объявляем деструктор для типа ghost
void ghost_for_test_destructor(void* ptr) {
std::cout << "ghost_for_test::destructor (0x";
std::cout << std::hex << reinterpret_cast<u64>(ptr) << std::dec << ") {\n";
delete static_cast<u32*>(ptr);
std::cout << " delete 0x" << std::hex;
std::cout << reinterpret_cast<u64>(ptr) << std::dec << ";\n";
std::cout << "}\n";
}
var create_new_ghost(var* args, usize size, gc* ngc) {
var res = ngc->alloc(vm_obj);
// создаём тип ghost
res.ghost().set(ghost_for_test, ghost_for_test_destructor, new u32);
return res;
}
var set_new_ghost(var* args, usize size, gc* ngc) {
var res = args[0];
if (!res.object_check(ghost_for_test)) {
std::cout << "set_new_ghost: not ghost for test type.\n";
return nil;
}
f64 num = args[1].num();
*(reinterpret_cast<u32*>(res.ghost().pointer)) = static_cast<u32>(num);
std::cout << "set_new_ghost: successfully set ghost = " << num << "\n";
return nil;
}
var print_new_ghost(var* args, usize size, gc* ngc) {
var res = args[0];
// проверяем тип ghost по имени типа
if (!res.object_check(ghost_for_test)) {
std::cout << "print_new_ghost: not ghost for test type.\n";
return nil;
}
std::cout << "print_new_ghost: " << res.ghost() << " result = "
<< *((u32*)res.ghost().pointer) << "\n";
return nil;
}
Мы используем эту функцию для создания нового типа ghost:
void nas_ghost::set(const std::string&, nasal::nas_ghost::destructor, void*);
const std::string&
— это имя типа ghost.
nasal::nas_ghost::destructor
— указатель деструктора типа ghost.
void*
— указатель экземпляра типа ghost.
И мы используем эту функцию, чтобы проверить, является ли значение правильным типом ghost:
bool var::object_check(const std::string&);
Параметр — это имя типа ghost.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарий ( 0 )