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)
: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)))

View File

@ -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))

View File

@ -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)

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
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)

View File

@ -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))
)

View File

@ -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?

View File

@ -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)