223 lines
5.5 KiB
OCaml
223 lines
5.5 KiB
OCaml
open Lexer
|
|
open Expr
|
|
|
|
exception Parse_error of string * position
|
|
|
|
type parse_state = { tokens : token_with_pos list; mutable pos : int }
|
|
|
|
let token_name = function
|
|
| Num _ -> "number"
|
|
| Var _ -> "variable"
|
|
| Ident _ -> "identifier"
|
|
| Plus -> "'+'"
|
|
| Minus -> "'-'"
|
|
| Star -> "'*'"
|
|
| Slash -> "'/'"
|
|
| Caret -> "'^'"
|
|
| LParen -> "'('"
|
|
| RParen -> "')'"
|
|
| Comma -> "','"
|
|
| EOF -> "end of input"
|
|
|
|
let peek state =
|
|
if state.pos < List.length state.tokens then
|
|
(List.nth state.tokens state.pos).token
|
|
else EOF
|
|
|
|
let peek_pos state =
|
|
if state.pos < List.length state.tokens then
|
|
(List.nth state.tokens state.pos).start_pos
|
|
else
|
|
{ line = 0; col = 0; offset = 0 }
|
|
|
|
let advance state =
|
|
state.pos <- state.pos + 1
|
|
|
|
let expect state tok =
|
|
if peek state = tok then advance state
|
|
else
|
|
let pos = peek_pos state in
|
|
raise (Parse_error (
|
|
Printf.sprintf "expected %s but found %s at line %d, column %d"
|
|
(token_name tok) (token_name (peek state)) pos.line pos.col,
|
|
pos))
|
|
|
|
let rec parse_expr state =
|
|
parse_additive state
|
|
|
|
and parse_additive state =
|
|
let left = ref (parse_multiplicative state) in
|
|
while peek state = Plus || peek state = Minus do
|
|
let op = peek state in
|
|
advance state;
|
|
let right = parse_multiplicative state in
|
|
left := if op = Plus then Add (!left, right) else Sub (!left, right)
|
|
done;
|
|
!left
|
|
|
|
and parse_multiplicative state =
|
|
let left = ref (parse_power state) in
|
|
while peek state = Star || peek state = Slash do
|
|
let op = peek state in
|
|
advance state;
|
|
let right = parse_power state in
|
|
left := if op = Star then Mul (!left, right) else Div (!left, right)
|
|
done;
|
|
!left
|
|
|
|
and parse_power state =
|
|
let left = parse_unary state in
|
|
if peek state = Caret then begin
|
|
advance state;
|
|
let right = parse_power state in
|
|
match left with
|
|
| Var "e" -> Exp right
|
|
| _ -> Pow (left, right)
|
|
end else left
|
|
|
|
and parse_unary state =
|
|
match peek state with
|
|
| Minus ->
|
|
advance state;
|
|
Neg (parse_unary state)
|
|
| _ -> parse_primary state
|
|
|
|
and parse_primary state =
|
|
match peek state with
|
|
| Num n ->
|
|
advance state;
|
|
Const n
|
|
| Var v ->
|
|
advance state;
|
|
(match v with
|
|
| "pi" | "π" -> SymConst Pi
|
|
| "e" -> SymConst E
|
|
| _ -> Var v)
|
|
| Ident "sin" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Sin arg
|
|
| Ident "cos" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Cos arg
|
|
| Ident "tan" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Tan arg
|
|
| Ident "sinh" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Sinh arg
|
|
| Ident "cosh" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Cosh arg
|
|
| Ident "tanh" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Tanh arg
|
|
| Ident "asin" | Ident "arcsin" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Asin arg
|
|
| Ident "acos" | Ident "arccos" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Acos arg
|
|
| Ident "atan" | Ident "arctan" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Atan arg
|
|
| Ident "atan2" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg1 = parse_expr state in
|
|
expect state Comma;
|
|
let arg2 = parse_expr state in
|
|
expect state RParen;
|
|
Atan2 (arg1, arg2)
|
|
| Ident "exp" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Exp arg
|
|
| Ident "ln" | Ident "log" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg1 = parse_expr state in
|
|
(match peek state with
|
|
| Comma ->
|
|
advance state;
|
|
let arg2 = parse_expr state in
|
|
expect state RParen;
|
|
Log (arg1, arg2)
|
|
| RParen ->
|
|
advance state;
|
|
Ln arg1
|
|
| _ ->
|
|
let pos = peek_pos state in
|
|
raise (Parse_error (
|
|
Printf.sprintf "expected ',' or ')' in log function at line %d, column %d"
|
|
pos.line pos.col,
|
|
pos)))
|
|
| Ident "sqrt" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Sqrt arg
|
|
| Ident "abs" ->
|
|
advance state;
|
|
expect state LParen;
|
|
let arg = parse_expr state in
|
|
expect state RParen;
|
|
Abs arg
|
|
| LParen ->
|
|
advance state;
|
|
let expr = parse_expr state in
|
|
expect state RParen;
|
|
expr
|
|
| _ ->
|
|
let pos = peek_pos state in
|
|
raise (Parse_error (
|
|
Printf.sprintf "unexpected %s at line %d, column %d"
|
|
(token_name (peek state)) pos.line pos.col,
|
|
pos))
|
|
|
|
let parse str =
|
|
try
|
|
let tokens = tokenize str in
|
|
let state = { tokens; pos = 0 } in
|
|
let expr = parse_expr state in
|
|
if peek state = EOF then expr
|
|
else
|
|
let pos = peek_pos state in
|
|
raise (Parse_error (
|
|
Printf.sprintf "unexpected tokens after expression at line %d, column %d"
|
|
pos.line pos.col,
|
|
pos))
|
|
with
|
|
| Parse_error (msg, _) ->
|
|
failwith msg
|
|
| Failure msg ->
|
|
failwith msg
|