]> ocean-lang.org Git - ocean/blobdiff - csrc/parsergen.mdc
parsergen: allow pointers as well as struct to be associated with nonterminals.
[ocean] / csrc / parsergen.mdc
index 37dcf7eb12a1348e6a07378f2cbf67cf7fa78ffc..19d1b5a4e94f6c894853202b8108419668663664 100644 (file)
@@ -17,7 +17,10 @@ There are several distinct sections.
 5. `parser_run` is a library function intended to be linked together
    with the generated parser tables to complete the implementation of
    a parser.
-6. `calc.cgm` is a test grammar for a simple calculator.
+6. Finally `calc` is a test grammar for a simple calculator.  The
+   `parsergen` program built from the C code in this file can extract
+   that grammar directly from this file and process it.
+
 
 ###### File: parsergen.c
        #include <unistd.h>
@@ -42,12 +45,10 @@ There are several distinct sections.
        ## parser includes
        ## parser functions
        ## parser_run
-###### File: calc.cgm
-       ## demo grammar
 ###### File: parsergen.mk
        CFLAGS += -Wall -g
        all :: parsergen calc
-       parsergen.c parsergen.mk calc.cgm libparser.c parser.h : parsergen.mdc
+       parsergen.c parsergen.mk libparser.c parser.h : parsergen.mdc
                ./md2c parsergen.mdc
 
 ## Reading the grammar
@@ -58,6 +59,12 @@ sections: `header`, `code`, and `grammar`.  The first two will be
 literally copied into the generated `.c` and `.h`. files.  The last
 contains the grammar.  This is tokenised with "[scanner][]".
 
+If the `--tag` option is given, then any top level header that doesn't
+start with the tag is ignored, and the tag is striped from the rest.  So
+`--tag Foo`
+means that the three needed sections must be `Foo: header`, `Foo: code`,
+and `Foo: grammar`.
+
 [mdcode]: mdcode.html
 [scanner]: scanner.html
 
@@ -74,9 +81,10 @@ declarations, and data type declarations.  These are all parsed with
 _ad hoc_ parsing as we don't have a parser generator yet.
 
 The precedence and associativity can be set for each production, but
-can be inherited from symbols.  The data type is potentially defined
-for each non-terminal and describes what C structure is used to store
-information about each symbol.
+can be inherited from symbols.  The data type (either structure or a
+reference to a structure) is potentially defined for each non-terminal
+and describes what C structure is used to store information about each
+symbol.
 
 ###### declarations
        enum assoc {Left, Right, Non};
