Login

Subversion Repositories NedoOS

Rev

Rev 637 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

        Введение

Язык NedoLang - подмножество языка Си со строгой типизацией. Программа на NedoLang, оформленная по рекомендациям совместимости, может компилироваться компилятором Си при подключении файла nedodefs.h.

Пакет NedoLang позволяет разрабатывать загружаемые программы (todo и ПЗУ) для Z80 и ARM Thumb без привлечения внешних утилит и библиотек, не считая эмуляторов/отладчиков. Кроме того, поскольку компилятор может компилировать сам себя, то можно работать непосредственно на Z80 (todo и на ARM Thumb).

        Как начать работу

# Создайте в каталоге nedolang каталог своего проекта. Перейдите в этот каталог.

# Создайте исходник главного модуля main.c. В нём напишите:
PROC main()
{
}

# Создайте файл стартапа main.s. В нём напишите:
        org 0x6000 ;адрес размещения программы для Z80 (для ARM см. инструкцию к вашему процессору)
        jp main ;переход на процедуру main()
        include "main.ast" ;код главного модуля
        include "main.var" ;переменные главного модуля
        include "../_sdk/lib.i" ;стандартная библиотека

# Создайте скрипт компиляции compile.bat. В нём напишите (для Z80 - для ARM используется nedolarm, nedotarm, nedoaarm и нет стандартной библиотеки lib.i):
@echo off
path=..\_sdk\

echo ...compiling...
nedolang main.c
type err.f

echo ...tokenizing...
nedotok main.s main.ast main.var ../_sdk/lib.i

echo ...assembling...
nedoasm main.S_
type asmerr.f

pause

Уже можно проверять, что ваша программа компилируется - запускайте compile.bat и смотрите ошибки. Сейчас ошибок нет, показывается только lbls ... buf ... - количество меток и размер буфера под них. Ваша программа скомпилирована в main.bin.

# Подключите необходимые библиотеки, например:
В начале main.c:
#include "../_sdk/print.h"
В конце main.s:
        include "../_sdk/print.i" ;код библиотеки print
В compile.bat исправьте строчку:
nedotok main.s main.ast main.var ../_sdk/lib.i ../_sdk/print.i

Можно проверить, что программа компилируется с библиотеками.

# Добавьте код в main():
  setxy(0x00,0x00);
  nprintf("Hello world!\n");

# Добавьте скрипт запуска run.bat (для Z80):
call compile.bat
nedotrd test.trd -n
nedotrd test.trd -s 24576 -ac main.bin
..\..\emul\emul.exe test.trd

Предполагается, что эмулятор emul.exe лежит в каталоге ..\..\emul\ (т.е. в соседнем с каталогом nedolang).

Теперь у вас на диске test.trd есть запускаемый кодовый файл main.C с адресом 24576 и открывается эмулятор.

# В ..\batch\basics.trd есть бейсик-загрузчик boot, который загружает и запускает main.C с адресом 24576. Добавьте в свой run.bat перед запуском эмулятора:
nedotrd ..\batch\basics.trd -eh boot.$b
nedotrd test.trd -ah boot.$b

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

# Можете удалить временные файлы (*.ast, *.var, *.org, *.pst, ошибки и токенизированные ассемблерные тексты) утилитой clean.bat в корневом каталоге nedolang.

Если когда-нибудь понадобится подключить самодельный ассемблерный файл, назначьте ему расширение *.s, чтобы утилита clean.bat его не удалила.

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

