From b254176c3474f211ec88c96f9db858de2e0a9747 Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Fri, 20 Feb 2026 12:34:51 -0800 Subject: [PATCH 1/5] update MILESTONES --- MILESTONES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MILESTONES.md b/MILESTONES.md index c5b6c7a..58471f7 100644 --- a/MILESTONES.md +++ b/MILESTONES.md @@ -5,7 +5,7 @@ ## v0.2 - initial pre-release - [ ] Migrate AST to use actual Ast module -- [ ] Regular expressions +- [ ] Regular expressions (libpcre?) - [ ] Test suite - [ ] Constraint system - language version @@ -25,6 +25,7 @@ ## Stretch goals - [ ] Bytecode VM (this should not happen until API becomes stable) +- [ ] From scratch regular expression engine - [ ] Unicode strings - [ ] Method syntax for hash maps - [ ] Pattern matching From e7b5358d2a36da0b87f7e50f50b218e6c0bb11a7 Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Fri, 20 Feb 2026 15:50:26 -0800 Subject: [PATCH 2/5] wip get semver unit tests passing --- MILESTONES.md | 8 ++- lib/common/semver.ml | 84 +++++++++++++++++++++++ lib/common/semver.mli | 9 +++ lib/common/stdlib_interface.ml | 6 ++ test/dune | 2 +- test/main.ml | 2 +- test/{unit_tests.ml => run_unit_tests.ml} | 1 + test/unit_tests/dune | 4 ++ test/unit_tests/semver_test.ml | 35 ++++++++++ 9 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 lib/common/semver.ml create mode 100644 lib/common/semver.mli rename test/{unit_tests.ml => run_unit_tests.ml} (99%) create mode 100644 test/unit_tests/dune create mode 100644 test/unit_tests/semver_test.ml diff --git a/MILESTONES.md b/MILESTONES.md index 58471f7..db3a54c 100644 --- a/MILESTONES.md +++ b/MILESTONES.md @@ -3,14 +3,16 @@ ## v0.1 - initially tagged version - [ ] Language versioning -## v0.2 - initial pre-release +## v0.2 +- [ ] Test suite +- [ ] JSON module + +## v0.3 - initial pre-release - [ ] Migrate AST to use actual Ast module - [ ] Regular expressions (libpcre?) -- [ ] Test suite - [ ] Constraint system - language version - permissions -- [ ] JSON module - [ ] Allow non-zero exit codes on sub-processes - [ ] Bug: stacktrace printing is wrong in REPL diff --git a/lib/common/semver.ml b/lib/common/semver.ml new file mode 100644 index 0000000..7baef13 --- /dev/null +++ b/lib/common/semver.ml @@ -0,0 +1,84 @@ +open Core + +type t = { major : int; minor : int; patch : int } + +(* +type constraint_t = Exact of t +*) + +let create ?(major = 0) ?(minor = 0) ?(patch = 0) () = + let open Result.Monad_infix in + let verify_positive name i = + if i < 0 then + Error (Printf.sprintf "Field \"%s\" cannot be negative, got %d" name i) + else Ok () + in + verify_positive "major" major >>= fun () -> + verify_positive "minor" minor >>= fun () -> + verify_positive "patch" patch >>= fun () -> Ok { major; minor; patch } + +module Parser = struct + type state = Major | Minor of int | Patch of int * int | Done of t + + let parse str = + print_endline str; + let open Result.Monad_infix in + let str_len = String.length str in + let c_to_digit_opt c = + let i = Char.to_int c - 48 in + if i >= 0 && i <= 9 then Some i else None + in + let lex_dot i = + let next_i = i + 1 in + (* TODO validate *) + next_i + in + let lex_int idx = + let rec lex_int_rec prefix idx = + Printf.printf "lex_int_rec %d %d\n" prefix idx; + let ch = String.get str idx in + match c_to_digit_opt ch with + | Some i -> ( + Printf.printf "Parsed %c at %d of %d!\n" ch idx str_len; + if idx + 1 >= str_len then Ok (idx + 1, prefix + i) + else + let next_digit_opt = String.get str (idx + 1) |> c_to_digit_opt in + match next_digit_opt with + | Some _ -> + (* TODO optimize *) + Printf.printf "\t-> lex_int_rec %d %d\n" + Int.(prefix * 10) + (idx + 1); + lex_int_rec Int.((prefix + i) * 10) (idx + 1) + | None -> Ok (lex_dot (idx + 1), prefix + i)) + | None -> Error "parse error" + in + lex_int_rec 0 idx + in + let rec lex_rec idx = function + | Major -> + Printf.printf "Major at %d\n" idx; + lex_int idx >>= fun (new_idx, major) -> lex_rec new_idx (Minor major) + | Minor major -> + Printf.printf "Minor at %d\n" idx; + lex_int idx >>= fun (new_idx, minor) -> + lex_rec new_idx (Patch (major, minor)) + | Patch (major, minor) -> + Printf.printf "Patch at %d\n" idx; + lex_int idx >>= fun (new_idx, patch) -> + let t' = { major; minor; patch } in + lex_rec new_idx (Done t') + | Done t' -> + Printf.printf "Done at %d\n" idx; + + Ok t' + in + lex_rec 0 Major +end + +let parse str = Parser.parse str + +let is_equal t1 t2 = + t1.major = t2.major && t1.minor = t2.minor && t1.patch = t2.patch + +let to_string t' = Printf.sprintf "%d.%d.%d" t'.major t'.minor t'.patch diff --git a/lib/common/semver.mli b/lib/common/semver.mli new file mode 100644 index 0000000..d87449c --- /dev/null +++ b/lib/common/semver.mli @@ -0,0 +1,9 @@ +type t = private { major : int; minor : int; patch : int } + +val parse : string -> (t, string) Result.t + +val create : + ?major:int -> ?minor:int -> ?patch:int -> unit -> (t, string) Result.t + +val is_equal : t -> t -> bool +val to_string : t -> string diff --git a/lib/common/stdlib_interface.ml b/lib/common/stdlib_interface.ml index f9e2720..3b801f7 100644 --- a/lib/common/stdlib_interface.ml +++ b/lib/common/stdlib_interface.ml @@ -110,5 +110,11 @@ let globals = setters = []; static_getters = []; }; + { + name = "Version"; + getters = [ "toString" ]; + setters = []; + static_getters = [ "current" ]; + }; ]; } diff --git a/test/dune b/test/dune index 22baf82..277016b 100644 --- a/test/dune +++ b/test/dune @@ -2,7 +2,7 @@ (test (name main) - (libraries ounit2 sloth_script re core_unix) + (libraries ounit2 sloth_script re core_unix unit_tests) (deps (source_tree green_specs) (source_tree red_specs)) diff --git a/test/main.ml b/test/main.ml index 32798eb..c0788ae 100644 --- a/test/main.ml +++ b/test/main.ml @@ -184,7 +184,7 @@ let tests = >::: [ "green" >::: List.map ~f:make_test (Specs.green ()); "red" >::: List.map ~f:make_failing_test (Specs.red ()); - "unit" >::: Unit_tests.get (); + "unit" >::: Run_unit_tests.get (); ] let () = run_test_tt_main tests diff --git a/test/unit_tests.ml b/test/run_unit_tests.ml similarity index 99% rename from test/unit_tests.ml rename to test/run_unit_tests.ml index 8aa9967..b7f9330 100644 --- a/test/unit_tests.ml +++ b/test/run_unit_tests.ml @@ -141,4 +141,5 @@ let get () = "pretty" >::: List.map pretty ~f; "STDLIB interface & implementation match" >:: stdlib_interface_and_impl_match; + "semver" >::: Unit_tests.Semver_test.tests; ] diff --git a/test/unit_tests/dune b/test/unit_tests/dune new file mode 100644 index 0000000..e5ebbc9 --- /dev/null +++ b/test/unit_tests/dune @@ -0,0 +1,4 @@ +(library + (name unit_tests) + ; i.e. deps + (libraries sloth_script ounit2)) diff --git a/test/unit_tests/semver_test.ml b/test/unit_tests/semver_test.ml new file mode 100644 index 0000000..bd365b2 --- /dev/null +++ b/test/unit_tests/semver_test.ml @@ -0,0 +1,35 @@ +open OUnit2 +open Core + +let tests = + let open Sloth_common.Semver in + let open Result.Monad_infix in + [ + ( "Semver.create" >:: fun _ -> + create ~major:0 ~minor:1 ~patch:0 () + >>= (fun t' -> + assert_equal t'.major 0; + assert_equal t'.minor 1; + assert_equal t'.patch 0; + Ok ()) + |> Result.ok_or_failwith ); + ( "Semver.is_equal" >:: fun _ -> + create ~major:2 ~minor:0 ~patch:0 () + >>= (fun t1 -> + create ~major:2 ~minor:0 ~patch:0 () >>= fun t2 -> + create ~major:2 ~minor:0 ~patch:1 () >>= fun t3 -> + assert_bool "is_equal" (is_equal t1 t2); + assert_bool "is_equal" (not (is_equal t1 t3)); + Ok ()) + |> Result.ok_or_failwith ); + ( "Semver.parse" >:: fun _ -> + parse "4.0.123" + >>= (fun from_parse -> + create ~major:4 ~minor:0 ~patch:123 () >>= fun from_create -> + Ok + (assert_bool + (Printf.sprintf ".parse -> %s\t.create -> %s" (to_string from_parse) + (to_string from_create)) + (is_equal from_parse from_create))) + |> Result.ok_or_failwith ); + ] From 692bf246a0c43bf366f7c5d25dce2667f32db23b Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Fri, 20 Feb 2026 16:30:43 -0800 Subject: [PATCH 3/5] wip wire through runtime --- MILESTONES.md | 5 ++++- lib/common/semver.ml | 14 +------------- lib/interpreter/globals.ml | 4 +++- lib/interpreter/runtime.ml | 17 ++++++++++------- lib/interpreter/runtime.mli | 2 ++ lib/interpreter/stdlib_impl.ml | 21 +++++++++++++++++++++ test/green_specs/version.sloth | 4 ++++ test/main.ml | 7 +++++-- 8 files changed, 50 insertions(+), 24 deletions(-) create mode 100644 test/green_specs/version.sloth diff --git a/MILESTONES.md b/MILESTONES.md index db3a54c..fd510b7 100644 --- a/MILESTONES.md +++ b/MILESTONES.md @@ -14,7 +14,6 @@ - language version - permissions - [ ] Allow non-zero exit codes on sub-processes -- [ ] Bug: stacktrace printing is wrong in REPL ## v1.0 - [ ] Formatter @@ -109,3 +108,7 @@ - `.push(element)` - `.pop(element)` - [x] Bug: $scriptDir and $cwd should be absolute, so they can be chained + +## Bugs +- [ ] bug: stacktrace printing is wrong in REPL +- [ ] bug: Do static methods need to take self as first arg? diff --git a/lib/common/semver.ml b/lib/common/semver.ml index 7baef13..7fbbb50 100644 --- a/lib/common/semver.ml +++ b/lib/common/semver.ml @@ -21,7 +21,6 @@ module Parser = struct type state = Major | Minor of int | Patch of int * int | Done of t let parse str = - print_endline str; let open Result.Monad_infix in let str_len = String.length str in let c_to_digit_opt c = @@ -35,20 +34,15 @@ module Parser = struct in let lex_int idx = let rec lex_int_rec prefix idx = - Printf.printf "lex_int_rec %d %d\n" prefix idx; let ch = String.get str idx in match c_to_digit_opt ch with | Some i -> ( - Printf.printf "Parsed %c at %d of %d!\n" ch idx str_len; if idx + 1 >= str_len then Ok (idx + 1, prefix + i) else let next_digit_opt = String.get str (idx + 1) |> c_to_digit_opt in match next_digit_opt with | Some _ -> (* TODO optimize *) - Printf.printf "\t-> lex_int_rec %d %d\n" - Int.(prefix * 10) - (idx + 1); lex_int_rec Int.((prefix + i) * 10) (idx + 1) | None -> Ok (lex_dot (idx + 1), prefix + i)) | None -> Error "parse error" @@ -57,21 +51,15 @@ module Parser = struct in let rec lex_rec idx = function | Major -> - Printf.printf "Major at %d\n" idx; lex_int idx >>= fun (new_idx, major) -> lex_rec new_idx (Minor major) | Minor major -> - Printf.printf "Minor at %d\n" idx; lex_int idx >>= fun (new_idx, minor) -> lex_rec new_idx (Patch (major, minor)) | Patch (major, minor) -> - Printf.printf "Patch at %d\n" idx; lex_int idx >>= fun (new_idx, patch) -> let t' = { major; minor; patch } in lex_rec new_idx (Done t') - | Done t' -> - Printf.printf "Done at %d\n" idx; - - Ok t' + | Done t' -> Ok t' in lex_rec 0 Major end diff --git a/lib/interpreter/globals.ml b/lib/interpreter/globals.ml index 9e88ea2..c6dc30f 100644 --- a/lib/interpreter/globals.ml +++ b/lib/interpreter/globals.ml @@ -14,6 +14,7 @@ type t = { stack_frames : Runtime.backtrace; current_function_name : string; (* Store this since stack_frames stores the name of the enclosing func *) + version : Sloth_common.Semver.t; } let push_frame t next_func pos = @@ -24,7 +25,7 @@ let push_frame t next_func pos = } (* TODO memoize *) -let make_globals m src script_path ~argv ~env = +let make_globals m src script_path ~argv ~env ~version = let module M = (val m : Native.Sig) in let classes = Hashtbl.create (module String) in let identifiers : Runtime.t Identifiers.t = Identifiers.create () in @@ -80,4 +81,5 @@ let make_globals m src script_path ~argv ~env = argv; stack_frames = []; current_function_name = "(top-level)"; + version; } diff --git a/lib/interpreter/runtime.ml b/lib/interpreter/runtime.ml index c7d05cb..85d676d 100644 --- a/lib/interpreter/runtime.ml +++ b/lib/interpreter/runtime.ml @@ -66,20 +66,15 @@ let backtrace_to_s ~pos bt src msg type_s = Buffer.contents buf type t = - (* Primitives *) | String of string | Bool of bool | Num of float | Null - (* Collections *) | List of t Dynarray.t | HashMap of (t, t) Stdlib.Hashtbl.t - (* Functions *) | Func of function_t | Method of t * function_t (** This is created by Object de-referencing *) - (* Type *) | Prototype of prototype - (* Stdlib Types *) | Directory of string | File of file | FileDescriptor of Core_unix.File_descr.t @@ -87,6 +82,7 @@ type t = | Process of process | ProcessHandle of process_handle | ProcessResult of process_result + | Version of Sloth_common.Semver.t and breaking_type = | Return of t @@ -139,6 +135,7 @@ let to_class_name = function | File _ -> "File" | FileDescriptor _ -> "FileDescriptor" | Directory _ -> "Directory" + | Version _ -> "Version" let rec to_s t' = let stringify_list l = @@ -207,6 +204,7 @@ let rec to_s t' = | FileDescriptor fd -> Printf.sprintf "FileDescriptor(fd=%d)" @@ Core_unix.File_descr.to_int fd | Directory path -> Printf.sprintf "Directory(path=%s)" path + | Version ver -> Sloth_common.Semver.to_string ver let num_of_val = function Num f -> Some f | _ -> None let string_of_val = function String s -> Some s | _ -> None @@ -223,6 +221,7 @@ let hashmap_of_val = function HashMap h -> Some h | _ -> None let process_of_t = function Process p -> Some p | _ -> None let process_handle_of_t = function ProcessHandle p -> Some p | _ -> None let process_result_of_val = function ProcessResult p -> Some p | _ -> None +let version_of_t = function Version v -> Some v | _ -> None let func_of_val = function Func func -> Some func | _ -> None let method_of_val = function @@ -401,7 +400,11 @@ let rec is_equal is_equality lhs rhs = Core_unix.File_descr.(equal read r_read && equal write r_write) in Bool.(same_pipe = is_equality) - | ProcessResult _ -> failwith "TODO" - | Func _ | Method _ -> + | Version left_ver -> + let right_ver = version_of_t rhs |> option_value ~message:__LOC__ in + let same_version = + Sloth_common.Semver.is_equal left_ver right_ver in + Bool.(same_version = is_equality) + | ProcessResult _ | Func _ | Method _ -> Printf.sprintf "is_equal the type %s is not implemented" lh_s |> failwith diff --git a/lib/interpreter/runtime.mli b/lib/interpreter/runtime.mli index 18c28e0..ac234e1 100644 --- a/lib/interpreter/runtime.mli +++ b/lib/interpreter/runtime.mli @@ -43,6 +43,7 @@ type t = | Process of process | ProcessHandle of process_handle | ProcessResult of process_result + | Version of Sloth_common.Semver.t and breaking_type = | Return of t @@ -94,6 +95,7 @@ val pipe_of_t : t -> (Core_unix.File_descr.t * Core_unix.File_descr.t) option val process_handle_of_t : t -> process_handle option val process_of_t : t -> process option val process_result_of_val : t -> process_result option +val version_of_t : t -> Sloth_common.Semver.t option val string_of_val : t -> string option val val_of_env : string array -> t val to_class_name : t -> string diff --git a/lib/interpreter/stdlib_impl.ml b/lib/interpreter/stdlib_impl.ml index ec11d64..6c1964b 100644 --- a/lib/interpreter/stdlib_impl.ml +++ b/lib/interpreter/stdlib_impl.ml @@ -902,6 +902,27 @@ let make_protos m = setters = []; static_getters = []; }; + { + name = "Version"; + getters = + [ + ( "toString", + make_method ~arity:(Some 0) ~name:"Version.toString" + (fun self _ _ _ -> + let version = + Runtime.version_of_t self |> Option.value_exn ~message:__LOC__ + in + First (Runtime.String (Sloth_common.Semver.to_string version))) + ); + ]; + setters = []; + static_getters = + [ + ( "current", + make_method ~arity:(Some 1) ~name:"Version.current" (fun _ _ _ _ -> + failwith "TODO") ); + ]; + }; ] let context_ids ~cwd ~env ~script_path ~argv = diff --git a/test/green_specs/version.sloth b/test/green_specs/version.sloth new file mode 100644 index 0000000..0ae35b8 --- /dev/null +++ b/test/green_specs/version.sloth @@ -0,0 +1,4 @@ +### Program + +let ver = Version.current() +assert(ver.toString() == "1.2.3") diff --git a/test/main.ml b/test/main.ml index c0788ae..43ea1b2 100644 --- a/test/main.ml +++ b/test/main.ml @@ -3,6 +3,9 @@ open Sloth_common.Common open Common open Core +let test_version = + Sloth_common.Semver.create ~minor:1 () |> Result.ok_or_failwith + let rec indent buf n = if n = 0 then Buffer.contents buf else ( @@ -54,7 +57,7 @@ let make_test spec = Interpreter.Globals.make_globals (module Lib) spec.program "/parent/unit_test.sloth" ~env:[| "UNIT_TEST=true" |] - ~argv:[] + ~argv:[] ~version:test_version in let either = match @@ -148,7 +151,7 @@ let make_failing_test spec = let globals = Interpreter.Globals.make_globals (module Lib) - spec.program "unit_test.sloth" ~env:[||] ~argv:[] + spec.program "unit_test.sloth" ~env:[||] ~argv:[] ~version:test_version in let _, either = Interpreter.Interpret.interpret_prog globals prog in match either with From 59d66f76e889cc62e46073d771e59e621b790864 Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Fri, 20 Feb 2026 17:15:07 -0800 Subject: [PATCH 4/5] get version working fully --- MILESTONES.md | 6 ++--- bin/main.ml | 4 +-- lib/common/common.ml | 7 +++++ lib/common/stdlib_interface.ml | 1 + lib/interpreter/globals.ml | 3 ++- lib/interpreter/runtime.ml | 3 +-- lib/interpreter/stdlib_impl.ml | 20 +++++++++++--- lib/interpreter/stdlib_impl.mli | 1 + release/README.md | 1 + test/common.ml | 2 ++ test/green_specs/version.sloth | 48 +++++++++++++++++++++++++++++++-- test/main.ml | 8 +++--- test/run_unit_tests.ml | 1 + test/test_one.ml | 2 +- 14 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 release/README.md diff --git a/MILESTONES.md b/MILESTONES.md index fd510b7..af08dec 100644 --- a/MILESTONES.md +++ b/MILESTONES.md @@ -1,8 +1,5 @@ # Milestones -## v0.1 - initially tagged version -- [ ] Language versioning - ## v0.2 - [ ] Test suite - [ ] JSON module @@ -39,6 +36,9 @@ # Done +## v0.1 - initially tagged version +- [x] Language versioning + ## pre-v0.1 - [x] Lexical scoping - [x] Closures diff --git a/bin/main.ml b/bin/main.ml index f5cd1c7..96eebf5 100644 --- a/bin/main.ml +++ b/bin/main.ml @@ -15,7 +15,7 @@ let repl cwd = let globals = Globals.make_globals (module Native.Prod) - "" script_path ~env:(Core_unix.environment ()) ~argv:[] + "" script_path ~env:(Core_unix.environment ()) ~argv:[] ~version in let env = Compiler.Environment.create "" |> Compiler.Environment.populate in let rec repl_inner globals env = @@ -71,7 +71,7 @@ let interpreter program program_name argv = let globals = Globals.make_globals (module Native.Prod) - ~argv program program_name ~env:(Core_unix.environment ()) + ~argv program program_name ~env:(Core_unix.environment ()) ~version in let result = diff --git a/lib/common/common.ml b/lib/common/common.ml index a4a0111..6181e32 100644 --- a/lib/common/common.ml +++ b/lib/common/common.ml @@ -33,3 +33,10 @@ let debug_mode = false (* See BUFSIZ in stdio.h = 8192; although apparently OCaml's IO buffers are 65536? *) let bufsiz = 65536 + +(* +TODO generate this at build time, from git +https://discuss.ocaml.org/t/how-to-generate-different-code-based-on-an-environmental-variable-at-build-time/16104 +*) +let version = + Semver.create ~major:0 ~minor:1 ~patch:0 () |> Core.Result.ok_or_failwith diff --git a/lib/common/stdlib_interface.ml b/lib/common/stdlib_interface.ml index 3b801f7..93d438a 100644 --- a/lib/common/stdlib_interface.ml +++ b/lib/common/stdlib_interface.ml @@ -21,6 +21,7 @@ let globals = "$stderr"; "$stdin"; "$stdout"; + "$version"; ]; protos = [ diff --git a/lib/interpreter/globals.ml b/lib/interpreter/globals.ml index c6dc30f..6343369 100644 --- a/lib/interpreter/globals.ml +++ b/lib/interpreter/globals.ml @@ -39,7 +39,8 @@ let make_globals m src script_path ~argv ~env ~version = (* TODO inject cwd *) List.iter - (Stdlib_impl.context_ids ~cwd:(Sys_unix.getcwd ()) ~env ~script_path ~argv) + (Stdlib_impl.context_ids ~cwd:(Sys_unix.getcwd ()) ~env ~script_path ~argv + ~version) ~f:(fun (name, t) -> Context.bind context_ids name t |> Option.value_exn ~message:__LOC__); diff --git a/lib/interpreter/runtime.ml b/lib/interpreter/runtime.ml index 85d676d..4fb4619 100644 --- a/lib/interpreter/runtime.ml +++ b/lib/interpreter/runtime.ml @@ -402,8 +402,7 @@ let rec is_equal is_equality lhs rhs = Bool.(same_pipe = is_equality) | Version left_ver -> let right_ver = version_of_t rhs |> option_value ~message:__LOC__ in - let same_version = - Sloth_common.Semver.is_equal left_ver right_ver in + let same_version = Sloth_common.Semver.is_equal left_ver right_ver in Bool.(same_version = is_equality) | ProcessResult _ | Func _ | Method _ -> Printf.sprintf "is_equal the type %s is not implemented" lh_s diff --git a/lib/interpreter/stdlib_impl.ml b/lib/interpreter/stdlib_impl.ml index 6c1964b..8ee59ce 100644 --- a/lib/interpreter/stdlib_impl.ml +++ b/lib/interpreter/stdlib_impl.ml @@ -907,7 +907,7 @@ let make_protos m = getters = [ ( "toString", - make_method ~arity:(Some 0) ~name:"Version.toString" + make_method ~arity:(Some 1) ~name:"Version.toString" (fun self _ _ _ -> let version = Runtime.version_of_t self |> Option.value_exn ~message:__LOC__ @@ -919,13 +919,24 @@ let make_protos m = static_getters = [ ( "current", - make_method ~arity:(Some 1) ~name:"Version.current" (fun _ _ _ _ -> - failwith "TODO") ); + make_method ~arity:(Some 1) ~name:"Version.current" + (fun _ ctx _ _ -> + let ver = + Context.get ctx "$version" + |> Option.value_exn ~message:__LOC__ + in + (* This is just a type check *) + match Runtime.version_of_t ver with + | Some _ -> First ver + | None -> + Printf.sprintf "type error at %s: %s" __LOC__ + (Runtime.to_s ver) + |> Sloth_common.Common.internal_failure) ); ]; }; ] -let context_ids ~cwd ~env ~script_path ~argv = +let context_ids ~cwd ~env ~script_path ~argv ~version = [ ( "$argv", Runtime.List @@ -937,4 +948,5 @@ let context_ids ~cwd ~env ~script_path ~argv = ("$stderr", Runtime.FileDescriptor Core_unix.stderr); ("$stdin", Runtime.FileDescriptor Core_unix.stdin); ("$stdout", Runtime.FileDescriptor Core_unix.stdout); + ("$version", Runtime.Version version); ] diff --git a/lib/interpreter/stdlib_impl.mli b/lib/interpreter/stdlib_impl.mli index bd8eb0c..bd8316a 100644 --- a/lib/interpreter/stdlib_impl.mli +++ b/lib/interpreter/stdlib_impl.mli @@ -13,4 +13,5 @@ val context_ids : env:string array -> script_path:string -> argv:string list -> + version:Sloth_common.Semver.t -> (string * Runtime.t) list diff --git a/release/README.md b/release/README.md new file mode 100644 index 0000000..908d2a1 --- /dev/null +++ b/release/README.md @@ -0,0 +1 @@ +Use annotated tags: `git tag -a v0.0.0 -m "my message"` diff --git a/test/common.ml b/test/common.ml index ffbed9b..f97d7c6 100644 --- a/test/common.ml +++ b/test/common.ml @@ -23,3 +23,5 @@ let find_child_specs dir_path = (* TODO handle whether or not dir_path ends in path separator *) List.map (inner_rec [] dir_fd) ~f:(fun base -> Printf.sprintf "%s/%s" dir_path base) + +let version = Sloth_common.Common.version diff --git a/test/green_specs/version.sloth b/test/green_specs/version.sloth index 0ae35b8..89c3b9b 100644 --- a/test/green_specs/version.sloth +++ b/test/green_specs/version.sloth @@ -1,4 +1,48 @@ ### Program +assert($version.toString() == "0.1.0", "actual: ${$version}") -let ver = Version.current() -assert(ver.toString() == "1.2.3") +### Processes +() + +### Ast +((StmtDecl + (ExprStmt + (FuncInvoc + (IdRef + assert + ) + ((Equality + (FuncInvoc + (ObjDeref + (ContextId + $version + ) + toString + ) + () + ) + (String + ((FullString + 0.1.0 + )) + ) + true + ) + (String + ((StartStringInterp + "actual: " + ) + (ExpressionStringInterp + (ContextId + $version + )) + (EndStringInterp + "" + )) + )) + )))) + +### Stdout + + +### Failure diff --git a/test/main.ml b/test/main.ml index 43ea1b2..1a1ea03 100644 --- a/test/main.ml +++ b/test/main.ml @@ -3,9 +3,6 @@ open Sloth_common.Common open Common open Core -let test_version = - Sloth_common.Semver.create ~minor:1 () |> Result.ok_or_failwith - let rec indent buf n = if n = 0 then Buffer.contents buf else ( @@ -57,7 +54,7 @@ let make_test spec = Interpreter.Globals.make_globals (module Lib) spec.program "/parent/unit_test.sloth" ~env:[| "UNIT_TEST=true" |] - ~argv:[] ~version:test_version + ~argv:[] ~version:Common.version in let either = match @@ -151,7 +148,8 @@ let make_failing_test spec = let globals = Interpreter.Globals.make_globals (module Lib) - spec.program "unit_test.sloth" ~env:[||] ~argv:[] ~version:test_version + spec.program "unit_test.sloth" ~env:[||] ~argv:[] + ~version:Common.version in let _, either = Interpreter.Interpret.interpret_prog globals prog in match either with diff --git a/test/run_unit_tests.ml b/test/run_unit_tests.ml index b7f9330..9f1a6c3 100644 --- a/test/run_unit_tests.ml +++ b/test/run_unit_tests.ml @@ -93,6 +93,7 @@ let stdlib_interface_and_impl_match _ = let impl_context_ids = Interpreter.Stdlib_impl.context_ids ~cwd:"/home/user" ~env:[| "USER=sloth" |] ~script_path:"/home/user/script.sloth" ~argv:[] + ~version:Common.version |> List.map ~f:(fun (name, _) -> name) in compare_two_string_lists interface.ids impl_ids; diff --git a/test/test_one.ml b/test/test_one.ml index 46e721e..a972ae9 100644 --- a/test/test_one.ml +++ b/test/test_one.ml @@ -30,7 +30,7 @@ let () = let globals = Interpreter.Globals.make_globals (module M) - spec.program test ~env:[| "UNIT_TEST=true" |] ~argv:[] + spec.program test ~env:[| "UNIT_TEST=true" |] ~argv:[] ~version in let _, either = Interpreter.Interpret.interpret_prog globals ir in From 359073b41aeaf35738ae936d1169f443fae9a3fc Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Fri, 20 Feb 2026 17:19:33 -0800 Subject: [PATCH 5/5] Update MILESTONES.md --- MILESTONES.md | 130 +++++++++++++++++++++++++------------------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/MILESTONES.md b/MILESTONES.md index af08dec..a124d28 100644 --- a/MILESTONES.md +++ b/MILESTONES.md @@ -40,74 +40,74 @@ - [x] Language versioning ## pre-v0.1 -- [x] Lexical scoping -- [x] Closures -- [x] Variable re-assignment -- [x] Function arguments -- [x] Invoking function expressions -- [x] Conditionals -- [x] Tooling to diagnose shift/reduce conflicts -- [x] Infix functions -- [x] Recursion (depends on conditionals) +- [x] Bug: $scriptDir and $cwd should be absolute, so they can be chained +- [x] `List` methods + - `.pop(element)` + - `.push(element)` + - `.reduce(callback)` + - `.filter(callback)` + - `.map(callback)` + - `.forEach(callback)` + - `.contains(element)` +- [x] Stack traces +- [x] Add `FileDescriptor::write()` +- [x] Blank identifier does not bind +- [x] `String` methods + - `.contains(substring)` + - `.split(sep)` +- [x] Mock out standard I/O for tests +- [x] Reading from `$stdin` +- [x] `File::open()` -> `FileDescriptor` + - `fd.close() + - `fd.writeAll("Hello, World!\n")` +- [x] `throw` keyword +- [x] error handling (`let result = mayError() catch (e) DEFAULT;`) +- [x] $stdin, $stderr +- [x] CLI args +- [x] `&` and `&!` operators for spawning sub-processes +- [x] Implement exit function +- [x] Parser errors +- [x] $env +- [x] $script & $scriptDir +- [x] Mock out processes, files for unit tests +- [x] Context variables, with statements; $cwd +- [x] Break and continue statements +- [x] First class for loops +- [x] Migrate var declarations and reassignment to expressions +- [x] Allow single statement blocks to omit semicolons +- [x] Return statements +- [x] Files +- [x] raw string literals +- [x] operators are top level functions that DO one thing, but allow for implicit casting of its operands +- [x] Use readline for REPL +- [x] Class prototypes +- [x] Class static methods +- [x] Process class +- [x] README.md +- [x] Do blocks +- [x] For-in loops - [x] Comments -- [x] Lists -- [x] Loops -- [x] Hashmaps -- [x] Updating lists -- [x] String interpolation -- [x] Implement all arithmetic operators -- [x] Automatic semicolon insertion -- [x] HashMap literals -- [x] Store locs in runtime values -- [x] Assertions - [x] Classes +- [x] Assertions +- [x] Store locs in runtime values +- [x] HashMap literals +- [x] Automatic semicolon insertion +- [x] Implement all arithmetic operators +- [x] String interpolation +- [x] Updating lists +- [x] Hashmaps +- [x] Loops +- [x] Lists - [x] Comments -- [x] For-in loops -- [x] Do blocks -- [x] README.md -- [x] Process class -- [x] Class static methods -- [x] Class prototypes -- [x] Use readline for REPL -- [x] operators are top level functions that DO one thing, but allow for implicit casting of its operands -- [x] raw string literals -- [x] Files -- [x] Return statements -- [x] Allow single statement blocks to omit semicolons -- [x] Migrate var declarations and reassignment to expressions -- [x] First class for loops -- [x] Break and continue statements -- [x] Context variables, with statements; $cwd -- [x] Mock out processes, files for unit tests -- [x] $script & $scriptDir -- [x] $env -- [x] Parser errors -- [x] Implement exit function -- [x] `&` and `&!` operators for spawning sub-processes -- [x] CLI args -- [x] $stdin, $stderr -- [x] error handling (`let result = mayError() catch (e) DEFAULT;`) -- [x] `throw` keyword -- [x] `File::open()` -> `FileDescriptor` - - `fd.writeAll("Hello, World!\n")` - - `fd.close() -- [x] Reading from `$stdin` -- [x] Mock out standard I/O for tests -- [x] `String` methods - - `.split(sep)` - - `.contains(substring)` -- [x] Blank identifier does not bind -- [x] Add `FileDescriptor::write()` -- [x] Stack traces -- [x] `List` methods - - `.contains(element)` - - `.forEach(callback)` - - `.map(callback)` - - `.filter(callback)` - - `.reduce(callback)` - - `.push(element)` - - `.pop(element)` -- [x] Bug: $scriptDir and $cwd should be absolute, so they can be chained +- [x] Recursion (depends on conditionals) +- [x] Infix functions +- [x] Tooling to diagnose shift/reduce conflicts +- [x] Conditionals +- [x] Invoking function expressions +- [x] Function arguments +- [x] Variable re-assignment +- [x] Closures +- [x] Lexical scoping ## Bugs - [ ] bug: stacktrace printing is wrong in REPL