prelude now passes validator

This commit is contained in:
Scott Richmond 2024-06-05 15:52:03 -04:00
parent 5874a56090
commit 20cb689d12
7 changed files with 202 additions and 140 deletions

View File

@ -44,7 +44,7 @@
:dict (dict-str value) :dict (dict-str value)
:set :set
(string/join (map stringify (keys value)) ", ") (string/join (map stringify (keys value)) ", ")
:ref (stringify (value :^value)) :box (stringify (value :^value))
:fn (string "fn " (value :name)) :fn (string "fn " (value :name))
:applied (string "fn " (value :name)) :applied (string "fn " (value :name))
:function (string "builtin " (string value)) :function (string "builtin " (string value))
@ -68,7 +68,7 @@
:list (string "[" (stringify x) "]") :list (string "[" (stringify x) "]")
:dict (string "#{" (stringify x) "}") :dict (string "#{" (stringify x) "}")
:set (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) :pkg (show-pkg x)
(stringify x))) (stringify x)))

View File

@ -1,10 +1,34 @@
(import spork/json :as j) (import spork/json :as j)
(import /base :as b) (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 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)) (defn runtime-error [e out] (set (out :errors) e) (j/encode out))

View File

@ -340,7 +340,7 @@
(defn- ref [ast ctx] (defn- ref [ast ctx]
(def {:data value-ast :name name} ast) (def {:data value-ast :name name} ast)
(def value (interpret value-ast ctx)) (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) (set (ctx name) box)
box) box)

View File

@ -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 * `errors`: an array of errors, which are just strings
This new scene will have to return a JSON POJSO: This new scene will have to return a JSON POJSO:
{:console [...] :result "..." :draw [...] :errors [...]} {:console "..." :result "..." :draw [...] :errors [...]}
) )
(def console @"") (def prelude-src (slurp "prelude.ld"))
(setdyn :out console) (def prelude-scanned (s/scan prelude-src))
(print "foo") (def prelude-parsed (p/parse prelude-scanned))
(pp {:a 1 :b 2}) (def parse-errors (prelude-parsed :errors))
(setdyn :out stdout) (when (any? parse-errors) (each err parse-errors (e/parse-error err)))
(print "collected out") (def prelude-validated (v/valid prelude-parsed @{"base" b/base}))
(print console) (each err (prelude-validated :errors) (e/validation-error err))
(defn run [source] (defn run [source]
(def errors @[]) (def errors @[])
@ -44,7 +44,7 @@ This new scene will have to return a JSON POJSO:
(break (-> :errors validated (e/validation-error out)))) (break (-> :errors validated (e/validation-error out))))
(setdyn :out console) (setdyn :out console)
(try (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))) ([err] (e/runtime-error err out)))
(set (out :result) result) (set (out :result) result)
(j/encode out)) (j/encode out))
@ -56,8 +56,8 @@ This new scene will have to return a JSON POJSO:
(run source)) (run source))
(def source ` (def source `
fn foo () -> :foo let foo = fn () -> :bar
foo () foo ()
`) `)
(-> source run j/decode) (-> source run j/decode)

View File

