Compare commits

...

3 Commits

Author SHA1 Message Date
Scott Richmond
5c037ed46e Add a global ref tracker, make some prelude changes 2024-01-22 17:04:33 -05:00
Scott Richmond
c2943e0ba7 Draft changes. 2024-01-20 18:42:12 -05:00
Scott Richmond
90c97e7cfe Fix exports & bump version 2024-01-19 17:49:26 -05:00
7 changed files with 127 additions and 29 deletions

View File

@ -1,6 +1,33 @@
# start a repl set dotenv-load
repl:
clj -X:repl
default:
@just --list
test:
@echo $test
# build clojurescript release
build: build:
shadow-cljs release module shadow-cljs release module
# open a janet repl in a different os window
repl:
kitten @ launch --type=os-window --allow-remote-control --cwd=current --title=hx_repl:ludus
kitten @ send-text -m "title:hx_repl:ludus" "lein repl"
restart:
kitten @ send-text -m "title:hx_repl:ludus" "\04"
kitten @ send-text -m "title:hx_repl:ludus" "janet -s\n"
# send what's selected to the repl and evaluate it
eval:
sd "$" "\n" | sd "\n\n" "\n" | kitten @ send-text -m "title:hx_repl:ludus" --stdin
# send what's selected to a buffer, and then evaluate what's in the buffer
buffer:
sd "$" "\n" | sd "\n\n" "\n" > .repl-buffer.janet
kitten @ send-text -m "title:hx_repl:ludus" "(import ./.repl-buffer :prefix \"\")"

View File

@ -1,6 +1,6 @@
{ {
"name": "@ludus/ludus-js-pure", "name": "@ludus/ludus-js-pure",
"version": "0.1.0-alpha.8", "version": "0.1.1",
"description": "A Ludus interpreter in a pure JS function.", "description": "A Ludus interpreter in a pure JS function.",
"main": "target/js/ludus.js", "main": "target/js/ludus.js",
"type": "module", "type": "module",

View File

@ -10,7 +10,7 @@
:modules {:main {:entries [ludus.node]}}} :modules {:main {:entries [ludus.node]}}}
:module {:target :esm :module {:target :esm
:output-dir "target/js" :output-dir "target/js"
:modules {:ludus {:exports {run ludus.node/run test ludus.node/run-test}}} :modules {:ludus {:exports {run ludus.node/run test ludus.node/doug}}}
} }
:browser {:target :browser :browser {:target :browser
:output-dir "target/js" :output-dir "target/js"

View File

@ -664,16 +664,21 @@
) )
)) ))
(def state (atom {})) ;; for now use a global state atom
(defn- interpret-ref [ast ctx] (defn- interpret-ref [ast ctx]
(let [data (:data ast) (let [data (:data ast)
name (-> data first :data first) name (-> data first :data first)
expr (-> data second)] expr (-> data second)]
(when (contains? @ctx name) (when (contains? @ctx name)
(throw (ex-info (str "Name " name " is already bound") {:ast ast}))) (throw (ex-info (str "Name " name " is already bound") {:ast ast})))
(when (contains? @state name)
(throw (ex-info (str "A ref already has " name " name") {:ast ast})))
(let [value (interpret-ast expr ctx) (let [value (interpret-ast expr ctx)
box (atom value) box (atom value)
ref {::data/ref true ::data/value box ::data/name name}] ref {::data/ref true ::data/value box ::data/name name ::data/state state}]
(vswap! ctx update-ctx {name ref}) (vswap! ctx update-ctx {name ref})
(swap! state assoc name ref)
ref))) ref)))
(defn- interpret-loop [ast ctx] (defn- interpret-loop [ast ctx]
@ -812,7 +817,7 @@
(resolve-word times-expr ctx) (resolve-word times-expr ctx)
(-> times-expr :data first)) (-> times-expr :data first))
expr (second data)] expr (second data)]
(if (not (number? times)) (throw (ex-info (str "Repeat needs a number, not a " (base/get-type times)) {}))) (when (not (number? times)) (throw (ex-info (str "Repeat needs a number, not a " (base/get-type times)) {})))
(dotimes [_ times] (interpret-ast expr ctx)))) (dotimes [_ times] (interpret-ast expr ctx))))
(defn- interpret-literal [ast] (-> ast :data first)) (defn- interpret-literal [ast] (-> ast :data first))

View File

