Skip to content

Instantly share code, notes, and snippets.

@keigoi
Last active November 4, 2016 23:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save keigoi/ae58e1ddd9d7b156c10ceaecbaa6559b to your computer and use it in GitHub Desktop.
Save keigoi/ae58e1ddd9d7b156c10ceaecbaa6559b to your computer and use it in GitHub Desktop.
This is a sample interactive calculator built using ML-Yacc and ML-Lex.
(keigoi: Code is modified to compile with SML# 3.2.0 toolchain.)
The calculator is defined by the files
calc.lex (* defines lexer *)
calc.grm (* defines grammar *)
calc.sml (* defines driver function, Calc.parse *)
sources.cm (* cm description file *)
To compile this example, type
$ make
in this directory. make will invoke smllex and smlyacc to process the
lexer specification calc.lex and the grammar specification calc.grm
respectively. Then it will compile the resulting SML source files
calc.lex.sml
calc.grm.sig
calc.grm.sml
and the calc.sml file containing the driver code.
The end result of loading these files is a structure Calc containing a
top-level driver function named parse.
Calc.parse : unit -> unit
The calculator can be invoked by applying Calc.parse to the unit value.
- Calc.parse();
1+3;
result = 4
or, make will also generate ./calc:
$ ./calc
1+3;
result = 4
The calculator reads a sequence of expressions from the standard input
and prints the value of each expression after reading the expression.
Expressions must be separated by semicolons. An expression is not
evaluated until the semicolon is encountered. The calculator
terminates when an end-of-file is encountered. There is no attempt to
fix input errors: a lexical error will cause exception LexError to be
raised, while a syntax error will cause ParseError to be raised.
(* Sample interactive calculator for ML-Yacc *)
fun lookup "bogus" = 10000
| lookup s = 0
%%
%header (
structure CalcLrVals
)
%footer ()
%eop EOF SEMI
(* %pos declares the type of positions for terminals.
Each symbol has an associated left and right position. *)
%pos int
%left SUB PLUS
%left TIMES DIV
%right CARAT
%term ID of string | NUM of int | PLUS | TIMES | PRINT |
SEMI | EOF | CARAT | DIV | SUB
%nonterm EXP of int | START of int option
%name Calc
%subst PRINT for ID
%prefer PLUS TIMES DIV SUB
%keyword PRINT SEMI
%noshift EOF
%value ID ("bogus")
%verbose
%%
(* the parser returns the value associated with the expression *)
START : PRINT EXP (print (Int.toString EXP);
print "\n";
SOME EXP)
| EXP (SOME EXP)
| (NONE)
EXP : NUM (NUM)
| ID (lookup ID)
| EXP PLUS EXP (EXP1+EXP2)
| EXP TIMES EXP (EXP1*EXP2)
| EXP DIV EXP (EXP1 div EXP2)
| EXP SUB EXP (EXP1-EXP2)
| EXP CARAT EXP (let fun e (m,0) = 1
| e (m,l) = m*e(m,l-1)
in e (EXP1,EXP2)
end)
_require "basis.smi"
_require "ml-yacc-lib.smi"
_require "./calc.grm.sig"
structure CalcLrVals = struct
structure Tokens =
struct
type pos = int
type token (= boxed)
val SUB: pos * pos -> token
val DIV: pos * pos -> token
val CARAT: pos * pos -> token
val EOF: pos * pos -> token
val SEMI: pos * pos -> token
val PRINT: pos * pos -> token
val TIMES: pos * pos -> token
val PLUS: pos * pos -> token
val NUM: (int) * pos * pos -> token
val ID: (string) * pos * pos -> token
end
structure ParserData = struct
structure LrParser =
struct
structure Token =
struct
val sameToken : Tokens.token * Tokens.token -> bool
end
structure Stream =
struct
type tok = Tokens.token
type stream (= boxed)
val streamify : (unit -> tok) -> stream
val cons : tok * stream -> stream
val get : stream -> tok * stream
end
end
end
structure Parser = struct
type pos = int
type arg = unit
type stream = ParserData.LrParser.Stream.stream
val parse :
{arg: arg, error:string * pos * pos-> unit, lookahead: int, stream: stream}
-> int option * stream
end
end
structure Tokens = CalcLrVals.Tokens
type pos = int
type token = CalcLrVals.Tokens.token
type lexresult= token
val pos = ref 0
fun eof () = Tokens.EOF(!pos,!pos)
fun error (e,l : int,_) = TextIO.output (TextIO.stdOut, String.concat[
"line ", (Int.toString l), ": ", e, "\n"
])
%%
%structure CalcLex
alpha=[A-Za-z];
digit=[0-9];
ws = [\ \t];
%%
\n => (pos := (!pos) + 1; lex());
{ws}+ => (lex());
{digit}+ => (Tokens.NUM (valOf (Int.fromString yytext), !pos, !pos));
"+" => (Tokens.PLUS(!pos,!pos));
"*" => (Tokens.TIMES(!pos,!pos));
";" => (Tokens.SEMI(!pos,!pos));
{alpha}+ => (if yytext="print"
then Tokens.PRINT(!pos,!pos)
else Tokens.ID(yytext,!pos,!pos)
);
"-" => (Tokens.SUB(!pos,!pos));
"^" => (Tokens.CARAT(!pos,!pos));
"/" => (Tokens.DIV(!pos,!pos));
"." => (error ("ignoring bad character "^yytext,!pos,!pos);
lex());
_require "basis.smi"
_require "ml-yacc-lib.smi"
_require "./calc.grm.smi"
structure CalcLex=
struct
structure UserDeclarations =
struct
type token = CalcLrVals.Tokens.token
type pos = CalcLrVals.Tokens.pos
end
val makeLexer
: (int -> string) -> unit
-> UserDeclarations.token
end
_require "basis.smi"
_require "ml-yacc-lib.smi"
_require "./calc.grm.smi"
_require "./calc.lex.smi"
(* calc.sml *)
(* This file provides glue code for building the calculator using the
* parser and lexer specified in calc.lex and calc.grm.
*)
structure Calc : sig
val parse : unit -> unit
end =
struct
(*
* We need a function which given a lexer invokes the parser. The
* function invoke does this.
*)
fun invoke lexstream =
let fun print_error (s,i:int,_) =
TextIO.output(TextIO.stdOut,
"Error, line " ^ (Int.toString i) ^ ", " ^ s ^ "\n")
in CalcLrVals.Parser.parse{lookahead=0,stream=lexstream,error=print_error,arg=()}
end
(*
* Finally, we need a driver function that reads one or more expressions
* from the standard input. The function parse, shown below, does
* this. It runs the calculator on the standard input and terminates when
* an end-of-file is encountered.
*)
fun parse () =
let val lexer =
let val lexer = CalcLex.makeLexer (fn _ =>
(case TextIO.inputLine TextIO.stdIn
of SOME s => s
| _ => ""))
in CalcLrVals.ParserData.LrParser.Stream.streamify lexer
end
val dummyEOF = CalcLrVals.Tokens.EOF(0,0)
val dummySEMI = CalcLrVals.Tokens.SEMI(0,0)
fun loop lexer =
let val (result,lexer) = invoke lexer
val (nextToken,lexer) = CalcLrVals.ParserData.LrParser.Stream.get lexer
val _ = case result
of SOME r =>
TextIO.output(TextIO.stdOut,
"result = " ^ (Int.toString r) ^ "\n")
| NONE => ()
in if CalcLrVals.ParserData.LrParser.Token.sameToken(nextToken,dummyEOF) then ()
else loop lexer
end
in loop lexer
end
end (* structure Calc *)
val _ = Calc.parse()
val _ = print "end."
SMLYACC=smlyacc
SMLLEX=smllex
SMLSHARP=smlsharp
OBJS=calc.grm.o calc.lex.o calc.o
SMI=calc.grm.smi calc.lex.smi calc.smi
all: calc
calc: $(OBJS) $(SMI)
$(SMLSHARP) -o calc calc.smi
# Common rules
.SUFFIXES:
.SUFFIXES: .sml .o .grm .lex .smi
%.o: %.sml
$(SMLSHARP) -c $<
# Clean up
clean:
rm -f calc *.o calc.grm.sml calc.grm.sig calc.lex.sml calc.grm.desc
# Specific rules
calc.o: calc.grm.smi calc.grm.sig calc.lex.smi calc.smi
# smlyacc/smllex
calc.grm.sml calc.grm.sig: calc.grm
$(SMLYACC) calc.grm
calc.lex.sml: calc.lex
$(SMLLEX) calc.lex
calc.grm.o: calc.grm.sml calc.grm.smi calc.grm.sig
$(SMLSHARP) -c calc.grm.sml
calc.lex.o: calc.lex.sml calc.lex.smi calc.grm.smi calc.grm.sig
$(SMLSHARP) -c calc.lex.sml
SML# smlyacc/smllex calc example
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment