Add better error handling, improve prelude, postlude.
This commit is contained in:
parent
480e7abcf0
commit
8ce97081d0
33
src/ludus/error.cljc
Normal file
33
src/ludus/error.cljc
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
(ns ludus.error
|
||||||
|
(:require [clojure.string :as string]))
|
||||||
|
|
||||||
|
(defn get-line [source {:keys [line]}]
|
||||||
|
(let [lines (string/split-lines source)
|
||||||
|
the_line (nth lines (dec line))]
|
||||||
|
the_line))
|
||||||
|
|
||||||
|
(defn get-underline [source {:keys [line start lexeme]} prefix]
|
||||||
|
(let [lines (string/split-lines source)
|
||||||
|
lines-before (subvec lines 0 (dec line))
|
||||||
|
line-start (reduce (fn [len line] (+ len (count line))) (count lines-before) lines-before)
|
||||||
|
from-start (- start line-start)
|
||||||
|
underline-length (count lexeme)
|
||||||
|
padding (string/join (take (+ prefix from-start) (repeat " ")))
|
||||||
|
underline (string/join (take underline-length (repeat "^")))]
|
||||||
|
(apply str padding underline)
|
||||||
|
))
|
||||||
|
|
||||||
|
(defn scan-error [] :TODO)
|
||||||
|
|
||||||
|
(defn parse-error [source {:keys [trace token]}]
|
||||||
|
(let [line (get-line source token)
|
||||||
|
line-num (:line token)
|
||||||
|
prefix (str line-num ": ")
|
||||||
|
underline (get-underline source token (count prefix))
|
||||||
|
expected (first trace)
|
||||||
|
got (:type token)
|
||||||
|
message (str "Ludus found a parsing error on line " line-num ".\nExpected: " expected "\nGot: " got "\n")
|
||||||
|
]
|
||||||
|
(str message "\n" prefix line "\n" underline)
|
||||||
|
)
|
||||||
|
)
|
|
@ -946,7 +946,7 @@
|
||||||
(defn interpret-safe [source parsed ctx]
|
(defn interpret-safe [source parsed ctx]
|
||||||
(let [base-ctx (volatile! {::parent (volatile! (merge ludus-prelude ctx))})]
|
(let [base-ctx (volatile! {::parent (volatile! (merge ludus-prelude ctx))})]
|
||||||
(try
|
(try
|
||||||
(println "Running source: " source)
|
;(println "Running source: " source)
|
||||||
(interpret-ast parsed base-ctx)
|
(interpret-ast parsed base-ctx)
|
||||||
(catch #?(:clj Throwable :cljs js/Object) e
|
(catch #?(:clj Throwable :cljs js/Object) e
|
||||||
(println "Ludus panicked!")
|
(println "Ludus panicked!")
|
||||||
|
@ -954,17 +954,20 @@
|
||||||
(println ">>> " (get-line source (get-in (ex-data e) [:ast :token :line])))
|
(println ">>> " (get-line source (get-in (ex-data e) [:ast :token :line])))
|
||||||
(println (ex-message e))
|
(println (ex-message e))
|
||||||
;(pp/pprint (ex-data e))
|
;(pp/pprint (ex-data e))
|
||||||
(throw e)
|
;(throw e)
|
||||||
|
{::data/error true
|
||||||
|
:line (get-in (ex-data e) [:ast :token :line])
|
||||||
|
:message (ex-message e)}
|
||||||
))))
|
))))
|
||||||
|
|
||||||
;; repl
|
;; repl
|
||||||
(comment
|
(do
|
||||||
|
|
||||||
(def source "fn foo () -> :foo")
|
(def source "1 2")
|
||||||
|
|
||||||
(def tokens (-> source scanner/scan :tokens))
|
(def tokens (-> source scanner/scan :tokens))
|
||||||
|
|
||||||
(def ast (p/apply-parser g/fn-named tokens))
|
(def ast (p/apply-parser g/script tokens))
|
||||||
|
|
||||||
;(def result (interpret-safe source ast {}))
|
;(def result (interpret-safe source ast {}))
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
[ludus.show :as show]
|
[ludus.show :as show]
|
||||||
[ludus.base :as base]
|
[ludus.base :as base]
|
||||||
[ludus.data :as data]
|
[ludus.data :as data]
|
||||||
|
[ludus.error :as error]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,28 +27,38 @@
|
||||||
|
|
||||||
:fn (throw (ex-info (str "Cannot export functions from Ludus to Clojure. You tried exporting " (show/show value)) {}))))
|
:fn (throw (ex-info (str "Cannot export functions from Ludus to Clojure. You tried exporting " (show/show value)) {}))))
|
||||||
|
|
||||||
|
(defn clean-out [value]
|
||||||
|
#?(:clj value :cljs (clj->js value)))
|
||||||
|
|
||||||
(defn run [source]
|
(defn run [source]
|
||||||
(let [user_scanned (s/scan source)
|
(let [user_scanned (s/scan source)
|
||||||
user_tokens (:tokens user_scanned)
|
user_tokens (:tokens user_scanned)
|
||||||
user_parsed (p/apply-parser g/script user_tokens)
|
user_parsed (p/apply-parser g/script user_tokens)
|
||||||
user_result (i/interpret-safe source user_parsed {})
|
user_result (i/interpret-safe source user_parsed {})
|
||||||
|
result_str (show/show user_result)
|
||||||
post_scanned (s/scan pre/postlude)
|
post_scanned (s/scan pre/postlude)
|
||||||
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 {})
|
post_result (i/interpret-safe source post_parsed {})
|
||||||
ludus_result (assoc post_result :result user_result)
|
ludus_result (assoc post_result :result result_str)
|
||||||
clj_result (ld->clj ludus_result)
|
clj_result (ld->clj ludus_result)
|
||||||
]
|
]
|
||||||
#?(:clj clj_result :cljs (clj->js clj_result))
|
(cond
|
||||||
|
(not-empty (:errors user_tokens))
|
||||||
|
(clean-out {:errors (:errors user_tokens)})
|
||||||
|
|
||||||
|
(= :err (:status user_parsed))
|
||||||
|
(clean-out {:errors [(error/parse-error source user_parsed)]})
|
||||||
|
|
||||||
|
(::data/error user_result)
|
||||||
|
(clean-out {:errors [user_result]})
|
||||||
|
|
||||||
|
:else
|
||||||
|
(clean-out clj_result)
|
||||||
|
)
|
||||||
))
|
))
|
||||||
|
|
||||||
(comment
|
(do
|
||||||
(def res (run "
|
|
||||||
fd! (100)
|
(-> "foo" run :errors)
|
||||||
rt! (0.25)
|
)
|
||||||
fd! (100)
|
|
||||||
pencolor! (200)
|
|
||||||
fd! (50)
|
|
||||||
"))
|
|
||||||
(:draw res)
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
& this file runs after any given interpretation
|
& this file runs after any given interpretation
|
||||||
& the goal is to output any global state
|
& even if the original interpretation panics
|
||||||
& this does not have base loaded into it: must be pure ludus
|
& the goal is to output any global state held in Ludus
|
||||||
|
& this does not have base loaded into it, only prelude: must be pure Ludus
|
||||||
|
|
||||||
if turtle_state() :visible? then render_turtle! () else nil
|
if turtle_state() :visible? then render_turtle! () else nil
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,25 @@
|
||||||
& this file, uniquely, gets `base` loaded as context. See src/ludus/base.cljc for exports
|
& this file, uniquely, gets `base` loaded as context. See src/ludus/base.cljc for exports
|
||||||
|
|
||||||
|
& the very base: know something's type
|
||||||
|
fn type {
|
||||||
|
"Returns a keyword representing the type of the value passed in."
|
||||||
|
(x) -> base :type (x)
|
||||||
|
}
|
||||||
|
|
||||||
|
& ...and if two things are the same
|
||||||
|
fn eq? {
|
||||||
|
"Returns true if all arguments have the same value."
|
||||||
|
(x) -> true
|
||||||
|
(x, y) -> base :eq (x, y)
|
||||||
|
(x, y, ...zs) -> loop (y, zs) with {
|
||||||
|
(a, [b]) -> base :eq (a, b)
|
||||||
|
(a, [b, ...cs]) -> if base :eq (a, b)
|
||||||
|
then recur (b, cs)
|
||||||
|
else false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& what we need for some very basic list manipulation
|
||||||
fn rest {
|
fn rest {
|
||||||
"Returns all but the first element of a list or tuple, as a list."
|
"Returns all but the first element of a list or tuple, as a list."
|
||||||
(xs as :list) -> base :rest (xs)
|
(xs as :list) -> base :rest (xs)
|
||||||
|
@ -16,7 +36,7 @@ fn dec {
|
||||||
(x as :number) -> base :dec (x)
|
(x as :number) -> base :dec (x)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nth {
|
fn at {
|
||||||
"Returns the element at index n of a list or tuple. Zero-indexed: the first element is at index 0."
|
"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 {
|
(xs as :list, n as :number) -> when {
|
||||||
neg? (n) -> nil
|
neg? (n) -> nil
|
||||||
|
@ -32,17 +52,17 @@ fn nth {
|
||||||
|
|
||||||
fn first {
|
fn first {
|
||||||
"Returns the first element of a list or tuple."
|
"Returns the first element of a list or tuple."
|
||||||
(xs) -> nth (xs, 0)
|
(xs) -> at (xs, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn second {
|
fn second {
|
||||||
"Returns the second element of a list or tuple."
|
"Returns the second element of a list or tuple."
|
||||||
(xs) -> nth (xs, 1)
|
(xs) -> at (xs, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn last {
|
fn last {
|
||||||
"Returns the last element of a list or tuple."
|
"Returns the last element of a list or tuple."
|
||||||
(xs) -> nth (xs, sub (count (xs), 1))
|
(xs) -> at (xs, sub (count (xs), 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn butlast {
|
fn butlast {
|
||||||
|
@ -125,6 +145,17 @@ fn append {
|
||||||
(xs as :set, x) -> base :conj (xs, x)
|
(xs as :set, x) -> base :conj (xs, x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn concat {
|
||||||
|
"Combines two lists, strings, or sets."
|
||||||
|
(x as :string, y as :string) -> base :str (x, y)
|
||||||
|
(xs as :list, ys as :list) -> base :concat (xs, ys)
|
||||||
|
(xs as :set, ys as :set) -> base :concat (xs, ys)
|
||||||
|
(xs, ys, ...zs) -> fold (concat, zs, concat(xs, ys))
|
||||||
|
}
|
||||||
|
|
||||||
|
& 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 = []
|
ref console = []
|
||||||
|
|
||||||
fn flush! {
|
fn flush! {
|
||||||
|
@ -138,11 +169,10 @@ fn flush! {
|
||||||
|
|
||||||
fn add_msg! {
|
fn add_msg! {
|
||||||
"Adds a message to the console."
|
"Adds a message to the console."
|
||||||
(msgs) -> {
|
(msg as :string) -> update! (console, append, (_, msg))
|
||||||
let strs = map (show, msgs)
|
(msgs as :list) -> {
|
||||||
let msg = fold (concat, strs, "")
|
let msg = do msgs > map (string, _) > join
|
||||||
update! (console, append (_, msg))
|
update! (console, append (_, msg))
|
||||||
:ok
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +181,7 @@ fn print! {
|
||||||
(...args) -> {
|
(...args) -> {
|
||||||
base :print (args)
|
base :print (args)
|
||||||
add_msg! (args)
|
add_msg! (args)
|
||||||
|
:ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,24 +190,11 @@ fn show {
|
||||||
(x) -> base :show (x)
|
(x) -> base :show (x)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type {
|
|
||||||
"Returns a keyword representing the type of the value passed in."
|
|
||||||
(x) -> base :type (x)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prn! {
|
fn prn! {
|
||||||
"Prints the underlying Clojure data structure of a Ludus value."
|
"Prints the underlying Clojure data structure of a Ludus value."
|
||||||
(x) -> base :prn (x)
|
(x) -> base :prn (x)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn concat {
|
|
||||||
"Combines two lists, strings, or sets."
|
|
||||||
(x as :string, y as :string) -> base :str (x, y)
|
|
||||||
(xs as :list, ys as :list) -> base :concat (xs, ys)
|
|
||||||
(xs as :set, ys as :set) -> base :concat (xs, ys)
|
|
||||||
(xs, ys, ...zs) -> fold (concat, zs, concat(xs, ys))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn report! {
|
fn report! {
|
||||||
"Prints a value, then returns it."
|
"Prints a value, then returns it."
|
||||||
(x) -> {
|
(x) -> {
|
||||||
|
@ -189,6 +207,56 @@ fn report! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn panic! {
|
||||||
|
"Causes Ludus to panic, outputting any arguments as messages."
|
||||||
|
() -> {
|
||||||
|
add_msg! ("Ludus panicked!")
|
||||||
|
base :panic! ()
|
||||||
|
}
|
||||||
|
(...args) -> {
|
||||||
|
add_msg! ("Ludus panicked!")
|
||||||
|
add_msg! (args)
|
||||||
|
base :panic! (args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doc! {
|
||||||
|
"Prints the documentation of a function to the console."
|
||||||
|
(f as :fn) -> base :doc (f)
|
||||||
|
(_) -> :none
|
||||||
|
}
|
||||||
|
|
||||||
|
&&& strings: harder than they look!
|
||||||
|
fn string? {
|
||||||
|
"Returns true if a value is a string."
|
||||||
|
(x as :string) -> true
|
||||||
|
(_) -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn string {
|
||||||
|
"Converts a value to a string by using `show`. If it is a string, returns it unharmed. Use this to build up strings of differen kinds of values."
|
||||||
|
(x as :string) -> x
|
||||||
|
(x) -> show (x)
|
||||||
|
(x, ...xs) -> fold (string, xs, x)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn join {
|
||||||
|
"Takes a list of strings, and joins them into a single string, interposing an optional separator."
|
||||||
|
([]) -> ""
|
||||||
|
([str as :string]) -> s
|
||||||
|
(strs as :list) -> join (strs, "")
|
||||||
|
(strs, separator as :string) -> fold (
|
||||||
|
fn (joined, to_join) -> concat (joined, separator, to_join)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
& in another prelude, with a better actual base language than Java (thanks, Rich), counting strings would be reasonable but complex: count/bytes, count/points, count/glyphs. Java's UTF16 strings make this unweildy.
|
||||||
|
|
||||||
|
& TODO: add trim, trim/left, trim/right; pad/left, pad/right
|
||||||
|
& ...also a version of at,
|
||||||
|
|
||||||
|
&&& references: mutable state and state changes
|
||||||
|
|
||||||
fn ref? {
|
fn ref? {
|
||||||
"Returns true if a value is a ref."
|
"Returns true if a value is a ref."
|
||||||
(r as :ref) -> true
|
(r as :ref) -> true
|
||||||
|
@ -214,6 +282,8 @@ fn update! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&&& numbers, basically: arithmetic and not much else, yet
|
||||||
|
|
||||||
fn number? {
|
fn number? {
|
||||||
"Returns true if a value is a number."
|
"Returns true if a value is a number."
|
||||||
(x as :number) -> true
|
(x as :number) -> true
|
||||||
|
@ -261,7 +331,7 @@ fn div {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn div/0 {
|
fn div/0 {
|
||||||
"Divides number. Returns 0 on division by zero."
|
"Divides numbers. Returns 0 on division by zero."
|
||||||
(x as :number) -> x
|
(x as :number) -> x
|
||||||
(_, 0) -> 0
|
(_, 0) -> 0
|
||||||
(x as :number, y as :number) -> base :div (x, y)
|
(x as :number, y as :number) -> base :div (x, y)
|
||||||
|
@ -304,18 +374,6 @@ fn zero? {
|
||||||
(_) -> false
|
(_) -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eq? {
|
|
||||||
"Returns true if all arguments have the same value."
|
|
||||||
(x) -> true
|
|
||||||
(x, y) -> base :eq (x, y)
|
|
||||||
(x, y, ...zs) -> loop (y, zs) with {
|
|
||||||
(a, [b]) -> base :eq (a, b)
|
|
||||||
(a, [b, ...cs]) -> if base :eq (a, b)
|
|
||||||
then recur (b, cs)
|
|
||||||
else false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gt? {
|
fn gt? {
|
||||||
"Returns true if numbers are in decreasing order."
|
"Returns true if numbers are in decreasing order."
|
||||||
(x as :number) -> true
|
(x as :number) -> true
|
||||||
|
@ -376,12 +434,22 @@ fn pos? {
|
||||||
(_) -> false
|
(_) -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&&& nil: working with nothing
|
||||||
|
|
||||||
fn nil? {
|
fn nil? {
|
||||||
"Returns true if a value is nil."
|
"Returns true if a value is nil."
|
||||||
(nil) -> true
|
(nil) -> true
|
||||||
(_) -> false
|
(_) -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn some? {
|
||||||
|
"Returns true if a value is not nil."
|
||||||
|
(nil) -> false
|
||||||
|
(_) -> true
|
||||||
|
}
|
||||||
|
|
||||||
|
&&& true & false: boolean logic
|
||||||
|
|
||||||
fn bool? {
|
fn bool? {
|
||||||
"Returns true if a value is of type :boolean."
|
"Returns true if a value is of type :boolean."
|
||||||
(false) -> true
|
(false) -> true
|
||||||
|
@ -420,6 +488,8 @@ fn or {
|
||||||
(x, y, ...zs) -> fold (base :or, zs, base :or (x, y))
|
(x, y, ...zs) -> fold (base :or, zs, base :or (x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&&& associative collections: dicts, structs, namespaces
|
||||||
|
& TODO?: get_in, update_in
|
||||||
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."
|
||||||
() -> #{}
|
() -> #{}
|
||||||
|
@ -471,6 +541,7 @@ fn coll? {
|
||||||
(coll as :list) -> true
|
(coll as :list) -> true
|
||||||
(coll as :tuple) -> true
|
(coll as :tuple) -> true
|
||||||
(coll as :set) -> true
|
(coll as :set) -> true
|
||||||
|
(coll as :ns) -> true
|
||||||
(_) -> false
|
(_) -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,20 +587,12 @@ fn each! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn panic! {
|
|
||||||
"Causes Ludus to panic, outputting any arguments as messages."
|
|
||||||
() -> base :panic! ()
|
|
||||||
(...args) -> base :panic! (args)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn doc! {
|
|
||||||
"Prints the documentation of a function to the console."
|
|
||||||
(f as :fn) -> base :doc (f)
|
|
||||||
(_) -> :none
|
|
||||||
}
|
|
||||||
|
|
||||||
&&& Trigonometry functions
|
&&& Trigonometry functions
|
||||||
|
|
||||||
|
& Ludus uses turns as its default unit to measure angles
|
||||||
|
& However, anything that takes an angle can also take a
|
||||||
|
& units argument, that's a keyword of :turns, :degrees, or :radians
|
||||||
|
|
||||||
let pi = base :pi
|
let pi = base :pi
|
||||||
|
|
||||||
let tau = mult (2, pi)
|
let tau = mult (2, pi)
|
||||||
|
@ -665,7 +728,55 @@ fn range {
|
||||||
(start as :number, end as :number) -> base :range (start, end)
|
(start as :number, end as :number) -> base :range (start, end)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&&& Results, errors and other unhappy values
|
||||||
|
|
||||||
|
fn ok {
|
||||||
|
"Takes a value and wraps it in an :ok result tuple."
|
||||||
|
(value) -> (:ok, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ok? {
|
||||||
|
"Takes a value and returns true if it is an :ok result tuple."
|
||||||
|
((:ok, _)) -> true
|
||||||
|
(_) -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn err {
|
||||||
|
"Takes a value and wraps it in an :err result tuple, presumably as an error message."
|
||||||
|
(msg) -> (:err, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn err? {
|
||||||
|
"Takes a value and returns true if it is an :err result tuple."
|
||||||
|
((:err, _)) -> true
|
||||||
|
(_) -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap! {
|
||||||
|
"Takes a result tuple. If it's :ok, then returns the value. If it's not :ok, then it panics. If it's not a result tuple, it also panics."
|
||||||
|
((:ok, value)) -> value
|
||||||
|
((:err, msg)) -> panic! ("Unwrapped :err", msg)
|
||||||
|
(_) -> panic! ("Cannot unwrap something that's not an error tuple.")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unwrap_or {
|
||||||
|
"Takes a value that is a result tuple and a default value. If it's :ok, then it returns the value. If it's :err, returns the default value."
|
||||||
|
((:ok, value), _) -> value
|
||||||
|
((:err, _), default) -> default
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert! {
|
||||||
|
"Asserts a condition: returns the value if the value is truthy, panics if the value is falsy. Takes an optional message."
|
||||||
|
(value) -> if value then value else panic! ("Assert failed", value)
|
||||||
|
(value, message) -> if value
|
||||||
|
then value
|
||||||
|
else panic! ("Assert failed:", message, value)
|
||||||
|
}
|
||||||
|
|
||||||
&&& Turtle & other graphics
|
&&& Turtle & other graphics
|
||||||
|
|
||||||
|
& some basic colors
|
||||||
|
&&& TODO: add colors
|
||||||
let colors = @{
|
let colors = @{
|
||||||
:white (255, 255, 255, 255)
|
:white (255, 255, 255, 255)
|
||||||
:light_gray (150, 150, 150, 255)
|
:light_gray (150, 150, 150, 255)
|
||||||
|
@ -686,15 +797,19 @@ let turtle_init = #{
|
||||||
:visible? true
|
:visible? true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& 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
|
||||||
& the first member of each tuple is the command
|
|
||||||
ref turtle_commands = []
|
ref turtle_commands = []
|
||||||
|
|
||||||
& and a list of turtle states
|
& and a list of turtle states
|
||||||
ref turtle_states = [turtle_init]
|
ref turtle_states = [turtle_init]
|
||||||
|
|
||||||
|
& and a list of calls to p5--at least for now
|
||||||
ref p5_calls = []
|
ref 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
|
ref bgcolor = colors :black
|
||||||
|
|
||||||
fn add_call! (call) -> update! (p5_calls, append (_, call))
|
fn add_call! (call) -> update! (p5_calls, append (_, call))
|
||||||
|
@ -747,7 +862,7 @@ fn state/call () -> {
|
||||||
let cmd = do turtle_commands > deref > last > first
|
let cmd = do turtle_commands > deref > last > first
|
||||||
let states = deref (turtle_states)
|
let states = deref (turtle_states)
|
||||||
let curr = last (states)
|
let curr = last (states)
|
||||||
let prev = nth (states, sub (count (states), 2))
|
let prev = at (states, sub (count (states), 2))
|
||||||
match cmd with {
|
match cmd with {
|
||||||
:forward -> if curr :pendown?
|
:forward -> if curr :pendown?
|
||||||
then make_line (prev :position, curr :position)
|
then make_line (prev :position, curr :position)
|
||||||
|
@ -785,6 +900,8 @@ fn back! {
|
||||||
|
|
||||||
let bk! = back!
|
let bk! = back!
|
||||||
|
|
||||||
|
& turtles, like eveyrthing else in Ludus, use turns by default,
|
||||||
|
& not degrees
|
||||||
fn left! {
|
fn left! {
|
||||||
"Rotates the turtle left, measured in turns. Alias: lt!"
|
"Rotates the turtle left, measured in turns. Alias: lt!"
|
||||||
(turns as :number) -> add_command! ((:left, turns))
|
(turns as :number) -> add_command! ((:left, turns))
|
||||||
|
@ -857,7 +974,7 @@ fn goto! {
|
||||||
fn heading/vector {
|
fn heading/vector {
|
||||||
"Takes a turtle heading, and returns a unit vector of that heading."
|
"Takes a turtle heading, and returns a unit vector of that heading."
|
||||||
(heading) -> {
|
(heading) -> {
|
||||||
& 0 is 90º/0.25T, 0.25 is 180º/0.5T, 0.5 is 270º, 0.75 is 0º
|
& 0 is 90º/0.25T, 0.25 is 180º/0.5T, 0.5 is 270º/0.75T, 0.75 is 0º/0T
|
||||||
let angle = add (heading, 0.25)
|
let angle = add (heading, 0.25)
|
||||||
(cos (angle), sin (angle))
|
(cos (angle), sin (angle))
|
||||||
}
|
}
|
||||||
|
@ -925,7 +1042,7 @@ ns prelude {
|
||||||
first
|
first
|
||||||
second
|
second
|
||||||
rest
|
rest
|
||||||
nth
|
at
|
||||||
last
|
last
|
||||||
butlast
|
butlast
|
||||||
slice
|
slice
|
||||||
|
@ -966,6 +1083,7 @@ ns prelude {
|
||||||
lt?
|
lt?
|
||||||
lte?
|
lte?
|
||||||
nil?
|
nil?
|
||||||
|
some?
|
||||||
bool?
|
bool?
|
||||||
bool
|
bool
|
||||||
not
|
not
|
||||||
|
@ -1006,6 +1124,12 @@ ns prelude {
|
||||||
ceil
|
ceil
|
||||||
round
|
round
|
||||||
range
|
range
|
||||||
|
ok
|
||||||
|
ok?
|
||||||
|
err
|
||||||
|
err?
|
||||||
|
unwrap!
|
||||||
|
unwrap_or
|
||||||
colors
|
colors
|
||||||
forward!, fd!
|
forward!, fd!
|
||||||
back!, bk!
|
back!, bk!
|
||||||
|
|
Loading…
Reference in New Issue
Block a user