Commit 3be3e4a5 authored by POTTIER Francois's avatar POTTIER Francois

Introduction of token aliases.

parent 2d531597
# Changes # Changes
## 2018/10/26
* A new syntactic sugar facility, "token aliases", has been added.
The declaration of a terminal symbol may also declare an alias,
which takes the form of a name between double quotes, as in
`%token PLUS "+"`.
Thereafter, `"+"` may be used freely in place of `PLUS` throughout
the grammar. This makes it slightly easier to read grammars.
(Contributed by Perry E. Metzger.)
## 2018/10/25 ## 2018/10/25
* Until today, the semicolon character `;` was insignificant: it was * Until today, the semicolon character `;` was insignificant: it was
......
...@@ -131,13 +131,6 @@ ...@@ -131,13 +131,6 @@
Ou alors, exposer juste le symbole de départ correspondant? Ou alors, exposer juste le symbole de départ correspondant?
Mais comment le retrouver? Mais comment le retrouver?
* Allow %token PLUS "+"
as a definition of "+" as syntactic sugar for PLUS
(Check what Bison allows, and follow it.)
This could be desugared on the fly in the fancy parser
(although it would then be impossible to print the sugared grammar).
(Suggested by Perry Metzger.)
* autoriser %token FOO [@unused] * autoriser %token FOO [@unused]
équivalent à --unused-token FOO sur la ligne de commande. équivalent à --unused-token FOO sur la ligne de commande.
Add an analogous mechanism for nonterminals that are known Add an analogous mechanism for nonterminals that are known
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
DEMOS := \ DEMOS := \
calc \ calc \
calc-alias \
calc-two \ calc-two \
calc-param \ calc-param \
calc-incremental \ calc-incremental \
......
.PHONY: all clean test
# Find Menhir.
ifndef MENHIR
MENHIR := $(shell ../find-menhir.sh)
endif
MENHIRFLAGS := --infer
OCAMLBUILD := ocamlbuild -use-ocamlfind -use-menhir -menhir "$(MENHIR) $(MENHIRFLAGS)"
MAIN := calc
all:
$(OCAMLBUILD) $(MAIN).native
clean:
rm -f *~ .*~
$(OCAMLBUILD) -clean
test: all
@echo "The following command should print 42:"
echo "(1 + 2 * 10) * 2" | ./$(MAIN).native
This tiny program reads arithmetic expressions from the standard input
channel. Each expression is expected to be complete when the current line
ends. Its value is then displayed on the standard output channel. This
code is adapted from ocamlyacc's documentation.
The difference between this demo and "calc" is that in this case, we make use
of token aliases to make the grammar look slightly nicer.
let process (line : string) =
let linebuf = Lexing.from_string line in
try
(* Run the parser on this line of input. *)
Printf.printf "%d\n%!" (Parser.main Lexer.token linebuf)
with
| Lexer.Error msg ->
Printf.fprintf stderr "%s%!" msg
| Parser.Error ->
Printf.fprintf stderr "At offset %d: syntax error.\n%!" (Lexing.lexeme_start linebuf)
let process (optional_line : string option) =
match optional_line with
| None ->
()
| Some line ->
process line
let rec repeat channel =
(* Attempt to read one line. *)
let optional_line, continue = Lexer.line channel in
process optional_line;
if continue then
repeat channel
let () =
repeat (Lexing.from_channel stdin)
{
open Parser
exception Error of string
}
(* This rule looks for a single line, terminated with '\n' or eof.
It returns a pair of an optional string (the line that was found)
and a Boolean flag (false if eof was reached). *)
rule line = parse
| ([^'\n']* '\n') as line
(* Normal case: one line, no eof. *)
{ Some line, true }
| eof
(* Normal case: no data, eof. *)
{ None, false }
| ([^'\n']+ as line) eof
(* Special case: some data but missing '\n', then eof.
Consider this as the last line, and add the missing '\n'. *)
{ Some (line ^ "\n"), false }
(* This rule analyzes a single line and turns it into a stream of
tokens. *)
and token = parse
| [' ' '\t']
{ token lexbuf }
| '\n'
{ EOL }
| ['0'-'9']+ as i
{ INT (int_of_string i) }
| '+'
{ PLUS }
| '-'
{ MINUS }
| '*'
{ TIMES }
| '/'
{ DIV }
| '('
{ LPAREN }
| ')'
{ RPAREN }
| _
{ raise (Error (Printf.sprintf "At offset %d: unexpected character.\n" (Lexing.lexeme_start lexbuf))) }
(* In the following, we exploit token aliases: e.g., "+" is an alias
for PLUS, "-" is an alias for MINUS, and so on. We exhibit a tiny
bit of bad taste and define "42" as an alias for the token INT.
Obviously there are integers other than 42; the token INT and its
alias "42" stand for an arbitrary integer literal. *)
(* Token, and token aliases, are declared here: *)
%token<int> INT "42"
%token PLUS "+"
%token MINUS "-"
%token TIMES "*"
%token DIV "/"
%token LPAREN "("
%token RPAREN ")"
%token EOL
(* Token aliases can be used throughout the rest of the grammar. E.g.,
they can be used in precedence declarations: *)
%left "+" "-" /* lowest precedence */
%left "*" "/" /* medium precedence */
%nonassoc UMINUS /* highest precedence */
%start <int> main
%%
main:
| e = expr EOL
{ e }
(* Token aliases can also be used inside rules: *)
expr:
| i = "42"
{ i }
| "(" e = expr ")"
{ e }
| e1 = expr "+" e2 = expr
{ e1 + e2 }
| e1 = expr "-" e2 = expr
{ e1 - e2 }
| e1 = expr "*" e2 = expr
{ e1 * e2 }
| e1 = expr "/" e2 = expr
{ e1 / e2 }
| "-" e = expr %prec UMINUS
{ - e }
...@@ -2,7 +2,11 @@ ...@@ -2,7 +2,11 @@
\let\nt\textit % Nonterminal. \let\nt\textit % Nonterminal.
\newcommand{\is}{& ${} ::= {}$ &} \newcommand{\is}{& ${} ::= {}$ &}
\newcommand{\optional}[1]{$[\,\text{#1}\,]$} % Option. \newcommand{\optional}[1]{%
\ifmmode [\,#1\,]%
\else $[\,\text{#1}\,]$%
\fi
} % Option.
\newcommand{\seplist}[2]{#2#1${}\ldots{}$#1#2} \newcommand{\seplist}[2]{#2#1${}\ldots{}$#1#2}
\newcommand{\sepspacelist}[1]{\seplist{\ }{#1}} \newcommand{\sepspacelist}[1]{\seplist{\ }{#1}}
\newcommand{\sepcommalist}[1]{\seplist{,\ }{#1}} \newcommand{\sepcommalist}[1]{\seplist{,\ }{#1}}
......
...@@ -336,6 +336,10 @@ allowed to contain the quote (\kw{'}) character. Following ...@@ -336,6 +336,10 @@ allowed to contain the quote (\kw{'}) character. Following
\ocaml, identifiers that begin with a lowercase letter \ocaml, identifiers that begin with a lowercase letter
(\nt{lid}) or with an uppercase letter (\nt{uid}) are distinguished. (\nt{lid}) or with an uppercase letter (\nt{uid}) are distinguished.
A quoted identifier \nt{qid} is a string enclosed in double quotes.
Such a string cannot contain a double quote or a backslash.
Quoted identifiers are used as token aliases (\sref{sec:tokens}).
Comments are C-style (surrounded with \kw{/*} and \kw{*/}, cannot be nested), Comments are C-style (surrounded with \kw{/*} and \kw{*/}, cannot be nested),
C++-style (announced by \kw{/$\!$/} and extending until the end of the line), or C++-style (announced by \kw{/$\!$/} and extending until the end of the line), or
\ocaml-style (surrounded with \kw{(*} and \kw{*)}, can be nested). Of course, \ocaml-style (surrounded with \kw{(*} and \kw{*)}, can be nested). Of course,
...@@ -362,7 +366,7 @@ must be fully qualified. ...@@ -362,7 +366,7 @@ must be fully qualified.
\nt{declaration} \is \nt{declaration} \is
\dheader{\textit{\ocaml code}} \\ \dheader{\textit{\ocaml code}} \\
&& \dparameter \ocamlparam \\ && \dparameter \ocamlparam \\
&& \dtoken \optional{\ocamltype} \sepspacelist{\nt{uid}} \\ && \dtoken \optional{\ocamltype} \sepspacelist{\nt{uid} \optional{\nt{qid}}} \\
&& \dnonassoc \sepspacelist{\nt{uid}} \\ && \dnonassoc \sepspacelist{\nt{uid}} \\
&& \dleft \sepspacelist{\nt{uid}} \\ && \dleft \sepspacelist{\nt{uid}} \\
&& \dright \sepspacelist{\nt{uid}} \\ && \dright \sepspacelist{\nt{uid}} \\
...@@ -453,16 +457,44 @@ This means, in particular, that the side effects of an \ocaml header are ...@@ -453,16 +457,44 @@ This means, in particular, that the side effects of an \ocaml header are
observed only when the functor is applied, not when it is defined. observed only when the functor is applied, not when it is defined.
\subsubsection{Tokens} \subsubsection{Tokens}
\label{sec:tokens}
A declaration of the form: A declaration of the form:
\begin{quote} \begin{quote}
\dtoken \optional{\ocamltype} $\nt{uid}_1, \ldots, \nt{uid}_n$ \dtoken
\optional{\ocamltype}
$\nt{uid}_1$ \optional{$\nt{qid}_1$} $\;\ldots\;$
$\nt{uid}_n$ \optional{$\nt{qid}_n$}
\end{quote} \end{quote}
defines the identifiers $\nt{uid}_1, \ldots, \nt{uid}_n$ as tokens, that is, defines the identifiers $\nt{uid}_1, \ldots, \nt{uid}_n$ as tokens, that is,
as terminal symbols in the grammar specification and as data constructors in as terminal symbols in the grammar specification and as data constructors in
the \textit{token} type. If an \ocaml type $t$ is present, then these tokens the \textit{token} type.
are considered to carry a semantic value of type $t$, otherwise they are
considered to carry no semantic value. If an \ocaml type $t$ is present, then these tokens are considered to carry a
semantic value of type $t$, otherwise they are considered to carry no semantic
value.
If a quoted identifier $\nt{qid}_i$ is present, then it is considered an alias
for the terminal symbol $\nt{uid}_i$. (This feature, known as ``token
aliases'', is borrowed from Bison.)
% https://www.gnu.org/software/bison/manual/html_node/Token-Decl.html#Token-Decl
Throughout the grammar, the quoted identifier $\nt{qid}_i$ is then
synonymous with the identifier $\nt{uid}_i$.
%
For example, if one declares:
\begin{verbatim}
%token PLUS "+"
\end{verbatim}
then the quoted identifier \texttt{"+"} stands for the terminal symbol
\texttt{PLUS} throughout the grammar. An example of the use of token aliases
appears in the directory \distrib{demos/calc-alias}.
%
Token aliases can be used to improve the readability of a grammar. One must
keep in mind, however, that they are just syntactic sugar: they are not
interpreted in any way by Menhir or conveyed to tools like \ocamllex.
%
They could be considered confusing by a reader who mistakenly believes that
they are interpreted as string literals.
\subsubsection{Priority and associativity} \subsubsection{Priority and associativity}
\label{sec:assoc} \label{sec:assoc}
......
open Syntax
(* We first build an alias map, which records the token aliases declared
across all partial grammars. This is a map of aliases to pairs of a
terminal symbol and the position where this symbol is declared. Then, we
walk the partial grammars before they are joined, expanding the token
aliases along the way. *)
type aliasmap =
(terminal * Positions.t) StringMap.t
(* -------------------------------------------------------------------------- *)
(* Extend an alias map with the token aliases present in a declaration. *)
let collect_aliases_from_declaration (aliasmap : aliasmap) decl : aliasmap =
match Positions.value decl with
| DToken (_, id, Some qid, _) ->
begin match StringMap.find qid aliasmap with
| exception Not_found ->
(* Good: this alias does not exist yet. Record it. *)
StringMap.add qid (id, Positions.position decl) aliasmap
| id0, pos ->
(* Oops: [qid] has already been declared as an alias for
some other token. *)
Error.error
[Positions.position decl; pos]
"%s cannot be declared as an alias for the symbol %s.\n\
It has already been declared as an alias for %s."
qid id id0
end
| _ ->
aliasmap
(* Extend an alias map with the token aliases present in a partial grammar. *)
let collect_aliases_from_grammar aliasmap g =
List.fold_left collect_aliases_from_declaration aliasmap g.pg_declarations
let collect_aliases_from_grammars gs : aliasmap =
List.fold_left collect_aliases_from_grammar StringMap.empty gs
(* -------------------------------------------------------------------------- *)
(* Expand a possible alias, returning a name which definitely is not an
alias (and may or may not be a valid terminal symbol). *)
let dealias_terminal (aliasmap : aliasmap) pos (t : terminal) : terminal =
(* [t] is either a terminal symbol or a token alias. If it
starts with double quote, then it must be a token alias. *)
if t.[0] = '"' then
match StringMap.find t aliasmap with
| id, _ ->
id
| exception Not_found ->
Error.error
[pos]
"the token alias %s was never declared." t
else
t
(* Perform alias expansion throughout a partial grammar.
(Visitors could be useful here!) *)
let dealias_symbol aliasmap (sym : terminal Positions.located) =
Positions.pmap (dealias_terminal aliasmap) sym
let rec dealias_parameter aliasmap (param : parameter) =
match param with
| ParameterVar sym ->
ParameterVar (dealias_symbol aliasmap sym)
| ParameterApp (sym, params) ->
ParameterApp (
dealias_symbol aliasmap sym,
dealias_parameters aliasmap params
)
| ParameterAnonymous branches ->
ParameterAnonymous (Positions.map (dealias_branches aliasmap) branches)
and dealias_parameters aliasmap params =
List.map (dealias_parameter aliasmap) params
and dealias_producer aliasmap (producer : producer) =
let id, param, attrs = producer in
id, (dealias_parameter aliasmap param), attrs
and dealias_producers aliasmap producers =
List.map (dealias_producer aliasmap) producers
and dealias_branch aliasmap (branch : parameterized_branch) =
{ branch with pr_producers = dealias_producers aliasmap branch.pr_producers }
and dealias_branches aliasmap branches =
List.map (dealias_branch aliasmap) branches
let dealias_rule aliasmap rule =
{ rule with pr_branches = dealias_branches aliasmap rule.pr_branches }
let dealias_decl aliasmap (decl : declaration Positions.located) =
Positions.pmap (fun pos (decl : declaration) ->
match decl with
| DCode _
| DParameter _
| DToken _
| DStart _
| DGrammarAttribute _ ->
decl
| DTokenProperties (t, assoc, prec) ->
DTokenProperties (dealias_terminal aliasmap pos t, assoc, prec)
| DType (ty, param) ->
DType (ty, dealias_parameter aliasmap param)
| DSymbolAttributes (params, attrs) ->
DSymbolAttributes (dealias_parameters aliasmap params, attrs)
| DOnErrorReduce (param, level) ->
DOnErrorReduce (dealias_parameter aliasmap param, level)
) decl
let dealias_grammar aliasmap g =
{ g with
pg_declarations = List.map (dealias_decl aliasmap) g.pg_declarations;
pg_rules = List.map (dealias_rule aliasmap) g.pg_rules }
let dealias_grammars aliasmap gs =
List.map (dealias_grammar aliasmap) gs
(* -------------------------------------------------------------------------- *)
(* The two phases above are combined as follows. *)
let dealias_grammars gs =
let aliasmap = collect_aliases_from_grammars gs in
dealias_grammars aliasmap gs
(* Token aliases are quoted strings that are used to provide syntactic sugar
for terminal symbols, for example, to allow "+" to be used in grammar rules
instead of PLUS, or to allow ")" instead of RPAREN. *)
(* This transformation eliminates all references to token aliases in a list of
partial grammars. (An alias declared in one partial grammar can be used in
another partial grammar.) Declarations of token aliases are preserved, and
could be used if desired (e.g. for printing). *)
open Syntax
val dealias_grammars: partial_grammar list -> partial_grammar list
...@@ -34,7 +34,7 @@ open Positions ...@@ -34,7 +34,7 @@ open Positions
%token TOKEN TYPE LEFT RIGHT NONASSOC START PREC PUBLIC COLON BAR EOF EQUAL %token TOKEN TYPE LEFT RIGHT NONASSOC START PREC PUBLIC COLON BAR EOF EQUAL
%token INLINE LPAREN RPAREN COMMA QUESTION STAR PLUS PARAMETER ON_ERROR_REDUCE %token INLINE LPAREN RPAREN COMMA QUESTION STAR PLUS PARAMETER ON_ERROR_REDUCE
%token PERCENTATTRIBUTE SEMI %token PERCENTATTRIBUTE SEMI
%token <string Positions.located> LID UID %token <string Positions.located> LID UID QID
%token <Stretch.t> HEADER %token <Stretch.t> HEADER
%token <Stretch.ocamltype> OCAMLTYPE %token <Stretch.ocamltype> OCAMLTYPE
%token <Stretch.t Lazy.t> PERCENTPERCENT %token <Stretch.t Lazy.t> PERCENTPERCENT
...@@ -90,8 +90,10 @@ declaration: ...@@ -90,8 +90,10 @@ declaration:
| h = HEADER /* lexically delimited by %{ ... %} */ | h = HEADER /* lexically delimited by %{ ... %} */
{ [ with_loc $loc (DCode h) ] } { [ with_loc $loc (DCode h) ] }
| TOKEN ty = OCAMLTYPE? ts = clist(terminal) | TOKEN ty = OCAMLTYPE? ts = clist(terminal_alias_attrs)
{ List.map (Positions.map (fun (terminal, attrs) -> DToken (ty, terminal, attrs))) ts } { List.map (Positions.map (fun (terminal, alias, attrs) ->
DToken (ty, terminal, alias, attrs)
)) ts }
| START t = OCAMLTYPE? nts = clist(nonterminal) | START t = OCAMLTYPE? nts = clist(nonterminal)
/* %start <ocamltype> foo is syntactic sugar for %start foo %type <ocamltype> foo */ /* %start <ocamltype> foo is syntactic sugar for %start foo %type <ocamltype> foo */
...@@ -164,25 +166,34 @@ priority_keyword: ...@@ -164,25 +166,34 @@ priority_keyword:
{ xs } { xs }
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/* A symbol is a terminal or nonterminal symbol. One would like to /* A symbol is a terminal or nonterminal symbol. */
require nonterminal symbols to begin with a lowercase letter, so as
to lexically distinguish them from terminal symbols, which must /* One would like to require nonterminal symbols to begin with a lowercase
begin with an uppercase letter. However, for compatibility with letter, so as to lexically distinguish them from terminal symbols, which
ocamlyacc, this is impossible. It can be required only for must begin with an uppercase letter. However, for compatibility with
nonterminal symbols that are also start symbols. */ ocamlyacc, this is impossible. It can be required only for nonterminal
symbols that are also start symbols. */
/* We also accept token aliases in place of ordinary terminal symbols.
Token aliases are quoted strings. */
symbol: symbol:
id = LID id = LID
| id = UID | id = UID
| id = QID
{ id } { id }
/* ------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------- */
/* Terminals must begin with an uppercase letter. Nonterminals that are /* Terminals must begin with an uppercase letter. Nonterminals that are
declared to be start symbols must begin with a lowercase letter. */ declared to be start symbols must begin with a lowercase letter. */
%inline terminal: /* In declarations, terminals must be UIDs, but we may also declare
id = UID attrs = ATTRIBUTE* token aliases, which are QIDs. */
{ Positions.map (fun uid -> (uid, attrs)) id }
%inline terminal_alias_attrs:
id = UID alias = QID? attrs = ATTRIBUTE*
{ let alias = Option.map Positions.value alias in
Positions.map (fun uid -> uid, alias, attrs) id }
%inline nonterminal: %inline nonterminal:
id = LID id = LID
......
...@@ -444,6 +444,11 @@ rule main = parse ...@@ -444,6 +444,11 @@ rule main = parse
} }
| (uppercase identchar *) as id | (uppercase identchar *) as id
{ UID (with_pos (cpos lexbuf) id) } { UID (with_pos (cpos lexbuf) id) }
(* Quoted strings, which are used as aliases for tokens.
For simplicity, we just disallow double quotes and backslash outright.
Given the use of terminal strings in grammars, this is fine. *)
| ( "\"" ( [' ' - '~'] # ['"' '\\'] + ) "\"" ) as id
{ QID (with_pos (cpos lexbuf) id) }
| "//" [^ '\010' '\013']* newline (* skip C++ style comment *) | "//" [^ '\010' '\013']* newline (* skip C++ style comment *)
| newline | newline
{ new_line lexbuf; main lexbuf } { new_line lexbuf; main lexbuf }
......
...@@ -34,13 +34,15 @@ Examples of well-formed declarations: ...@@ -34,13 +34,15 @@ Examples of well-formed declarations:
grammar: TOKEN TYPE grammar: TOKEN TYPE
grammar: TOKEN OCAMLTYPE TYPE grammar: TOKEN OCAMLTYPE TYPE
grammar: TOKEN UID STAR grammar: TOKEN UID STAR
grammar: TOKEN UID QID STAR
grammar: TOKEN UID COMMA TYPE grammar: TOKEN UID COMMA TYPE
grammar: TOKEN UID ATTRIBUTE STAR grammar: TOKEN UID ATTRIBUTE STAR
Ill-formed %token declaration. Ill-formed %token declaration.
Examples of well-formed declarations: Examples of well-formed declarations:
%token FOO %token FOO
%token DOT SEMICOLON %token BAR "|"
%token DOT "." SEMICOLON ";"
%token <string> LID UID %token <string> LID UID
%token FOO [@cost 0] %token FOO [@cost 0]
......
...@@ -36,7 +36,9 @@ let join_declaration filename (grammar : grammar) decl = ...@@ -36,7 +36,9 @@ let join_declaration filename (grammar : grammar) decl =
difficult by the fact that %token and %left-%right-%nonassoc difficult by the fact that %token and %left-%right-%nonassoc
declarations are independent. *) declarations are independent. *)
| DToken (ocamltype, terminal, attributes) -> (* Declarations of token aliases are lost at this point. *)
| DToken (ocamltype, terminal, _alias, attributes) ->
let token_property = let token_property =
try try
...@@ -755,13 +757,17 @@ let check_parameterized_grammar_is_well_defined grammar = ...@@ -755,13 +757,17 @@ let check_parameterized_grammar_is_well_defined grammar =
Error.warning [p] Error.warning [p]
"the token %s is unused." token "the token %s is unused." token
) grammar.p_tokens ) grammar.p_tokens
end; end
grammar
let join_partial_grammars pgs = let join_partial_grammars pgs =
(* Prior to joining the partial grammars, remove all uses of token aliases. *)
let pgs = ExpandTokenAliases.dealias_grammars pgs in
(* Join the partial grammars. *)
let grammar = List.fold_left join empty_grammar pgs in let grammar = List.fold_left join empty_grammar pgs in
let symbols = List.map (symbols_of grammar) pgs in let symbols = List.map (symbols_of grammar) pgs in
let tpgs = List.combine symbols pgs in let tpgs = List.combine symbols pgs in
let rules = merge_rules symbols tpgs in let rules = merge_rules symbols tpgs in
check_parameterized_grammar_is_well_defined { grammar with p_rules = rules } let grammar = { grammar with p_rules = rules } in
(* Check well-formedness. *)
check_parameterized_grammar_is_well_defined grammar;
grammar
...@@ -21,9 +21,7 @@ ...@@ -21,9 +21,7 @@
(* ------------------------------------------------------------------------ *) (* ------------------------------------------------------------------------ *)
(* Terminals and nonterminal symbols are strings. Identifiers (* Terminals and nonterminal symbols are strings. *)
(which are used to refer to a symbol's semantic value) are
strings. A file name is a string. *)
type terminal = type terminal =
string string
...@@ -34,9 +32,28 @@ type nonterminal = ...@@ -34,9 +32,28 @@ type nonterminal =
type symbol = type symbol =
string string
(* In a somewhat fragile convention, in a partial grammar, a reference to a
terminal symbol either is a normal identifier [LID], in which case it is
the name of the terminal symbol, or is a quoted identifier [QID], in which
case it is a token alias.
Token aliases are eliminated by replacing them with the corresponding
terminal symbols very early on during the joining of the partial grammars
-- see the function [dealias_pg] in [PartialGrammar].
In a complete grammar, there are no token aliases any longer. *)
type alias =
string option
(* Identifiers (which are used to refer to a symbol's semantic value) are
strings. *)
type identifier = type identifier =
string string
(* A file name is a string. *)
type filename = type filename =
string string
...@@ -200,7 +217,7 @@ type declaration = ...@@ -200,7 +217,7 @@ type declaration =
(* Terminal symbol (token) declaration. *) (* Terminal symbol (token) declaration. *)
| DToken of Stretch.ocamltype option * terminal * attributes | DToken of Stretch.ocamltype option * terminal * alias * attributes
(* Start symbol declaration. *) (* Start symbol declaration. *)
...@@ -233,7 +250,7 @@ type declaration = ...@@ -233,7 +250,7 @@ type declaration =
type partial_grammar = type partial_grammar =
{ {
pg_filename : filename; pg_filename : filename;
pg_postlude : postlude option; pg_postlude : postlude option;
pg_declarations : declaration Positions.located list; pg_declarations : declaration Positions.located list;
pg_rules : parameterized_rule list; pg_rules : parameterized_rule list;
} }
...@@ -249,9 +266,9 @@ type partial_grammar = ...@@ -249,9 +266,9 @@ type partial_grammar =
functor %parameters, %start symbols, %types, %tokens, %on_error_reduce, functor %parameters, %start symbols, %types, %tokens, %on_error_reduce,
grammar attributes, %attributes. grammar attributes, %attributes.
4. rules are stored in a map, indexed by symbol names, instead of a list. 4. rules are stored in a map, indexed by symbol names, instead of a list.
5. token aliases have been replaced with ordinary named terminal symbols.
*) *)
type grammar =
type grammar =
{ {
p_preludes : Stretch.t list; p_preludes : Stretch.t list;
p_postludes : postlude list; p_postludes : postlude list;
......
...@@ -29,7 +29,7 @@ open Positions ...@@ -29,7 +29,7 @@ open Positions