Язык в текущей версии не обеспечивает аналогов следующих конструкций Си:
- многомерные массивы (можно взамен использовать вычисления индекса или массивы указателей);
- числа с плавающей точкой (todo!);
- sizeof(<expr>) - есть только +sizeof(<type>), +sizeof(<var>) не для массивов;
- sizeof нельзя использовать в константах;
- #include <filename> (есть только #include "filename");
- макросы #define (нерекомендуемое поведение, есть только константы #define);
- for (следует взамен использовать while или repeat);
- статические структуры и массивы структур (сейчас доступ к структурам только по указателю);
- вложенные структуры (todo!);
- объединения (нерекомендуемое поведение);
- копирование константных строк через =;
- выход из середины функции по return (нерекомендуемое поведение);
- выход из середины программы по exit (нерекомендуемое поведение);
- вызов функции по указателю с параметром и возвращаемым значением (сейчас можно вызывать по указателю только процедуры без параметров);
- тип указателя на указатель (можно обойти, т.к. тип указателя указывается при разыменовании - *(<poitype>)poi; poke *(<poitype>)(<poiexpr>) = <expr>).

Команды собираются в блоки, окружённые фигурными скобками {<command><command>...<command>}.
Такой блок эквивалентен одиночной команде и допустим везде, где допустима одиночная команда.
После каждой команды для совместимости с Си рекомендуется (но не требуется) ставить точку с запятой.

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

Стандартные комментарии оформляются как /**комментарий*/. Если впереди только одна звёздочка, то это не комментарий.
Стандартные комментарии не могут быть вложенными.
Есть также стандартные однострочные комментарии от // до конца строки.
Нестандартные однострочные комментарии: от ;; до конца строки.
Также игнорируются строки, которые начинаются с #, кроме реализованных команд с # (см. ниже).
Комментарии можно ставить между любыми словами, кроме следующих случаев:
- после первого слова команды (даже для команд, определяемых по формату, даже между меткой и двоеточием и между <varname> и знаком '=');
- внутри размера массива при его объявлении, то есть так нельзя: var int a[100/**комментарий*/];
- между именем переменной и квадратной скобкой индекса массива, то есть так нельзя: a/**комментарий*/[10].
- между именем типа и *.
Все комментарии при компиляции передаются в ассемблерный текст тоже в виде комментариев (если задана соответствующая опция компилятора, см. ниже).

Полное имя переменной (которое используется в ассемблере) строится из имени текущей области видимости (в текущей версии пустое), имени самой переменной и постфикса.
Полное имя "__sys._petyamodule._pet" означает: область видимости "__sys._petyamodule", переменная "_pet", заданная в исходном тексте (нет точки в конце).
Полное имя "__globalarr" означает: глобальная переменная "__globalarr", заданная в исходном тексте (нет точки в конце).
Полное имя "__sys.mainproc.A." означает: область видимости "__sys.mainproc", переменная "A.", созданная автоматически (отмечено точкой ".").
Таким образом, автоматически созданные переменные не пересекаются по именам с переменными, заданными в исходном тексте.
Так же строятся полные имена заголовков функций (без точки в конце) и меток для перехода (пользовательские без точки, автоматические с точкой в конце).

Переменные и прочие имена доступны из языка следующим образом:
i для доступа к переменной i, определённой в текущей области видимости (то есть в текущей функции) или к глобальной переменной.
func2() для доступа к функции func2(), определённой снаружи текущей области видимости (то есть в текущем модуле).
//_submodule._v для доступа к переменной _v, определённой в дочерней области видимости _submodule.
//Не имеет смысла называть модули без подчёркивания - они будут недоступны из внешнего модуля.
//_submodule.proc2() для доступа к процедуре proc2(), определённой в дочерней области видимости _submodule.
//__sys._anothermodule.func5() для доступа к функции func5(), определённой в модуле _anothermodule, включенном в глобальный модуль __sys.

Автоматически созданные переменные недоступны из языка.

Идентификаторы (имена переменных, процедур, функций, меток) должны начинаться с буквы (или знака подчёркивания для глобальных переменных и глобальных процедур/функций) и состоять из букв, цифр и знаков подчёркивания. Полное имя переменной не должно быть длиннее 79 знаков. Разрешается использовать русские буквы в идентификаторах. Русские буквы остаются в том же виде, как они закодированы в исходном тексте (в кодировке cp866, cp1251 или utf8).

