diff --git a/janet/base.janet b/janet/base.janet index 22e7472..88488ed 100644 --- a/janet/base.janet +++ b/janet/base.janet @@ -44,7 +44,7 @@ :dict (dict-str value) :set (string/join (map stringify (keys value)) ", ") - :ref (stringify (value :^value)) + :box (stringify (value :^value)) :fn (string "fn " (value :name)) :applied (string "fn " (value :name)) :function (string "builtin " (string value)) @@ -68,7 +68,7 @@ :list (string "[" (stringify x) "]") :dict (string "#{" (stringify x) "}") :set (string "${" (stringify x) "}") - :ref (string "box " (x :name) " [ " (stringify x) " ]") + :box (string "box " (x :name) " [ " (stringify x) " ]") :pkg (show-pkg x) (stringify x))) diff --git a/janet/errors.janet b/janet/errors.janet index 6e49987..b4357ea 100644 --- a/janet/errors.janet +++ b/janet/errors.janet @@ -1,10 +1,34 @@ (import spork/json :as j) (import /base :as b) +(defn- get-line [source line] + ((string/split "\n" source) (dec line))) + (defn scan-error [e out] (set (out :errors) e) (j/encode out)) -(defn parse-error [e out] (set (out :errors) e) (j/encode out)) +(defn parse-error [e] + (def msg (e :msg)) + (def line-num (get-in e [:token :line])) + (def source (get-in e [:token :source])) + (def source-line (get-line source line-num)) + (print "Parsing error: " msg) + (print "On line " line-num ":") + (print source-line)) -(defn validation-error [e out] (set (out :errors) e) (j/encode out)) + +(defn validation-error [e] + (def msg (e :msg)) + (def line-num (get-in e [:node :token :line])) + (def source (get-in e [:node :token :source])) + (def source-line (get-line source line-num)) + (case msg + "unbound name" + (do + (print "Validation error: " msg " " (get-in e [:node :data])) + (print "on line " line-num) + (print source-line) + ) + ) +) (defn runtime-error [e out] (set (out :errors) e) (j/encode out)) diff --git a/janet/interpreter.janet b/janet/interpreter.janet index 6d0950a..f53ae8f 100644 --- a/janet/interpreter.janet +++ b/janet/interpreter.janet @@ -340,7 +340,7 @@ (defn- ref [ast ctx] (def {:data value-ast :name name} ast) (def value (interpret value-ast ctx)) - (def box @{:^type :ref :^value value :name name}) + (def box @{:^type :box :^value value :name name}) (set (ctx name) box) box) diff --git a/janet/ludus.janet b/janet/ludus.janet index d7e1680..1e8d9fe 100644 --- a/janet/ludus.janet +++ b/janet/ludus.janet @@ -16,16 +16,16 @@ The API from the old Clojure Ludus interpreter returns an object with four field * `errors`: an array of errors, which are just strings This new scene will have to return a JSON POJSO: -{:console [...] :result "..." :draw [...] :errors [...]} +{:console "..." :result "..." :draw [...] :errors [...]} ) -(def console @"") -(setdyn :out console) -(print "foo") -(pp {:a 1 :b 2}) -(setdyn :out stdout) -(print "collected out") -(print console) +(def prelude-src (slurp "prelude.ld")) +(def prelude-scanned (s/scan prelude-src)) +(def prelude-parsed (p/parse prelude-scanned)) +(def parse-errors (prelude-parsed :errors)) +(when (any? parse-errors) (each err parse-errors (e/parse-error err))) +(def prelude-validated (v/valid prelude-parsed @{"base" b/base})) +(each err (prelude-validated :errors) (e/validation-error err)) (defn run [source] (def errors @[]) @@ -44,7 +44,7 @@ This new scene will have to return a JSON POJSO: (break (-> :errors validated (e/validation-error out)))) (setdyn :out console) (try - (set result (b/show (i/interpret (parsed :ast) @{}))) + (set result (b/show (i/interpret (parsed :ast) @{:^parent b/base}))) ([err] (e/runtime-error err out))) (set (out :result) result) (j/encode out)) @@ -56,8 +56,8 @@ This new scene will have to return a JSON POJSO: (run source)) (def source ` - fn foo () -> :foo - foo () +let foo = fn () -> :bar +foo () `) (-> source run j/decode) diff --git a/janet/parser.janet b/janet/parser.janet index ec8aa01..a4f5056 100644 --- a/janet/parser.janet +++ b/janet/parser.janet @@ -1,7 +1,7 @@ ### A recursive descent parser for Ludus ### We still need to scan some things -# (try (os/cd "janet") ([_] nil)) # when in repl to do relative imports +(try (os/cd "janet") ([_] nil)) # when in repl to do relative imports (import ./scanner :as s) (defmacro declare @@ -773,7 +773,7 @@ (defn- lambda [parser] (def origin (current parser)) (expect parser :fn) (advance parser) - @{:type :fn :data (fn-simple parser) :token origin}) + @{:type :fn :data ((fn-simple parser) :clauses) :token origin}) (defn- fnn [parser] (if (= :lparen (-> parser peek type)) (break (lambda parser))) @@ -1104,6 +1104,7 @@ (def origin (current parser)) (def lines @[]) (while (not (check parser :eof)) + (accept-many parser :newline) (array/push lines (capture toplevel parser)) (capture terminator parser)) {:type :script :data lines :token origin}) @@ -1116,10 +1117,10 @@ # (do (comment -(def source `pkg Foo {} +(def source `fn () -> 42 `) (def scanned (s/scan source)) # (print "\n***NEW PARSE***\n") (def a-parser (new-parser scanned)) -(def parsed (pkg a-parser)) +(def parsed (fnn a-parser)) ) diff --git a/janet/prelude.ld b/janet/prelude.ld index 3a0bb72..1005a61 100644 --- a/janet/prelude.ld +++ b/janet/prelude.ld @@ -7,6 +7,29 @@ & tuple? & ref? +& some forward declarations +& TODO: fix this so that we don't need (as many of) them +fn first +fn append +fn some? +fn update! +fn string +fn join +fn neg? +fn atan/2 +fn mod +fn assoc? +fn dict +fn get +fn unbox +fn store! +fn turn/rad +fn deg/rad +fn floor +fn and +fn apply_command +fn state/call + & the very base: know something's type fn type { "Returns a keyword representing the type of the value passed in." @@ -28,6 +51,40 @@ fn eq? { else false } +&&& true & false: boolean logic (part the first) +fn bool? { + "Returns true if a value is of type :boolean." + (false) -> true + (true) -> true + (_) -> false +} + +fn true? { + "Returns true if a value is boolean `true`. Useful to distinguish between `true` and anything else." + (true) -> true + (_) -> false +} + +fn false? { + "Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`." + (false) -> true + (_) -> false +} + +fn bool { + "Returns false if a value is nil or false, otherwise returns true." + (nil) -> false + (false) -> false + (_) -> true +} + +fn not { + "Returns false if a value is truthy, true if a value is falsy." + (nil) -> true + (false) -> true + (_) -> false +} + fn neq? { "Returns true if none of the arguments have the same value." (x) -> false @@ -73,55 +130,6 @@ fn dec { (x as :number) -> base :dec (x) } -fn at { - "Returns the element at index n of a list or tuple. Zero-indexed: the first element is at index 0." - (xs as :list, n as :number) -> when { - neg? (n) -> nil - gte? (n, count (xs)) -> nil - else -> base :nth (xs, inc (n)) - } - (xs as :tuple, n as :number) -> when { - neg? (n) -> nil - gte? (n, count (xs)) -> nil - else -> base :nth (xs, inc (n)) - } - (_) -> nil -} - -fn first { - "Returns the first element of a list or tuple." - (xs) -> at (xs, 0) -} - -fn second { - "Returns the second element of a list or tuple." - (xs) -> at (xs, 1) -} - -fn last { - "Returns the last element of a list or tuple." - (xs) -> at (xs, sub (count (xs), 1)) -} - -fn butlast { - "Returns a list, omitting the last element." - (xs as :list) -> base :slice (xs, sub (count (xs), 1)) -} - -fn slice { - "Returns a slice of a list, representing a sub-list." - (xs as :list, end as :number) -> slice (xs, 0, end) - (xs as :list, start as :number, end as :number) -> when { - gte? (start, end) -> [] - gt? (end, count (xs)) -> slice (xs, start, count (xs)) - neg? (start) -> slice (xs, 0, end) - else -> { - let slice = base :slice (xs, inc (start), inc (end)) - base :into ([], slice) - } - } -} - fn count { "Returns the number of elements in a collection (including string)." (xs as :list) -> dec (base :count (xs)) @@ -142,6 +150,16 @@ fn empty? { (_) -> false } +fn any? { + "Returns true if something is not empty, otherwise returns false (including for things that can't be logically full, like numbers)." + ([...]) -> true + (#{...}) -> true + (s as :set) -> not (empty? (s)) + ((...)) -> true + (s as :string) -> not (empty? (s)) + (_) -> false +} + fn list? { "Returns true if the value is a list." (l as :list) -> true @@ -153,6 +171,7 @@ fn list { (x) -> base :to_list (x) } +& TODO: make this work with Janet base fn set { "Takes an ordered collection--list or tuple--and turns it into a set." (xs as :list) -> base :into (${}, xs) @@ -180,6 +199,7 @@ fn fold { } } +& TODO: optimize these with base :conj! fn map { "Maps a function over a list: returns a new list with elements that are the result of applying the function to each element in the original list. E.g., `map ([1, 2, 3], inc) &=> [2, 3, 4]`." (f as :fn, xs) -> { @@ -227,13 +247,13 @@ fn concat { & the console: sending messages to the outside world & the console is *both* something we send to the host language's console & ...and also a list of messages. -ref console = [] +box console = [] fn flush! { "Clears the console, and returns the messages." () -> { - let msgs = deref (console) - make! (console, []) + let msgs = unbox (console) + store! (console, []) msgs } } @@ -320,28 +340,28 @@ fn join { &&& references: mutable state and state changes -fn ref? { - "Returns true if a value is a ref." - (r as :ref) -> true +fn box? { + "Returns true if a value is a box." + (b as :box) -> true (_) -> false } -fn deref { - "Resolves a ref into a value." - (r as :ref) -> base :deref (r) +fn unbox { + "Returns the value that is stored in a box." + (b as :box) -> base :unbox (b) } -fn make! { - "Sets the value of a ref." - (r as :ref, value) -> base :set! (r, value) +fn store! { + "Stores a value in a box, replacing the value that was previously there. Returns the value." + (b as :box, value) -> base :set! (b, value) } fn update! { - "Updates a ref by applying a function to its value. Returns the new value." - (r as :ref, f as :fn) -> { - let current = deref (r) + "Updates a box by applying a function to its value. Returns the new value." + (b as :box, f as :fn) -> { + let current = unbox (b) let new = f (current) - make! (r, new) + store! (b, new) } } @@ -541,6 +561,56 @@ fn max { (x, y, ...zs) -> fold (max, zs, max (x, y)) } +& additional list operations now that we have comparitors +fn at { + "Returns the element at index n of a list or tuple. Zero-indexed: the first element is at index 0." + (xs as :list, n as :number) -> when { + neg? (n) -> nil + gte? (n, count (xs)) -> nil + true -> base :nth (xs, inc (n)) + } + (xs as :tuple, n as :number) -> when { + neg? (n) -> nil + gte? (n, count (xs)) -> nil + true -> base :nth (xs, inc (n)) + } + (_) -> nil +} + +fn first { + "Returns the first element of a list or tuple." + (xs) -> at (xs, 0) +} + +fn second { + "Returns the second element of a list or tuple." + (xs) -> at (xs, 1) +} + +fn last { + "Returns the last element of a list or tuple." + (xs) -> at (xs, dec (count (xs))) +} + +fn butlast { + "Returns a list, omitting the last element." + (xs as :list) -> base :slice (xs, dec (count (xs))) +} + +fn slice { + "Returns a slice of a list, representing a sub-list." + (xs as :list, end as :number) -> slice (xs, 0, end) + (xs as :list, start as :number, end as :number) -> when { + gte? (start, end) -> [] + gt? (end, count (xs)) -> slice (xs, start, count (xs)) + neg? (start) -> slice (xs, 0, end) + true -> { + let slice = base :slice (xs, inc (start), inc (end)) + base :into ([], slice) + } + } +} + &&& keywords: funny names fn keyword? { "Returns true if a value is a keyword, otherwise returns false." @@ -570,40 +640,6 @@ fn some { (value, _) -> value } -&&& true & false: boolean logic - -fn bool? { - "Returns true if a value is of type :boolean." - (false) -> true - (true) -> true - (_) -> false -} - -fn true? { - "Returns true if a value is boolean `true`. Useful to distinguish between `true` and anything else." - (true) -> true - (_) -> false -} - -fn false? { - "Returns `true` if a value is `false`, otherwise returns `false`. Useful to distinguish between `false` and `nil`." - (false) -> true - (_) -> false -} - -fn bool { - "Returns false if a value is nil or false, otherwise returns true." - (nil) -> false - (false) -> false - (_) -> true -} - -fn not { - "Returns false if a value is truthy, true if a value is falsy." - (nil) -> true - (false) -> true - (_) -> false -} & TODO: make `and` and `or` special forms which lazily evaluate arguments fn and { @@ -689,7 +725,6 @@ fn diff { fn coll? { "Returns true if a value is a collection: dict, struct, list, tuple, or set." (coll as :dict) -> true - (coll as :struct) -> true (coll as :list) -> true (coll as :tuple) -> true (coll as :set) -> true @@ -988,29 +1023,29 @@ let turtle_init = #{ & turtle states: refs that get modified by calls & turtle_commands is a list of commands, expressed as tuples -ref turtle_commands = [] +box turtle_commands = [] & and a list of turtle states -ref turtle_states = [turtle_init] +box turtle_states = [turtle_init] fn reset_turtle! { "Resets the turtle to its original state." - () -> make! (turtle_states, [turtle_init]) + () -> store! (turtle_states, [turtle_init]) } & and a list of calls to p5--at least for now -ref p5_calls = [] +box p5_calls = [] & ...and finally, a background color & we need to store this separately because, while it can be updated later, & it must be the first call to p5. -ref bgcolor = colors :black +box bgcolor = colors :black fn add_call! (call) -> update! (p5_calls, append (_, call)) fn add_command! (command) -> { update! (turtle_commands, append (_, command)) - let prev = do turtle_states > deref > last + let prev = do turtle_states > unbox > last let curr = apply_command (prev, command) update! (turtle_states, append (_, curr)) let call = state/call () @@ -1026,7 +1061,7 @@ let turtle_angle = 0.385 let turtle_color = (255, 255, 255, 150) fn render_turtle! () -> { - let state = do turtle_states > deref > last + let state = do turtle_states > unbox > last if state :visible? then { let (r, g, b, a) = turtle_color @@ -1055,8 +1090,8 @@ fn render_turtle! () -> { } fn state/call () -> { - let cmd = do turtle_commands > deref > last > first - let states = deref (turtle_states) + let cmd = do turtle_commands > unbox > last > first + let states = unbox (turtle_states) let curr = last (states) let prev = at (states, sub (count (states), 2)) match cmd with { @@ -1078,7 +1113,7 @@ fn state/call () -> { (:stroke, r, g, b, a) } :clear -> (:background, 0, 0, 0, 255) - else -> nil + _ -> nil } } @@ -1144,9 +1179,9 @@ let pw! = penwidth! fn background! { "Sets the background color behind the turtle and path. Alias: bg!" - (gray as :number) -> make! (bgcolor, (gray, gray, gray, 255)) - ((r as :number, g as :number, b as :number)) -> make! (bgcolor, (r, b, g, 255)) - ((r as :number, g as :number, b as :number, a as :number)) -> make! (bgcolor, (r, g, b, a)) + (gray as :number) -> store! (bgcolor, (gray, gray, gray, 255)) + ((r as :number, g as :number, b as :number)) -> store! (bgcolor, (r, b, g, 255)) + ((r as :number, g as :number, b as :number, a as :number)) -> store! (bgcolor, (r, g, b, a)) } let bg! = background! @@ -1205,7 +1240,7 @@ fn apply_command { fn turtle_state { "Returns the turtle's current state." - () -> do turtle_states > deref > last + () -> do turtle_states > unbox > last } & position () -> (x, y) @@ -1234,12 +1269,14 @@ fn penwidth { () -> turtle_state () :pencolor } -ns prelude { +pkg Prelude { type eq? neq? tuple? fn? + empty? + any? first second rest @@ -1266,9 +1303,9 @@ ns prelude { report! doc! concat - ref? - deref - make! + box? + unbox + store! update! string string? diff --git a/janet/validate.janet b/janet/validate.janet index d6c8e6f..bbb94ea 100644 --- a/janet/validate.janet +++ b/janet/validate.janet @@ -173,7 +173,7 @@ Deferred until a later iteration of Ludus: :dict :list :fn - :ref + :box :pkg ]) @@ -296,7 +296,7 @@ Deferred until a later iteration of Ludus: (defn- fnn [validator] (def ast (validator :ast)) (def name (ast :name)) - # (print "function name: " name) + (print "function name: " name) (def status (validator :status)) (def tail? (status :tail)) (set (status :tail) true)