XMConC
Введение
Данный учебник учит программированию на языке XMConC.
XMConC (также XmConC) — это высокоуровневый стековый язык программирования, который можно использовать для написания программ под Xmtwolime[1] и (раньше) GNU+Linux. XmConC многословен, и, в некоторых местах, усложняет написание программы, но его можно использовать как промежуточный язык. Учебник может устареть.
Синтаксис
XMConC использует для передачи параметров и возвращаемых значений стек.
2 3 + putn ;
Приведённый выше образец программы делает следующее:
- кладёт число 2 на вершину стека
- помещает число 3 на вершину стека
- вызывает функцию «+», которая складывает два числа из стека и кладёт результат в стек
- вызывает функцию «putn» для вывода числа из стека на экран
- очищает указатель стека, пробел перед «;» необязателен
Можно также записывать со скобками для упрощения чтения: (2 3 +) putn;.
- ASCII-строка создаётся в XMConC следующим образом:
"строка" название_строки. Далее, можно помещать адрес созданной строки в стек так:&название_строки
- препроцессорные директивы начинаются со знака «/». Пример —
/define one :1.
@функцияэквивалентно~__Cx ~функция goto __Cx:(x может быть 0, 1 и т. д.). Используется для вызова собственных функций (см. также программу из README-файла репозитория xmconcc).
- ассемблерные вставки задаются в формате
$ ассемблерный_код(работает только для Xmtwolime).
- комментарии начинаются со знака решётки (
#).
- именованные константы вставляются в код вот так:
{konstanta}.
- вместо целых чисел, в стек можно помещать коды символов вот так (если символ — не пробел):
'символ'.
- метки можно создавать в формате
название_метки:. Адрес метки можно положить в стек, используя конструкцию~название_метки.<название_метки>— для вставки адреса внешней метки (только для Xmtwolime).
- последовательности
\<новая строка>в коде игнорируются.
- Выражения
{переменная}!и{переменная} .синонимичны.
Препроцессор
Препроцессор выполняет обработку препроцессорных директив. Ниже представлена таблица всех его команд:
| Название | Описание | Примеры использования |
|---|---|---|
| define | Создание именованной константы. | /define CONST :12345
|
| alloc | Статическое выделение памяти. Адрес начала выделенной области можно вставлять как им. константу. Когда выделяется массив, также создаётся им. константа название_массива.length |
/alloc variable
|
Метки, переходы и «сон»
Функция goto выполняет переход без условия. sleep и msleep приостанавливают выполнение на определённое кол-во секунд или миллисекунд. Пример программы:
loop:
"hello, world" s
&s puts
1 sleep
~loop goto
Новые cat и echo…
Следующая программа принимает от пользователя символ, после чего печатает его, используя функцию putc:
loop:
getc putc
~loop goto
Как вам идея создать программу, которая будет выводит свои аргументы командной строки? Для XMConC, это легко!:
/alloc buf[128]
# копирование аргументов в массив buf
{buf} getargs
{buf} puts newline
0 exit
Радуга
Функции setbg и setcolor меняют цвета фона и текста. Они принимают один аргумент. Коды цветов практически как в iiixmish2:
| Значение | Цвет |
|---|---|
| 1 | белый |
| 2 | зелёный |
| 3 | голубой или синий |
| 4 | тёмно-зелёный |
| 5 | серый |
| 6 | красный |
| 7 | жёлтый |
Напишите определения им. констант с кодами цветов и создайте с ними программу, выводящую цвета радуги.
Что можно делать с числами
Узнаем, как производить вычитание, умножение и др. операции.
Азы
Сложение (+), вычитание (-), умножение (*) и деление (/) принимают два аргумента-числа. Точно также и с возведением в степень (**), ИЛИ (|), И (and), исключающим ИЛИ (^), побитовыми сдвигами (lsh и rsh) и получением остатка от деления (mod).
Инкремент и декремент
число ++
число --
Изменение знака (сделать отрицательное целое число положительным и наоборот)
число neg
Генерация
Следующая программа генерирует число от 0 до 7 (восемь минус один) и выводит результат на экран:
8 sel putn
Память
Функция . пишет в стек значение ячейки памяти:
{var} .
Команда = присваивает ячейке значение:
12345 {var} =
Как вы могли заметить, стек — не единственное хранилище. Можно создавать переменные и массивы, получать и изменять их содержимое. Кстати, значение на вершине стека можно «выбросить» командой drop; dup дублирует последний элемент стека.
Ветвления
Четыре команды — =?, !?, gt? и lt?, (равно, не равно, больше или меньше) — возвращают 0 или 1 в зависимости от того, истинно ли условие. Обычно, эти функции используются вместе с then. Пример перехода к метке «label», если переменные «a» и «b» равны:
{a}! {b}! =? ~label then
? возвращает истину только тогда, когда два предыдущих условия истинны. |? кладёт в стек число 1 лишь тогда, когда истинно хотя бы одно из двух предыдущих условий. ! инвертирует результат выполнения предыдущего условия.
Кто ты?
Ниже представлен текст программы, выводящей на экран идентификатор текущего пользователя:
getuid putn newline 0 exit
Работа с указателем вывода
newline выполняет переход на новую строку, backspace выполняет переход на один символ назад (в зависимости от реализации, действие может различаться), clear_output стирает текст с экрана. Напишем программу наподобие ncurses «clear»:
clear_output 0 exit
Как получить число?
Для получения 6-значного беззнакового числа от пользователя, можно воспользоваться приведённым ниже кодом:
getc 48 - getc 48 - getc 48 - getc 48 - getc 48 - getc 48 - # cat склеивает шесть чисел в стеке cat # выводим результат на экран putn newline 0 exit
Ниже представлен код функции для вывода n числа Фибоначчи (без рекурсии):
/alloc __ret
/define function :{__ret} =
/define return :({__ret} .) goto
/alloc fib.a
/alloc fib.b
/alloc fib.c
fib: {function}
/alloc fib.n
-- {fib.n} =
{fib.n}! 1 lt? ~fib.endif0 else # {
1 {return}
fib.endif0: # }
0 {fib.a} =
1 {fib.b} =
/alloc fib.i
(0 {fib.i} =) fib.for0: ({fib.i}! {fib.n}! lt? ~fib.endfor0 else) # {
{fib.a}! {fib.c} =
{fib.b}! {fib.a} =
({fib.c}! {fib.a}! +) {fib.b} =
(({fib.i}! ++) {fib.i} =) (~fib.for0 goto) fib.endfor0: # }
({fib.a}! {fib.b}! +) {return}
Пример использования:
/alloc i
(0 {i} =) for0: ({i}! 20 lt? ~endfor0 else) # {
{i}! @fib putn
32 putc # печатает пробел
(({i}! ++) {i} =) (~for0 goto) endfor0: # }
newline
0 exit
Ещё немного сведений о строках
- строки, как и в языке Си, заканчиваются символом NUL (
'\0'). - длину строки можно легко получить функцией strlen:
{my_string12345} strlen - не рекомендуется (для Xmtwolime) изменять содержимое строки, созданной конструкцией
"строка" название.
Проверка операционной системы
gnu_code выполняет переход, если программа выполняется в GNU+Linux; другими словами, она аналогична goto в операционной системе GNU. xm2_code делает то же, но только если программу исполняет iiixmish2.
Программа «Числа»
Задание
Найдите ошибку в данной программе, исправьте её.
/alloc c
# установка белого цвета
1 setcolor
loop:
getc {c} =
;
({c} .) '0' =? ~zero then
({c} .) '1' =? ~one then
({c} .) '2' =? ~two then
({c} .) '3' =? ~three then
({c} .) '4' =? ~four then
({c} .) '5' =? ~five then
({c} .) '6' =? ~six then
({c} .) '7' =? ~seven then
({c} .) '8' =? ~eight then
({c} .) '9' =? ~nine then
# 27 = ESC
({c} .) 27 =? ~exit then
~loop goto
zero:
"zero, " s0
&s0 puts
~loop goto
one:
"one, " s1
&s1 puts
~loop goto
two:
"two, " s2
&s2 puts
~loop goto
three:
"three, " s3
&s3 puts
~loop goto
four:
"four, " s3
&s3 puts
~loop goto
five:
"five, " s5
&s5 puts
~loop goto
six:
"six, " s6
&s6 puts
~loop goto
seven:
"seven, " s7
&s7 puts
~loop goto
eight:
"eight, " s8
&s8 puts
~loop goto
nine:
"nine, " s9
&s9 puts
~loop goto
exit:
newline
0 exit
На XmConC можно писать игры!
Задание
Скомпилируйте и запустите программу у себя на компьютере. Переведите все {переменная} . в коде, в {переменная}!.
"\
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \
@................@..@.....................@.....@ \
@................@..@...................@...@...@ \
@.....@@@@@@@@@@@@@.@.................@@@@@@@@..@ \
@...................@.............@@@@@.........@ \
@....@@@@@@@@@@@@@@...@@@@@@@@@@@@@.............@ \
@....@.............@..@.........................@ \
@....@........@.....@..@...........@@@..........@ \
@....@@@@@@@@@@......@..@..........@.....@@@@@@@@ \
@.....................@..@.........@............@ \
@.........................@..............@......@ \
@@@@@@@@@@@@....@@@@@@@@@@@@@@@@@@@@@@@@@@@@@...@ \
@....................@..........................@ \
@..........@.........@.......................@@@@ \
@..........@.........@..........................@ \
@..........@.........,........................,...............\
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ \
" map
/define WIDTH :62
/alloc P
63 {P} =
mainLoop:
clear_output
/alloc i
0 {i} =
# цикл вывода карты
loop:
;
({P} .) ({i} .) !? ~loop_1 then
'P' putc
(({i} .) ++) {i} =
~loop_2 goto
loop_1:
(({i} .) {WIDTH} mod) 0 !? ~loop_1_1 then
newline
loop_1_1:
((&map ({i} .) +) .) putc
(({i} .) ++) {i} =
loop_2:
(((&map ({i} .) +) ++) .) 0 !? ~loop then
#####################################
/alloc c
getc {c} =
({c} .) 'w' =? ~up then
({c} .) 'a' =? ~left then
({c} .) 's' =? ~down then
({c} .) 'd' =? ~right then
# 27 = ESC
({c} .) 27 =? ~exit then
~mainLoop goto
exit:
clear_output
0 exit
up:
((((&map ({P} .) +) {WIDTH} -) .) '.' !? ~exit then
(({P} .) {WIDTH} -) {P} =
~mainLoop goto
down:
((((&map ({P} .) +) {WIDTH} +) .) '.' !? ~exit then
(({P} .) {WIDTH} +) {P} =
~mainLoop goto
left:
((((&map ({P} .) +) --) .) '.' !? ~exit then
(({P} .) --) {P} =
~mainLoop goto
right:
((((&map ({P} .) +) ++) .) '.' !? ~exit then
(({P} .) ++) {P} =
~mainLoop goto
«Hello, world!», но на русском
…/xmconcc$ cat > ru
s::Привет, мир!
…/xmconcc$ ./unicode-tool.py ru > data.xcch
…/xmconcc$ cat > hey.xcc
/include "data.xcch"
~gnu gnu_code
{X_s} puts newline
0 exit
gnu:
{G_s} puts newline
0 exit