mirror of
https://github.com/MorizzG/MLox.git
synced 2025-12-06 04:22:41 +00:00
init commit
This commit is contained in:
commit
d33023f435
13 changed files with 325 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Dune artifacts
|
||||
_build/
|
||||
dune.lock
|
||||
|
||||
# Local OPAM switch
|
||||
_opam/
|
||||
4
.ocamlformat
Normal file
4
.ocamlformat
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
profile = default
|
||||
# profile = ocamlformat
|
||||
# profile = janestreet
|
||||
margin = 100
|
||||
31
MlLox.opam
Normal file
31
MlLox.opam
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# This file is generated by dune, edit dune-project instead
|
||||
opam-version: "2.0"
|
||||
synopsis: "A short synopsis"
|
||||
description: "A longer description"
|
||||
maintainer: ["Maintainer Name"]
|
||||
authors: ["Author Name"]
|
||||
license: "LICENSE"
|
||||
tags: ["topics" "to describe" "your" "project"]
|
||||
homepage: "https://github.com/username/reponame"
|
||||
doc: "https://url/to/documentation"
|
||||
bug-reports: "https://github.com/username/reponame/issues"
|
||||
depends: [
|
||||
"ocaml"
|
||||
"dune" {>= "3.16"}
|
||||
"odoc" {with-doc}
|
||||
]
|
||||
build: [
|
||||
["dune" "subst"] {dev}
|
||||
[
|
||||
"dune"
|
||||
"build"
|
||||
"-p"
|
||||
name
|
||||
"-j"
|
||||
jobs
|
||||
"@install"
|
||||
"@runtest" {with-test}
|
||||
"@doc" {with-doc}
|
||||
]
|
||||
]
|
||||
dev-repo: "git+https://github.com/username/reponame.git"
|
||||
4
bin/dune
Normal file
4
bin/dune
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
(executable
|
||||
(public_name MlLox)
|
||||
(libraries Lox)
|
||||
(name main))
|
||||
16
bin/main.ml
Normal file
16
bin/main.ml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
let printUsage () =
|
||||
print_endline "Usage: jlox [script]";
|
||||
exit 64
|
||||
|
||||
let () =
|
||||
let argc = Array.length Sys.argv in
|
||||
match argc - 1 with
|
||||
| 0 -> Lox.runRepl ()
|
||||
| 1 ->
|
||||
let path = Sys.argv.(1) in
|
||||
Printf.printf "Running script %s\n" path;
|
||||
let ic = open_in path in
|
||||
let source = In_channel.input_all ic in
|
||||
let result = Lox.run source in
|
||||
Result.iter_error Lox.Error.print_error result
|
||||
| _ -> printUsage ()
|
||||
26
dune-project
Normal file
26
dune-project
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
(lang dune 3.16)
|
||||
|
||||
(name MlLox)
|
||||
|
||||
(generate_opam_files true)
|
||||
|
||||
(source
|
||||
(github username/reponame))
|
||||
|
||||
(authors "Author Name")
|
||||
|
||||
(maintainers "Maintainer Name")
|
||||
|
||||
(license LICENSE)
|
||||
|
||||
(documentation https://url/to/documentation)
|
||||
|
||||
(package
|
||||
(name MlLox)
|
||||
(synopsis "A short synopsis")
|
||||
(description "A longer description")
|
||||
(depends ocaml dune)
|
||||
(tags
|
||||
(topics "to describe" your project)))
|
||||
|
||||
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/reference/dune-project/index.html
|
||||
4
lib/dune
Normal file
4
lib/dune
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
(library
|
||||
(name Lox)
|
||||
(preprocess
|
||||
(pps ppx_deriving.show)))
|
||||
26
lib/error.ml
Normal file
26
lib/error.ml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
type code_pos = { line : int; col : int }
|
||||
type lexer_error = { pos : code_pos; msg : string }
|
||||
|
||||
module LexerError = struct
|
||||
type t = lexer_error
|
||||
|
||||
let make (pos : code_pos) (msg : string) : lexer_error =
|
||||
(* let pos = { line; col } in *)
|
||||
{ pos; msg }
|
||||
|
||||
let print (e : lexer_error) =
|
||||
Printf.printf "LexerError at line %d, column %d: %s\n" e.pos.line e.pos.col e.msg
|
||||
end
|
||||
|
||||
type lox_error = LexerError of lexer_error list
|
||||
|
||||
let print_error (e : lox_error) =
|
||||
match e with
|
||||
| LexerError es ->
|
||||
let num_errors = List.length es in
|
||||
assert (num_errors != 0);
|
||||
Printf.printf "found %d %s:\n" num_errors
|
||||
(if num_errors = 1 then "LexerError" else "LexerErrors");
|
||||
List.iter LexerError.print es
|
||||
|
||||
let of_lexer_error e = Result.map_error (fun e -> LexerError e) e
|
||||
177
lib/lexer.ml
Normal file
177
lib/lexer.ml
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
open Error
|
||||
|
||||
[@@@ocamlformat "disable"]
|
||||
type token_type =
|
||||
| LeftParen | RightParen | LeftBrace | RightBrace
|
||||
|
||||
| Plus | Minus | Star | Slash | Bang
|
||||
|
||||
| Dot | Comma | Semicolon | Equal
|
||||
|
||||
| EqualEqual | BangEqual | Greater | Less | GreaterEqual | LessEqual
|
||||
|
||||
| Identifier of string
|
||||
| String of string
|
||||
| Number of float
|
||||
|
||||
| And | Class | Else | False | Fun | For | If | Nil | Or | Print | Return | Super | This | True
|
||||
| Var | While
|
||||
|
||||
| Comment of string
|
||||
|
||||
| Eof
|
||||
[@@deriving show { with_path = false }]
|
||||
[@@@ocamlformat "enable"]
|
||||
|
||||
type token = { token_type : token_type; pos : code_pos }
|
||||
|
||||
let show_token (token : token) =
|
||||
let { line; col } = token.pos in
|
||||
Printf.sprintf "<%s at %d:%d>" (show_token_type token.token_type) line col
|
||||
|
||||
type lexer_result = (token list, lexer_error list) result
|
||||
|
||||
type state = {
|
||||
(* source code *)
|
||||
source : string;
|
||||
start_pos : int;
|
||||
cur_pos : int;
|
||||
(* store tokens and errors in reverse to make building the list more efficient *)
|
||||
tokens_rev : token list;
|
||||
errors_rev : lexer_error list;
|
||||
(* position of current char in source *)
|
||||
line : int;
|
||||
col : int;
|
||||
}
|
||||
|
||||
module State = struct
|
||||
type t = state
|
||||
|
||||
let is_digit c = match c with '0' .. '9' -> true | _ -> false
|
||||
let is_alpha c = match c with 'a' .. 'z' | 'A' .. 'Z' -> true | _ -> false
|
||||
let is_alphanum c = is_digit c || is_alpha c
|
||||
let is_identifier c = is_alphanum c || c = '_'
|
||||
let is_at_end (state : state) : bool = state.cur_pos = String.length state.source
|
||||
|
||||
let get_lexeme (state : state) (first : int) (last : int) =
|
||||
String.sub state.source first (last - first)
|
||||
|
||||
let advance (state : state) : char * state =
|
||||
let c = state.source.[state.cur_pos] in
|
||||
let state = { state with cur_pos = state.cur_pos + 1 } in
|
||||
let state =
|
||||
match c with
|
||||
| '\t' -> { state with col = state.col + 4 }
|
||||
| '\n' -> { state with line = state.line + 1; col = 0 }
|
||||
| _ -> { state with col = state.col + 1 }
|
||||
in
|
||||
(c, state)
|
||||
|
||||
let peek (state : state) : char option =
|
||||
if not (is_at_end state) then Some state.source.[state.cur_pos] else None
|
||||
|
||||
let advance_if (c : char) (state : state) : bool * state =
|
||||
if peek state = Some c then (true, snd (advance state)) else (false, state)
|
||||
|
||||
let rec advance_until (c : char) (state : state) : bool * state =
|
||||
if is_at_end state then (false, state)
|
||||
else
|
||||
let c', state = advance state in
|
||||
if c' = c then (true, state) else advance_until c state
|
||||
|
||||
let rec advance_while (f : char -> bool) (state : state) : state =
|
||||
match peek state with
|
||||
| Some c when f c -> advance_while f (snd (advance state))
|
||||
| _ -> state (* EOF or no match *)
|
||||
|
||||
let last_char (state : state) =
|
||||
assert (state.cur_pos > 0);
|
||||
state.source.[state.cur_pos - 1]
|
||||
|
||||
let append_token pos state token_type =
|
||||
(* let pos = { line = state.line; col = state.col } in *)
|
||||
{ state with tokens_rev = { token_type; pos } :: state.tokens_rev }
|
||||
|
||||
let append_error pos state msg =
|
||||
(* let pos = { line = state.line; col = state.col } in *)
|
||||
{ state with errors_rev = LexerError.make pos msg :: state.errors_rev }
|
||||
|
||||
let parse_number (state : state) =
|
||||
let skip c state = snd @@ advance_if c state in
|
||||
let code_pos = { line = state.line; col = state.col } in
|
||||
let state =
|
||||
state |> advance_while is_digit |> skip '.' |> advance_while is_digit |> skip 'e'
|
||||
|> advance_while is_digit
|
||||
in
|
||||
let lexeme = get_lexeme state state.start_pos state.cur_pos in
|
||||
let f = Float.of_string_opt lexeme in
|
||||
match f with
|
||||
| None -> append_error code_pos state (Printf.sprintf "Invalid float literal %s" lexeme)
|
||||
| Some f -> append_token code_pos state (Number f)
|
||||
|
||||
let rec tokenize_rec (state : state) : state =
|
||||
let pos = { line = state.line; col = state.col } in
|
||||
let append_token = append_token pos in
|
||||
let append_error = append_error pos in
|
||||
if is_at_end state then append_token state Eof
|
||||
else
|
||||
let state = { state with start_pos = state.cur_pos } in
|
||||
let c, state = advance state in
|
||||
let state =
|
||||
match c with
|
||||
| '(' -> append_token state LeftParen
|
||||
| ')' -> append_token state RightParen
|
||||
| '{' -> append_token state LeftBrace
|
||||
| '}' -> append_token state RightBrace
|
||||
| ',' -> append_token state Comma
|
||||
| ';' -> append_token state Semicolon
|
||||
| '.' -> append_token state Dot
|
||||
| '+' -> append_token state Plus
|
||||
| '-' -> append_token state Minus
|
||||
| '*' -> append_token state Star
|
||||
| '!' ->
|
||||
let b, state = advance_if '=' state in
|
||||
append_token state (if b then BangEqual else Bang)
|
||||
| '=' ->
|
||||
let b, state = advance_if '=' state in
|
||||
append_token state (if b then EqualEqual else Equal)
|
||||
| '<' ->
|
||||
let b, state = advance_if '=' state in
|
||||
append_token state (if b then LessEqual else Less)
|
||||
| '>' ->
|
||||
let b, state = advance_if '=' state in
|
||||
append_token state (if b then GreaterEqual else Greater)
|
||||
| '/' ->
|
||||
let found, state = advance_if '/' state in
|
||||
if not found then append_token state Slash
|
||||
else
|
||||
let start_pos = state.cur_pos in
|
||||
let _, state = advance_until '\n' state in
|
||||
let lexeme = String.trim @@ get_lexeme state start_pos state.cur_pos in
|
||||
append_token state (Comment lexeme)
|
||||
| '"' ->
|
||||
let found, state = advance_until '"' state in
|
||||
if not found then append_error state "Unterminated string literal"
|
||||
else
|
||||
let lexeme = get_lexeme state (state.start_pos + 1) (state.cur_pos - 1) in
|
||||
append_token state (String lexeme)
|
||||
| '0' .. '9' -> parse_number state
|
||||
| ' ' | '\t' | '\n' -> parse_number state
|
||||
| c -> append_error state (String.escaped @@ Printf.sprintf "Unexpected character '%c'" c)
|
||||
in
|
||||
tokenize_rec state
|
||||
end
|
||||
|
||||
let tokenize (source : string) : lexer_result =
|
||||
print_endline "Scanning source";
|
||||
print_endline "---";
|
||||
print_endline source;
|
||||
print_endline "---";
|
||||
(* Ok [] *)
|
||||
let state =
|
||||
State.tokenize_rec
|
||||
{ source; start_pos = 0; cur_pos = 0; tokens_rev = []; errors_rev = []; line = 1; col = 0 }
|
||||
in
|
||||
(* reverse the reversed tokens/errors *)
|
||||
if List.length state.errors_rev = 0 then Ok (List.rev state.tokens_rev)
|
||||
else Error (List.rev state.errors_rev)
|
||||
26
lib/lox.ml
Normal file
26
lib/lox.ml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
let ( let* ) = Result.bind
|
||||
|
||||
module Lexer = Lexer
|
||||
module Error = Error
|
||||
|
||||
type token = Lexer.token
|
||||
type lox_error = Error.lox_error
|
||||
type lox_value = Nil
|
||||
|
||||
let run (source : string) : (unit, lox_error) result =
|
||||
let* tokens = Error.of_lexer_error (Lexer.tokenize source) in
|
||||
let f token = Printf.printf "%s " (Lexer.show_token token) in
|
||||
Printf.printf "Got %d tokens\n" (List.length tokens);
|
||||
List.iter f tokens;
|
||||
print_endline "";
|
||||
Ok ()
|
||||
|
||||
let runRepl () : unit =
|
||||
try
|
||||
while true do
|
||||
print_string "> ";
|
||||
let line = read_line () in
|
||||
let result = run line in
|
||||
Result.iter_error Error.print_error result
|
||||
done
|
||||
with End_of_file -> ()
|
||||
3
lox/test.lox
Normal file
3
lox/test.lox
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// test comment
|
||||
"string"
|
||||
{}(
|
||||
2
test/dune
Normal file
2
test/dune
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
(test
|
||||
(name test_MlLox))
|
||||
0
test/test_MlLox.ml
Normal file
0
test/test_MlLox.ml
Normal file
Loading…
Add table
Add a link
Reference in a new issue