D2 Programming Language (описание языка)

  1. Введение
  2. Синтаксис
  3. Основные понятия (язык и представление)
  4. Объявления и области видимости
  5. Выражения
  6. Операторы
  7. Синтаксис d2
  8. Пример игровой программы Tetris

Введение

Д2 - это язык программирования высокого уровня. Предками языка, оказавшими на него наибольшее влияние, можно назвать Forth, Pascal (Modula-2, Oberon) и С. Язык был задуман как обладающий намеренно простым синтаксисом и возможно большей расширяемостью. Первое было достигнуто за счет сокращения до реального минимума управляющих конструкций, и отказа от такого понятия современных языков алголоидного типа как тип данных, однако же, оставить "классический" синтаксис языка типа Pascal. Формально язык содержит единственный тип данных - указатель. Косвенно поддерживаются такие типы как числа и строки символов. Второе, т.е. расширяемость, было достигнуто за счет введения понятия библиотеки или модуля, а также, в основном за счет внутреннего устройства ядра языка идею которого он позаимствовал у Forth, хотя это скорее вопросы реализации, чем синтаксиса... Язык поддерживает такие понятия как процедура (векторная процедура), локальные и глобальные переменные, параметры. В языке нет каких либо предопределенных операций и функций кроме, как я уже отмечал, операций управления процессом исполнения. Все функции ввода-вывода, математические, процедуры работы с графикой и т.д. поставляются в виде библиотек и в исходных текстах.

2. Синтаксис

Для описания синтаксиса Д-2 используются Расширенные Бэкуса-Наура Формы (РБНФ). Варианты разделяются знаком |. Квадратные скобки [ и ] означают необязательность записанного внутри них выражения, а фигурные скобки { и } означают его повторение (возможно 0 раз). Терминальные символы заключаются в кавычки.

3. Основные понятия (язык и представление)

3.1. Коментарий

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

Коментарий заключается в фигурные скобки { и }, и может занимать несколько строк.

Коментарий не может быть вложенным

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

  
  Пример:

    {Это коментарий}

Комментарий = '{' ЛюбойСимвол '}'


3.2. Идентификатор

Идентификатор начинается с буквы ( a..z,A..Z), вторым своим символом он может иметь букву и цифру ( 0..9 ), а также символ подчеркивания _ . В некоторых случаях в качестве идентификатора могут выступать строки, эти случаи будут оговорены подробнее.

  Пример:
     this_is_D2_identifier

Идентификатор = буква { буква | цифра }


3.3. Строки

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

  Пример:

    'It''s a string'

Строка = "'" ЛюбойСимвол "'"


3.4. Числа

Числом является набор символов 0..9 образующих номер, лежащий в диапазоне 0..FFFFh. Основание числа можно указать явно в формате: 0 { x|h|d|e|o|b } { цифра }. Где символ определяет систему исчисления x,h - шестнадцатиричная, d,e - десятичная (по умолчанию) o - восмеричная и b - двоичная.

  Пример:

    345
    0x90
    0b10011

Число = [ 0 ] [ основание ] { цифра }
Цифра = 0..9,A..F
Основание = x|h|d|e|o|b


3.5. Зарезервированные слова

Д2 содержит следующие зарезервированные слова:


	ASM 
	CASE CONST CONTINUE CYCLE
	DO
 	ELSE ELSIF ELSIFN END EXIT
	FI FOR
	IF IFN IN INLINE
	LEAVE LIBRARY LOOP
	MODULE
	NEW NEXT
	OF
	RECUR REPEAT RET
	SUB
	THEN TYPE
	UNTIL USE
	VAR VECT
	WEND WHILE
3.6. Разделители

Разделителями в Д2 являются следующие символы: , ; ( ) [ ] .

Разделитель = ',' | ';' | '(' | ')' | '[' | ']'

3.7. Символы

