symbolic mathematics engine in OCaml with differentiation, integration, simplification, and numerical methods
1open Lexer
2open Expr
3
4exception Parse_error of string * position
5
6type parse_state = { tokens : token_with_pos list; mutable pos : int }
7
8let token_name = function
9 | Num _ -> "number"
10 | Var _ -> "variable"
11 | Ident _ -> "identifier"
12 | Plus -> "'+'"
13 | Minus -> "'-'"
14 | Star -> "'*'"
15 | Slash -> "'/'"
16 | Caret -> "'^'"
17 | LParen -> "'('"
18 | RParen -> "')'"
19 | Comma -> "','"
20 | EOF -> "end of input"
21
22let peek state =
23 if state.pos < List.length state.tokens then
24 (List.nth state.tokens state.pos).token
25 else EOF
26
27let peek_pos state =
28 if state.pos < List.length state.tokens then
29 (List.nth state.tokens state.pos).start_pos
30 else
31 { line = 0; col = 0; offset = 0 }
32
33let advance state =
34 state.pos <- state.pos + 1
35
36let expect state tok =
37 if peek state = tok then advance state
38 else
39 let pos = peek_pos state in
40 raise (Parse_error (
41 Printf.sprintf "expected %s but found %s at line %d, column %d"
42 (token_name tok) (token_name (peek state)) pos.line pos.col,
43 pos))
44
45let rec parse_expr state =
46 parse_additive state
47
48and parse_additive state =
49 let left = ref (parse_multiplicative state) in
50 while peek state = Plus || peek state = Minus do
51 let op = peek state in
52 advance state;
53 let right = parse_multiplicative state in
54 left := if op = Plus then Add (!left, right) else Sub (!left, right)
55 done;
56 !left
57
58and parse_multiplicative state =
59 let left = ref (parse_power state) in
60 while peek state = Star || peek state = Slash do
61 let op = peek state in
62 advance state;
63 let right = parse_power state in
64 left := if op = Star then Mul (!left, right) else Div (!left, right)
65 done;
66 !left
67
68and parse_power state =
69 let left = parse_unary state in
70 if peek state = Caret then begin
71 advance state;
72 let right = parse_power state in
73 match left with
74 | Var "e" -> Exp right
75 | _ -> Pow (left, right)
76 end else left
77
78and parse_unary state =
79 match peek state with
80 | Minus ->
81 advance state;
82 Neg (parse_unary state)
83 | _ -> parse_primary state
84
85and parse_primary state =
86 match peek state with
87 | Num n ->
88 advance state;
89 Const n
90 | Var v ->
91 advance state;
92 (match v with
93 | "pi" | "π" -> SymConst Pi
94 | "e" -> SymConst E
95 | _ -> Var v)
96 | Ident "sin" ->
97 advance state;
98 expect state LParen;
99 let arg = parse_expr state in
100 expect state RParen;
101 Sin arg
102 | Ident "cos" ->
103 advance state;
104 expect state LParen;
105 let arg = parse_expr state in
106 expect state RParen;
107 Cos arg
108 | Ident "tan" ->
109 advance state;
110 expect state LParen;
111 let arg = parse_expr state in
112 expect state RParen;
113 Tan arg
114 | Ident "sinh" ->
115 advance state;
116 expect state LParen;
117 let arg = parse_expr state in
118 expect state RParen;
119 Sinh arg
120 | Ident "cosh" ->
121 advance state;
122 expect state LParen;
123 let arg = parse_expr state in
124 expect state RParen;
125 Cosh arg
126 | Ident "tanh" ->
127 advance state;
128 expect state LParen;
129 let arg = parse_expr state in
130 expect state RParen;
131 Tanh arg
132 | Ident "asin" | Ident "arcsin" ->
133 advance state;
134 expect state LParen;
135 let arg = parse_expr state in
136 expect state RParen;
137 Asin arg
138 | Ident "acos" | Ident "arccos" ->
139 advance state;
140 expect state LParen;
141 let arg = parse_expr state in
142 expect state RParen;
143 Acos arg
144 | Ident "atan" | Ident "arctan" ->
145 advance state;
146 expect state LParen;
147 let arg = parse_expr state in
148 expect state RParen;
149 Atan arg
150 | Ident "atan2" ->
151 advance state;
152 expect state LParen;
153 let arg1 = parse_expr state in
154 expect state Comma;
155 let arg2 = parse_expr state in
156 expect state RParen;
157 Atan2 (arg1, arg2)
158 | Ident "exp" ->
159 advance state;
160 expect state LParen;
161 let arg = parse_expr state in
162 expect state RParen;
163 Exp arg
164 | Ident "ln" | Ident "log" ->
165 advance state;
166 expect state LParen;
167 let arg1 = parse_expr state in
168 (match peek state with
169 | Comma ->
170 advance state;
171 let arg2 = parse_expr state in
172 expect state RParen;
173 Log (arg1, arg2)
174 | RParen ->
175 advance state;
176 Ln arg1
177 | _ ->
178 let pos = peek_pos state in
179 raise (Parse_error (
180 Printf.sprintf "expected ',' or ')' in log function at line %d, column %d"
181 pos.line pos.col,
182 pos)))
183 | Ident "sqrt" ->
184 advance state;
185 expect state LParen;
186 let arg = parse_expr state in
187 expect state RParen;
188 Sqrt arg
189 | Ident "abs" ->
190 advance state;
191 expect state LParen;
192 let arg = parse_expr state in
193 expect state RParen;
194 Abs arg
195 | LParen ->
196 advance state;
197 let expr = parse_expr state in
198 expect state RParen;
199 expr
200 | _ ->
201 let pos = peek_pos state in
202 raise (Parse_error (
203 Printf.sprintf "unexpected %s at line %d, column %d"
204 (token_name (peek state)) pos.line pos.col,
205 pos))
206
207let parse str =
208 try
209 let tokens = tokenize str in
210 let state = { tokens; pos = 0 } in
211 let expr = parse_expr state in
212 if peek state = EOF then expr
213 else
214 let pos = peek_pos state in
215 raise (Parse_error (
216 Printf.sprintf "unexpected tokens after expression at line %d, column %d"
217 pos.line pos.col,
218 pos))
219 with
220 | Parse_error (msg, _) ->
221 failwith msg
222 | Failure msg ->
223 failwith msg