@ -1,7 +1,7 @@
### A recursive descent parser for Ludus ### A recursive descent parser for Ludus
### We still need to scan some things ### 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) (import ./scanner :as s)
(defmacro declare (defmacro declare
@ -773,7 +773,7 @@
(defn- lambda [parser] (defn- lambda [parser]
(def origin (current parser)) (def origin (current parser))
(expect parser :fn) (advance 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] (defn- fnn [parser]
(if (= :lparen (-> parser peek type)) (break (lambda parser))) (if (= :lparen (-> parser peek type)) (break (lambda parser)))
@ -1104,6 +1104,7 @@
(def origin (current parser)) (def origin (current parser))
(def lines @[]) (def lines @[])
(while (not (check parser :eof)) (while (not (check parser :eof))
(accept-many parser :newline)
(array/push lines (capture toplevel parser)) (array/push lines (capture toplevel parser))
(capture terminator parser)) (capture terminator parser))
{:type :script :data lines :token origin}) {:type :script :data lines :token origin})
@ -1116,10 +1117,10 @@
# (do # (do
(comment (comment
(def source `pkg Foo {} (def source `fn () -> 42
`) `)
(def scanned (s/scan source)) (def scanned (s/scan source))
# (print "\n***NEW PARSE***\n") # (print "\n***NEW PARSE***\n")
(def a-parser (new-parser scanned)) (def a-parser (new-parser scanned))
(def parsed (pkg a-parser)) (def parsed (fnn a-parser))
) )

View File

@ -7,6 +7,29 @@
& tuple? & tuple?
& ref? & 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 & the very base: know something's type
fn type { fn type {
"Returns a keyword representing the type of the value passed in." "Returns a keyword representing the type of the value passed in."
@ -28,6 +51,40 @@ fn eq? {
else false 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? { fn neq? {
"Returns true if none of the arguments have the same value." "Returns true if none of the arguments have the same value."
(x) -> false (x) -> false
@ -73,55 +130,6 @@ fn dec {
(x as :number) -> base :dec (x) (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 { fn count {
"Returns the number of elements in a collection (including string)." "Returns the number of elements in a collection (including string)."
(xs as :list) -> dec (base :count (xs)) (xs as :list) -> dec (base :count (xs))
@ -142,6 +150,16 @@ fn empty? {
(_) -> false (_) -> 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? { fn list? {
"Returns true if the value is a list." "Returns true if the value is a list."
(l as :list) -> true (l as :list) -> true
@ -153,6 +171,7 @@ fn list {
(x) -> base :to_list (x) (x) -> base :to_list (x)
} }
& TODO: make this work with Janet base
fn set { fn set {
"Takes an ordered collection--list or tuple--and turns it into a set." "Takes an ordered collection--list or tuple--and turns it into a set."
(xs as :list) -> base :into (${}, xs) (xs as :list) -> base :into (${}, xs)
@ -180,6 +199,7 @@ fn fold {
} }
} }
& TODO: optimize these with base :conj!
fn map { 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]`." "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) -> { (f as :fn, xs) -> {
@ -227,13 +247,13 @@ fn concat {
& the console: sending messages to the outside world & the console: sending messages to the outside world
& the console is *both* something we send to the host language's console & the console is *both* something we send to the host language's console
& ...and also a list of messages. & ...and also a list of messages.
ref console = [] box console = []
fn flush! { fn flush! {
"Clears the console, and returns the messages." "Clears the console, and returns the messages."
() -> { () -> {
let msgs = deref (console) let msgs = unbox (console)
make! (console, []) store! (console, [])
msgs msgs
} }
} }
@ -320,28 +340,28 @@ fn join {
&&& references: mutable state and state changes &&& references: mutable state and state changes
fn ref? { fn box? {
"Returns true if a value is a ref." "Returns true if a value is a box."
(r as :ref) -> true (b as :box) -> true
(_) -> false (_) -> false
} }
fn deref { fn unbox {
"Resolves a ref into a value." "Returns the value that is stored in a box."
(r as :ref) -> base :deref (r) (b as :box) -> base :unbox (b)
} }
fn make! { fn store! {
"Sets the value of a ref." "Stores a value in a box, replacing the value that was previously there. Returns the value."
(r as :ref, value) -> base :set! (r, value) (b as :box, value) -> base :set! (b, value)
} }
fn update! { fn update! {
"Updates a ref by applying a function to its value. Returns the new value." "Updates a box by applying a function to its value. Returns the new value."
(r as :ref, f as :fn) -> { (b as :box, f as :fn) -> {
let current = deref (r) let current = unbox (b)
let new = f (current) 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)) (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 &&& keywords: funny names
fn keyword? { fn keyword? {
"Returns true if a value is a keyword, otherwise returns false." "Returns true if a value is a keyword, otherwise returns false."
@ -570,40 +640,6 @@ fn some {
(value, _) -> value (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 & TODO: make `and` and `or` special forms which lazily evaluate arguments
fn and { fn and {
@ -689,7 +725,6 @@ fn diff {
fn coll? { fn coll? {
"Returns true if a value is a collection: dict, struct, list, tuple, or set." "Returns true if a value is a collection: dict, struct, list, tuple, or set."
(coll as :dict) -> true (coll as :dict) -> true
(coll as :struct) -> true
(coll as :list) -> true (coll as :list) -> true
(coll as :tuple) -> true (coll as :tuple) -> true
(coll as :set) -> true (coll as :set) -> true
@ -988,29 +1023,29 @@ let turtle_init = #{
& turtle states: refs that get modified by calls & turtle states: refs that get modified by calls
& turtle_commands is a list of commands, expressed as tuples & turtle_commands is a list of commands, expressed as tuples
ref turtle_commands = [] box turtle_commands = []
& and a list of turtle states & and a list of turtle states
ref turtle_states = [turtle_init] box turtle_states = [turtle_init]
fn reset_turtle! { fn reset_turtle! {
"Resets the turtle to its original state." "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 & and a list of calls to p5--at least for now
ref p5_calls = [] box p5_calls = []
& ...and finally, a background color & ...and finally, a background color
& we need to store this separately because, while it can be updated later, & we need to store this separately because, while it can be updated later,
& it must be the first call to p5. & 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_call! (call) -> update! (p5_calls, append (_, call))
fn add_command! (command) -> { fn add_command! (command) -> {
update! (turtle_commands, append (_, 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) let curr = apply_command (prev, command)
update! (turtle_states, append (_, curr)) update! (turtle_states, append (_, curr))
let call = state/call () let call = state/call ()
@ -1026,7 +1061,7 @@ let turtle_angle = 0.385
let turtle_color = (255, 255, 255, 150) let turtle_color = (255, 255, 255, 150)
fn render_turtle! () -> { fn render_turtle! () -> {
let state = do turtle_states > deref > last let state = do turtle_states > unbox > last
if state :visible? if state :visible?
then { then {
let (r, g, b, a) = turtle_color let (r, g, b, a) = turtle_color
@ -1055,8 +1090,8 @@ fn render_turtle! () -> {
} }
fn state/call () -> { fn state/call () -> {
let cmd = do turtle_commands > deref > last > first let cmd = do turtle_commands > unbox > last > first
let states = deref (turtle_states) let states = unbox (turtle_states)
let curr = last (states) let curr = last (states)
let prev = at (states, sub (count (states), 2)) let prev = at (states, sub (count (states), 2))
match cmd with { match cmd with {
@ -1078,7 +1113,7 @@ fn state/call () -> {
(:stroke, r, g, b, a) (:stroke, r, g, b, a)
} }
:clear -> (:background, 0, 0, 0, 255) :clear -> (:background, 0, 0, 0, 255)
else -> nil _ -> nil
} }
} }
@ -1144,9 +1179,9 @@ let pw! = penwidth!
fn background! { fn background! {
"Sets the background color behind the turtle and path. Alias: bg!" "Sets the background color behind the turtle and path. Alias: bg!"
(gray as :number) -> make! (bgcolor, (gray, gray, gray, 255)) (gray as :number) -> store! (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)) -> store! (bgcolor, (r, b, g, 255))
((r as :number, g as :number, b as :number, a as :number)) -> make! (bgcolor, (r, g, b, a)) ((r as :number, g as :number, b as :number, a as :number)) -> store! (bgcolor, (r, g, b, a))
} }
let bg! = background! let bg! = background!
@ -1205,7 +1240,7 @@ fn apply_command {
fn turtle_state { fn turtle_state {
"Returns the turtle's current state." "Returns the turtle's current state."
() -> do turtle_states > deref > last () -> do turtle_states > unbox > last
} }
& position () -> (x, y) & position () -> (x, y)
@ -1234,12 +1269,14 @@ fn penwidth {
() -> turtle_state () :pencolor () -> turtle_state () :pencolor
} }
ns prelude { pkg Prelude {
type type
eq? eq?
neq? neq?
tuple? tuple?
fn? fn?
empty?
any?
first first
second second
rest rest
@ -1266,9 +1303,9 @@ ns prelude {
report! report!
doc! doc!
concat concat
ref? box?
deref unbox
make! store!
update! update!
string string
string? string?

View File

@ -173,7 +173,7 @@ Deferred until a later iteration of Ludus:
:dict :dict
:list :list
:fn :fn
:ref :box
:pkg :pkg
]) ])
@ -296,7 +296,7 @@ Deferred until a later iteration of Ludus:
(defn- fnn [validator] (defn- fnn [validator]
(def ast (validator :ast)) (def ast (validator :ast))
(def name (ast :name)) (def name (ast :name))
# (print "function name: " name) (print "function name: " name)
(def status (validator :status)) (def status (validator :status))
(def tail? (status :tail)) (def tail? (status :tail))
(set (status :tail) true) (set (status :tail) true)