Символами в д2 являются: ` ~ ! @ # $ % ^ & * - + = " ; : < > . \ | / ? .

Из них также можно составить идентификатор.

Идентификатор = Символ { Символ }
Символ = '`' | '!' | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '-' | '+' | '=' | '"' | ';' | ':' | '<' | '>' | '.' | '\' | '|' | '/' | '?'

Пример:
	+
	-
	++
	>>
3.8. Игнорируемые символы

Символы с ascii кодами 0..32,127 игнорируются, если они не являются в данном контексте разделителями между идентификаторами или какими-либо другими лексемами.


3.9. Другие символы

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

4. Объявления и области видимости

Программа на d2 называется модулем и содержит в себе описания переменных и процедур, а также списки импорта библиотек.

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

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

Программа = MODULE | LIBRARY Идентификатор { Описание } END
Описание = ОписаниеПеременных | ОписаниеПроцедуры | ОписаниеЭкспорта | ОписаниеКонстант

Пример (минимальная d2 программа):

   MODULE minimal_d2_program

     SUB main
       { do nothing }
     RET

   END

4.1. Объявление глобальных переменных

Обявление глобальной переменной начинается с зарезервированного слова VAR и последующего перечисления идетификаторов переменных через запятую. По умолчанию под переменную резервируется место равное машинному слову. Этот размер можно переопределить, указав новый в [] скобках: [ new_size ]. По умолчанию переменная инициализируется нулями, но ее можно инициализировать другими данными, перечислив их в <> скобках: < ... data >.

ОписаниеПеременных = ОписаниеПеременной { , ОписаниеПеременной } ;
ОписаниеПеременной = Идентификатор [ Размер ] [ Инициализатор ]
Размер = '[' Число ']'
Инициализатор = < { Числа | Строки | ИменаПроцедур } >
Пример:
  
  VAR
    a,b,c,   
    x<3>,y<5>,
    arr[10]<0 1 2 3 4 5 6 7 8 9>,
    ptrs[6]<sub_1 sub_2 sub_3>,
    str_ptr[4]<'Иванов' 'Иван' 'Иваныч'>;
4.2. Описание процедур

Описание процедуры начинается с зарезервированного слова SUB или VECT, затем следует имя процедуры. Заканчивается описание словом RET. Процедуры описанные как VECT являются векторными, обладающими векторным полем кода, и могут переопределяться в момент исполнения.

Процедуры могут перегружаться, глобальные переменные не могут перегружаться.

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

Для процедуры можно установить приоритет, аналогичный приоритету операндов в алгебраических выражениях, для этого приоритет указывается в [] скобках: [ Число ]. Чем больше число, тем ниже приоритет процедуры, по умолчанию процедуре присваивается приоритет 1, переменные обладают приоритетом -1.

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

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

ОписаниеПроцедуры = SUB [ ЭкспортнаяМетка ] [ Приоритет ] [ ОписаниеЛокальныхПеременных ] { Оператор | Выражение } RET
ЭкспортнаяМетка = '*'
Приоритет = '[' Число ']'
ОписаниеЛокальныхПеременных = VAR ОписаниеПеременной | ОписаниеПараметра;
ОписаниеПараметра = Идентификатор ИмпортнаяМетка
ИмпортнаяМетка = '*'
Пример:

MODULE lview;

USE
  system,fio,iostd,param,putnum,math;

CONST
  buf = 10000;

SUB readlib VAR h;
  @h = fopen(paramstr(1));
  fread(h buf 0h7FFF);
  fclose(h);
RET

SUB checklib VAR ptr<0>,str<'lib2d'>;
  REPEAT
    IF byte(str+ptr) <> byte(buf+ptr) THEN
      FALSE;
      EXIT
    FI
    inc(@ptr)
  UNTIL ptr==length(str) LOOP
  TRUE
RET

SUB viewhdr VAR pos*;
  puthex(pos-buf);
  bl;
  putword(byte(pos+2));
  bl;
  putword(byte(pos+3));
  bl;
  putword(byte(pos+4));
  bl;
  putstring(pos+5);
  cr