@@ -84,6 +92,7 @@ information about each symbol.
 
        struct symbol {
                struct text struct_name;
+               int isref;
                enum assoc assoc;
                unsigned short precedence;
                ## symbol fields
@@ -103,6 +112,9 @@ comparing we define `text_is` and `prtxt`, which should possibly go in
 `mdcode`.  `scanner` does provide `text_dump` which is useful for strings
 which might contain control characters.
 
+`strip_tag` is a bit like `strncmp`, but adds a test for a colon,
+because that is what we need to detect tags.
+
 ###### functions
        static int text_is(struct text t, char *s)
        {
@@ -114,6 +126,20 @@ which might contain control characters.
                printf("%.*s", t.len, t.txt);
        }
 
+       static int strip_tag(struct text *t, char *tag)
+       {
+               int skip = strlen(tag) + 1;
+               if (skip >= t->len ||
+                   strncmp(t->txt, tag, skip-1) != 0 ||
+                   t->txt[skip-1] != ':')
+                       return 0;
+               while (skip < t->len && t->txt[skip] == ' ')
+                       skip++;
+               t->len -= skip;
+               t->txt += skip;
+               return 1;
+       }
+
 ### Symbols
 
 Productions are comprised primarily of symbols - terminal and
@@ -232,14 +258,17 @@ word is `LEFT`, `RIGHT` or `NON`, then the line specifies a
 precedence, otherwise it specifies a data type.
 
 The data type name is simply stored and applied to the head of all
-subsequent productions.  It must be the name of a structure, so `$expr`
-maps to `struct expr`.
-
-Any productions given before the first data type will have no data type
-and can carry no information.  In order to allow other non-terminals to
-have no type, the data type `$void` can be given.  This does *not* mean
-that `struct void` will be used, but rather than no type will be
-associated with future non-terminals.
+subsequent productions.  It must be the name of a structure optionally
+preceded by an asterisk which means a reference or "pointer".  So
+`$expression` maps to `struct expression` and `$*statement` maps to
+`struct statement *`.
+
+Any productions given before the first data type declaration will have
+no data type associated with them and can carry no information.  In
+order to allow other non-terminals to have no type, the data type
+`$void` can be given.  This does *not* mean that `struct void` will be
+used, but rather than no type will be associated with future
+non-terminals.
 
 The precedence line must contain a list of symbols - typically
 terminal symbols, but not necessarily.  It can only contain symbols
@@ -262,6 +291,7 @@ production inherits from the last symbol which has a precedence.
 
 ###### grammar fields
        struct text current_type;
+       int type_isref;
        int prec_levels;
 
 ###### declarations
@@ -269,7 +299,7 @@ production inherits from the last symbol which has a precedence.
        static const char *known[] = { "$$", "${", "}$" };
 
 ###### functions
-       static char *dollar_line(struct token_state *ts, struct grammar *g)
+       static char *dollar_line(struct token_state *ts, struct grammar *g, int isref)
        {
                struct token t = token_next(ts);
                char *err;
@@ -288,6 +318,7 @@ production inherits from the last symbol which has a precedence.
                        assoc = Non;
                else {
                        g->current_type = t.txt;
+                       g->type_isref = isref;
                        if (text_is(t.txt, "void"))
                                g->current_type.txt = NULL;
                        t = token_next(ts);
@@ -298,6 +329,11 @@ production inherits from the last symbol which has a precedence.
                        return NULL;
                }
 
+               if (isref) {
+                       err = "$* cannot be followed by a precedence";
+                       goto abort;
+               }
+
                // This is a precedence line, need some symbols.
                found = 0;
                g->prec_levels += 1;
@@ -517,15 +553,11 @@ where `START` is the first non-terminal given.
        p->head->type = Nonterminal;
        array_add(&p->body, &p->body_size, head);
        array_add(&p->body, &p->body_size, sym_find(g, eof));
-       g->start = p->head->num;
        p->head->first_production = g->production_count;
        array_add(&g->productions, &g->production_count, p);
 
 Now we are ready to read in the grammar.
 
-###### grammar fields
-       int start;      // the 'start' symbol.
-
 ###### grammar_read
        static struct grammar *grammar_read(struct code_node *code)
        {
@@ -567,7 +599,8 @@ Now we are ready to read in the grammar.
                                else {
                                        head->type = Nonterminal;
                                        head->struct_name = g->current_type;
-                                       if (g->start == 0) {
+                                       head->isref = g->type_isref;
+                                       if (g->production_count == 0) {
                                                ## create production zero
                                        }
                                        head->first_production = g->production_count;
@@ -587,7 +620,10 @@ Now we are ready to read in the grammar.
                                        err = "First production must have a head";
                        } else if (tk.num == TK_mark
                                   && text_is(tk.txt, "$")) {
-                               err = dollar_line(state, g);
+                               err = dollar_line(state, g, 0);
+                       } else if (tk.num == TK_mark
+                                  && text_is(tk.txt, "$*")) {
+                               err = dollar_line(state, g, 1);
                        } else {
                                err = "Unrecognised token at start of line.";
                        }
@@ -1953,9 +1989,10 @@ to the appropriate type for each access.  All this is handling in
                                n = n * 10 + *c - '0';
                        }
                        if (n == 0)
-                               fprintf(f, "(*(struct %.*s*)ret)",
+                               fprintf(f, "(*(struct %.*s*%s)ret)",
                                        p->head->struct_name.len,
-                                       p->head->struct_name.txt);
+                                       p->head->struct_name.txt,
+                                       p->head->isref ? "*":"");
                        else if (n > p->body_size)
                                fprintf(f, "$%d", n);
                        else if (p->body[n-1]->type == Terminal)
@@ -1964,9 +2001,10 @@ to the appropriate type for each access.  All this is handling in
                        else if (p->body[n-1]->struct_name.txt == NULL)
                                fprintf(f, "$%d", n);
                        else
-                               fprintf(f, "(*(struct %.*s*)body[%d])",
+                               fprintf(f, "(*(struct %.*s*%s)body[%d])",
                                        p->body[n-1]->struct_name.len,
-                                       p->body[n-1]->struct_name.txt, n-1);
+                                       p->body[n-1]->struct_name.txt,
+                                       p->body[n-1]->isref ? "*":"", n-1);
                }
                fputs("\n", f);
        }
@@ -1990,9 +2028,10 @@ to the appropriate type for each access.  All this is handling in
                                gen_code(p, f, g);
 
                        if (p->head->struct_name.txt)
-                               fprintf(f, "\t\tret_size = sizeof(struct %.*s);\n",
+                               fprintf(f, "\t\tret_size = sizeof(struct %.*s%s);\n",
                                        p->head->struct_name.len,
-                                       p->head->struct_name.txt);
+                                       p->head->struct_name.txt,
+                                       p->head->isref ? "*":"");
 
                        fprintf(f, "\t\tbreak;\n");
                }