Имеются следующие типы данных:
* BYTE (имеет размер 1 байт, без знака)
* BOOL (допустимы только значения +TRUE и +FALSE, размер не регламентируется)
* CHAR (имеет размер 1 байт, знаковость не регламентируется)
* INT (знаковое целое, имеет размер в одно слово процессора)
* UINT (беззнаковое целое, имеет размер в одно слово процессора)
* LONG (беззнаковое длинное целое, имеет размер в два слова процессора)
[* FLOAT (пока клон LONG, кроме таргета Script - там работает как double)]
* PBYTE,PBOOL,PCHAR,PINT,PUINT,PLONG,[PFLOAT], <typename>* (указатель - имеет размер в одно слово процессора)
* STRUCT <structname> (структура <structname> - только для константных структур и +sizeof(STRUCT <structname>))
* STRUCT <structname>* (указатель на структуру <structname>)

Тип BOOL в текущей версии имеет размер 1 байт, логические значения следует писать +TRUE и +FALSE (в текущей реализации равны 0xff и 0x00, но не рекомендуется смешивать логические значения с числовыми).
Любое значение, отличное от FALSE, воспринимается как TRUE, но не рекомендуется использовать этот факт для будущей совместимости.

Узнать размер типа, переменной простого типа или структуры в байтах можно с помощью +sizeof(<type>). Выражение +sizeof(<type>*) выдаёт размер указателя.

Разрешается использовать необъявленные константы в форме +<variable>, если имя начинается со знака подчёркивания '_'. В этом случае константа получает тип BYTE (иначе BOOL). Рекомендуется использовать такую запись только для констант из enum.

В выражениях строго проверяются типы. В любой операции с двумя операндами типы операндов должны совпадать.

Приведение типов выглядит так:
(<type>)<value>

Поскольку наш язык не допускает разные типы в одном операторе, то для смещения указателя на N байт вперёд используется выражение:
(<type>)((UINT)<pointername> + N)
Или для PBYTE:
&<pointername>[N]
Для получения указателя на массив используется выражение:
(PCHAR)<arrayname>
При использовании вызова функции в качестве параметра надо ставить правильный тип указателя (указатель не равен массиву, в отличие от Си).

Чтение по указателю допускается только с непосредственным приведением типа: *(<pointertype>)<pointervalue>

Вызов функций оформляется как <procname>([<expression>,...]).
Процедуры и функции технически имеют плавающее число параметров, но не рекомендуется использовать это поведение.

В выражениях <expression> имеются следующие приоритеты операций:
1. Индексные квадратные скобки: arr[] или pointer[]
2. Префиксы (+, -, ~ (инверсия), ! (логическое отрицание), & (взятие адреса переменной &<variable>, процедуры/функции &<func>, элемента массива &<array>[<expression>], поля структуры &(<structname>-><field>)), * (чтение по указателю)).
3. Умножение, деление, &, &&.
4. Сложение, вычитание, |, ^, ||, -> (адресация элемента структуры по указателю).
5. Сравнения и сдвиги (<<, >>). Сравнения и сдвиги работают только для типов BYTE, CHAR, INT, UINT. Сдвиги - также для LONG. Для BOOL разрешены только сравнения на равенство и неравенство. Сравнение на равенство можно писать == или =, но второй вариант несовместим с Си.

В выражениях допустимы следующие виды значений:
* идентификатор переменной - получает тип по типу переменной.
* целая числовая константа без знака - получает тип BYTE (если запись в стиле 0xff с не более чем 2 цифрами или в стиле 0b111 с не более чем 8 цифрами), LONG (если в конце стоит L) или UINT (в остальных случаях).
* целая числовая константа со знаком (+ или -) - получает тип INT.
* числовая константа с плавающей точкой - получает тип FLOAT (который пока не поддерживается).
* символьная константа 'c' или '\<символ>' (допустимы только '\n', '\r', '\t', '\0', '\'', '\"', '\\'), где <символ> - один символ - получает тип char.
* строковая константа "строка" - получает тип PCHAR. Допускается запись строковых констант в виде "str1""str2" (между ними допустим перевод строки) для наглядности и для избегания переполнения буфера строки (79 символов) в компиляторе. Строковые константы создаются автоматически с нулевым кодом в конце.
* константное выражение +(expr) - получает тип по левому контексту.