RET

SUB showlib VAR next,old;
  @next=word(buf+5);
  WHILE next<>0 DO
    @old=next;
    @next=word(buf+next);
    viewhdr(old+buf);
  WEND
RET

SUB main
  IF paramcount THEN
    readlib
    IF checklib THEN
      showlib
    ELSE
      putstring 'unknown library format'
    FI
  ELSE
    cr;
    putstring 'usage: lview [filename''.''ext]';
    cr;
    cr
  FI
RET

END

4.3. Экспорт процедур

Процедуры могут экспортироваться из библиотек. Библиотека, процедуры из которой должны экспортироваться должна быть явно упомянута с помощью зарезервированного слова USE, при этом все процедуры, обладающие экспортными метками становятся видимыми в текущем модуле.

ОписаниеЭкспорта = USE ИмяБиблиотеки { , ИмяБиблиотеки }
Пример:
  USE system, math, '..\my_libs\lib', lib;
4.4. Описание констант

Константы описываются с помощью зарезервированого слова CONST.

ОписаниКонстант = CONST ОписаниеКонстанты {, ОписаниеКонстанты }
ОписаниеКонстанты = Идетификатор '=' Терм
Пример:

  CONST
    c1 = 12,
    str = 'Hello, world!',
    plus = + ;

5. Выражения

Язык d2 не имеет ни одной встроенной или стандартной функции, поэтому все функции создаются или на языке ассемблера или на самом d2. Описание процедуры подразумевает указание ее приоритета. Данный приоритет используется при разборе выражений. Выражение может содержать любую допустимую лексему кроме операторов.

Идентификатор, если это процедура - приводит к ее вызову, если это переменная - на стеке остается ее значение, для взятия адреса, как переменной (глобальной или локальной), так и процедуры, используется символ '@'.

Число оставляет на стеке свое значение.

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

В выражении могут быть использованы скобки, запятые и символ ';'. Символ ';', а также зарезервированное слово приводит к завершению вычисления текущего выражения. Выражение должно быть сбалансировано по скобкам.

Выражение = { Идентификатор | Строка | Число | Символ | '(' Выражение ')' }

Пример написания библиотеки math:

include	libmacro.inc

beglib 'math'

item 'inc','*'
  pop bx
  xchg ax, bx
  inc word ptr [bx]

item 'dec','*'
  pop bx
  xchg ax, bx
  dec word ptr [bx]

item '+','*',5
  pop bx
  add ax,bx

item '-','*',5
  pop bx
  xchg ax, bx
  sub ax,bx

item '*','*',4
  pop bx
  imul bx

item '/','*',4
  cwd
  pop bx
  xchg ax,bx
  idiv bx

item '%','*',4
  cwd
  pop bx
  xchg ax,bx
  div bx
  mov ax,dx

item '>','*',6
  pop bx
  cmp bx,ax
  setg al

item '<','*',6
  pop bx
  cmp bx,ax
  setl al

item '=','*',10
  pop	bx
  mov	[bx], ax
  pop	ax

item '<=','*',6
  pop bx
  cmp bx,ax
  setle al

item 'byte','*',1
  mov bx,ax
  xor ah,ah
  mov al,[bx]

item 'word','*',1
  mov bx,ax
  mov ax,[bx]
 
item '>=','*',6
  pop bx
  cmp bx,ax
  setge al

item '==','*',6
  pop bx
  cmp bx,ax
  sete al

item '<>','*',6
  pop bx
  cmp bx,ax
  setne al

item '&','*',7
  pop bx
  and ax,bx

item '|','*',7
  pop bx
  or ax,bx

item '^','*',7
  pop bx
  xor ax,bx

endlib


6. Операторы

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

