spice lang parser and lexer
This commit is contained in:
commit
466b224e65
|
@ -0,0 +1 @@
|
|||
_build
|
|
@ -0,0 +1,4 @@
|
|||
(executable
|
||||
(public_name spicec)
|
||||
(name main)
|
||||
(libraries spice fmt logs))
|
|
@ -0,0 +1,6 @@
|
|||
let () =
|
||||
try
|
||||
let modl = Spice.parse "fun main(x) { val y = x + 2 IO.print(y * x) }" in
|
||||
Format.fprintf Format.std_formatter "%a\n" Spice.Syn.pp_modl modl
|
||||
with Spice.Error.Error err ->
|
||||
Format.fprintf Format.err_formatter "error: %a\n" Spice.Error.pp err
|
|
@ -0,0 +1,12 @@
|
|||
(lang dune 3.8)
|
||||
(using menhir 2.0)
|
||||
(name spice)
|
||||
|
||||
(generate_opam_files true)
|
||||
|
||||
(package
|
||||
(name spice)
|
||||
(synopsis "Simple language")
|
||||
(description "Compiler and interpeter for the spice language")
|
||||
(depends ocaml dune)
|
||||
(tags (topics "interpreter" "compiler")))
|
|
@ -0,0 +1,19 @@
|
|||
(library
|
||||
(name spice))
|
||||
|
||||
(menhir
|
||||
(modules parser)
|
||||
(flags --unused-tokens))
|
||||
|
||||
(ocamllex
|
||||
(modules lexer))
|
||||
|
||||
; (menhir
|
||||
; (modules parser))
|
||||
|
||||
; (ocamllex lexer)
|
||||
|
||||
; (library
|
||||
; (name json_parser)
|
||||
; (modules parser lexer json)
|
||||
; (libraries core))
|
|
@ -0,0 +1,8 @@
|
|||
type t = Syntax of string
|
||||
|
||||
exception Error of t
|
||||
|
||||
let raise_syntax_error msg = raise (Error (Syntax msg))
|
||||
|
||||
let pp ppf = function
|
||||
| Syntax why -> Format.fprintf ppf "syntax error: %s" why
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
open Parser
|
||||
|
||||
let keywords = Hashtbl.create 17
|
||||
let _ = begin
|
||||
Hashtbl.add keywords "true" Kw_true;
|
||||
Hashtbl.add keywords "false" Kw_false;
|
||||
Hashtbl.add keywords "nil" Kw_nil;
|
||||
Hashtbl.add keywords "val" Kw_val;
|
||||
Hashtbl.add keywords "fun" Kw_fun;
|
||||
Hashtbl.add keywords "obj" Kw_obj;
|
||||
end
|
||||
|
||||
let is_digit ch = ch >= '0' && ch <= '9'
|
||||
let is_digit_or_sep ch = ch = '_' || is_digit ch
|
||||
|
||||
let is_integer str =
|
||||
String.exists is_digit str &&
|
||||
String.for_all is_digit_or_sep str
|
||||
|
||||
let parse_integer str =
|
||||
let buffer = Buffer.create (String.length str) in
|
||||
String.iter (fun ch ->
|
||||
if is_digit ch then
|
||||
Buffer.add_char buffer ch)
|
||||
str;
|
||||
if Buffer.length buffer > 18 then
|
||||
Error.raise_syntax_error "integer literal is too long";
|
||||
Int64.of_string (Buffer.contents buffer)
|
||||
|
||||
let identifier str =
|
||||
match Hashtbl.find keywords str with
|
||||
| kw -> kw
|
||||
| exception Not_found ->
|
||||
if is_integer str then
|
||||
Int (parse_integer str)
|
||||
else
|
||||
Name str
|
||||
}
|
||||
|
||||
let int = ['0'-'9'] ['0'-'9']*
|
||||
let white = [' ' '\t']+
|
||||
let newline = '\n'
|
||||
let id = ['a'-'z' 'A'-'Z' '0'-'9' '_']+
|
||||
|
||||
rule read =
|
||||
parse
|
||||
| white { read lexbuf }
|
||||
| newline { Lexing.new_line lexbuf; read lexbuf }
|
||||
| id { identifier (Lexing.lexeme lexbuf) }
|
||||
| "(" { LP }
|
||||
| ")" { RP }
|
||||
| "{" { LC }
|
||||
| "}" { RC }
|
||||
| "+" { Binop Add }
|
||||
| "-" { Binop Sub }
|
||||
| "*" { Binop Mul }
|
||||
| "/" { Binop Div }
|
||||
| "%" { Binop Mod }
|
||||
| "==" { Binop Eql }
|
||||
| "=" { Eq }
|
||||
| ">" { Binop Grt }
|
||||
| "<=" { Binop Lst_eql }
|
||||
| "<" { Binop Lst }
|
||||
| ">=" { Binop Grt_eql }
|
||||
| "!=" { Binop Not_eql }
|
||||
| "." { Dot }
|
||||
| "," { Com }
|
||||
| _ { Error.raise_syntax_error "unrecognized character" }
|
||||
| eof { EOF }
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
%token <int64> Int
|
||||
%token <Syn.name> Name
|
||||
%token <Syn.binop> Binop
|
||||
|
||||
%token LP "(" RP ")"
|
||||
%token LC "{" RC "}"
|
||||
%token Eq "="
|
||||
%token Dot "."
|
||||
%token Com ","
|
||||
|
||||
%token Kw_true "true"
|
||||
%token Kw_false "false"
|
||||
%token Kw_nil "nil"
|
||||
%token Kw_val "val"
|
||||
%token Kw_fun "fun"
|
||||
%token Kw_obj "obj"
|
||||
|
||||
%token EOF
|
||||
%start <Syn.modl> modl
|
||||
|
||||
%{
|
||||
open Syn
|
||||
|
||||
let maybe_call prefix = function
|
||||
| None -> Path prefix
|
||||
| Some args -> Call (prefix, args)
|
||||
|
||||
let infix_base e0 = (e0, [])
|
||||
let infix_app (e0, ops) op e2 = (e0, (op, e2) :: ops)
|
||||
|
||||
let resolve_fixity (e0, ops) =
|
||||
List.fold_right
|
||||
(fun (op, e2) e1 -> Binop (op, e1, e2))
|
||||
ops e0
|
||||
%}
|
||||
%%
|
||||
|
||||
modl:
|
||||
| items = list(item); EOF { { items } }
|
||||
|
||||
item:
|
||||
| e = exp { Item_exp e }
|
||||
| "val"; n = Name; "="; e = exp { Item_val (n, e) }
|
||||
| "fun"; n = Name; p = params; e = exp { Item_fun (n, p, e) }
|
||||
| "obj"; n = Name; b = block { Item_obj (n, b) }
|
||||
|
||||
exp:
|
||||
| e = infixexp { resolve_fixity e }
|
||||
| "fun"; p = params; e = exp { Fun (p, e) }
|
||||
| "obj"; b = block { Obj b }
|
||||
|
||||
infixexp:
|
||||
| e0 = singleexp { infix_base e0 }
|
||||
| e1 = infixexp; op = Binop; e2 = singleexp { infix_app e1 op e2 }
|
||||
|
||||
singleexp:
|
||||
| i = Int { Literal (Int i) }
|
||||
| p = path; a = option(args) { maybe_call p a }
|
||||
| b = block { Scope b }
|
||||
|
||||
path:
|
||||
| x = Name { Var x }
|
||||
| e = singleexp; "."; x = Name { Ele (e, x) }
|
||||
|
||||
params: "("; list = trailing_list(Name); ")" { list }
|
||||
args: "("; list = trailing_list(exp); ")" { list }
|
||||
block: "{"; list = list(item); "}" { list }
|
||||
|
||||
trailing_list(X):
|
||||
| { [] }
|
||||
| x = X { [x] }
|
||||
| x = X; ","; xs = trailing_list(X) { x :: xs }
|
|
@ -0,0 +1,7 @@
|
|||
module Error = Error
|
||||
module Syn = Syn
|
||||
|
||||
let parse input =
|
||||
let lexbuf = Lexing.from_string input ~with_positions:true in
|
||||
try Parser.modl Lexer.read lexbuf
|
||||
with Parser.Error -> Error.raise_syntax_error "bad syntax"
|
|
@ -0,0 +1,114 @@
|
|||
type name = string
|
||||
|
||||
type literal =
|
||||
| Int of int64
|
||||
| True
|
||||
| False
|
||||
| Nil
|
||||
|
||||
type binop =
|
||||
| Add
|
||||
| Sub
|
||||
| Mul
|
||||
| Div
|
||||
| Mod
|
||||
| Eql
|
||||
| Grt
|
||||
| Lst
|
||||
| Not_eql
|
||||
| Grt_eql
|
||||
| Lst_eql
|
||||
|
||||
type modl = { items : item list }
|
||||
|
||||
and item =
|
||||
| Item_exp of exp
|
||||
| Item_val of name * exp
|
||||
| Item_fun of name * params * exp
|
||||
| Item_obj of name * block
|
||||
|
||||
and params = name list
|
||||
|
||||
and exp =
|
||||
| Literal of literal
|
||||
| Path of path
|
||||
| Call of path * exp list
|
||||
| Binop of binop * exp * exp
|
||||
| Fun of params * exp
|
||||
| Obj of block
|
||||
| Scope of block
|
||||
|
||||
and path =
|
||||
| Var of name
|
||||
| Ele of exp * name
|
||||
|
||||
and block = item list
|
||||
|
||||
(* pretty printer *)
|
||||
|
||||
let string_of_literal = function
|
||||
| Int n -> Int64.to_string n
|
||||
| True -> "true"
|
||||
| False -> "false"
|
||||
| Nil -> "nil"
|
||||
|
||||
let string_of_binop = function
|
||||
| Add -> "+"
|
||||
| Sub -> "-"
|
||||
| Mul -> "*"
|
||||
| Div -> "/"
|
||||
| Mod -> "%"
|
||||
| Eql -> "=="
|
||||
| Grt -> ">"
|
||||
| Lst -> "<"
|
||||
| Not_eql -> "!="
|
||||
| Grt_eql -> ">="
|
||||
| Lst_eql -> "<="
|
||||
|
||||
let pf = Format.fprintf
|
||||
let pp_str ppf str = pf ppf "%S" str
|
||||
|
||||
let pp_list pp_ele ppf list =
|
||||
pf ppf "[";
|
||||
List.iteri
|
||||
(fun i ele ->
|
||||
if i > 0 then pf ppf ",";
|
||||
pp_ele ppf ele)
|
||||
list;
|
||||
pf ppf "]"
|
||||
|
||||
let rec pp_exp ppf = function
|
||||
| Literal (Int n) -> pf ppf "{\"int\":%s}" (Int64.to_string n)
|
||||
| Literal l -> pf ppf "%s" (string_of_literal l)
|
||||
| Path (Var x) -> pf ppf "{\"var\":%S}" x
|
||||
| Path (Ele (e, x)) -> pf ppf "{\"ele\":%a,\"field\":%S}" pp_exp e x
|
||||
| Call (fn, args) -> pf ppf "{\"call\":%a}" (pp_list pp_exp) (Path fn :: args)
|
||||
| Binop (op, e1, e2) ->
|
||||
pf
|
||||
ppf
|
||||
"{\"binop\":%S,\"lhs\":%a,\"rhs\":%a}"
|
||||
(string_of_binop op)
|
||||
pp_exp
|
||||
e1
|
||||
pp_exp
|
||||
e2
|
||||
| Fun (params, body) ->
|
||||
pf ppf "{\"fun\":%a,\"body\":%a}" (pp_list pp_str) params pp_exp body
|
||||
| Obj body -> pf ppf "{\"obj\":%a}" (pp_list pp_item) body
|
||||
| Scope body -> pf ppf "{\"scope\":%a}" (pp_list pp_item) body
|
||||
|
||||
and pp_item ppf = function
|
||||
| Item_exp e -> pf ppf "{\"exp\":%a}" pp_exp e
|
||||
| Item_val (name, rhs) -> pf ppf "{\"val\":%S,\"rhs\":%a}" name pp_exp rhs
|
||||
| Item_fun (name, params, body) ->
|
||||
pf
|
||||
ppf
|
||||
"{\"fun\":%S,\"params\":%a,\"body\":%a}"
|
||||
name
|
||||
(pp_list pp_str)
|
||||
params
|
||||
pp_exp
|
||||
body
|
||||
| Item_obj (name, body) -> pf ppf "{\"obj\":%S,\"body\":%a}" name (pp_list pp_item) body
|
||||
|
||||
let pp_modl ppf m = pp_list pp_item ppf m.items
|
|
@ -0,0 +1,24 @@
|
|||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "A short synopsis"
|
||||
description: "A longer description"
|
||||
tags: ["topics" "to describe" "your" "project"]
|
||||
depends: [
|
||||
"ocaml"
|
||||
"dune" {>= "3.8"}
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
["dune" "subst"] {dev}
|
||||
[
|
||||
"dune"
|
||||
"build"
|
||||
"-p"
|
||||
name
|
||||
"-j"
|
||||
jobs
|
||||
"@install"
|
||||
"@runtest" {with-test}
|
||||
"@doc" {with-doc}
|
||||
]
|
||||
]
|
Loading…
Reference in New Issue