@ -43,7 +43,7 @@
post_tokens (:tokens post_scanned) post_tokens (:tokens post_scanned)
post_parsed (p/apply-parser g/script post_tokens) post_parsed (p/apply-parser g/script post_tokens)
post_result (i/interpret-safe source post_parsed {} false) post_result (i/interpret-safe source post_parsed {} false)
ludus_result (assoc post_result :result result_str :test test_results) ludus_result (assoc post_result :result result_str :test test_results :state @i/state)
clj_result (ld->clj ludus_result) clj_result (ld->clj ludus_result)
] ]
(cond (cond
@ -62,7 +62,7 @@
)) ))
) )
(defn test-run [source] (run source true)) (defn doug [source] (run source true))
(comment (comment

View File

@ -136,7 +136,7 @@ fn empty? {
"Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers)." "Returns true if something is empty. Otherwise returns false (including for things that can't logically be empty, like numbers)."
([]) -> true ([]) -> true
(#{}) -> true (#{}) -> true
(s as :set) -> eq (s, ${}) (s as :set) -> eq? (s, ${})
(()) -> true (()) -> true
("") -> true ("") -> true
(_) -> false (_) -> false
@ -165,6 +165,8 @@ fn set? {
(_) -> false (_) -> false
} }
& add a contains? or has? function
fn fold { fn fold {
"Folds a list." "Folds a list."
(f as :fn, xs as :list) -> fold (f, xs, f ()) (f as :fn, xs as :list) -> fold (f, xs, f ())
@ -179,7 +181,7 @@ fn fold {
} }
fn map { fn map {
"Maps over a list." "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) -> {
fn mapper (prev, curr) -> append (prev, f (curr)) fn mapper (prev, curr) -> append (prev, f (curr))
fold (mapper, xs, []) fold (mapper, xs, [])
@ -191,7 +193,7 @@ fn map {
} }
fn filter { fn filter {
"Takes a list and a predicate function, and returns a new list with only the items that produce truthy values when the function is called on them." "Takes a list and a predicate function, and returns a new list with only the items that produce truthy values when the function is called on them. E.g., `filter ([1, 2, 3, 4], odd?) &=> [1, 3]`."
(p? as :fn, xs) -> { (p? as :fn, xs) -> {
fn filterer (filtered, x) -> if p? (x) fn filterer (filtered, x) -> if p? (x)
then append (filtered, x) then append (filtered, x)
@ -413,10 +415,20 @@ fn div/safe {
} }
} }
fn inv {
"Returns the inverse of a number: 1/n or `div (1, n)`. Panics on division by zero."
(x as :number) -> div (1, x)
}
fn inv/0 {
"Returns the inverse of a number: 1/n or `div/0 (1, n)`. Returns 0 on division by zero."
(x as :number) -> div/0 (1, x)
}
fn abs { fn abs {
"Returns the absolute value of a number." "Returns the absolute value of a number."
(0) -> 0 (0) -> 0
(n) -> if neg? (n) then mult (-1, n) else n (n as :number) -> if neg? (n) then mult (-1, n) else n
} }
fn neg { fn neg {
@ -483,6 +495,14 @@ fn lte? {
} }
} }
fn between? {
"Returns true if a number is in the range [lower, higher): greater than or equal to the lower number, less than the higher."
(lower as :number, higher as :number, x as :number) -> and (
gte? (x, lower)
lt? (x, higher)
)
}
fn neg? { fn neg? {
"Returns true if a value is a negative number, otherwise returns false." "Returns true if a value is a negative number, otherwise returns false."
(x as :number) if lt? (x, 0) -> true (x as :number) if lt? (x, 0) -> true
@ -497,16 +517,30 @@ fn pos? {
fn even? { fn even? {
"Returns true if a value is an even number, otherwise returns false." "Returns true if a value is an even number, otherwise returns false."
(x as :number) if eq (0, mod (x, 2)) -> true (x as :number) if eq? (0, mod (x, 2)) -> true
(_) -> false (_) -> false
} }
fn odd? { fn odd? {
"Returns true if a value is an odd number, otherwise returns false." "Returns true if a value is an odd number, otherwise returns false."
(x as :number) if eq (1, mod (x, 2)) -> true (x as :number) if eq? (1, mod (x, 2)) -> true
(_) -> false (_) -> false
} }
fn min {
"Returns the number in its arguments that is closest to negative infinity."
(x as :number) -> x
(x as :number, y as :number) -> if lt? (x, y) then x else y
(x, y, ...zs) -> fold (min, zs, min (x, y))
}
fn max {
"Returns the number in its arguments that is closest to positive infinity."
(x as :number) -> x
(x as :number, y as :number) -> if gt? (x, y) then x else y
(x, y, ...zs) -> fold (max, zs, max (x, y))
}
&&& 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."
@ -590,6 +624,7 @@ fn or {
&&& associative collections: dicts, structs, namespaces &&& associative collections: dicts, structs, namespaces
& TODO?: get_in, update_in, merge & TODO?: get_in, update_in, merge
& TODO?: consider renaming these: put & take, not assoc/dissoc
fn assoc { fn assoc {
"Takes a dict, key, and value, and returns a new dict with the key set to value." "Takes a dict, key, and value, and returns a new dict with the key set to value."
() -> #{} () -> #{}
@ -677,6 +712,7 @@ fn assoc? {
(_) -> false (_) -> false
} }
& TODO: consider merging `get` and `at`
fn get { fn get {
"Takes a dict or struct, key, and optional default value; returns the value at key. If the value is not found, returns nil or the default value. Returns nil or default if the first argument is not a dict or struct." "Takes a dict or struct, key, and optional default value; returns the value at key. If the value is not found, returns nil or the default value. Returns nil or default if the first argument is not a dict or struct."
(key as :keyword) -> get (key, _) (key as :keyword) -> get (key, _)
@ -684,6 +720,7 @@ fn get {
(key as :keyword, coll, default) -> base :get (key, coll, default) (key as :keyword, coll, default) -> base :get (key, coll, default)
} }
& TODO: add sets to this?
fn has? { fn has? {
"Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key." "Takes a key and a dict, and returns true if there is a non-`nil` value stored at the key."
(key as :keyword) -> has? (key, _) (key as :keyword) -> has? (key, _)
@ -691,8 +728,7 @@ fn has? {
} }
fn dict { fn dict {
"Takes a struct or ns, and returns it as a dict. Or, takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed." "Takes an ns, and returns it as a dict. Or, takes a list or tuple of (key, value) tuples and returns it as a dict. Returns dicts unharmed."
(struct as :struct) -> base :to_dict (struct)
(ns_ as :ns) -> base :to_dict (ns_) (ns_ as :ns) -> base :to_dict (ns_)
(dict as :dict) -> dict (dict as :dict) -> dict
(list as :list) -> fold (assoc, list) (list as :list) -> fold (assoc, list)
@ -829,12 +865,25 @@ fn dist {
} }
&&& more number functions &&& more number functions
& TODO: add max, min
fn random { fn random {
"Returns a random number. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n." "Returns a random number. With zero arguments, returns a random number between 0 (inclusive) and 1 (exclusive). With one argument, returns a random number between 0 and n. With two arguments, returns a random number between m and n. Alternately, given a list, it returns a random member of that list."
() -> base :random () () -> base :random ()
(n as :number) -> base :random (n) (n as :number) -> base :random (n)
(m as :number, n as :number) -> add (m, random (n)) (m as :number, n as :number) -> add (m, random (n))
(l as :list) -> {
let i = do l > count > random > floor
at (l, i)
}
(d as :dict) -> {
let key = do d > keys > random
get (d, key)
}
}
fn random_int {
"Returns a random integer. With one argument, returns a random integer between 0 and that number. With two arguments, returns a random integer between them."
(n as :number) -> do n > random > floor
(m as :number, n as :number) -> floor (random (m, n))
} }
fn floor { fn floor {
@ -853,7 +902,7 @@ fn round {
} }
fn range { fn range {
"Returns the set of integers between start (inclusive) and end (exclusive) as a list. With one argument, starts at 0. If end is less than start, returns an empty list." "Returns the set of integers between start (inclusive) and end (exclusive) as a list: [start, end). With one argument, starts at 0. If end is less than start, returns an empty list."
(end as :number) -> base :range (0, end) (end as :number) -> base :range (0, end)
(start as :number, end as :number) -> base :range (start, end) (start as :number, end as :number) -> base :range (start, end)
} }
@ -906,15 +955,25 @@ fn assert! {
&&& Turtle & other graphics &&& Turtle & other graphics
& some basic colors & some basic colors
&&& TODO: add colors & these are the "basic" css colors
& https://developer.mozilla.org/en-US/docs/Web/CSS/named-color
let colors = #{ let colors = #{
:white (255, 255, 255, 255)
:light_gray (150, 150, 150, 255)
:dark_gray (50, 50, 50, 255)
:red (255, 0, 0, 255)
:green (0, 255, 0, 255)
:blue (0, 0, 255, 255)
:black (0, 0, 0, 255) :black (0, 0, 0, 255)
:silver (192, 192, 192, 255)
:gray (128, 128, 128, 255)
:white (255, 255, 255, 255)
:maroon (128, 0, 0, 255)
:red (255, 0, 0, 255)
:purple (128, 0, 128, 255)
:fuchsia (255, 0, 255, 255)
:green (0, 128, 0, 255)
:lime (0, 255, 0, 255)
:olive (128, 128, 0, 255)
:yellow (255, 255, 0, 255)
:navy (0, 0, 128, 255)
:blue (0, 0, 255, 255)
:teal (0, 128, 128, 255)
:aqua (0, 255, 25, 255)
} }
& the initial turtle state & the initial turtle state
@ -986,6 +1045,7 @@ fn render_turtle! () -> {
add_call! ((:vertex, x2, y2)) add_call! ((:vertex, x2, y2))
add_call! ((:vertex, x3, y3)) add_call! ((:vertex, x3, y3))
add_call! ((:endShape)) add_call! ((:endShape))
& there's a happy bug here: the stroke will be the same width as the pen width. Keep this for now. Consider also showing the pen colour here?
add_call! ((:stroke, 0)) add_call! ((:stroke, 0))
add_call! ((:line, 0, 0, x1, y1)) add_call! ((:line, 0, 0, x1, y1))
add_call! ((:pop)) add_call! ((:pop))
@ -1219,6 +1279,8 @@ ns prelude {
div div
div/0 div/0
div/safe div/safe
inv
inv/0
angle angle
abs abs
neg neg
@ -1231,6 +1293,9 @@ ns prelude {
gte? gte?
lt? lt?
lte? lte?
min
max
between?
keyword? keyword?
nil? nil?
some? some?
@ -1269,6 +1334,7 @@ ns prelude {
sum_of_squares sum_of_squares
dist dist
random random
random_int
pi pi
tau tau
floor floor