Оператор = ОператорIf | ОператорWhile | ОператорRepeat | ОператорDo | ОператорFor | ОператорCycle | ОператорInline | ОператорContinue | ОператорLeave | ОператорRecur | ОператорExit
Блок = { Оператор | Выражение }

6.1. Оператор If

ОператорIf = IF | IFN Блок THEN Блок { ELSIF | ELSIFN Блок THEN Блок } [ ELSE Блок ] FI

Оператор IF позволяет выполнять ветвление по условию, блок между IF|IFN и THEN должен оставлять значение, которое интерпретируется как булевое (TRUE, если не равно 0), при этом в случае успеха (или неуспеха для IFN) выполняется блок операторов THEN, иначе блок ELSIF|ELSIFN или ELSE в случае их наличия. Части ELSIF могут повторяться произвольное число раз.

Пример:

  IF ch==cUP THEN
    @y=(y-1) & 2
  ELSIF ch==cDOWN THEN
    @y=(y+1) & 2
  ELSIF ch==cRIGHT THEN
    @x=(x+1) & 2
  ELSIF ch==cLEFT THEN
    @x=(x-1) & 2
  FI
6.2. Оператор While

ОператорWhile = WHILE Блок DO Блок WEND

Оператор организует цикл с предусловием.

Пример:
  @i=15;
  WHILE i DO  { печать чисел от 15 до 1 }
    putword(i);
    bl;
    @i--
  WEND
6.3. Оператор Repeat

ОператорRepeat = REPEAT Блок UNTIL Блок LOOP

Оператор организует цикл с постусловием

Пример:
  @i=1;
  REPEAT { печать чисел от 1 до 15 }
    putword(i);
    bl;
    @i++
  UNTIL i>15 LOOP
6.4. Оператор Do

ОператорDo = DO Блок LOOP

Оператор организует бесконечный цикл.

6.5. Оператор For

ОператорFor = FOR Идентификатор {, Идентификатор } IN Данные DO Блок NEXT

6.6. Оператор Cycle

ОператорCycle = CYCLE Блок DO Блок LOOP

Оператор позволяет выполнить группу операторов заданное на перед число раз.

6.7. Оператор Inline

ОператорInline = INLINE ( Данные )

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

Пример:

  MODULE HelloWorld

  SUB main
    INLINE(
	 	0xBA,'Hello, world!$',
		0xB4,0x09,
		0xCD,0x21
    )
  RET

END


6.8. Оператор Continue

ОператорContinue = CONTINUE

Оператор осуществляет безусловный переход на начало цикла (начинается следующая итерация). Циклами являются операторы: WHILE, REPEAT, DO, FOR и CYCLE.

6.9. Оператор Leave

ОператорLeave = LEAVE

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

Пример:
  DO
    @ch=readkey()
    IF ch==cESC THEN LEAVE FI
    execute(ch)
  LOOP
6.10. Оператор Recur

ОператорRecur = RECUR

Оператор осуществляет вызов текущей процедуры (рекурсия).

6.11. Оператор Exit

ОператорExit = EXIT

Оператор EXIT осуществляет выход из текущей процедуры.

7. Синтаксис d2.

