Lisp грамматика в yacc

Я пытаюсь построить грамматику Lisp. Легко, правда? Видимо, нет.

Я представляю эти входы и получаю ошибки...

( 1 1)
23 23 23 
ui ui

Это грамматика...

%%
sexpr: atom                 {printf("matched sexpr\n");}
    | list
    ;
list: '(' members ')'       {printf("matched list\n");}
    | '('')'                {printf("matched empty list\n");}
    ;
members: sexpr              {printf("members 1\n");}
    | sexpr members         {printf("members 2\n");}
    ;
atom: ID                    {printf("ID\n");}
    | NUM                   {printf("NUM\n");}
    | STR                   {printf("STR\n");}
    ;
%%

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

edit - это был мой подход "верхнего терминала":

program: slist;

slist: slist sexpr | sexpr;

Но он позволяет такие проблемы, как:

( 1 1 

Edit2: Код FLEX...

%{
    #include <stdio.h>
    #include "a.yacc.tab.h"
    int linenumber;
    extern int yylval;
%}
%%
\n                         { linenumber++; }
[0-9]+                     { yylval = atoi(yytext); return NUM; }
\"[^\"\n]*\"               { return STR; }
[a-zA-Z][a-zA-Z0-9]*       { return ID; }
.
%%

Пример пересогласования...

(1 1 1)
NUM
matched sexpr
NUM
matched sexpr
NUM
matched sexpr
(1 1
NUM
matched sexpr
NUM
matched sexpr

Какая ошибка здесь?

edit: ошибка была в лексере.

Ответ 1

Ошибка действительно в лексере. Ваши круглые скобки заканчиваются как последние "." в лексере и не отображаются в круглых скобках в парсере.

Добавьте такие правила, как

\)     { return RPAREN; }
\(     { return LPAREN; }

для лексера и изменить все вхождения '(', ')' в LPAREN и RPAREN соответственно в синтаксический анализатор. (также вам нужно #define LPAREN и RPAREN, где вы определяете свой список токенов)

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

Ответ 2

Lisp грамматика не может быть представлена ​​как контекстно-свободная грамматика, и yacc не может проанализировать весь код lisp. Это из-за lisp функций, таких как считывание-считывание и программируемый считыватель. Итак, чтобы просто прочитать произвольный код lisp, вам нужно выполнить полный lisp запуск. Это не какая-то неясная, неиспользуемая функция, но она фактически используется. Например, CL-INTERPOL, CL-SQL.

Если целью является синтаксический анализ подмножества lisp, тогда текст программы представляет собой последовательность sexprs.

Ответ 3

Вы правы в том, что вам нужно определить нетерминал. Это будет определяться как набор sexpr. Я не уверен в синтаксисе YACC для этого. Я частично отношусь к ANTLR для генераторов парсеров, и синтаксис будет выглядеть следующим образом:

program: sexpr*

Указание 0 или более sexpr.

Обновление с синтаксисом YACC:

program :  /* empty */
        | program sexpr
        ;

Не в YACC, но может быть полезно в любом случае, здесь полная грамматика в ANTLR v3, которая работает для описанных вами случаев (исключая строки в лексере, потому что это не важно для этого примера, также использует вывод консоли С#, потому что то, что я протестировали его):

program: (sexpr)*;

sexpr: list
    |  atom            {Console.WriteLine("matched sexpr");}
    ;

list:     
   '('')'              {Console.WriteLine("matched empty list");}
   | '(' members ')'   {Console.WriteLine("matched list");}

    ;

members: (sexpr)+      {Console.WriteLine("members 1");};

atom: Id               {Console.WriteLine("ID");}
    | Num              {Console.WriteLine("NUM");}
    ;


Num: ( '0' .. '9')+;
Id: ('a' .. 'z' | 'A' .. 'Z')+;
Whitespace : ( ' ' | '\r' '\n' | '\n' | '\t' ) {Skip();};

Это не будет работать точно так же, как в YACC, потому что YACC генерирует и LALR-парсер, а ANTLR - модифицированный рекурсивный спуск. Для ANTLR существует конечная цель вывода C/С++, если вы хотите пойти таким образом.

Ответ 4

Вам нужен обозреватель yacc/bison? A "читает подмножество lisp синтаксиса", который не так сложно реализовать в C (начинать с функции read_sexpr, отправлять в read_list, когда вы видите '(', который, в свою очередь, строит список содержащихся sexprs до тех пор, пока a ')', в противном случае вызовите read_atom, который собирает атом и возвращает его, когда он больше не может читать атомы-составляющие символы).

Однако, если вы хотите иметь возможность читать общедоступный общий Lisp, вам нужно (в худшем случае) реализовать Common Lisp, поскольку CL может изменять время выполнения считывателя (и даже переключаться между разное время чтения таблиц под управлением программы, довольно удобно, когда вы хотите загрузить код, написанный на другом языке или диалекте lisp).

Ответ 5

Прошло много времени с тех пор, как я работал с YACC, но вам нужен не-терминал верхнего уровня. Не могли бы вы уточнить, что "попробовали" и "похоже, что это не работает"? Или, если на то пошло, какие ошибки?

Я также подозреваю, что YACC может быть излишним для такого языка синтаксиса. Что-то более простое (например, рекурсивное спуск) может работать лучше.

Ответ 7

Я просто попробовал, моя "yacc lisp грамматика" отлично работает:

%start exprs

exprs:
    | exprs expr
    /// if you prefer right recursion :
    /// | expr exprs
    ;

list:
    '(' exprs ')'
    ;

expr:
    atom
    | list
    ;

atom:
    IDENTIFIER
    | CONSTANT
    | NIL
    | '+'
    | '-'
    | '*'
    | '^'
    | '/'
    ;