Compare commits
No commits in common. "main" and "post_computer_class" have entirely different histories.
main
...
post_compu
Binary file not shown.
367
build/ludus.mjs
367
build/ludus.mjs
|
@ -2,369 +2,8 @@ import init from "./out.mjs"
|
||||||
|
|
||||||
const mod = await init()
|
const mod = await init()
|
||||||
|
|
||||||
let res = null
|
|
||||||
|
|
||||||
let code = null
|
|
||||||
|
|
||||||
export function run (source) {
|
export function run (source) {
|
||||||
code = source
|
const result = mod.ludus(source).value
|
||||||
const output = mod.ludus(source).value
|
console.log(result)
|
||||||
res = JSON.parse(output)
|
return JSON.parse(result)
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stdout () {
|
|
||||||
if (!res) return ""
|
|
||||||
return res.io.stdout.data
|
|
||||||
}
|
|
||||||
|
|
||||||
export function turtle_commands () {
|
|
||||||
if (!res) return []
|
|
||||||
return res.io.turtle.data
|
|
||||||
}
|
|
||||||
|
|
||||||
export function result () {
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
const turtle_init = {
|
|
||||||
position: [0, 0],
|
|
||||||
heading: 0,
|
|
||||||
pendown: true,
|
|
||||||
pencolor: "white",
|
|
||||||
penwidth: 1,
|
|
||||||
visible: true
|
|
||||||
}
|
|
||||||
|
|
||||||
const colors = {
|
|
||||||
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],
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolve_color (color) {
|
|
||||||
if (typeof color === 'string') return colors[color]
|
|
||||||
if (typeof color === 'number') return [color, color, color, 255]
|
|
||||||
if (Array.isArray(color)) return color
|
|
||||||
return [0, 0, 0, 255] // default to black?
|
|
||||||
}
|
|
||||||
|
|
||||||
let background_color = "black"
|
|
||||||
|
|
||||||
function add (v1, v2) {
|
|
||||||
const [x1, y1] = v1
|
|
||||||
const [x2, y2] = v2
|
|
||||||
return [x1 + x2, y1 + y2]
|
|
||||||
}
|
|
||||||
|
|
||||||
function mult (vector, scalar) {
|
|
||||||
const [x, y] = vector
|
|
||||||
return [x * scalar, y * scalar]
|
|
||||||
}
|
|
||||||
|
|
||||||
function unit_of (heading) {
|
|
||||||
const turns = -heading + 0.25
|
|
||||||
const radians = turn_to_rad(turns)
|
|
||||||
return [Math.cos(radians), Math.sin(radians)]
|
|
||||||
}
|
|
||||||
|
|
||||||
function command_to_state (prev_state, curr_command) {
|
|
||||||
const verb = curr_command[0]
|
|
||||||
switch (verb) {
|
|
||||||
case "goto": {
|
|
||||||
const [_, x, y] = curr_command
|
|
||||||
return {...prev_state, position: [x, y]}
|
|
||||||
}
|
|
||||||
case "home": {
|
|
||||||
return {...prev_state, position: [0, 0], heading: 0}
|
|
||||||
}
|
|
||||||
case "right": {
|
|
||||||
const [_, angle] = curr_command
|
|
||||||
const {heading} = prev_state
|
|
||||||
return {...prev_state, heading: heading + angle}
|
|
||||||
}
|
|
||||||
case "left": {
|
|
||||||
const [_, angle] = curr_command
|
|
||||||
const {heading} = prev_state
|
|
||||||
return {...prev_state, heading: heading - angle}
|
|
||||||
}
|
|
||||||
case "forward": {
|
|
||||||
const [_, steps] = curr_command
|
|
||||||
const {heading, position} = prev_state
|
|
||||||
const unit = unit_of(heading)
|
|
||||||
const move = mult(unit, steps)
|
|
||||||
return {...prev_state, position: add(position, move)}
|
|
||||||
}
|
|
||||||
case "back": {
|
|
||||||
const [_, steps] = curr_command
|
|
||||||
const {heading, position} = prev_state
|
|
||||||
const unit = unit_of(heading)
|
|
||||||
const move = mult(unit, -steps)
|
|
||||||
return {...prev_state, position: add(position, move)}
|
|
||||||
}
|
|
||||||
case "penup": {
|
|
||||||
return {...prev_state, pendown: false}
|
|
||||||
}
|
|
||||||
case "pendown": {
|
|
||||||
return {...prev_state, pendown: true}
|
|
||||||
}
|
|
||||||
case "penwidth": {
|
|
||||||
const [_, width] = curr_command
|
|
||||||
return {...prev_state, penwidth: width}
|
|
||||||
}
|
|
||||||
case "pencolor": {
|
|
||||||
const [_, color] = curr_command
|
|
||||||
return {...prev_state, pencolor: color}
|
|
||||||
}
|
|
||||||
case "setheading": {
|
|
||||||
const [_, heading] = curr_command
|
|
||||||
return {...prev_state, heading: heading}
|
|
||||||
}
|
|
||||||
case "loadstate": {
|
|
||||||
// console.log("LOADSTATE: ", curr_command)
|
|
||||||
const [_, [x, y], heading, visible, pendown, penwidth, pencolor] = curr_command
|
|
||||||
return {position: [x, y], heading, visible, pendown, penwidth, pencolor}
|
|
||||||
}
|
|
||||||
case "show": {
|
|
||||||
return {...prev_state, visible: true}
|
|
||||||
}
|
|
||||||
case "hide": {
|
|
||||||
return {...prev_state, visible: false}
|
|
||||||
}
|
|
||||||
case "background": {
|
|
||||||
background_color = curr_command[1]
|
|
||||||
return prev_state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function eq_vect (v1, v2) {
|
|
||||||
const [x1, y1] = v1
|
|
||||||
const [x2, y2] = v2
|
|
||||||
return (x1 === x2) && (y1 === y2)
|
|
||||||
}
|
|
||||||
|
|
||||||
function eq_color (c1, c2) {
|
|
||||||
if (c1 === c2) return true
|
|
||||||
const res1 = resolve_color(c1)
|
|
||||||
const res2 = resolve_color(c2)
|
|
||||||
for (let i = 0; i < res1.length; ++i) {
|
|
||||||
if (res1[i] !== res2[i]) return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function states_to_call (prev, curr) {
|
|
||||||
const calls = []
|
|
||||||
// whose state should we use?
|
|
||||||
// pen states will only differ on more than one property
|
|
||||||
// if we use `loadstate`
|
|
||||||
// my sense is `prev`, but that may change
|
|
||||||
if (prev.pendown && !eq_vect(prev.position, curr.position)) {
|
|
||||||
calls.push(["line", prev.position[0], prev.position[1], curr.position[0], curr.position[1]])
|
|
||||||
}
|
|
||||||
if (!eq_color(curr.pencolor, prev.pencolor)) {
|
|
||||||
calls.push(["stroke", ...resolve_color(curr.pencolor)])
|
|
||||||
}
|
|
||||||
if (curr.penwidth !== prev.penwidth) {
|
|
||||||
calls.push(["strokeWeight", curr.penwidth])
|
|
||||||
}
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
const turtle_radius = 20
|
|
||||||
|
|
||||||
const turtle_angle = 0.385
|
|
||||||
|
|
||||||
const turtle_color = [255, 255, 255, 150]
|
|
||||||
|
|
||||||
const p5_call_root = [
|
|
||||||
["background", ...resolve_color(background_color)],
|
|
||||||
["push"],
|
|
||||||
["rotate", Math.PI],
|
|
||||||
["scale", -1, 1],
|
|
||||||
["stroke", ...resolve_color(turtle_init.pencolor)],
|
|
||||||
]
|
|
||||||
|
|
||||||
function rotate (vector, heading) {
|
|
||||||
const radians = turn_to_rad(heading)
|
|
||||||
const [x, y] = vector
|
|
||||||
return [
|
|
||||||
(x * Math.cos (radians)) - (y * Math.sin (radians)),
|
|
||||||
(x * Math.sin (radians)) + (y * Math.cos (radians))
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
function turn_to_rad (heading) {
|
|
||||||
return (heading % 1) * 2 * Math.PI
|
|
||||||
}
|
|
||||||
|
|
||||||
function turn_to_deg (heading) {
|
|
||||||
return (heading % 1) * 360
|
|
||||||
}
|
|
||||||
|
|
||||||
function svg_render_line (prev, curr) {
|
|
||||||
if (!prev.pendown) return ""
|
|
||||||
if (eq_vect(prev.position, curr.position)) return ""
|
|
||||||
const {position: [x1, y1], pencolor, penwidth} = prev
|
|
||||||
const {position: [x2, y2]} = curr
|
|
||||||
const [r, g, b, a] = resolve_color(pencolor)
|
|
||||||
return `
|
|
||||||
<line x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}" stroke="rgb(${r} ${g} ${b})" stroke-opacity="${a/255}" stroke-width="${penwidth}"/>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
function escape_svg (svg) {
|
|
||||||
return svg
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, """)
|
|
||||||
.replace(/'/g, "'")
|
|
||||||
}
|
|
||||||
|
|
||||||
export function extract_ludus (svg) {
|
|
||||||
const code = svg.split("<ludus>")[1]?.split("</ludus>")[0] ?? ""
|
|
||||||
return code
|
|
||||||
.replace(/&/g, "&")
|
|
||||||
.replace(/</g, "<")
|
|
||||||
.replace(/>/g, ">")
|
|
||||||
.replace(/"/g, `"`)
|
|
||||||
.replace(/'/g, `'`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function svg_render_path (states) {
|
|
||||||
const path = []
|
|
||||||
for (let i = 1; i < states.length; ++i) {
|
|
||||||
const prev = states[i - 1]
|
|
||||||
const curr = states[i]
|
|
||||||
path.push(svg_render_line(prev, curr))
|
|
||||||
}
|
|
||||||
return path.join("")
|
|
||||||
}
|
|
||||||
|
|
||||||
function svg_render_turtle (state) {
|
|
||||||
if (!state.visible) return ""
|
|
||||||
const [fr, fg, fb, fa] = turtle_color
|
|
||||||
const fill_alpha = fa/255
|
|
||||||
const {heading, pencolor, position: [x, y], pendown, penwidth} = state
|
|
||||||
const origin = [0, turtle_radius]
|
|
||||||
const [x1, y1] = origin
|
|
||||||
const [x2, y2] = rotate(origin, turtle_angle)
|
|
||||||
const [x3, y3] = rotate(origin, -turtle_angle)
|
|
||||||
const [pr, pg, pb, pa] = resolve_color(pencolor)
|
|
||||||
const pen_alpha = pa/255
|
|
||||||
const ink = pendown ? `<line x1="${x1}" y1="${y1}" x2="0" y2="0" stroke="rgb(${pr} ${pg} ${pb})" stroke-opacity="${pen_alpha}" stroke-width="${penwidth}" />` : ""
|
|
||||||
return `
|
|
||||||
<g transform="translate(${x}, ${y})rotate(${-turn_to_deg(heading)})">
|
|
||||||
<polygon points="${x1} ${y1} ${x2} ${y2} ${x3} ${y3}" stroke="none" fill="rgb(${fr} ${fg} ${fb})" fill-opacity="${fill_alpha}"/>
|
|
||||||
${ink}
|
|
||||||
</g>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function svg (commands) {
|
|
||||||
// console.log(commands)
|
|
||||||
const states = [turtle_init]
|
|
||||||
commands.reduce((prev_state, command) => {
|
|
||||||
const new_state = command_to_state(prev_state, command)
|
|
||||||
states.push(new_state)
|
|
||||||
return new_state
|
|
||||||
}, turtle_init)
|
|
||||||
// console.log(states)
|
|
||||||
const {maxX, maxY, minX, minY} = states.reduce((accum, {position: [x, y]}) => {
|
|
||||||
accum.maxX = Math.max(accum.maxX, x)
|
|
||||||
accum.maxY = Math.max(accum.maxY, y)
|
|
||||||
accum.minX = Math.min(accum.minX, x)
|
|
||||||
accum.minY = Math.min(accum.minY, y)
|
|
||||||
return accum
|
|
||||||
|
|
||||||
}, {maxX: 0, maxY: 0, minX: 0, minY: 0})
|
|
||||||
const [r, g, b, a] = resolve_color(background_color)
|
|
||||||
const view_width = (maxX - minX) * 1.2
|
|
||||||
const view_height = (maxY - minY) * 1.2
|
|
||||||
const margin = Math.max(view_width, view_height) * 0.1
|
|
||||||
const x1 = minX - margin
|
|
||||||
// don't actually need these:
|
|
||||||
// const y1 = minY - margin
|
|
||||||
// const x2 = maxX + margin
|
|
||||||
const y2 = maxY + margin
|
|
||||||
const path = svg_render_path(states)
|
|
||||||
const turtle = svg_render_turtle(states[states.length - 1])
|
|
||||||
return `<?xml version="1.0" standalone="no"?>
|
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="background-color:rgb(${r} ${g} ${b}); background-opacity: ${a/255}" viewBox="${x1} ${-y2} ${view_width} ${view_height}">
|
|
||||||
|
|
||||||
<g transform="scale(-1, 1) rotate(180)">
|
|
||||||
${path}
|
|
||||||
${turtle}
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<ludus>
|
|
||||||
${escape_svg(code)}
|
|
||||||
</ludus>
|
|
||||||
</svg>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
function p5_render_turtle (state, calls) {
|
|
||||||
if (!state.visible) return
|
|
||||||
calls.push(["push"])
|
|
||||||
const [r, g, b, a] = turtle_color
|
|
||||||
calls.push(["fill", r, g, b, a])
|
|
||||||
const {heading, pencolor, position: [x, y], pendown, penwidth} = state
|
|
||||||
const origin = [0, turtle_radius]
|
|
||||||
const [x1, y1] = origin
|
|
||||||
const [x2, y2] = rotate(origin, turtle_angle)
|
|
||||||
const [x3, y3] = rotate(origin, -turtle_angle)
|
|
||||||
calls.push(["translate", x, y])
|
|
||||||
// need negative turtle rotation with the other p5 translations
|
|
||||||
calls.push(["rotate", -turn_to_rad(heading)])
|
|
||||||
calls.push(["noStroke"])
|
|
||||||
calls.push(["beginShape"])
|
|
||||||
calls.push(["vertex", x1, y1])
|
|
||||||
calls.push(["vertex", x2, y2])
|
|
||||||
calls.push(["vertex", x3, y3])
|
|
||||||
calls.push(["endShape"])
|
|
||||||
calls.push(["strokeWeight", penwidth])
|
|
||||||
calls.push(["stroke", ...resolve_color(pencolor)])
|
|
||||||
if (pendown) calls.push(["line", 0, 0, x1, y1])
|
|
||||||
calls.push(["pop"])
|
|
||||||
return calls
|
|
||||||
}
|
|
||||||
|
|
||||||
export function p5 (commands) {
|
|
||||||
const states = [turtle_init]
|
|
||||||
commands.reduce((prev_state, command) => {
|
|
||||||
const new_state = command_to_state(prev_state, command)
|
|
||||||
states.push(new_state)
|
|
||||||
return new_state
|
|
||||||
}, turtle_init)
|
|
||||||
// console.log(states)
|
|
||||||
const p5_calls = [...p5_call_root]
|
|
||||||
for (let i = 1; i < states.length; ++i) {
|
|
||||||
const prev = states[i - 1]
|
|
||||||
const curr = states[i]
|
|
||||||
const calls = states_to_call(prev, curr)
|
|
||||||
for (const call of calls) {
|
|
||||||
p5_calls.push(call)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p5_calls[0] = ["background", ...resolve_color(background_color)]
|
|
||||||
p5_render_turtle(states[states.length - 1], p5_calls)
|
|
||||||
p5_calls.push(["pop"])
|
|
||||||
return p5_calls
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -6489,7 +6489,7 @@ var __emscripten_stack_alloc = (a0) => (__emscripten_stack_alloc = wasmExports['
|
||||||
var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmExports['emscripten_stack_get_current'])();
|
var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmExports['emscripten_stack_get_current'])();
|
||||||
var ___cxa_is_pointer_type = createExportWrapper('__cxa_is_pointer_type', 1);
|
var ___cxa_is_pointer_type = createExportWrapper('__cxa_is_pointer_type', 1);
|
||||||
var dynCall_jiji = Module['dynCall_jiji'] = createExportWrapper('dynCall_jiji', 5);
|
var dynCall_jiji = Module['dynCall_jiji'] = createExportWrapper('dynCall_jiji', 5);
|
||||||
var ___emscripten_embedded_file_data = Module['___emscripten_embedded_file_data'] = 1819972;
|
var ___emscripten_embedded_file_data = Module['___emscripten_embedded_file_data'] = 1840592;
|
||||||
function invoke_i(index) {
|
function invoke_i(index) {
|
||||||
var sp = stackSave();
|
var sp = stackSave();
|
||||||
try {
|
try {
|
||||||
|
|
BIN
build/out.wasm
BIN
build/out.wasm
Binary file not shown.
|
@ -1,13 +0,0 @@
|
||||||
import {run, p5} from "./ludus.mjs"
|
|
||||||
|
|
||||||
const code = `
|
|
||||||
print! ("Hello, world!")
|
|
||||||
pencolor! (colors :white)
|
|
||||||
fd! (50)
|
|
||||||
pw! (3)
|
|
||||||
`
|
|
||||||
|
|
||||||
const result = run(code)
|
|
||||||
|
|
||||||
console.log(result.io.stdout.data)
|
|
||||||
console.log(p5(result.io.turtle.data))
|
|
|
@ -1,22 +0,0 @@
|
||||||
import {run, svg, stdout} from "./ludus.mjs"
|
|
||||||
|
|
||||||
const code = `
|
|
||||||
let start = unbox (turtle_state)
|
|
||||||
fd! (100)
|
|
||||||
rt! (0.25)
|
|
||||||
fd! (100)
|
|
||||||
|
|
||||||
loadstate! (start)
|
|
||||||
& home! ()
|
|
||||||
|
|
||||||
rt! (0.25)
|
|
||||||
fd! (100)
|
|
||||||
lt! (0.25)
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
const result = run(code)
|
|
||||||
|
|
||||||
// console.log(stdout(result))
|
|
||||||
|
|
||||||
console.log(svg(result.io.turtle.data))
|
|
|
@ -1,37 +0,0 @@
|
||||||
<?xml version="1.0" standalone="no"?>
|
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" style="background-color:rgb(0 0 0); background-opacity: 1" viewBox="-12 -112 120 120">
|
|
||||||
|
|
||||||
<g transform="scale(-1, 1) rotate(180)">
|
|
||||||
|
|
||||||
<line x1="0" y1="0" x2="6.123233995736766e-15" y2="100" stroke="rgb(255 255 255)" stroke-opacity="1" stroke-width="1"/>
|
|
||||||
|
|
||||||
<line x1="6.123233995736766e-15" y1="100" x2="0" y2="0" stroke="rgb(255 255 255)" stroke-opacity="1" stroke-width="1"/>
|
|
||||||
|
|
||||||
<line x1="0" y1="0" x2="100" y2="0" stroke="rgb(255 255 255)" stroke-opacity="1" stroke-width="1"/>
|
|
||||||
|
|
||||||
|
|
||||||
<g transform="translate(100, 0)rotate(90)">
|
|
||||||
<polygon points="0 20 -13.226237306473037 -15.00222139260919 13.226237306473037 -15.00222139260919" stroke="none" fill="rgb(255 255 255)" fill-opacity="0.5882352941176471"/>
|
|
||||||
<line x1="0" y1="20" x2="0" y2="0" stroke="rgb(255 255 255)" stroke-opacity="1" stroke-width="1" />
|
|
||||||
</g>
|
|
||||||
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<ludus>
|
|
||||||
|
|
||||||
|
|
||||||
let home = unbox (turtle_state)
|
|
||||||
|
|
||||||
fd! (100)
|
|
||||||
|
|
||||||
loadstate! (home)
|
|
||||||
|
|
||||||
rt! (0.25)
|
|
||||||
fd! (100)
|
|
||||||
|
|
||||||
do turtle_state > unbox
|
|
||||||
|
|
||||||
|
|
||||||
</ludus>
|
|
||||||
</svg>
|
|
||||||
|
|
Before Width: | Height: | Size: 1.1 KiB |
|
@ -1,9 +1,3 @@
|
||||||
import {run} from "./ludus.mjs"
|
import {run} from "./ludus.mjs"
|
||||||
|
|
||||||
console.log(run(`
|
console.log(run(`let foo = 42; "{foo} bar"`))
|
||||||
|
|
||||||
forward! (100)
|
|
||||||
right! (0.25)
|
|
||||||
print! ("foobar")
|
|
||||||
|
|
||||||
`))
|
|
||||||
|
|
6
justfile
6
justfile
|
@ -23,9 +23,9 @@ publish:
|
||||||
|
|
||||||
# build the ludus jimage
|
# build the ludus jimage
|
||||||
build:
|
build:
|
||||||
rm -f build/out.mjs
|
rm build/out.mjs
|
||||||
rm -f build/out.wasm
|
rm build/out.wasm
|
||||||
rm -f build/ludus.jimage
|
rm build/ludus.jimage
|
||||||
janet -c src/ludus.janet build/ludus.jimage
|
janet -c src/ludus.janet build/ludus.jimage
|
||||||
cd build && just build
|
cd build && just build
|
||||||
git commit -am "build"
|
git commit -am "build"
|
||||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@ludus/ludus-js-pure",
|
"name": "@ludus/ludus-js-pure",
|
||||||
"version": "0.1.36",
|
"version": "0.1.26",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@ludus/ludus-js-pure",
|
"name": "@ludus/ludus-js-pure",
|
||||||
"version": "0.1.36",
|
"version": "0.1.26",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"shadow-cljs": "^2.26.0",
|
"shadow-cljs": "^2.26.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@ludus/ludus-js-pure",
|
"name": "@ludus/ludus-js-pure",
|
||||||
"version": "0.1.36",
|
"version": "0.1.26",
|
||||||
"description": "A Ludus interpreter in a pure JS function.",
|
"description": "A Ludus interpreter in a pure JS function.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "build/ludus.mjs",
|
"main": "build/ludus.mjs",
|
||||||
|
|
22
postlude.ld
22
postlude.ld
|
@ -3,5 +3,23 @@
|
||||||
& the goal is to output any global state held in 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
|
& this does not have base loaded into it, only prelude: must be pure Ludus
|
||||||
|
|
||||||
store! (turtle_state, turtle_init)
|
if turtle_state () :visible? then render_turtle! () else nil
|
||||||
store! (turtle_commands, [])
|
|
||||||
|
reset_turtle! ()
|
||||||
|
|
||||||
|
& let console_msgs = flush! ()
|
||||||
|
|
||||||
|
let (r, g, b, a) = unbox (bgcolor)
|
||||||
|
store! (bgcolor, colors :black)
|
||||||
|
|
||||||
|
let draw_calls = unbox (p5_calls)
|
||||||
|
store! (p5_calls, [])
|
||||||
|
|
||||||
|
#{
|
||||||
|
& :result result is provided elsewhere
|
||||||
|
& :errors [] & if we get here there are no errors
|
||||||
|
& :console console_msgs
|
||||||
|
:draw concat (
|
||||||
|
[(:background, r, g, b, a), (:stroke, 255, 255, 255, 255)]
|
||||||
|
draw_calls)
|
||||||
|
}
|
||||||
|
|
175
prelude.ld
175
prelude.ld
|
@ -17,6 +17,7 @@ fn mod
|
||||||
fn neg?
|
fn neg?
|
||||||
fn print!
|
fn print!
|
||||||
fn some?
|
fn some?
|
||||||
|
fn state/call
|
||||||
fn store!
|
fn store!
|
||||||
fn string
|
fn string
|
||||||
fn turn/rad
|
fn turn/rad
|
||||||
|
@ -279,10 +280,9 @@ fn concat {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set {
|
fn set {
|
||||||
"Takes an ordered collection--list or tuple--and turns it into a set. Returns sets unharmed."
|
"Takes an ordered collection--list or tuple--and turns it into a set."
|
||||||
(xs as :list) -> fold (append, xs, ${})
|
(xs as :list) -> fold (append, xs, ${})
|
||||||
(xs as :tuple) -> do xs > list > set
|
(xs as :tuple) -> do xs > list > set
|
||||||
(xs as :set) -> xs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set? {
|
fn set? {
|
||||||
|
@ -435,11 +435,6 @@ fn sentence {
|
||||||
(strs as :list) -> join (strs, " ")
|
(strs as :list) -> join (strs, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_number {
|
|
||||||
"Takes a string that presumably contains a representation of a number, and tries to give you back the number represented. Returns a result tuple."
|
|
||||||
(num as :string) -> base :to_number (num)
|
|
||||||
}
|
|
||||||
|
|
||||||
&&& boxes: mutable state and state changes
|
&&& boxes: mutable state and state changes
|
||||||
|
|
||||||
fn box? {
|
fn box? {
|
||||||
|
@ -973,18 +968,9 @@ fn dist {
|
||||||
((x, y)) -> dist (x, y)
|
((x, y)) -> dist (x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn heading/vector {
|
|
||||||
"Takes a turtle heading, and returns a unit vector of that heading."
|
|
||||||
(heading) -> {
|
|
||||||
& 0 is 90º/0.25T, 0.25 is 180º/0.5T, 0.5 is 270º/0.75T, 0.75 is 0º/0T
|
|
||||||
let a = add (neg (heading), 0.25)
|
|
||||||
(cos (a), sin (a))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&&& more number functions
|
&&& more number functions
|
||||||
fn random {
|
fn random {
|
||||||
"Returns a random something. 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 collection (tuple, list, dict, set), it returns a random member of that collection."
|
"Returns a random something. 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 collection (list, dict, set), it returns a random member of that collection."
|
||||||
() -> base :random ()
|
() -> base :random ()
|
||||||
(n as :number) -> mult (n, random ())
|
(n as :number) -> mult (n, random ())
|
||||||
(m as :number, n as :number) -> add (m, random (sub (n, m)))
|
(m as :number, n as :number) -> add (m, random (sub (n, m)))
|
||||||
|
@ -992,10 +978,6 @@ fn random {
|
||||||
let i = do l > count > random > floor
|
let i = do l > count > random > floor
|
||||||
at (l, i)
|
at (l, i)
|
||||||
}
|
}
|
||||||
(t as :tuple) -> {
|
|
||||||
let i = do t > count > random > floor
|
|
||||||
at (t, i)
|
|
||||||
}
|
|
||||||
(d as :dict) -> {
|
(d as :dict) -> {
|
||||||
let key = do d > keys > random
|
let key = do d > keys > random
|
||||||
get (key, d)
|
get (key, d)
|
||||||
|
@ -1106,7 +1088,7 @@ let turtle_init = #{
|
||||||
:position (0, 0) & let's call this the origin for now
|
:position (0, 0) & let's call this the origin for now
|
||||||
:heading 0 & this is straight up
|
:heading 0 & this is straight up
|
||||||
:pendown? true
|
:pendown? true
|
||||||
:pencolor :white
|
:pencolor colors :white
|
||||||
:penwidth 1
|
:penwidth 1
|
||||||
:visible? true
|
:visible? true
|
||||||
}
|
}
|
||||||
|
@ -1114,15 +1096,102 @@ 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
|
||||||
box turtle_commands = []
|
box turtle_commands = []
|
||||||
box turtle_state = turtle_init
|
|
||||||
|
& and a list of turtle states
|
||||||
|
box turtle_states = [turtle_init]
|
||||||
|
|
||||||
|
fn reset_turtle! {
|
||||||
|
"Resets the turtle to its original state."
|
||||||
|
() -> store! (turtle_states, [turtle_init])
|
||||||
|
}
|
||||||
|
|
||||||
|
& and a list of calls to p5--at least for now
|
||||||
|
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.
|
||||||
|
box bgcolor = colors :black
|
||||||
|
|
||||||
|
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 = unbox (turtle_state)
|
let prev = do turtle_states > unbox > last
|
||||||
let curr = apply_command (prev, command)
|
let curr = apply_command (prev, command)
|
||||||
store! (turtle_state, curr)
|
update! (turtle_states, append! (_, curr))
|
||||||
|
let call = state/call ()
|
||||||
|
if call then { add_call! (call); :ok } else :ok
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_line ((x1, y1), (x2, y2)) -> (:line, x1, y1, x2, y2)
|
||||||
|
|
||||||
|
let turtle_radius = 20
|
||||||
|
|
||||||
|
let turtle_angle = 0.385
|
||||||
|
|
||||||
|
let turtle_color = (255, 255, 255, 150)
|
||||||
|
|
||||||
|
fn render_turtle! () -> {
|
||||||
|
let state = do turtle_states > unbox > last
|
||||||
|
if state :visible?
|
||||||
|
then {
|
||||||
|
let (r, g, b, a) = turtle_color
|
||||||
|
add_call! ((:fill, r, g, b, a))
|
||||||
|
let #{heading
|
||||||
|
:pencolor (pen_r, pen_g, pen_b, pen_a)
|
||||||
|
:position (x, y)
|
||||||
|
pendown?
|
||||||
|
...} = state
|
||||||
|
let origin = mult ((0, 1), turtle_radius)
|
||||||
|
let (x1, y1) = origin
|
||||||
|
let (x2, y2) = rotate (origin, turtle_angle)
|
||||||
|
let (x3, y3) = rotate (origin, neg (turtle_angle))
|
||||||
|
add_call! ((:push))
|
||||||
|
add_call! ((:translate, x, y))
|
||||||
|
add_call! ((:rotate, turn/rad (heading)))
|
||||||
|
add_call! ((:noStroke))
|
||||||
|
add_call! ((:beginShape))
|
||||||
|
add_call! ((:vertex, x1, y1))
|
||||||
|
add_call! ((:vertex, x2, y2))
|
||||||
|
add_call! ((:vertex, x3, y3))
|
||||||
|
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, pen_r, pen_g, pen_b, pen_a))
|
||||||
|
if pendown? then add_call! ((:line, 0, 0, x1, y1)) else nil
|
||||||
|
add_call! ((:pop))
|
||||||
:ok
|
:ok
|
||||||
}
|
}
|
||||||
|
else :ok
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state/call () -> {
|
||||||
|
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 {
|
||||||
|
:forward -> if curr :pendown?
|
||||||
|
then make_line (prev :position, curr :position)
|
||||||
|
else nil
|
||||||
|
:back -> if curr :pendown?
|
||||||
|
then make_line (prev :position, curr :position)
|
||||||
|
else nil
|
||||||
|
:home -> if curr :pendown?
|
||||||
|
then make_line (prev :position, curr :position)
|
||||||
|
else nil
|
||||||
|
:goto -> if curr :pendown?
|
||||||
|
then make_line (prev :position, curr :position)
|
||||||
|
else nil
|
||||||
|
:penwidth -> (:strokeWeight, curr :penwidth)
|
||||||
|
:pencolor -> {
|
||||||
|
let (r, g, b, a) = curr :pencolor
|
||||||
|
(:stroke, r, g, b, a)
|
||||||
|
}
|
||||||
|
:clear -> (:background, 0, 0, 0, 255)
|
||||||
|
_ -> nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn forward! {
|
fn forward! {
|
||||||
"Moves the turtle forward by a number of steps. Alias: fd!"
|
"Moves the turtle forward by a number of steps. Alias: fd!"
|
||||||
|
@ -1168,7 +1237,6 @@ let pd! = pendown!
|
||||||
|
|
||||||
fn pencolor! {
|
fn pencolor! {
|
||||||
"Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc!"
|
"Changes the turtle's pen color. Takes a single grayscale value, an rgb tuple, or an rgba tuple. Alias: pc!"
|
||||||
(color as :keyword) -> add_command! ((:pencolor, color))
|
|
||||||
(gray as :number) -> add_command! ((:pencolor, (gray, gray, gray, 255)))
|
(gray as :number) -> add_command! ((:pencolor, (gray, gray, gray, 255)))
|
||||||
((r as :number, g as :number, b as :number)) -> add_command! ((:pencolor, (r, g, b, 255)))
|
((r as :number, g as :number, b as :number)) -> add_command! ((:pencolor, (r, g, b, 255)))
|
||||||
((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:pencolor, (r, g, b, a)))
|
((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:pencolor, (r, g, b, a)))
|
||||||
|
@ -1185,10 +1253,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!"
|
||||||
(color as :keyword) -> add_command! ((:background, color))
|
(gray as :number) -> store! (bgcolor, (gray, gray, gray, 255))
|
||||||
(gray as :number) -> add_command! ((:background, (gray, gray, gray, 255)))
|
((r as :number, g as :number, b as :number)) -> store! (bgcolor, (r, g, b, 255))
|
||||||
((r as :number, g as :number, b as :number)) -> add_command! ((:background, (r, g, b, 255)))
|
((r as :number, g as :number, b as :number, a as :number)) -> store! (bgcolor, (r, g, b, a))
|
||||||
((r as :number, g as :number, b as :number, a as :number)) -> add_command! ((:background, (r, g, b, a)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let bg! = background!
|
let bg! = background!
|
||||||
|
@ -1224,11 +1291,12 @@ fn hideturtle! {
|
||||||
() -> add_command! ((:hide))
|
() -> add_command! ((:hide))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadstate! {
|
fn heading/vector {
|
||||||
"Sets the turtle state to a previously saved state."
|
"Takes a turtle heading, and returns a unit vector of that heading."
|
||||||
(state) -> {
|
(heading) -> {
|
||||||
let #{position, heading, pendown?, pencolor, penwidth, visible?} = state
|
& 0 is 90º/0.25T, 0.25 is 180º/0.5T, 0.5 is 270º/0.75T, 0.75 is 0º/0T
|
||||||
add_command! ((:loadstate, position, heading, visible?, pendown?, penwidth, pencolor))
|
let a = add (heading, 0.25)
|
||||||
|
(cos (a), sin (a))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1261,37 +1329,49 @@ fn apply_command {
|
||||||
(:penwidth, pixels) -> assoc (state, :penwidth, pixels)
|
(:penwidth, pixels) -> assoc (state, :penwidth, pixels)
|
||||||
(:pencolor, color) -> assoc (state, :pencolor, color)
|
(:pencolor, color) -> assoc (state, :pencolor, color)
|
||||||
(:setheading, heading) -> assoc (state, :heading, heading)
|
(:setheading, heading) -> assoc (state, :heading, heading)
|
||||||
(:loadstate, position, heading, visible?, pendown?, penwidth, pencolor) -> #{position, heading, visible?, pendown?, penwidth, pencolor}
|
|
||||||
(:show) -> assoc (state, :visible?, true)
|
(:show) -> assoc (state, :visible?, true)
|
||||||
(:hide) -> assoc (state, :visible?, false)
|
(:hide) -> assoc (state, :visible?, false)
|
||||||
(:background, _) -> state
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn turtle_state {
|
||||||
|
"Returns the turtle's current state."
|
||||||
|
() -> do turtle_states > unbox > last
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_turtle_state! {
|
||||||
|
"Sets the turtle state to a previously saved state. Returns the state."
|
||||||
|
(state) -> {
|
||||||
|
update! (turtle_states, append! (_, state))
|
||||||
|
let call = state/call ()
|
||||||
|
if call then { add_call! (call); :ok } else :ok
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& position () -> (x, y)
|
& position () -> (x, y)
|
||||||
fn position {
|
fn position {
|
||||||
"Returns the turtle's current position."
|
"Returns the turtle's current position."
|
||||||
() -> do turtle_state > unbox > :position
|
() -> turtle_state () :position
|
||||||
}
|
}
|
||||||
|
|
||||||
fn heading {
|
fn heading {
|
||||||
"Returns the turtle's current heading."
|
"Returns the turtle's current heading."
|
||||||
() -> do turtle_state > unbox > :heading
|
() -> turtle_state () :heading
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pendown? {
|
fn pendown? {
|
||||||
"Returns the turtle's pen state: true if the pen is down."
|
"Returns the turtle's pen state: true if the pen is down."
|
||||||
() -> do turtle_state > unbox > :pendown?
|
() -> turtle_state () :pendown?
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pencolor {
|
fn pencolor {
|
||||||
"Returns the turtle's pen color as an (r, g, b, a) tuple or keyword."
|
"Returns the turtle's pen color as an (r, g, b, a) tuple."
|
||||||
() -> do turtle_state > unbox > :pencolor
|
() -> turtle_state () :pencolor
|
||||||
}
|
}
|
||||||
|
|
||||||
fn penwidth {
|
fn penwidth {
|
||||||
"Returns the turtle's pen width in pixels."
|
"Returns the turtle's pen width in pixels."
|
||||||
() -> do turtle_state > unbox > :penwidth
|
() -> turtle_state () :penwidth
|
||||||
}
|
}
|
||||||
|
|
||||||
box state = nil
|
box state = nil
|
||||||
|
@ -1312,6 +1392,7 @@ pkg Prelude {
|
||||||
background! & turtles
|
background! & turtles
|
||||||
between? & math
|
between? & math
|
||||||
bg! & turtles
|
bg! & turtles
|
||||||
|
bgcolor & turtles
|
||||||
bk! & turtles
|
bk! & turtles
|
||||||
bool & bool
|
bool & bool
|
||||||
bool? & bool
|
bool? & bool
|
||||||
|
@ -1373,7 +1454,7 @@ pkg Prelude {
|
||||||
left! & turtles
|
left! & turtles
|
||||||
list & lists
|
list & lists
|
||||||
list? & lists
|
list? & lists
|
||||||
loadstate! & turtles
|
load_turtle_state! & turtles
|
||||||
lt! & turtles
|
lt! & turtles
|
||||||
lt? & math
|
lt? & math
|
||||||
lte? & math
|
lte? & math
|
||||||
|
@ -1395,6 +1476,7 @@ pkg Prelude {
|
||||||
omit & set
|
omit & set
|
||||||
or & bool
|
or & bool
|
||||||
ordered? & lists tuples strings
|
ordered? & lists tuples strings
|
||||||
|
p5_calls & turtles
|
||||||
pc! & turtles
|
pc! & turtles
|
||||||
pd! & turtles
|
pd! & turtles
|
||||||
pencolor & turtles
|
pencolor & turtles
|
||||||
|
@ -1415,7 +1497,9 @@ pkg Prelude {
|
||||||
random & math dicts lists tuples sets
|
random & math dicts lists tuples sets
|
||||||
random_int & math
|
random_int & math
|
||||||
range & math lists
|
range & math lists
|
||||||
|
render_turtle! & turtles
|
||||||
report! & environment
|
report! & environment
|
||||||
|
reset_turtle! & turtles
|
||||||
rest & lists tuples
|
rest & lists tuples
|
||||||
right! & turtles
|
right! & turtles
|
||||||
round & math
|
round & math
|
||||||
|
@ -1444,14 +1528,13 @@ pkg Prelude {
|
||||||
sum_of_squares & math
|
sum_of_squares & math
|
||||||
tan & math
|
tan & math
|
||||||
tau & math
|
tau & math
|
||||||
to_number & strings numbers
|
|
||||||
trim & strings
|
trim & strings
|
||||||
tuple? & tuples
|
tuple? & tuples
|
||||||
turn/deg & math
|
turn/deg & math
|
||||||
turn/rad & math
|
turn/rad & math
|
||||||
turtle_commands & turtles
|
turtle_commands & turtles
|
||||||
turtle_init & turtles
|
|
||||||
turtle_state & turtles
|
turtle_state & turtles
|
||||||
|
turtle_states & turtles
|
||||||
type & values
|
type & values
|
||||||
unbox & boxes
|
unbox & boxes
|
||||||
unwrap! & results
|
unwrap! & results
|
||||||
|
|
207
prelude.md
207
prelude.md
File diff suppressed because one or more lines are too long
|
@ -1,8 +1,6 @@
|
||||||
# A base library for Ludus
|
# A base library for Ludus
|
||||||
# Only loaded in the prelude
|
# Only loaded in the prelude
|
||||||
|
|
||||||
(import /src/scanner :as s)
|
|
||||||
|
|
||||||
(defn bool [x] (if (= :^nil x) nil x))
|
(defn bool [x] (if (= :^nil x) nil x))
|
||||||
|
|
||||||
(defn ludus/and [& args] (every? (map bool args)))
|
(defn ludus/and [& args] (every? (map bool args)))
|
||||||
|
@ -250,20 +248,6 @@
|
||||||
([e] (break [:err e])))
|
([e] (break [:err e])))
|
||||||
[:ok out])
|
[:ok out])
|
||||||
|
|
||||||
(defn to_number [str]
|
|
||||||
(when (string/find "&" str)
|
|
||||||
(break [:err (string "Could not parse `" str "` as a number")]))
|
|
||||||
(def scanned (s/scan (string/trim str)))
|
|
||||||
(when (< 0 (length (scanned :errors)))
|
|
||||||
(break [:err (string "Could not parse `" str "` as a number")]))
|
|
||||||
(def tokens (scanned :tokens))
|
|
||||||
(when (< 3 (length tokens))
|
|
||||||
(break [:err (string "Could not parse `" str "` as a number")]))
|
|
||||||
(def fst (first tokens))
|
|
||||||
(when (not= :number (fst :type))
|
|
||||||
(break [:err (string "Could not parse `" str "` as a number")]))
|
|
||||||
[:ok (fst :literal)])
|
|
||||||
|
|
||||||
(def ctx {
|
(def ctx {
|
||||||
"add" +
|
"add" +
|
||||||
"and" ludus/and
|
"and" ludus/and
|
||||||
|
@ -320,7 +304,6 @@
|
||||||
"sub" -
|
"sub" -
|
||||||
"tan" math/tan
|
"tan" math/tan
|
||||||
"to_list" to_list
|
"to_list" to_list
|
||||||
"to_number" to_number
|
|
||||||
"trim" string/trim
|
"trim" string/trim
|
||||||
"triml" string/triml
|
"triml" string/triml
|
||||||
"trimr" string/trimr
|
"trimr" string/trimr
|
||||||
|
@ -334,4 +317,4 @@
|
||||||
(set (b (keyword k)) v))
|
(set (b (keyword k)) v))
|
||||||
b))
|
b))
|
||||||
|
|
||||||
(to_number " 123 a ")
|
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
|
|
||||||
(defn escape-punctuation [str] (->> str
|
(defn escape-punctuation [str] (->> str
|
||||||
(string/replace "?" "")
|
(string/replace "?" "")
|
||||||
(string/replace "!" "")
|
(string/replace "!" "")))
|
||||||
(string/replace "/" "")))
|
|
||||||
|
|
||||||
(defn toc-entry [name]
|
(defn toc-entry [name]
|
||||||
(def escaped (escape-underscores name))
|
(def escaped (escape-underscores name))
|
||||||
|
@ -23,18 +22,18 @@
|
||||||
(string/join (map toc-entry sorted-names) " "))
|
(string/join (map toc-entry sorted-names) " "))
|
||||||
|
|
||||||
(def topics {
|
(def topics {
|
||||||
"math" ["abs" "add" "angle" "atan/2" "between?" "ceil" "cos" "dec" "deg/rad" "deg/turn" "dist" "div" "div/0" "div/safe" "even?" "floor" "gt?" "gte?" "heading/vector" "inc" "inv" "inv/0" "inv/safe" "lt?" "lte?" "max" "min" "mod" "mod/0" "mod/safe" "mult" "neg" "neg?" "odd?" "pi" "pos?" "rad/deg" "rad/turn" "random" "random_int" "range" "round" "sin" "sqrt" "sqrt/safe" "square" "sub" "sum_of_squares" "tan" "tau" "to_number" "turn/deg" "turn/rad" "zero?"]
|
"math" ["abs" "add" "angle" "atan/2" "between?" "ceil" "cos" "dec" "deg/rad" "deg/turn" "dist" "div" "div/0" "div/safe" "even?" "floor" "gt?" "gte?" "heading/vector" "inc" "inv" "inv/0" "inv/safe" "lt?" "lte?" "max" "min" "mod" "mod/0" "mod/safe" "mult" "neg" "neg?" "odd?" "pi" "pos?" "rad/deg" "rad/turn" "random" "random_int" "range" "round" "sin" "sqrt" "sqrt/safe" "square" "sub" "sum_of_squares" "tan" "tau" "turn/deg" "turn/rad" "zero?"]
|
||||||
"boolean" ["and" "bool" "bool?" "false?" "not" "or" "true?"]
|
"boolean" ["and" "bool" "bool?" "false?" "not" "or" "true?"]
|
||||||
"dicts" ["any?" "assoc" "assoc?" "coll?" "count" "dict" "dict?" "diff" "dissoc" "empty?" "get" "keys" "random" "update" "values"]
|
"dicts" ["any?" "assoc" "assoc?" "coll?" "count" "dict" "dict?" "diff" "dissoc" "empty?" "get" "keys" "random" "update" "values"]
|
||||||
"lists" ["any?" "append" "at" "butlast" "coll?" "concat" "count" "each!" "empty?" "filter" "first" "fold" "join" "keep" "last" "list" "list?" "map" "ordered?" "random" "range" "rest" "second" "sentence" "slice"]
|
"lists" ["any?" "append" "at" "butlast" "coll?" "concat" "count" "each!" "empty?" "filter" "first" "fold" "join" "keep" "last" "list" "list?" "map" "ordered?" "random" "range" "rest" "second" "sentence" "slice"]
|
||||||
"sets" ["any?" "append" "coll?" "concat" "contains?" "count" "empty?" "omit" "random" "set" "set?"]
|
"sets" ["any?" "append" "coll?" "concat" "contains?" "count" "empty?" "omit" "random" "set" "set?"]
|
||||||
"tuples" ["any?" "at" "coll?" "count" "empty?" "first" "last" "ordered?" "rest" "second" "tuple?"]
|
"tuples" ["any?" "at" "coll?" "count" "empty?" "first" "last" "ordered?" "rest" "second" "tuple?"]
|
||||||
"strings" ["any?" "chars" "chars/safe" "concat" "count" "downcase" "empty?" "join" "sentence" "show" "slice" "split" "string" "string?" "strip" "to_number" "trim" "upcase" "words"]
|
"strings" ["any?" "chars" "chars/safe" "concat" "count" "downcase" "empty?" "join" "sentence" "show" "slice" "split" "string" "string?" "strip" "trim" "upcase" "words"]
|
||||||
"types and values" ["assoc?" "bool?" "box?" "coll?" "dict?" "eq?" "fn?" "keyword?" "list?" "neq?" "nil?" "number?" "ordered?" "set?" "show" "some" "some?" "string?" "tuple?" "type"]
|
"types and values" ["assoc?" "bool?" "box?" "coll?" "dict?" "eq?" "fn?" "keyword?" "list?" "neq?" "nil?" "number?" "ordered?" "set?" "show" "some" "some?" "string?" "tuple?" "type"]
|
||||||
"boxes and state" ["box?" "unbox" "store!" "update!"]
|
"boxes and state" ["box?" "unbox" "store!" "update!"]
|
||||||
"results" ["err" "err?" "ok" "ok?" "unwrap!" "unwrap_or"]
|
"results" ["err" "err?" "ok" "ok?" "unwrap!" "unwrap_or"]
|
||||||
"errors" ["assert!"]
|
"errors" ["assert!"]
|
||||||
"turtle graphics" ["back!" "background!" "bk!" "clear!" "colors" "fd!" "forward!" "goto!" "heading" "heading/vector" "hideturtle!" "home!" "left!" "loadstate!" "lt!" "pc!" "pd!" "pencolor" "pencolor!" "pendown!" "pendown?" "penup!" "penwidth" "penwidth!" "position" "pu!" "pw!" "render_turtle!" "reset_turtle!" "right!" "rt!" "setheading!" "showturtle!" "turtle_state"]
|
"turtle graphics" ["back!" "background!" "bk!" "clear!" "colors" "fd!" "forward!" "goto!" "heading" "heading/vector" "home!" "left!" "lt!" "pc!" "pd!" "pencolor" "pencolor!" "pendown!" "pendown?" "penup!" "penwidth" "penwidth!" "position" "pu!" "pw!" "render_turtle!" "reset_turtle!" "right!" "rt!" "turtle_state"]
|
||||||
"environment and i/o" ["doc!" "print!" "report!" "state"]
|
"environment and i/o" ["doc!" "print!" "report!" "state"]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -12,83 +12,52 @@
|
||||||
(import /src/json :as j)
|
(import /src/json :as j)
|
||||||
|
|
||||||
(defn ludus [source]
|
(defn ludus [source]
|
||||||
# if we can't load prelude, bail
|
|
||||||
(when (= :error prelude/pkg) (error "could not load prelude"))
|
(when (= :error prelude/pkg) (error "could not load prelude"))
|
||||||
|
|
||||||
# get us a clean working slate
|
|
||||||
(def ctx @{:^parent prelude/ctx})
|
(def ctx @{:^parent prelude/ctx})
|
||||||
(def errors @[])
|
(def errors @[])
|
||||||
|
(def draw @[])
|
||||||
(var result @"")
|
(var result @"")
|
||||||
(def console @"")
|
(def console @"")
|
||||||
|
|
||||||
# capture all `print`s
|
|
||||||
(setdyn :out console)
|
(setdyn :out console)
|
||||||
|
(def out @{:errors errors :draw draw :result result :console console})
|
||||||
# an output table
|
|
||||||
# this will change: the shape of our output
|
|
||||||
# at the moment, there's only one stack of turtle graphics
|
|
||||||
# we will be getting more
|
|
||||||
(def out @{:errors errors :result result
|
|
||||||
:io @{
|
|
||||||
:stdout @{:proto [:text-stream "0.1.0"] :data console}
|
|
||||||
:turtle @{:proto [:turtle-graphics "0.1.0"] :data @[]}}})
|
|
||||||
|
|
||||||
### start the program
|
|
||||||
# first, scanning
|
|
||||||
(def scanned (s/scan source))
|
(def scanned (s/scan source))
|
||||||
(when (any? (scanned :errors))
|
(when (any? (scanned :errors))
|
||||||
(each err (scanned :errors)
|
(each err (scanned :errors)
|
||||||
(e/scan-error err))
|
(e/scan-error err))
|
||||||
(break (-> out j/encode string)))
|
(break (-> out j/encode string)))
|
||||||
# then, parsing
|
|
||||||
(def parsed (p/parse scanned))
|
(def parsed (p/parse scanned))
|
||||||
(when (any? (parsed :errors))
|
(when (any? (parsed :errors))
|
||||||
(each err (parsed :errors)
|
(each err (parsed :errors)
|
||||||
(e/parse-error err))
|
(e/parse-error err))
|
||||||
(break (-> out j/encode string)))
|
(break (-> out j/encode string)))
|
||||||
# then, validation
|
|
||||||
(def validated (v/valid parsed ctx))
|
(def validated (v/valid parsed ctx))
|
||||||
(when (any? (validated :errors))
|
(when (any? (validated :errors))
|
||||||
(each err (validated :errors)
|
(each err (validated :errors)
|
||||||
(e/validation-error err))
|
(e/validation-error err))
|
||||||
(break (-> out j/encode string)))
|
(break (-> out j/encode string)))
|
||||||
# and, finally, try interpreting the program
|
(try
|
||||||
(try (do
|
(set result (i/interpret (parsed :ast) ctx))
|
||||||
# we need to do this every run or we get the very same sequence of "random" numbers every time we run a program
|
|
||||||
(math/seedrandom (os/cryptorand 8))
|
|
||||||
(set result (i/interpret (parsed :ast) ctx)))
|
|
||||||
([err]
|
([err]
|
||||||
(e/runtime-error err)
|
(e/runtime-error err)
|
||||||
(break (-> out j/encode string))))
|
(break (-> out j/encode string))))
|
||||||
|
|
||||||
# stop capturing output
|
|
||||||
(setdyn :out stdout)
|
(setdyn :out stdout)
|
||||||
|
|
||||||
# update our output table with our output
|
|
||||||
(set (out :result) (b/show result))
|
(set (out :result) (b/show result))
|
||||||
(set (((out :io) :turtle) :data) (get-in prelude/pkg [:turtle_commands :^value]))
|
(var post @{})
|
||||||
|
|
||||||
# run the "postlude": any Ludus code that needs to run after each program
|
|
||||||
# right now this is just resetting the boxes that hold turtle commands and state
|
|
||||||
(try
|
(try
|
||||||
(i/interpret prelude/post/ast ctx)
|
(set post (i/interpret prelude/post/ast ctx))
|
||||||
([err] (e/runtime-error err)))
|
([err] (e/runtime-error err)))
|
||||||
|
(set (out :draw) (post :draw))
|
||||||
|
# out
|
||||||
|
(-> out j/encode string)
|
||||||
|
)
|
||||||
|
|
||||||
# json-encode our output table, and convert it from a buffer to a string (which we require for playing nice with WASM/C)
|
|
||||||
(-> out j/encode string))
|
|
||||||
|
|
||||||
#### REPL
|
|
||||||
(comment
|
(comment
|
||||||
# (do
|
# (do
|
||||||
# (def start (os/clock))
|
# (def start (os/clock))
|
||||||
(def source `
|
(def source `
|
||||||
fd! (100)
|
box foo = :bar
|
||||||
rt! (0.25)
|
store! (foo, :baz)
|
||||||
fd! (100)
|
unbox (foo)
|
||||||
lt! (0.25)
|
|
||||||
fd! (100)
|
|
||||||
setheading! (0.75)
|
|
||||||
unbox (turtle_state)
|
|
||||||
`)
|
`)
|
||||||
(def out (-> source
|
(def out (-> source
|
||||||
ludus
|
ludus
|
||||||
|
|
|
@ -39,4 +39,3 @@
|
||||||
(def validation-errors (post-validated :errors))
|
(def validation-errors (post-validated :errors))
|
||||||
(when (any? validation-errors) (each err validation-errors (e/validation-error err)) (break :error))
|
(when (any? validation-errors) (each err validation-errors (e/validation-error err)) (break :error))
|
||||||
(post-parsed :ast)))
|
(post-parsed :ast)))
|
||||||
|
|
||||||
|
|
|
@ -349,7 +349,8 @@
|
||||||
(recur (-> scanner (scan-token) (next-token)))))
|
(recur (-> scanner (scan-token) (next-token)))))
|
||||||
(recur (new-scanner source input)))
|
(recur (new-scanner source input)))
|
||||||
|
|
||||||
# (comment
|
(comment
|
||||||
(do
|
# (do
|
||||||
(def source " -123 ")
|
(def source "add 1 2 () four")
|
||||||
(length ((scan source) :tokens)))
|
(scan source)
|
||||||
|
)
|
||||||
|
|
|
@ -765,7 +765,7 @@ Deferred until a later iteration of Ludus:
|
||||||
(defn- cleanup [validator]
|
(defn- cleanup [validator]
|
||||||
(def declared (get-in validator [:status :declared] {}))
|
(def declared (get-in validator [:status :declared] {}))
|
||||||
(when (any? declared)
|
(when (any? declared)
|
||||||
(each declaration (keys declared)
|
(each declaration declared
|
||||||
(array/push (validator :errors) {:node declaration :msg "declared fn, but not defined"})))
|
(array/push (validator :errors) {:node declaration :msg "declared fn, but not defined"})))
|
||||||
validator)
|
validator)
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ Turtle graphics describe the movements and drawing behaviours of screen, robot,
|
||||||
- Shows the turtle.
|
- Shows the turtle.
|
||||||
* `hide`, no arguments
|
* `hide`, no arguments
|
||||||
- Hides the turtle.
|
- Hides the turtle.
|
||||||
* `loadstate`, x: number, y: number, heading: number, visible: boolean, pendown: boolean, width: number, color: string OR r: number, g: number, b: number, a: number
|
* `loadstate`, x: number, y: number, heading: number, pendown: boolean, width: number, color: string OR r: number, g: number, b: number, a: number
|
||||||
- Loads a turtle state.
|
- Loads a turtle state.
|
||||||
* `clear`, no arguments
|
* `clear`, no arguments
|
||||||
- Erases any paths drawn and sets the background color to the default.
|
- Erases any paths drawn and sets the background color to the default.
|
||||||
|
@ -71,22 +71,11 @@ Colors should also be specifiable with strings corresponding to CSS basic colors
|
||||||
Ludus should have access to turtle states.
|
Ludus should have access to turtle states.
|
||||||
This is important for push/pop situations that we use for L-systems.
|
This is important for push/pop situations that we use for L-systems.
|
||||||
There are two ways to do this: Ludus does its own bookkeeping for turtle states, or it has a way to get the state from a turtle.
|
There are two ways to do this: Ludus does its own bookkeeping for turtle states, or it has a way to get the state from a turtle.
|
||||||
|
|
||||||
The latter has the value of being instantaneous, and gives us an _expected_ state of the turtle after the commands are all processed.
|
The latter has the value of being instantaneous, and gives us an _expected_ state of the turtle after the commands are all processed.
|
||||||
In particular, this will be necessary for the recursive L-systems that require pushing and popping turtle state.
|
In particular, this will be necessary for the recursive L-systems that require pushing and popping turtle state.
|
||||||
The latter has the drawback of potentially allowing the turtle state and expected turtle state to fall out of synch.
|
The latter has the drawback of potentially allowing the turtle state and expected turtle state to fall out of synch.
|
||||||
|
|
||||||
The former has the value of always giving us the correct, actual state of the turtle.
|
The former has the value of always giving us the correct, actual state of the turtle.
|
||||||
It has the drawback of requiring such state reporting to be asynchronous, and perhaps wildly asynchronous, as things like moving robots and plotters will take quite some time to actually draw what Ludus tells it to.
|
It has the drawback of requiring such state reporting to be asynchronous, and perhaps wildly asynchronous, as things like moving robots and plotters will take quite some time to actually draw what Ludus tells it to.
|
||||||
(Being able to wait until `eq? (expected, actual)` to do anything else may well be extremely useful.)
|
(Being able to wait until `eq? (expected, actual)` to do anything else may well be extremely useful.)
|
||||||
|
|
||||||
That suggests, then, that both forms of turtle state are desirable and necessary.
|
That suggests, then, that both forms of turtle state are desirable and necessary.
|
||||||
Thus: turtles should communicate states (and thus there ought to be a protocol for communicating state back to Ludus) and Ludus should always do the bookkeeping of calculating the expected state.
|
Thus: turtles should communicate states (and thus there ought to be a protocol for communicating state back to Ludus) and Ludus should always do the bookkeeping of calculating the expected state.
|
||||||
|
|
||||||
**Turtles use Cartesian, rather than screen, coordinates.**
|
|
||||||
The starting position of the turtle is `(0, 0)`, which is the origin, and _centred_ in the field of view.
|
|
||||||
Increasing the x-coordinate moves the turtle to the right; increasing the y-coordinate moves the turtle _up_.
|
|
||||||
|
|
||||||
**Turtles use compass headings, not mathematical angles.**
|
|
||||||
Turtles start pointing vertially, at heading `0`.
|
|
||||||
Turning right _increases_ the heading; pointing due "east" is `0.25`; south `0.5`, and west, `0.75`.
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user