Модуль = MODULE | LIBRARY Идентификатор { Описание } END
Описание = ОписаниеПеременных | ОписаниеПроцедуры | ОписаниеЭкспорта | ОписаниеКонстант
ОписаниеПеременных = ОписаниеПеременной { , ОписаниеПеременной } ;
ОписаниеПеременной = Идентификатор [ Размер ] [ Инициализатор ]
Размер = '[' Число ']'
Инициализатор = < { Числа | Строки | ИменаПроцедур } >
ОписаниеПроцедуры = SUB [ ЭкспортнаяМетка ] [ Приоритет ] [ ОписаниеЛокальныхПеременных ] { Оператор | Выражение } RET
ЭкспортнаяМетка = '*'
Приоритет = '[' Число ']'
ОписаниеЛокальныхПеременных = VAR ОписаниеПеременной | ОписаниеПараметра;
ОписаниеПараметра = Идентификатор ИмпортнаяМетка
ИмпортнаяМетка = '*'
ОписаниеЭкспорта = USE ИмяБиблиотеки { , ИмяБиблиотеки }
Выражение = { Число | Идентификатор | Строка | СпециальныйСимвол }
Оператор = ОператорIf | ОператорWhile | ОператорRepeat | ОператорDo | ОператорFor | ОператорCycle | ОператорInline | ОператорContinue | ОператорLeave | ОператорRecur | ОператорExit
Блок = { Оператор | Выражение }
ОператорIf = IF | IFN Блок THEN Блок { ELSIF | ELSIFN Блок THEN Блок } [ ELSE Блок ] FI
ОператорWhile = WHILE Блок DO Блок WEND
ОператорRepeat = REPEAT Блок UNTIL Блок LOOP
ОператорDo = DO Блок LOOP
ОператорFor = FOR Идентификатор IN Данные DO Блок NEXT
ОператорCycle = CYCLE Блок DO Блок LOOP
ОператорInline = INLINE ( Данные )
ОператорContinue = CONTINUE
ОператорLeave = LEAVE
ОператорRecur = RECUR
ОператорExit = EXIT
Идентификатор = буква { буква | цифра }
Строка = "'" ЛюбойСимвол "'"
Число = [ 0 ] [ основание ] { цифра }
Цифра = 0..9,A..F
Основание = x|h|d|e|o|b
Разделитель = ',' | ';' | '(' | ')' | '[' | ']'
Идентификатор = Символ { Символ }
Символ = '`' | '!' | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '-' | '+' | '=' | '"' | ';' | ':' | '<' | '>' | '.' | '\' | '|' | '/' | '?'
Буква = 'a'..'z','A'..'Z','а'..'я','А'..'Я'
Цифра = '0'..'9','A'..'F'

8. Пример игровой программы Tetris

MODULE	tetris;

USE	system,crt,math,write,timehund,putnum,iostd;

CONST

	N = 4,
	n_e = 8,
	e_size = 25,
	
	Y = 3, X = 2, _ = 0,

	buf_addr = 5000;
	
VAR
	{ элементы 5x5 }	
	
	E1 [e_size]  < 
			_ _ X _ _
			_ _ X _ _
			_ _ X _ _	
			_ _ X _ _
			_ _ X _ _ >,
			
	E2 [e_size]  <
			_ _ X _ _
			_ _ X _ _
			_ _ X _ _	
			_ X X _ _
			_ _ _ _ _ >,
			
	E2i [e_size]  <
			_ _ X _ _
			_ _ X _ _
			_ _ X _ _	
			_ _ X X _
			_ _ _ _ _ >,
	
	E3 [e_size]  <
			_ _ _ _ _
			_ X X _ _
			_ X X _ _
			_ _ _ _ _
			_ _ _ _ _ >,
			
	E4 [e_size]  <
	                _ _ _ _ _ 
			X X X _ _
			_ _ X X _
			_ _ _ _ _
			_ _ _ _ _ >,
			
	E5 [e_size]  <
			_ _ _ _ _
			_ _ _ _ _
			_ _ X X _
			X X X _ _
			_ _ _ _ _ >,
			
	E6 [e_size]  <  _ _ _ _ _
		        _ _ _ _ _
		        _ X X X _
		        _ _ X _ _
		        _ _ _ _ _ >,

	E7 [e_size]  <  _ _ _ _ _
		        _ _ X _ _
		        _ X X X _
		        _ _ X _ _
		        _ _ _ _ _ >,
		        
	E8 [e_size]  <  _ _ _ _ _
		        _ X _ _ _
		        _ X X X _
		        _ _ _ X _
		        _ _ _ _ _ >,
 			
			
	E [16]		< E1 E2 E3 E4 E5 E6 E2i E8 >,
	
	score
;
			


CONST	width = 17, height=24;
  
