standalone exapunks vm in ocaml
1(* This Source Code Form is subject to the terms of the Mozilla Public
2 License, v. 2.0. If a copy of the MPL was not distributed with this
3 file, You can obtain one at https://mozilla.org/MPL/2.0/. *)
4
5open MParser
6open Common
7open Utils
8
9let ws = skip_many1 blank
10
11let string_return str result = string str >>$ result
12
13let p_registers =
14 choice
15 [
16 string_return "X" X; string_return "T" T; string_return "F" F; string_return "M" M;
17 ]
18
19let p_R = ws >> p_registers
20
21let p_RN_ =
22 p_registers |>> (fun r -> R r) <|> (many1_chars digit |>> fun n -> N (int_of_string n))
23
24let p_RN = ws >> p_RN_
25
26let p_label = ws >> many1_chars uppercase
27
28let p_test_op =
29 ws >> choice [ string_return "<" `LT; string_return "=" `EQ; string_return ">" `GT ]
30
31let comment = string ";" >> skip_many_chars_until any_char newline
32
33let instructions =
34 [
35 string_return "NOOP" (Some NOOP);
36 ( string "COPY" >> p_RN >>= fun rn ->
37 p_R >>= fun r ->
38 match (rn, r) with
39 | R src, dest when Register.equal src dest -> fail "CANNOT REPEAT REGISTERS"
40 | _ -> return (Some (COPY (rn, r))) );
41 (* Math *)
42 string "ADDI" >> pipe3 p_RN p_RN p_R (fun src i dest -> Some (ADDI (src, i, dest)));
43 string "SUBI" >> pipe3 p_RN p_RN p_R (fun src i dest -> Some (SUBI (src, i, dest)));
44 string "MULI" >> pipe3 p_RN p_RN p_R (fun src i dest -> Some (MULI (src, i, dest)));
45 string "DIVI" >> pipe3 p_RN p_RN p_R (fun src i dest -> Some (DIVI (src, i, dest)));
46 string "MODI" >> pipe3 p_RN p_RN p_R (fun src i dest -> Some (MODI (src, i, dest)));
47 string "SWIZ" >> pipe3 p_RN p_RN p_R (fun src i dest -> Some (SWIZ (src, i, dest)));
48 (* Branching *)
49 (string "MARK" >> p_label |>> fun label -> Some (MARK label));
50 (string "JUMP" >> p_label |>> fun label -> Some (JUMP label));
51 (string "TJMP" >> p_label |>> fun label -> Some (TJMP label));
52 (string "FJMP" >> p_label |>> fun label -> Some (FJMP label));
53 (* Testing *)
54 string "TEST" >> ws
55 >> choice
56 [
57 attempt (string "MRD" >>$ Some TEST_MRD);
58 attempt (string "EOF" >>$ Some TEST_EOF);
59 pipe3 p_RN_ p_test_op p_RN (fun left op right ->
60 Some
61 (match op with
62 | `LT -> TEST_LT (left, right)
63 | `EQ -> TEST_EQ (left, right)
64 | `GT -> TEST_GT (left, right)));
65 ];
66 (* Lifecycle *)
67 (string "REPL" >> p_label |>> fun label -> Some (REPL label));
68 string_return "HALT" (Some HALT);
69 (* Movement *)
70 (string "LINK" >> p_RN |>> fun rn -> Some (LINK rn));
71 (string "HOST" >> p_R |>> fun r -> Some (HOST r));
72 (* Communication *)
73 string_return "MODE" (Some MODE);
74 ( string "VOID" >> ws >> choice [ string_return "M" `M; string_return "F" `F ]
75 |>> fun mf ->
76 Some
77 (match mf with
78 | `M -> VOID_M
79 | `F -> VOID_F) );
80 (* File Manipulation *)
81 string_return "MAKE" (Some MAKE);
82 (string "GRAB" >> p_RN |>> fun rn -> Some (GRAB rn));
83 (string "FILE" >> p_R |>> fun r -> Some (FILE r));
84 (string "SEEK" >> p_RN |>> fun rn -> Some (SEEK rn));
85 string_return "DROP" (Some DROP);
86 string_return "WIPE" (Some WIPE);
87 string "NOTE" >> skip_many_chars_until any_char newline >>$ None;
88 many1 blank >>$ None;
89 ]
90 |> List.map (fun inst -> inst << optional comment << optional newline)
91
92let exa_grammar = many (spaces >> ((comment >>$ None) <|> choice instructions))
93
94let parse_code str =
95 match parse_string exa_grammar (String.trim str) () with
96 | Success result ->
97 Ok
98 (result
99 |> List.mapi (fun idx line -> (idx, line))
100 |> List.filter_map (fun (idx, line) ->
101 match line with
102 | None -> None
103 | Some line -> Some (idx, line)))
104 | Failed (error, _) ->
105 Error error
106
107type op_loc = {
108 inst : op_code;
109 line_num : int;
110}
111
112let parse_code (input : string) : code =
113 let instructions = parse_code input in
114 let labels = StringMap.create 0 in
115 let marks = ref StringSet.empty in
116 let targets = ref StringSet.empty in
117 let ops = ref [] in
118 let errors = ref [] in
119 (match instructions with
120 | Error err -> errors := { msg = err; line_num = 0; exa = None } :: !errors
121 | Ok instructions ->
122 List.iter
123 (fun (line_num, line) ->
124 match line with
125 | MARK label as inst ->
126 marks := StringSet.add label !marks;
127 StringMap.replace labels label line_num;
128 ops := { inst; line_num } :: !ops
129 | (JUMP label as inst)
130 | (TJMP label as inst)
131 | (FJMP label as inst) ->
132 targets := StringSet.add label !targets;
133 ops := { inst; line_num } :: !ops
134 | inst -> ops := { inst; line_num } :: !ops)
135 instructions);
136 let extra_targets = StringSet.diff !targets !marks in
137 if not (StringSet.is_empty extra_targets) then
138 !ops
139 |> List.filter (fun { inst; _ } ->
140 match inst with
141 | JUMP label
142 | TJMP label
143 | FJMP label ->
144 StringSet.find_opt label extra_targets |> Option.is_some
145 | _ -> false)
146 |> List.iter (fun { inst; line_num } ->
147 match inst with
148 | JUMP label
149 | TJMP label
150 | FJMP label ->
151 let msg = Printf.sprintf "MISMATCHED TARGET: %s" label in
152 errors := { msg; line_num; exa = None } :: !errors
153 | _ -> ());
154 let ops = !ops |> List.rev |> List.map (fun { inst; _ } -> inst) in
155 { ops; labels; errors = List.rev !errors }