@@ -2035,9 +2074,14 @@ appropriate for tokens` on any terminal symbol.
                                continue;
 
                        fprintf(f, "\tcase %d:\n", s->num);
-                       fprintf(f, "\t\tfree_%.*s(asn);\n",
-                               s->struct_name.len,
-                               s->struct_name.txt);
+                       if (s->isref)
+                               fprintf(f, "\t\tfree_%.*s(*(void**)asn);\n",
+                                       s->struct_name.len,
+                                       s->struct_name.txt);
+                       else
+                               fprintf(f, "\t\tfree_%.*s(asn);\n",
+                                       s->struct_name.len,
+                                       s->struct_name.txt);
                        fprintf(f, "\t\tbreak;\n");
                }
                fprintf(f, "\t}\n}\n\n");
@@ -2069,17 +2113,19 @@ grammar file).
                { "SLR",        0, NULL, 'S' },
                { "LALR",       0, NULL, 'L' },
                { "LR1",        0, NULL, '1' },
+               { "tag",        1, NULL, 't' },
                { "report",     0, NULL, 'R' },
                { "output",     1, NULL, 'o' },
                { NULL,         0, NULL, 0   }
        };
-       const char *options = "05SL1Ro:";
+       const char *options = "05SL1t:Ro:";
 
 ###### process arguments
        int opt;
        char *outfile = NULL;
        char *infile;
        char *name;
+       char *tag = NULL;
        int report = 1;
        enum grammar_type type = LR05;
        while ((opt = getopt_long(argc, argv, options,
@@ -2099,6 +2145,8 @@ grammar file).
                        report = 2; break;
                case 'o':
                        outfile = optarg; break;
+               case 't':
+                       tag = optarg; break;
                default:
                        fprintf(stderr, "Usage: parsergen ...\n");
                        exit(1);
@@ -2164,11 +2212,14 @@ parser with neither. "grammar" must be provided.
        struct code_node *code = NULL;
        struct code_node *gram = NULL;
        for (s = table; s; s = s->next) {
-               if (text_is(s->section, "header"))
+               struct text sec = s->section;
+               if (tag && !strip_tag(&sec, tag))
+                       continue;
+               if (text_is(sec, "header"))
                        hdr = s->code;
-               else if (text_is(s->section, "code"))
+               else if (text_is(sec, "code"))
                        code = s->code;
-               else if (text_is(s->section, "grammar"))
+               else if (text_is(sec, "grammar"))
                        gram = s->code;
                else {
                        fprintf(stderr, "Unknown content section: %.*s\n",
@@ -2530,7 +2581,7 @@ at the start of a production.
                                // will fail).
                        }
                        if (p.next.sym == TK_newline) {
-                               if (! p.stack[p.tos-1].newline_permitted) {
+                               if (!p.tos || ! p.stack[p.tos-1].newline_permitted) {
                                        free(tk);
                                        tk = NULL;
                                        continue;
@@ -2703,15 +2754,12 @@ better approach, but as the grammar file must have 3 components I need
 something like this.
 
 ###### File: parsergen.mk
-       calc.c : parsergen calc.cgm
-               ./parsergen -o calc calc.cgm
+       calc.c calc.h : parsergen parsergen.mdc
+               ./parsergen --tag calc -o calc parsergen.mdc
        calc : calc.o libparser.o libscanner.o libmdcode.o libnumber.o
                $(CC) $(CFLAGS) -o calc calc.o libparser.o libscanner.o libmdcode.o libnumber.o -licuuc -lgmp
 
-###### demo grammar
-
-       # header
-       ~~~~~
+# calc: header
 
        #include "number.h"
        // what do we use for a demo-grammar?  A calculator of course.
@@ -2721,9 +2769,7 @@ something like this.
                int err;
        };
 
-       ~~~~~
-       # code
-       ~~~~~
+# calc: code
 
        #include <stdlib.h>
        #include <unistd.h>
@@ -2764,9 +2810,7 @@ something like this.
                exit(0);
        }
 
-       ~~~~~
-       # grammar
-       ~~~~~
+# calc: grammar
 
        Session -> Session Line
                | Line