{ 17 x 22 }

SUB	. [9]	VAR x*,y*;
	buf_addr+pred(y)*width+pred(x)
RET

SUB not VAR predicat*;
  IF predicat THEN
    FALSE
  ELSE
    TRUE
  FI
RET

SUB draw VAR i,j;
  @i=width;
  WHILE i DO
    @j=height;
    WHILE j DO
      w_char40(10+i,j,byte(i.j),219);
      dec(@j)
    WEND
    dec(@i)
  WEND
RET
  

SUB w_string VAR x*,y*,i<0>,str*;
  WHILE byte(str+i) DO
    x+i.y letb byte(str+i);
    inc(@i)
  WEND
RET

SUB write_score VAR i<5>,s;
  @s=score;
  REPEAT
    w_char40(i,1,4,s%10+0h30);
    @s=s/10;
    dec(@i);
  UNTIL s==0 LOOP
RET
  

VAR 
  ln[18]< Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y 0>;

SUB init VAR i<1>,j;
  
  w_mode(0);
  cursoroff;
  write_score;
  
  WHILE i<=height DO
    w_string(1,i,@ln);
    inc(@i);
  WEND
  
  @i=2;
  WHILE i;
  FOR x,y IN 0..N DO
    IF byte(Xt+x.Yt+y) & byte(x!y) THEN
      @F=FALSE
    FI
  NEXT F
RET


SUB rotate VAR NE[25],i,j;
  FOR i,j IN 0..N DO
    @NE+5*i+j letb byte(i!j)
  NEXT
  FOR i,j IN 0..N DO
    i!j letb byte(@NE+5*j+4-i)
  NEXT
  IF not(can(Xe,Ye)) THEN
    FOR i,j IN 0..N DO
      i!j letb byte(@NE+5*i+j)
    NEXT
  FI
RET


SUB show VAR x,y;
  FOR x,y IN 0..N DO
    IF byte(x!y) THEN (Xe+x.Ye+y) letb byte(x!y) FI
  NEXT
RET

SUB hide VAR x,y;
  FOR x,y IN 0..N DO
    IF byte(x!y) THEN (Xe+x.Ye+y) letb 0 FI
  NEXT
RET
  

SUB move VAR Xt,Yt;
  @Xt=Xe+dX;
  @Yt=Ye+dY;
  IF can(Xt,Yt) THEN
    @Xe=Xt;
    @Ye=Yt;
    FALSE
  ELSE
    TRUE
  FI
RET

SUB shift VAR line*,row;
  WHILE line>1 DO
    @row=2;
    WHILE row1 DO
    WHILE full(line) DO
      shift(line)
    WEND
    dec(@line)
  WEND
  write_score
RET


SUB run	VAR ch, delay_factor;
  REPEAT

    {пустить элемент в стакан}
    put;
    @delay_factor=5;
    
    REPEAT
    
      clrkbdbuf;
      show;
      draw;
      delay(delay_factor);
      hide;
      
      @dX=0;

      { обработать нажатие клавиши, если оно есть}
      IF keypressed THEN
        @ch=scancode;
        @dY=0;
        IF ch==RIGHT THEN
          @dX=1
        ELSIF ch==LEFT THEN
          @dX=0hFFFF
        ELSIF ch==UP THEN
          rotate
        ELSIF ch==DOWN | ch==SPACE THEN
          @delay_factor=0
        ELSIF ch==ENTER THEN
          REPEAT
            @ch=scancode
          UNTIL ch==ENTER | ch==ESC LOOP
        FI
      
      { иначе элемент опускается вниз }
      ELSE
        @dY=1
      FI
      
      UNTIL ch==ESC | (move & dY) LOOP
      
      show;
      replace      
    
  UNTIL ch==ESC LOOP

RET


SUB done
  w_mode(3);
  putstring('Eugene programming, KSU 2001');
RET

SUB main
  init
  run
  done
RET

END