Міністерство освіти і науки, молоді та спорту України
Національний університет “Львівська політехніка”
Кафедра СКС
Курсовий проект
На тему:
“ Розробка та реалізація компонент системного програмного забезпечення ”
Львів 2012
Зміст
Завдання на курсовий прооект..........................................................................................3
Формальний опис вхіднрї мови програмування............................................................3
Розробка компілятора вхідної мови програмування.....................................................5
Розробка лексичного аналізатора................................................................................5
Розробка синтаксичного аналізатора..........................................................................6
Розробка семантичного аналізатора............................................................................6
Розробка оптимізатора коду..........................................................................................8
Розробка генератора коду..............................................................................................8
Відладка та тестування компілятора................................................................................9
Висновки...............................................................................................................................12
Література.............................................................................................................................13
Додаток А - Лістинг програми компілятора вхідної мови програмування...................14
Додаток Б - Лістинг тестових програм на вхідній мові програмування .......................32
1. Завдання на курсовий проект
Розробити компілятор заданої вхідної мови програмування, до якої висуваються наступні вимоги. Реалізувати три типи даних: логічний тип даних (boolean), знаковий цілочисельний тип та беззнаковий цілочисельний тип розміром 1 байт. Реалізувати можливість використання змінних з іменами довільної довжини. Реалізувати арифметичні операції: +, -, *, /, “-” (унарний мінус), відкриваюча: “(“ та закриваюча: “)” дужки та додаткову арифметичну операцію % (залишок від ділення). Реалізувати логічні операції: <, >, ==, != та додаткову логічну операцію ! (логічне “не”). Реалізувати наступні структури управління: оператор присвоєння, оператори блоку, оператор виводу, оператор виконання дії за умовою, оператор циклу while - do.
Цільова мова компілятора: асемблер (i86). Для отримання виконавчого файлу на виході розробленого компілятора скористатися програмами tasm.exe (компілятор мови асемблера) і tlink.exe (редактор зв’язків). Мова розробки компілятора: ANSI C або C++ (платформа win32). Реалізувати інтерфейс командного рядка. На вхід розробленого компілятора має подаватися текстовий файл, написаний на заданій мові програмування. На виході розробленого компілятора мають з’являтися чотири файли: файл з повідомленнями про помилки (або про їх відсутність), файл на мові асемблера, об’єктний та виконавчий файли.
2. Формальний опис вхідної мови програмування
Одною з перших задач, що виникають при побудові компілятора, є визначення вхідної мови програмування. Для цього використовують різні способи формального опису. В своїй курсовій роботі я використав розширену нотацію Бекуса-Наура (Backus/Naur Form - BNF).
Опис вхідної мови :
- три типи даних: логічний, беззнакове ціле, знакове ціле;- змінні довільної довжини;- арифметичні операції над цілими: +, -, *, /,%;- символи групування арифметичних операцій: “(”, “)”;- логічні операції над цілими: <, >, ==, !=;- логічна операція над логічними: !;- оператор присвоєння: “=” ;- оператори блоку: “{”, “}”;- оператор виконання дії за умовою: if-then-else;– оператор виводу print;- оператор циклу: while. Визначимо окремі термінальні символи та нерозривні набори термінальних символів (ключові слова):
{};,!=+-*/%()<>==!=booleanshortbyteifelsewhiletruefalseprintДо термінальних символів віднесемо також усі цифри (0-9), латинські букви (a-z, A-Z) символ пробілу (“ ”) та символ табуляції. Всього: 27 + 10 + 52 + 2 = 91 термінальних виразів. Це “цеглинки”, з яких має будуватися текст будь-якої вхідної програми. Правила написання правил у розширеній нотації Бекуса-Наура: - нетермінальні вирази записуються у кутових дужках: “<”, “>” ;- термінальні вирази записуються жирним шрифтом або у подвійних лапках;- усі нетермінальні вирази мають бути “розкриті” за допомогою термінальних;- сивол “::=” відділяє праву частину правила від лівої;- символ “|” розділяє альтернативи;- сиволи “[”, “]” означають необов’язковість (вираз в дужках може бути відсутнім);- сиволи “{”, “}” означають повторення.Формальний опис заданої вхідної мови в термінах BNF:
<program>
::= <statment>
<statement>
::= “{“{[<declaration>] }|{[ <operator>]}”}”
<declaration>
::= <type> <ident>{“,”[<ident>]};
<operator>
::= <bind> | <if-else> | <while>|<print>
<block>
::=”{“{[{[<operator>]}|{[<bind>]}]}”}”|<operator>”;”|<bind>”;”
<bind>
::= <ident> = <expression>;
<if-else>
::= if “(“<bool_expression>”)” <block> [else <block>].
<while>
::= while”(“ <bool_expression>”)” <block>
<type>
::= boolean |short|byte
<ident>
::= <letter> [{<letter>}]|[{number}]
<int_const>
::= [“-“]< number > [{<number >}]
<bool_const>
::= true | false
<letter>
a|b|c|d|e|f|g|h|i|j|k|l|n|m|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|N|M|O|P|Q|R|S|T|U|V|W|X|Y|Z
<number>
::= 0|1|2|3|4|5|6|7|8|9
<expression>
::= <num_expression> | <bool_expression>.
<num_expression>
::= <num_operand> [{<num_operation> <num_operand>}].
<bool_expression>
::= ( <num_operand> <bool_operation> <num_operand> ) | <ident> | <bool_const>.
<num_operand>
::= ( <num_expression> ) | <ident> | <int_const>.
<num_operation>
::= *| / | + | - |%
<bool_operation>
::= < | >| == |!=|&
Формальний опис складено за допомогою 21 нетермінального виразу.
3. Розробка компілятора вхідної мови програмування
3.1 Розробка лексичного аналізатора
Сканер, або лексичний аналізатор – по суті надір підпрограм, які дозволяють працювати з вхідним текстом на рівні лексем, і не звертатись на вищих стадіях компіляції до таких деталей, як зчитування тексту програми з файлу, чи, грубо кажучи, вирішувати, що робити, якщо закінчилась зчитана стрічка. Також сканер повинен мати в своєму складі засоби, які виконують різні допоміжні функції, на зразок лексикологічного порівняння стрічок тексту і т. ін. Ці задачі можна вирішувати або самостійно, або покласти їх виконання на стандартні бібліотеки мови “C”. Весь комплекс засобів, що необхідні для лексичного аналізу вхідних текстів на заданій мові програмування, повністю представлений в складі компілятора розробленого в даній роботі.
Текст програми зчитується в глобальний символьний масив line, вказівник всередині її на поточний сивол також глобальна змінна lptr. Нижче наведений список функцій, які можна віднести до лексичного аналізатора та коротке пояснення її роботи. Ці функції містяться в модулі scaner.c.
void readString(void) - зчитує стрічки з вхідного потоку, поки не зчитає непусту стрічку;
void setlptr(void) - встановлює lptr на перший непустий символ в потоці;
int isToken(char *token) - перевіряє, чи лексема token є наступною у вхідному потоці;
void nextChar(void) - встановлює lptr на наступний непустий символ у вхідному потоці;
void nextToken(void) - встановлює lptr на наступну лексему у вхідному потоці;
char getChar(void) - отримує ненульовий символ з вхідного потоку;
char *getIdent(void) - отримує ідентифікатор з вхідного потоку;
char *getNumb(void) - отримує ціле число з вхідного потоку.
3.2 Розробка синтаксичного аналізатора
3.3 Розробка семантичного аналізатора
Оскільки компілятор, реалізований мною у даній курсовій роботі є однопрохідним, то синтаксичний і семантичний аналізатори та генератор коду є тісно пов’язані один з одним. По суті сканер зчитує лексему – ключове слово і тут же викликає відповідну функцію, яка проводить розбірку даного оператора. Ця функція, в свою чергу, викликає фідповідні функції, які записують у вихідний файл потрібний код. Якщо всередині якогось оператора (крім оператора виводу) зустрічається якийсь інший оператор, то викликається функція, яка опрацьовує його та генерує відповідний код на мові асемблера. Нижче наведені синтаксичні діаграми цих операторів.
Оператор циклу
Умовний оператор
Оператор блоку
Розбір математичних виразів
Розбір математичних виразів – одна з задач, яку доводиться вирішувати при створенні фактично будь-якого компілятора чи асемблера (асемблера хорошого рівня, хоча для асемблерів це взагалі-то не обов’язково). Подібні задачі також часто доводиться вирішувати при розробці програм, які займаються різними обчисленнями – щоб користувач міг вводити не тільки дані, а й формули, які будуть опрацьовувати ці дані. Для вирішення цієї проблеми існує кілька досить відомих алгоритмів. В даній курсовій роботі я застосовав так званий алгоритм “рекурсивного спуску”. Тексти функцій, що здійснюють цей розбір знаходяться в модулі expr.c. Зупинюсь тільки на деяких деталях. Функції, що, власне, реалізують алгоритм рекурсивного спуску мають назви типу heir# і знаходяться в модулі expr.c. Зчитує операнди з вхідного потоку функція redOper(), а генерацією коду займаються функції, назви яких починаються на літеру “g” – gadd(), gmul() і т. ін. Назви їх є досить інтуїтивними, і тому не важко зрозуміти, код на яку саме операцію породжує та чи інша функція.
Функції обмінюються інформацією про операнди між собою в форматі масивів типу long int з 4 елементів (lval). Ось формат цього масиву:
lval[0] – адреса змінної в таблиці ідентифікаторів, якщо це не змінна, то тут міститься нуль. Також тут може міститись значення
lval[1] – вказує на розміщення операнда в пам’яті. Тут можуть бути значення:
MEM - операнд – змінна;
STACK - операнд знаходиться в стеку;
IMM - операнд – константа;
ERROR - вказує на виникнення помилки.
lval[2] – вказує на нип операнда. Можливі значення:
BYTE - одно байтне знакове ціле;
SHORT - одно байтне беззнакове ціле;
BOOL - логічний
lval[3] – містить безпосереднє значення.
Таблиця пріоритетів операцій
Пріоритет виконання математичних та логічних операцій, які підтримує мова, реалізована в даній курсовій роботі, такий же, як і в мові програмування “C”.
1
( )
дужки
2
- !
унарний мінус, логічне заперечення
3
%
залишок від ділення
4
* /
множення, ділення
5
+ -
додавання, віднімання
6
< >
більше, менше
7
== !=
дорівнює, не дорівнює
8
=
присвоєння
Примітка.
Результати однієї операції передаються у іншу операцію через стек. При цьому іноді буває, що дві інструкції роботи із стеком самоанулюються, тобто є лишніми. Це така послідовність операцій:
push ax
pop ax
Це є побічним наслідком такого алгоритму розбору. При потребі це можна прибрати, якщо проводити компіляцію не в кінцевий файл, а в тимчасовий, а потім перебрати цей файл і видалити такі послідовності. Проте в даній роботі такий механізм не реалізований.
3.4 Оптимізатор коду
Оптимізація коду – таке перетворення вихідного коду, яке підвищує його швидкодію і (або) знижує його об’єм. Існують 2 види оптимізації – машиннозалежна та машиннонезалежна. Машиннозалежна оптимізація не залежить від типу комп’ютерної системи, на якій буде виконуватись програма. Засоби машиннонезалежної оптимізації – винесення незмінних підвиразів за межі циклів, заміна меншефективних операцій більш ефективними, викидання недосяжного коду і т. ін. Теоретичні засади цих методів є добре розвинутими і математично доведеними. Засоби машиннозалежної оптимізації, навпаки, не мають під собою настільки добре розвиненого теоретичного підгрунтя. Вони залежать в основному від конструкції процесора, на якому ця програма буде виконуватись. Машиннозалежна оптимізація програм є дуже серйозним засобом підвищення продуктивності сучасних комп’ютерних систем. Але написати хороший оптимізуючий компілятор є дуже складно, тому їх існує досить небагато.
В даній курсовій роботі використано такий метод машиннонезалежної оптимізації як часткове обчислення виразів на стадії компіляції. Тобто, якщо в програмі, яка підлягає компіляції, зустрінеться вираз X = 2+3, то він скомпілюється не у
mov ax, 2
add ax, 3
mov [X], ax
а в mov [X], 5.
Більше ніяких видів оптимізації в даній курсовій роботі я не застосовував.
3. 5 Розробка генератора коду
У компілятора, реалізованого в даній курсовій роботі, вихідна мова не об’єктний код, а програма на мові асемблера з набором інструкцій для процесора i8086 в режимі IDEAL Турбо Асемблера. Ця програма записується у файл, що має таку ж саму назву, як і файл з вхідним текстом, але розширення “asm”. Оскільки мій компілятор однопрохідний, то генерація коду відбувається у різних місцях – і в спеціальних функціях, які опрацьовують математичні операції, і у функціях, що обробляють оператори мови.
Окремо варто відзначити засоби перетворення числа в стрічку, які застосовуються при виводі оператором “print” числових змінних. Якщо такий оператор зустрічається в програмі, то до її закінчення приписуються процедури на мові асемблера _WordToStr та _IntToStr (вони суміщені) які і реалізують перетворення числа у текстову стрічку.
Якщо оператор “print” виводить текстову константу, то вона поміщається у сегменті коду.
Варто відзначити, що чи не найскладніше було реалізувати операції порівняння у випадку, коли порівнюються знакові та беззнакові операнди. У такому випадку порівняння реалізується за допомогою декількох допоміжних інструкцій.
4. Відлагодження та тестування компілятора
Нижче наведений набір програм, який показує, як виконується компіляція окремих операторів на компіляторі, реалізованому в даній курсовій роботі.
Умовний оператор
{
short x,y;
x = 5;
if(x<10) y = 1;
else y = -1;
}
;module x.k12
IDEAL
model small
stack 100h
DATASEG
_buffer db 6 DUP(?)
x db ?
y db ?
CODESEG
jmp Start
_enter db 13,10,'$'
Start:
mov ax,@data
mov ds,ax
mov [x], 5
mov ax, [x]
mov bx, 10
cmp ax, 0
jl __2
cmp ax, bx
jb __2
xor ax, ax
jmp SHORT __3
__2:
mov ax, 1
__3:
push ax
pop ax
cmp ax, 0
jnz ___0
jmp __0
___0:
mov [y], 1
jmp __1
__0:
mov [y], -1
__1:
mov ah,4Ch
int 21h
END Start
Оператор циклу
{
short x;
x = 5;
while(x<10)
x = x+1;
}
;module x.k12
IDEAL
model small
stack 100h
DATASEG
_buffer db 6 DUP(?)
x db ?
CODESEG
jmp Start
_enter db 13,10,'$'
Start:
mov ax,@data
mov ds,ax
mov [x], 5
__0:
mov ax, [x]
mov bx, 10
cmp ax, 0
jl __2
cmp ax, bx
jb __2
xor ax, ax
jmp SHORT __3
__2:
mov ax, 1
__3:
push ax
pop ax
cmp ax, 0
jnz ___1
jmp __1
___1:
mov ax, [x]
mov bx, 1
add ax, bx
push ax
pop ax
mov [x], ax
jmp __0
__1:
mov ah,4Ch
int 21h
END Start
Оператор виводу
{
short x;
x = 5;
print "x = ",x;
}
;module x.k12
IDEAL
model small
stack 100h
DATASEG
_buffer db 6 DUP(?)
x db ?
CODESEG
jmp Start
_enter db 13,10,'$'
Start:
mov ax,@data
mov ds,ax
mov [x], 5
jmp __0
__1 db "x = ",'$'
__0:
mov ax, @code
mov ds, ax
mov dx, OFFSET __1
mov ah, 9
int 21h
mov ax, @data
mov ds, ax
mov ax, [x]
call _IntToStr
mov ax, @code
mov ds, ax
mov dx, OFFSET _Enter
mov ah, 9
int 21h
mov ax, @data
mov ds, ax
mov ah,4Ch
int 21h
PROC _IntToStr
cmp ax, 0
jge @@0
mov dl, '-'
mov bx, ax
mov ah, 2
int 33
mov ax, bx
neg ax
@@0:
PROC _WordToStr
mov cx, 5
mov bx, 10
xor dx, dx
@@3:
div bx
add dx, 48
push dx
xor dx, dx
loop @@3
mov bx, OFFSET _buffer
mov cx, 5
mov dx, 0
@@2:
pop ax
cmp ax, 48
jne @@4
cmp dx, 0
je @@5
@@4:
mov dx, 1
mov [bx], al
inc bx
@@5:
loop @@2
cmp bx, OFFSET _buffer
jne @@6
mov [BYTE PTR bx], '0'
inc bx
@@6:
mov al, '$'
mov [bx], al
mov ah, 9
mov dx, OFFSET _buffer
int 33
ret
ENDP _WordToStr
ENDP _IntToStr
END Start
Як видно з наведених вище прикладів, компіляція всіх заданих операторів відбувається коректно.
5. Висновок
Задвдяки цій курсовій роботі я зрозумів структуру компілятора та основні засади їх створення. Ці навики можуть бути використані при створенні повноцінних систем програмування. Також це розвинуло розширене понняття багатостороннього аналізу поставленої задачі та її компромісного рішення.
Список використаної літератури
1. Ахо А., Ульман Дж. Теория синтаксического анализа, перевода и компиляции, Т.1. Синтаксический анализ. - М.: Мир, 1978.2. Хантер Р. Проектирование и конструирование компиляторов. – М.: ФиС, 1984.3. Грис Д. Конструирование компиляторов для цифровых вычислительных машин. – М.: Мир, 1975.4. Ваймгартен Ф. Трансляция языков программирования. – М.: Мир, 1977.5. Касьянов В.Н. Оптимизирующие преобразования программ. – М.: Наука, 1988.
Додаток А
Лістинг програми компілятора вхідної мови програмування
Модуль defs.h
#include <stdio.h>
#define TABLE_SIZE 100
#define M_LENGTH 16
#define S_LENGTH 256
typedef struct {char name[M_LENGTH];
char type;
}Record;
#define BOOL 1
#define SHORT 2
#define BYTE 3
#define FALSE 0
#define TRUE 1
#define NO 0
#define YES 1
#define MEM 1
#define STACK 2
#define IMM 3
#define ERROR 4
extern Record table[];
extern int tptr;
extern FILE *input, *output, *err;
extern char line[];
extern int lptr;
extern int label;
extern char *fname;
extern int fBegin;
extern char StrBuf[];
extern int PrintProc;
extern int PrnStep;
extern int lval[];
extern int ErrNumb;
extern int LNumb;
extern char *rWords[];
Текст програми G3.c
#include <stdlib.h>
#include <stdio.h>
#include <alloc.h>
#include <defs.h>
#include <process.h>
#include <string.h>
#include <ctype.h>
Record table[TABLE_SIZE];
char line[S_LENGTH];
FILE *input, *output, *err;
int lptr;
int tptr = 1;
int label = 0;
char *fname;
int fBegin = TRUE;
char StrBuf[S_LENGTH];
int PrintProc = FALSE;
int PrnStep = FALSE;
int lval[4];
int ErrNumb = 0;
int LNumb = 0;
char *rWords[9] = {"byte","short","boolean","if","while","else","print",
"true","false"};
void pError(char *s) {
fprintf(err,"Error in line %d\n",LNumb);
fprintf(err,"\t%s\n",s);
ErrNumb++;
return;
}
void errorReport(void) {
if(ErrNumb)
fprintf(err,"\n - %d errors in module %s\n",ErrNumb,fname);
else
fprintf(err,"\n No errors in module %s\n",fname);
return;
}
void readString(void) {
do {
fgets(line,S_LENGTH,input);
LNumb++;
}
while(line[0]=='\n');
lptr = 0;
return;
}
void setlptr(void) {
while((isspace(line[lptr]))||(!line[lptr])) {
if((line[lptr]=='\n')||(!line[lptr])) readString();
else lptr++;
}
return;
}
int isToken(char *token) {
int i1 = 0,i2;
setlptr();
i2 = lptr;
while(token[i1]) {
if(token[i1]!=line[i2]) return FALSE;
i1++;
i2++;
}
if(strlen(token)==1) return TRUE;
if(((line[i2]>='a')&&(line[i2]<='z'))||
((line[i2]>='A')&&(line[i2]<='Z')))
return FALSE;
return TRUE;
}
void nextChar(void) {
if(line[lptr]!='\n')
lptr++;
while(line[lptr]=='\n')
readString();
setlptr();
return;
}
void nextToken(void) {
setlptr();
if(!isalnum(line[lptr])) {
nextChar();
return;
}
while(isalnum(line[lptr]))
lptr++;
setlptr();
return;
}
char getChar(void) {
if(!line[lptr]) readString();
lptr++;
return line[lptr-1];
}
char *getIdent(void) {
char *res = NULL;
int i = 0;
res = calloc(1,(sizeof(char))*M_LENGTH);
setlptr();
if(!isalpha(line[lptr])) return NULL;
while(isalnum(line[lptr+i])&&(i<M_LENGTH))
res[i++] = line[lptr+i];
res[i] = '\x0';
return res;
}
char *getNumb(void) {
char *res = NULL;
int i = 0;
res = calloc(1,(sizeof(char))*8);
setlptr();
if(!isdigit(line[lptr])) return NULL;
while(isdigit(line[lptr+i]))
res[i++] = line[lptr+i];
if(((line[lptr+i]>='a')&&(line[lptr+i]<='z'))||
((line[lptr+i]>='A')&&(line[lptr+i]<='Z')))
return NULL;
res[i] = 0;
return res;
}
void oputs(char *s) {
fputs(s,output);
return;
}
int findIdent(char *ident) {
int i;
char *tmp = malloc((M_LENGTH+1)*sizeof(char));
strcpy(tmp,ident);
i = strlen(tmp);
tmp[i] = '_';
tmp[i+1] = '\0';
for(i = 1;i<tptr;i++)
if(!strcmp(tmp,table[i].name)) {
free(tmp);
return i;
}
free(tmp);
return FALSE;
}
int addIdent(char *name,int type) {
int i = 0;
if(isReserve(name)) {
pError("Can not use reserved word here");
return FALSE;
}
if(tptr>(TABLE_SIZE-1)) return FALSE;
if(!findIdent(name)) {
while(name[i]) {
table[tptr].name[i] = name[i];
i++;
}
table[tptr].name[i++] = '_';
table[tptr].name[i] = 0;
table[tptr].type = type;
tptr++;
return TRUE;
}
pError("Redeclaration variable");
return FALSE;
}
int getLabel(void) {
label++;
return label-1;
}
void printLab(void) {
int x;
x = getLabel();
fprintf(output,"__%d:\n",x);
return;
}
void prnLabel(int lab) {
fprintf(output,"__%d:\n",lab);
return;
}
int isReserve(char *ident) {
int i;
for(i = 0;i<9;i++)
if(!strcmp(ident,rWords[i]))
return TRUE;
return FALSE;
}
void heir1(int *);
void heir2(int *);
void heir3(int *);
void heir4(int *);
void heir5(int *);
void heir6(int *);
void heir7(int *);
void readOper(int *);
void gUnr(int *);
void gMult(int *,int *);
void gDiv(int *,int *);
void gPow(int *,int *);
void gAdd(int *,int *);
void gSub(int *,int *);
void gLess(int *,int *);
void gGreat(int *,int *);
void gEqu(int *,int *);
void gNequ(int *,int *);
void gNot(int *);
void gStore(int *,int *);
void exchange(int *lval1,int *lval2) {
int i,tmp;
for(i = 0;i<4;i++) {
tmp = lval1[i];
lval1[i] = lval2[i];
lval2[i] = tmp;
}
return;
}
int isByte(int *lval) {
if(lval[2]==BYTE) return TRUE; else return FALSE;
}
int typeNot(int *lval1,int *lval2) {
if(((lval1[2]==BOOL)&&(lval2[2]!=BOOL))||
((lval1[2]!=BOOL)&&(lval2[2]==BOOL)))
return TRUE;
return FALSE;
}
void setRegs(int *lval1,int *lval2) {
char *s;
if(lval1[1]==MEM) {
s = table[lval1[0]].name;
fprintf(output,"\tmov al, [%s]\n",s);
}
else if(lval1[1]==STACK)
oputs("\tpop ax\n");
else fprintf(output,"\tmov al, %d\n",lval1[3]);
oputs("\txor ah, ah\n");
if(lval2[1]==MEM) {
s = table[lval2[0]].name;
fprintf(output,"\tmov bl, [%s]\n",s);
}
else if(lval2[1]==STACK)
oputs("\tpop bx\
n");
else fprintf(output,"\tmov bl, %d\n",lval2[3]);
oputs("\txor bh, bh\n");
return;
}
void expression(int mode) {
char symb[2];
symb[1] = '\0';
if(mode) symb[0] = ';';
else symb[0] = ')';
lval[1] = 0;
while(!isToken(symb)) {
if(lval[1]==ERROR)
return;
heir1(lval);
}
nextChar();
return;
}
void heir1(int lval[]) {
int lval2[4];
heir2(lval);
if(lval[1]==ERROR) return;
if(isToken("=")) {
nextChar();
if(isToken("-")) {
nextChar();