standalone exapunks vm in ocaml
at main 155 lines 5.6 kB view raw
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 }