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

Introduction of token aliases.

parent 2d531597
# 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
* Until today, the semicolon character `;` was insignificant: it was
......
......@@ -131,13 +131,6 @@
Ou alors, exposer juste le symbole de départ correspondant?
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]
équivalent à --unused-token FOO sur la ligne de commande.
Add an analogous mechanism for nonterminals that are known
......
......@@ -10,6 +10,7 @@
DEMOS := \
calc \
calc-alias \
calc-two \
calc-param \
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 @@
\let\nt\textit % Nonterminal.
\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{\sepspacelist}[1]{\seplist{\ }{#1}}
\newcommand{\sepcommalist}[1]{\seplist{,\ }{#1}}
......
......@@ -336,6 +336,10 @@ allowed to contain the quote (\kw{'}) character. Following
\ocaml, identifiers that begin with a lowercase letter
(\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),
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,
......@@ -362,7 +366,7 @@ must be fully qualified.
\nt{declaration} \is
\dheader{\textit{\ocaml code}} \\
&& \dparameter \ocamlparam \\
&& \dtoken \optional{\ocamltype} \sepspacelist{\nt{uid}} \\
&& \dtoken \optional{\ocamltype} \sepspacelist{\nt{uid} \optional{\nt{qid}}} \\
&& \dnonassoc \sepspacelist{\nt{uid}} \\
&& \dleft \sepspacelist{\nt{uid}} \\
&& \dright \sepspacelist{\nt{uid}} \\
......@@ -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.
\subsubsection{Tokens}
\label{sec:tokens}
A declaration of the form:
\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}
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
the \textit{token} type. 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.
the \textit{token} type.
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}
\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
%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 PERCENTATTRIBUTE SEMI
%token <string Positions.located> LID UID
%token <string Positions.located> LID UID QID
%token <Stretch.t> HEADER
%token <Stretch.ocamltype> OCAMLTYPE
%token <Stretch.t Lazy.t> PERCENTPERCENT
......@@ -90,8 +90,10 @@ declaration:
| h = HEADER /* lexically delimited by %{ ... %} */
{ [ with_loc $loc (DCode h) ] }
| TOKEN ty = OCAMLTYPE? ts = clist(terminal)
{ List.map (Positions.map (fun (terminal, attrs) -> DToken (ty, terminal, attrs))) ts }
| TOKEN ty = OCAMLTYPE? ts = clist(terminal_alias_attrs)
{ List.map (Positions.map (fun (terminal, alias, attrs) ->
DToken (ty, terminal, alias, attrs)
)) ts }
| START t = OCAMLTYPE? nts = clist(nonterminal)
/* %start <ocamltype> foo is syntactic sugar for %start foo %type <ocamltype> foo */
......@@ -164,25 +166,34 @@ priority_keyword:
{ xs }
/* ------------------------------------------------------------------------- */
/* A symbol is a terminal or nonterminal symbol. One would like to
require nonterminal symbols to begin with a lowercase letter, so as
to lexically distinguish them from terminal symbols, which must
begin with an uppercase letter. However, for compatibility with
ocamlyacc, this is impossible. It can be required only for
nonterminal symbols that are also start symbols. */
/* A symbol is a terminal or nonterminal symbol. */
/* One would like to require nonterminal symbols to begin with a lowercase
letter, so as to lexically distinguish them from terminal symbols, which
must begin with an uppercase letter. However, for compatibility with
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:
id = LID
| id = UID
| id = QID
{ id }
/* ------------------------------------------------------------------------- */
/* Terminals must begin with an uppercase letter. Nonterminals that are
declared to be start symbols must begin with a lowercase letter. */
%inline terminal:
id = UID attrs = ATTRIBUTE*
{ Positions.map (fun uid -> (uid, attrs)) id }
/* In declarations, terminals must be UIDs, but we may also declare
token aliases, which are QIDs. */
%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:
id = LID
......
......@@ -444,6 +444,11 @@ rule main = parse
}
| (uppercase identchar *) as 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 *)
| newline
{ new_line lexbuf; main lexbuf }
......
......@@ -34,13 +34,15 @@ Examples of well-formed declarations:
grammar: TOKEN TYPE
grammar: TOKEN OCAMLTYPE TYPE
grammar: TOKEN UID STAR
grammar: TOKEN UID QID STAR
grammar: TOKEN UID COMMA TYPE
grammar: TOKEN UID ATTRIBUTE STAR
Ill-formed %token declaration.
Examples of well-formed declarations:
%token FOO
%token DOT SEMICOLON
%token BAR "|"
%token DOT "." SEMICOLON ";"
%token <string> LID UID
%token FOO [@cost 0]
......
......@@ -36,7 +36,9 @@ let join_declaration filename (grammar : grammar) decl =
difficult by the fact that %token and %left-%right-%nonassoc
declarations are independent. *)
| DToken (ocamltype, terminal, attributes) ->
(* Declarations of token aliases are lost at this point. *)
| DToken (ocamltype, terminal, _alias, attributes) ->
let token_property =
try
......@@ -755,13 +757,17 @@ let check_parameterized_grammar_is_well_defined grammar =
Error.warning [p]
"the token %s is unused." token
) grammar.p_tokens
end;
grammar
end
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 symbols = List.map (symbols_of grammar) pgs in
let tpgs = List.combine symbols pgs 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 @@
(* ------------------------------------------------------------------------ *)
(* Terminals and nonterminal symbols are strings. Identifiers
(which are used to refer to a symbol's semantic value) are
strings. A file name is a string. *)
(* Terminals and nonterminal symbols are strings. *)
type terminal =
string
......@@ -34,9 +32,28 @@ type nonterminal =
type symbol =
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 =
string
(* A file name is a string. *)
type filename =
string
......@@ -200,7 +217,7 @@ type declaration =
(* Terminal symbol (token) declaration. *)
| DToken of Stretch.ocamltype option * terminal * attributes
| DToken of Stretch.ocamltype option * terminal * alias * attributes
(* Start symbol declaration. *)
......@@ -249,9 +266,9 @@ type partial_grammar =
functor %parameters, %start symbols, %types, %tokens, %on_error_reduce,
grammar attributes, %attributes.
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_postludes : postlude list;
......
......@@ -29,7 +29,7 @@ open Positions
%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 PERCENTATTRIBUTE SEMI
%token <string Positions.located> LID UID
%token <string Positions.located> LID UID QID
%token <Stretch.t> HEADER
%token <Stretch.ocamltype> OCAMLTYPE
%token <Stretch.t Lazy.t> PERCENTPERCENT
......@@ -90,7 +90,10 @@ declaration:
{ [ unknown_pos (DCode $1) ] }
| TOKEN optional_ocamltype terminals
{ List.map (Positions.map (fun (terminal, attrs) -> DToken ($2, terminal, attrs))) $3 }
{ let ty, ts = $2, $3 in
List.map (Positions.map (fun (terminal, alias, attrs) ->
DToken (ty, terminal, alias, attrs)
)) ts }
| START nonterminals
{ List.map (Positions.map (fun nonterminal -> DStart nonterminal)) $2 }
......@@ -137,12 +140,16 @@ priority_keyword:
{ NonAssoc }
/* ------------------------------------------------------------------------- */
/* A symbol is a terminal or nonterminal symbol. One would like to
require nonterminal symbols to begin with a lowercase letter, so as
to lexically distinguish them from terminal symbols, which must
begin with an uppercase letter. However, for compatibility with
ocamlyacc, this is impossible. It can be required only for
nonterminal symbols that are also start symbols. */
/* A symbol is a terminal or nonterminal symbol. */
/* One would like to require nonterminal symbols to begin with a lowercase
letter, so as to lexically distinguish them from terminal symbols, which
must begin with an uppercase letter. However, for compatibility with
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. */
symbols:
/* epsilon */
......@@ -155,6 +162,8 @@ symbol:
{ $1 }
| UID
{ $1 }
| QID
{ $1 }
optional_comma:
/* epsilon */
......@@ -174,8 +183,10 @@ attributes:
terminals:
/* epsilon */
{ [] }
| terminals optional_comma UID attributes
{ (Positions.map (fun uid -> (uid, $4)) $3) :: $1 }
| terminals optional_comma UID optional_alias attributes
{ let ts, uid, alias, attrs = $1, $3, $4, $5 in
let alias = Option.map Positions.value alias in
Positions.map (fun uid -> uid, alias, attrs) uid :: ts }
nonterminals:
/* epsilon */
......@@ -183,6 +194,12 @@ nonterminals:
| nonterminals LID
{ $2 :: $1 }
optional_alias:
/* epsilon */
{ None }
| QID
{ Some $1 }
/* ------------------------------------------------------------------------- */
/* A rule defines a symbol. It is optionally declared %public, and optionally
carries a number of formal parameters. The right-hand side of the definition
......
......@@ -3,6 +3,7 @@ Error: syntax error after 'BAR' and before '<int>'.
Ill-formed %token declaration.
Examples of well-formed declarations:
%token FOO
%token DOT SEMICOLON
%token BAR "|"
%token DOT "." SEMICOLON ";"
%token <string> LID UID
%token FOO [@cost 0]
......@@ -3,6 +3,7 @@ Error: syntax error after 'BAR' and before 'int'.
Ill-formed %token declaration.
Examples of well-formed declarations:
%token FOO
%token DOT SEMICOLON
%token BAR "|"
%token DOT "." SEMICOLON ";"
%token <string> LID UID
%token FOO [@cost 0]
......@@ -3,6 +3,7 @@ Error: syntax error after 'BAR' and before '<int>'.
Ill-formed %token declaration.
Examples of well-formed declarations:
%token FOO
%token DOT SEMICOLON
%token BAR "|"
%token DOT "." SEMICOLON ";"
%token <string> LID UID
%token FOO [@cost 0]
......@@ -3,6 +3,7 @@ Error: syntax error after '%token' and before '('.
Ill-formed %token declaration.
Examples of well-formed declarations:
%token FOO