Целые числовые константы могут быть десятичные (100), шестнадцатеричные (0x10), двоичные (0b11), восьмеричные (0o177 или как вариант 0177 с выдачей предупреждения из-за двусмысленности записи).

Команды, которые определяются по формату:

* <labelname><:> - определить метку для перехода. Метка должна быть уникальной внутри текущей области видимости. Подчёркивание добавлено, чтобы метку было лучше видно в тексте программы.
* <label>([<expression>,...]) - вызвать процедуру. Нельзя ставить пробел перед открывающей скобкой (ср. формат if).
* <var>=<expression> - вычислить и записать в переменную. Тип выражения должен соответствовать типу переменной. Получить в указатель адрес массива можно так: poi=(PBYTE)arr.
* <var><[><expression><]>=<expression> - вычислить и записать в ячейку массива. Таким же образом можно писать в массив, адрес которого передан как указатель. Будьте осторожны, границы массива не проверяются!
Доступ к ячейкам массива на чтение делается так же: <var><[><expression><]> (в том числе если адрес массива передан как указатель).
* <var>-><field>=<expression> - вычислить и записать в поле структуры (которая лежит по указателю <var>). Допускаются цепочки ->.

Команды, которые начинаются с ключевого слова:

* const - определить константу: const<type><variable>[=<constnum>], где <constnum>::=[-|+]<num>|'<char>'|"<string>"["<string>"...] или для массивов/структур: const<type><variable><[><expr><]>={<constnum>[,<constnum>...]}
Пропуск значения или списка значений нужен для использования внешних констант (не из текущего компилируемого модуля). Список значений для массива должен соответствовать числу значений в массиве (не проверяется).
Нельзя определять два раза одну и ту же константу со значением (TODO проверять в ассемблере). См. выше про необъявленные константы.
* extern - описать тип внешней переменной: extern<type><variable> - допускается только снаружи процедур и функций (не проверяется).
* var - определить переменную: var<type><variable>[=<expression>] - если с присваиванием, то допускается только внутри процедур или функций (не проверяется). Это именно присваивание, а не определение начального значения переменной. Нельзя присваивать массивы (не проверяется). В рекурсивных процедурах и функциях присваивание в var запрещено (не проверяется), а после блока всех var всё последующее тело функции должно быть в фигурных скобках {} (не проверяется), иначе переменные не будут правильно восстанавливаться при рекурсии.
* var с квадратными скобками - определить массив фиксированного размера: var<type><variable><[><expression><]> - ячейки массива нумеруются с 0, то есть в массиве a[10] не существует ячейки с индексом 10. Индекс должен иметь тип byte или uint.
* enum - определить последовательный ряд констант: enum{<enumconstname>[=<number>],<enumconstname>[=<number>]...} - первая константа получит значение 0, вторая 1 и т.д., =<number> меняет этот счётчик. Эти константы невидимы как переменные, поэтому для использования их надо либо объявить через const, либо использовать +<enumconstname> (см. выше про необъявленные константы). Разрешена запятая после последнего элемента.
* evar {UINT var1 = 1, INT var2 = 2, FLOAT var3} и т.п. создаёт переменные заданого типа с заданными адресами.
* poke* - вычислить и записать в память с нужным типом: poke*<pointervalue>=<expression> - ключевое слово нужно, чтобы можно было пропускать ; в конце операторов.
[* module - определить модуль (область видимости): module<label><command> - команда <command> создаётся в области видимости внутри текущей области видимости (например, если была область видимости mainmodule, то внутри команды "module submodule{...}" будет область видимости mainmodule.submodule. Можно повторно определять одну и ту же область видимости, чтобы добавлять туда что-то новое.]
* proc - определить процедуру: proc<procname>[recursive][forward]([<type><par>,...])[<command>] - тоже создаёт область видимости внутри текущей области видимости. Поэтому <procname> должно быть уникальным внутри текущей области видимости. Если есть слово forward, то тело процедуры/функции не создаётся, и <command> не нужна (используется при использовании внешних процедур/функций или если их тело описано ниже вызова). Если есть слово recursive, то локальные переменные сохраняются при входе-выходе в процедуру/функцию, а при вызове сохраняются старые значения параметров. Внутри рекурсивной процедуры/функции нельзя объявлять массивы (не проверяется).
* func - определить функцию: func<type><funcname>[recursive][forward]([<type><par>,...])[<command>] - тоже создаёт область видимости внутри текущей области видимости. Поэтому <funccname> должно быть уникальным внутри текущей области видимости. См. выше про слова recursive и forward.
* if - альтернатива: if (<boolexpression>) <command>[else<command>]; - ';' против ошибки "if (expr); cmd" и против ошибки вложенных неполных альтернатив. Пробел после if обязателен (ср. формат вызова процедуры). При вложенных if достаточно одного ';'.
*TODO ifnot
*TODO ifz
*TODO ifnz
* while - цикл с предусловием: while (<boolexpression>) <command>[;] - ';' против ошибки "while(expr);cmd".
*TODO whilenot
*TODO whilez
*TODO whilenz
* repeat - цикл с постусловием: repeat<command>until (<boolexpression>) - если сделать скобки необязательными, то не получится определить "логическое выражение верхнего уровня", которое можно оставить во флаге.
*TODO untilnot
*TODO untilz
*TODO untilnz
* break - выйти из цикла while или repeat или из блока switch: параметров не имеет, просто break.
* return - вернуть значение из функции: return<expression> - должна быть последней командой в функции. Тип возвращаемого значения должен соответствовать типу функции.
* goto - перейти на метку: goto<labelname> - разрешается только внутри текущей процедуры или функции.
* call - вызвать процедуру по указателю: call (<expression>) - тип выражения - любой указатель или UINT (это не проверяется).
* asm - ассемблерная вставка: asm(" cmd1"" cmd2""label1"...) - каждая команда генерируется как отдельная строка. Нельзя писать команды без пробелов или табуляций вначале - токенизатор и ассемблер их не поймут.
* inc - увеличить значение переменной на единицу: inc <var>
* dec - уменьшить значение переменной на единицу: dec <var>
* struct - объявить структуру: struct<structname>{<type1><field1>[;]<type2><field2>[;]...} - допускается использование в структуре указателей на структуры, объявленные выше, и на саму себя (для совместимости с Си надо в объявлении структуры поля, являющиеся указателями на структуры, предварять словом struct). Потом можно объявить указатель на структуру: var struct <structname>* <structpointername> - и использовать поля структуры: <structpointername>-><field>
* switch - блок множественного ветвления по значению типа byte:
switch (<byteexpr>){
case <byteconst>: [<commands>]
case <byteconst>: [<commands>]
...
default: [<commands>]
};
Разрешается только один switch в процедуре/функции. Все названия веток <byteconst> должны быть описаны в const или enum. Поля "case <byteconst>:" и "default:" (ветка по умолчанию) эквивалентны меткам (label), поэтому они могут стоять в произвольных местах, а автоматический выход из веток не предусмотрен. Для этого можно использовать goto или break (break здесь выходит из switch).
* #include "filename" - вложенно включить текст из другого файла. Используется для подключения внешних определений (дублирования на данный момент запрещены).
* #define <constname> <value> или #define <costname> (<type>)(<expr>) - определить константу совместимо с Си. В отличие от const, можно преопределять. Требуется для констант в заголовочных файлах и констант размера массивов, можно и для других случаев.
# #undef <constname> - удалить ранее определённую константу (или любой другой идентификатор, но это будет несовместимо с Си).
* условная компиляция (разрешается вложенная): #ifdef <defined_const> ... [#else] ... #endif или #ifndef <defined_const> ... [#else] ... #endif.
* export - ставится перед определением (процедуры/функции, переменной, константного массива/структуры), смещение адреса которого нужно экспортировать в файл filename.D_.
* typedef <type> <typename> - определить новый тип на основе старого. Для структурных типов надо писать слева слово struct для совместимости с Си.

Все команды с # должны начинаться с начала строки и занимать всю строку.

В условиях (if, while, until) имеется оптимизация сравнений за счёт проверки, что они осуществляются на 1-м уровне вложенности выражения (скобки не учитываются - они являются частью синтаксиса команды). Поэтому на этом же уровне нельзя писать другие операции (например, нельзя a==b==c, можно (a==b)==c). Это не проверяется?
В вычислениях (присваивание, var, return, параметры вызовов) сравнения можно писать только в скобках, чтобы они не оказались на 1-м уровне вложенности (например, нельзя a = b==c, можно a = (b==c)).

Рекомендуемый стиль оформления условий:
IF (cond1) do1();
IF (cond2) {
  do2();
}ELSE IF (cond3) {
  if (cond4) {do3();
  }ELSE do4();
};
IF (cond5) {
  do5();
}ELSE IF (cond6) {
  do6();
}ELSE {
  do7();
};

Рекомендуется все ключевые слова языка писать большими буквами, а в текущей версии - типы и STRUCT обязательно большими буквами.
Чтобы скомпилировать такой же исходник с помощью компилятора Си, нужно использовать #include "nedodefs.h".

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

        Структура проекта
        
Проект состоит из модулей на NedoLang (могут включать другие модули через #include) и на ассемблере (могут включать другие ассемблерные модули через include). Модули по одному передаются в компилятор (кроме включаемых через #include), потом все ассемблерные файлы надо токенизировать, потом надо вызвать NedoAsm с именем стартап-файла *.s.

Стартап-файл - ассемблерный файл с запускающим кодом, должен включать (через include "filename.ast") остальные файлы проекта. В реальности вместо filename.ast будет грузиться filename.A_ и т.п.

nedolang.exe - компилятор, имеет следующие параметры:
filename1.c filename2.c ... - компилировать перечисленные модули по одному.
-C - включить выдачу комментариев (для компиляции).
-H - включить выдачу подробного лога (для компиляции).
Выходные файлы компилятора:
*.ast - ассемблерный текст с кодом
*.var - ассемблерный текст с данными
err.f - ошибки компиляции
[label.f - метки во внутреннем формате]

nedotok.exe - токенизатор, вызывается так:
nedotok filename1.ast filename2.var filename3.s filename4.i ...
(выходные файлы будут называться filename1.A_, filename2.V_ и т.п.).

nedoasm.exe - ассемблер, вызывается так:
nedoasm filename.A_
Выходные файлы ассемблера:
filename.bin - все сгенерированные байты кода и данных подряд
filename.org - данные релокации (в формате: 2 байта смещение, где лежит релоцируемый адрес)
[label0.f..label7.f - метки во внутреннем формате]
filename.pst - пост-метки во внутреннем формате (пока не поддерживается)
asmerr.f - ошибки ассемблирования
filename.D_ - токенизированный файл деклараций (команды export label в ассемблере)
Ассемблер выгружает код подряд, поэтому допустим только один ORG - начальный. Выравнивать данные можно через DS -$&mask.

nedoexp.exe - детокенизатор (отладочный экспорт токенизированного ассемблера), вызывается так:
nedoexp filename.A_
(выходной файл exp.f)

        Библиотеки:

- Ассемблерный файл lib.i содержит стандартные математические функции (умножение, деление, сдвиги) для Z80.
- Заголовочный файл str.h содержит функции работы со строками. Для его использования подключайте в стартапе под Z80 str.i (или же вместо него str.c компилируйте в str.ast, str.var и подключайте их в стартапе).
- Заголовочный файл io.h одержит функции работы с файлами. Для его использования подключайте в стартапе под Z80 iofast.i под TR-DOS или io_os.i под NedoOS (или же вместо него io.c под TR-DOS без функций низкого уровня компилируйте в io.ast, io.var и подключайте их в стартапе).
- runtime.h, runtime.i - необязательная рантайм-библиотека для Z80 (прерывания, порты).
- sprite.h, sprite.i - спрайтовая библиотека для Z80.
- print.h, print.i (print_os.i для NedoOS) - библиотека вывода текста для Z80 (setxy, prchar, nprintf).

        Утилиты для Windows:

- nedotrd - работа с образом диска в формате TRD. Можно перевести на NedoLang, если в файловой библиотеке поддержать ftell(pos)/fseek.
- nedores - конвертор графических ресурсов. Пока затруднительно перевести на NedoLang из-за размера массива.
- nedopad - обрезка или дополнение файла до заданного размера. Можно перевести на NedoLang.
- _gui - простейший интерфейс ввода и компиляции исходника на NedoLang (можно с автоматической перекомпиляцией в процессе ввода) с просмотром ошибок и результата в ассемблерном виде.

Утилиты на NedoLang:
- nedodefb - перевод файла в ассемблерное шестнадцатеричное представление с обрезкой или дополнением (пока только для Z80). Можно перевести на NedoLang. Необязательно при использовании предыдущей утилиты.
- nedodiff - сравнение двух файлов (пока только для Z80, т.к. использует print.i).
- batch - интерпретатор командных файлов (пока только для Z80, лежит по адресу 64000).
- nedodel - удаление файла.
- movedisk - уплотнение образа диска после удаления файлов.
- diff - сравнение двух файлов.

При указании путей в #include, include и в командной строке надо использовать прямую косую черту '/'.
Результат компиляции кладётся в ту же директорию, где лежит исходный файл.

Формат ассемблерного текста зависит от архитектуры целевого процессора.

Поддерживаются директивы:
- db <byte>,<byte>... (dcb для ARM Thumb) - скомпилировать байты
- dw <word>,<word>... (dcd для ARM Thumb) - скомпилировать слова
- dl <long>,<long>... (dcq для ARM Thumb) - скомпилировать двойные слова
- ds <size>[,<byte>] (space для ARM Thumb) - скомпилировать пустой блок заданного размера, можно 0
- org <addr> - указать адрес размещения программы, с которого она будет работать
- align <2^n> (для ARM Thumb) - пропустить байты для выравнивания адреса размещения на адрес, кратный параметру (того же можно добиться с помощью ds -$&<2^n-1>
- end (для ARM Thumb - ничего не делает)
- include "filename.ast" - подключить исходник с заданным именем. Фактически подключается токенизированный filename.A_. Разрешается вложенность
- incbin "filename" - подключить бинарный файл
- <label>=<expression> - присвоить метку (можно переприсвоить)
- <label> equ <expression> - присвоить метку (можно переприсвоить - todo запретить)
- export <label> - выгрузить значение метки в файл filename.D_ (токенизированный) в виде <label>=_+<labelvalue>. Этот файл можно включать в ассемблерный текст через include "filename.dcl".
- todo disp
- todo ent

Текущий адрес компиляции можно получить с помощью знака $.

Определяемая метка должна стоять в начале строки.

Разрешается писать несколько команд в строке через пробел (через двоеточие пока не работает).

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

dw $,$ работает как dw $ dw $