Fixes in lambda/function definitions (body args). Cleanup of examples and decumentation. Added convenience methods for JS interop, lists and objects.

This commit is contained in:
Kuba Antosik 2021-04-04 18:35:26 +10:00
parent 8d23e5264e
commit 0cebbe52cb
24 changed files with 655 additions and 120 deletions

View File

@ -59,11 +59,16 @@ To save an image in memory, open an image file with Ronin, or drag an image file
Library Library
Files
- (open name ~scale) Imports a graphic file with format. - (open name ~scale) Imports a graphic file with format.
- (import name ~shape) Imports a graphic file with format. - (import name ~shape) Imports a graphic file with format.
- (export ~format ~quality) Exports a graphic file with format. - (export ~format ~quality) Exports a graphic file with format.
- (files) Returns the list of loaded files. - (files) Returns the list of loaded files.
- (print string) Exports string to file. - (print string) Exports string to file.
Shapes
- (pos ~x ~y) Returns a position shape. - (pos ~x ~y) Returns a position shape.
- (line ax ay bx by) Returns a line shape. - (line ax ay bx by) Returns a line shape.
- (size w h) Returns a size shape. - (size w h) Returns a size shape.
@ -74,6 +79,9 @@ Library
- (poly ...pos) Returns a poly shape. - (poly ...pos) Returns a poly shape.
- (text x y p t ~a ~f) Returns a text shape. - (text x y p t ~a ~f) Returns a text shape.
- (svg x y d) Returns a svg shape. - (svg x y d) Returns a svg shape.
Canvas operations
- (color r g b ~a) Returns a color object. - (color r g b ~a) Returns a color object.
- (hsl h s l ~a) returns a HSL color object - (hsl h s l ~a) returns a HSL color object
- (resize ~w) Resizes the canvas to target w and h, returns the rect. - (resize ~w) Resizes the canvas to target w and h, returns the rect.
@ -92,6 +100,9 @@ Library
- (clear ~rect) Clears a rect. - (clear ~rect) Clears a rect.
- (gradient line ~colors 'black']) Defines a gradient color. - (gradient line ~colors 'black']) Defines a gradient color.
- (guide shape color) Draws a shape on the guide layer. - (guide shape color) Draws a shape on the guide layer.
Pixel operations
- (pixels fn ~q ~rect) - (pixels fn ~q ~rect)
- (saturation pixel q) Change the saturation of pixels. - (saturation pixel q) Change the saturation of pixels.
- (contrast pixel q) Change the contrast of pixels. - (contrast pixel q) Change the contrast of pixels.
@ -100,8 +111,14 @@ Library
- (multiply pixel q) Change the color balance of pixels. - (multiply pixel q) Change the color balance of pixels.
- (normalize pixel q) Normalize the color of pixels with another color. - (normalize pixel q) Normalize the color of pixels with another color.
- (lum color) Return the luminance of a color. - (lum color) Return the luminance of a color.
Strings
- (concat ...items) Concat multiple strings. - (concat ...items) Concat multiple strings.
- (split string char) Split string at character. - (split string char) Split string at character.
Math
- (add ...args) Adds values. - (add ...args) Adds values.
- (sub ...args) Subtracts values. - (sub ...args) Subtracts values.
- (mul ...args) Multiplies values. - (mul ...args) Multiplies values.
@ -125,38 +142,71 @@ Library
- (PI) - (PI)
- (TWO_PI) - (TWO_PI)
- (random ...args) - (random ...args)
Logic
- (gt a b) Returns true if a is greater than b, else false. - (gt a b) Returns true if a is greater than b, else false.
- (lt a b) Returns true if a is less than b, else false. - (lt a b) Returns true if a is less than b, else false.
- (eq a b) Returns true if a is equal to b, else false. - (eq a b) Returns true if a is equal to b, else false.
- (and ...args) Returns true if all conditions are true. - (and ...args) Returns true if all conditions are true.
- (or a b ...rest) Returns true if at least one condition is true. - (or a b ...rest) Returns true if at least one condition is true.
- (not a) Negation. Returns true if a is false. Returns false if a is true.
- (while fn action) While loop. Execute action for as long as fn is true.
Language
- (let name value)
- (def name value)
- (defn fname ~(...fnparams) ...instructions)
- (λ (...args) ...instructions)
- (if (condition) ...instruction-when-true ~...instruction-when-false)
Lists
- (each arr fn) Run a function for each element in a list. - (each arr fn) Run a function for each element in a list.
- (map arr fn) Run a function on each element in a list. - (map arr fn) Run a function on each element in a list.
- (filter arr fn) Remove from list, when function returns false. - (filter arr fn) Remove from list, when function returns false.
- (reduce arr fn acc) - (reduce arr fn acc)
- (len item) Returns the length of a list. - (len item) Returns the length of a list.
- (cons arr ...items) Returns a new array with the items appended.
- (push arr ...items) Appends the items into the existing list.
- (pop arr) Pop the last item from the list and return the item.
- (first arr) Returns the first item of a list. - (first arr) Returns the first item of a list.
- (last arr) Returns the last - (last arr) Returns the last
- (rest [_ ...arr]) - (rest [_ ...arr]) Returns all arguments except the first
- (range start end ~step) - (range start end ~step) Returns a list of numbers counting from start to end. Step defaults to 1.
Objects (maps)
- (get item key) Gets an object's parameter with name. - (get item key) Gets an object's parameter with name.
- (set item ...args) Sets an object's parameter with name as value. - (set item ...args) Sets an object's parameter with name as value.
- (of h ...keys) Gets object parameters with names. - (of h ...keys) Gets object parameters with names.
- (object ...entries) Creates an object with provided entries.
- (keys item) Returns a list of the object's keys - (keys item) Returns a list of the object's keys
- (values item) Returns a list of the object's values - (values item) Returns a list of the object's values
- (convolve kernel ~rect)
Convolution filters
- (convolve kernel ~rect) Apply convolution filter with given kernel on an area.
- (blur) Returns the blur kernel. - (blur) Returns the blur kernel.
- (sharpen) Returns the sharpen kernel. - (sharpen) Returns the sharpen kernel.
- (edge) Returns the edge kernel. - (edge) Returns the edge kernel.
Points
- (offset a b) Offsets pos a with pos b, returns a. - (offset a b) Offsets pos a with pos b, returns a.
- (distance a b) Get distance between positions. - (distance a b) Get distance between positions.
Utilities
- (echo ...args) Print arguments to interface. - (echo ...args) Print arguments to interface.
- (debug arg) Print arguments to console. - (debug arg) Print arguments to console.
- (time ~rate) Returns timestamp in milliseconds. - (time ~rate) Returns timestamp in milliseconds.
- (js) Javascript interop. - (js) Javascript interop. Returns window object.
- (on event f) Triggers on event. - (on event f) Triggers on event.
- (test name a b) - (test name a b) Unit test. Checks if a is equal to b, logs results to console.
- (benchmark fn) Logs time taken to execute a function. - (benchmark fn) Logs time taken to execute a function.
- (get-theme) Returns an object with current theme colors.
- (get-frame) Returns a shape object describing the current canvas.
Extras Extras

View File

@ -35,9 +35,9 @@
; ;
(defn redraw () (defn redraw ()
(
(clear) (clear)
(rec 300))) (rec 300))
; ;
(on "animate" redraw) (on "animate" redraw)

View File

@ -3,6 +3,9 @@
; ;
(clear) (clear)
(def frame
(get-frame))
; ;
(def gradient-line (def gradient-line
(line frame:c 0 frame:c frame:h)) (line frame:c 0 frame:c frame:h))

View File

@ -2,6 +2,9 @@
(clear) (clear)
(def frame
(get-frame))
; times ; times
@ -26,13 +29,13 @@
; position on a circle from angle ; position on a circle from angle
(defn circle-pos (defn circle-pos
(cx cy r a) {:x (cx cy r a) (object "x"
(add cx (add cx
(mul r (mul r
(cos a))) :y (cos a))) "y"
(add cy (add cy
(mul r (mul r
(sin a)))}) (sin a)))))
; draw ; draw
@ -42,10 +45,10 @@
( (
(stroke (stroke
(line cx cy (line cx cy
(:x (get
(circle-pos cx cy r a)) (circle-pos cx cy r a) "x")
(:y (get
(circle-pos cx cy r a))) "white" 2))) (circle-pos cx cy r a) "y")) "white" 2)))
; ;
(defn draw-star (defn draw-star

View File

@ -2,7 +2,8 @@
(def theme (def theme
(get-theme)) (get-theme))
(def frame
(get-frame))
; ex: theme:f_high ; ex: theme:f_high

View File

@ -1,5 +1,11 @@
(clear) (clear)
(def theme
(get-theme))
(def frame
(get-frame))
(defn branch (defn branch
(v) (v)
(if (if

View File

@ -2,6 +2,9 @@
(clear) (clear)
(def theme
(get-theme))
(transform:move 150 150) (transform:move 150 150)
(fill (fill

View File

@ -0,0 +1,168 @@
;A simple MIDI visualiser
;showing activity per note
;and channel.
(clear)
(def theme
(get-theme))
(resize 1200 600)
(def maxcirclesize 50)
(def mincirclesize 20)
(def circlexdist 40)
(def circleydist 30)
(def notes
(object 0 "A" 1 "A#" 2 "B" 3 "C" 4 "C#" 5 "D" 6 "D#" 7 "E" 8 "F" 9 "F#" 10 "G" 11 "G#"))
(def sharpnotes
(object 1 "A#" 4 "C#" 6 "D#" 9 "F#" 11 "G#"))
(def frame
(get-frame))
(def jswindow
(js))
(defn isblackkey
(num)
(def distfromA0
(sub num 21))
(def semitonenum
(mod distfromA0 12))
(not
(eq
(get sharpnotes semitonenum) undefined)))
(defn circlebynotenum
(num)
(circle
(mul
(sub num 47) circlexdist)
(if
(isblackkey num) circleydist
(mul 2.5 circleydist)) mincirclesize))
(def reactivecircles
(reduce
(range 48 76)
(λ
(acc num index)
(set acc num
(circlebynotenum num)))
(object)))
(def channelcircles
(map
(range 0 15)
(λ
(num)
(circle
(add
(mul num circlexdist 1.25) circlexdist) 200 mincirclesize))))
(defn js-exec
(obj fname listargs)
(def boundfunction
(js-bind
(get obj fname) obj))
(def result
(apply boundfunction
(if
(eq listargs undefined) () listargs))) result)
(defn midimsghandler
(midiMessage)
(def eventType
(get
(:data midiMessage) "0"))
;zero based
(def channelNum
(logand eventType 15))
;ignore clock in debug to keep things cleaner
(if
(not
(eq eventType 248))
(debug "incoming MIDI:" "CH" channelNum
(:data midiMessage)))
(def noteNum
(get
(:data midiMessage) "1"))
(def noteVelocity
(get
(:data midiMessage) "2"))
(set
(get channelcircles channelNum) "r"
(add mincirclesize
(mul
(sub maxcirclesize mincirclesize)
(div noteVelocity 100))))
(set
(or
(get reactivecircles noteNum)()) "r"
(add mincirclesize
(mul
(sub maxcirclesize mincirclesize)
(div noteVelocity 100)))))
(defn midiokhandler
(midiAccess)
(def midiInputs
(js-exec
(:inputs midiAccess) "values"))
(eachof midiInputs
(λ
(input id)
(debug "Setting listener on"
(:name input)
(:manufacturer input))
(set input "onmidimessage" midimsghandler))))
(defn midierrhandler
(err)
(debug "midierrhandler" err))
(js-exec
(js-exec
(:navigator jswindow) "requestMIDIAccess") "then"
(list midiokhandler midierrhandler))
(defn drawcircle
(arglist)
(def notenum
(first arglist))
(def i
(last arglist))
(if
(gt
(:r i) mincirclesize)
(set i "r"
(sub
(:r i) 0.6)))
(fill i
(if
(isblackkey notenum)
(theme:f_med)
(theme:f_low))))
(defn redraw ()
(clear)
(each
(entries reactivecircles) drawcircle)
(each channelcircles
(λ
(s i)
(fill s theme:b_high)
(fill
(text
(sub s:cx mincirclesize)
(add s:cy mincirclesize 18) 18
(concat "CH" i)) theme:b_inv 2))))
;
(on "animate" redraw)

View File

@ -0,0 +1,3 @@
# Not implemented (yet)
This set of examples shows the limitations of Ronin's LISP.

View File

@ -0,0 +1,44 @@
;To inspect the results of these tests,
;open your browser's debugging tools
;
(usually F12)
and navigate to the
;JavaScript console.
(clear)
(def mydog
(object "color" "gold" "coat" "long" :speed "quick" :health "good"))
(debug mydog)
(test "My dog's color is"
(get mydog "color") "gold")
(test "My dog's coat is"
(get mydog "coat") "long")
(test "My dog's speed is"
(get mydog "speed") "quick")
;the last one passes only because :health
;in the set instruction above
;resolves to undefined - and
;technically you can have one object value
;for key=undefined
(test "My dog's health is"
(get mydog :health) "good")
;You can, however, use obj:param syntax to
;get properties of objects.
;A shorthand for
(get obj "param")
.
;
(test "Get color" mydog:color "gold")
;Also,
(:coat mydog)
;is another shorthand.
(test "Get coat shorthand function"
(:coat mydog) "long")

View File

@ -2,17 +2,31 @@
(clear) (clear)
(defn glitch (import $path
(rec)
(if (gt rec 1)
(
(translate
(rect (random 400) (random 400) (random 10) (random 10))
(pos (random 400) (random 400)))
(glitch (sub rec 1)))))
(import
"../static/crystal.jpg"
(rect 0 0 400 400)) (rect 0 0 400 400))
(defn translate
(source-rect dest-pos)
(
(paste
(copy source-rect)
(rect dest-pos:x dest-pos:y source-rect:w source-rect:h))))
(defn glitch
(rec)
(if
(gt rec 1)
(
(translate
(rect
(random 1 400)
(random 1 400)
(random 1 10)
(random 1 10))
(pos
(random 1 400)
(random 1 400)))
(glitch
(sub rec 1)))))
(glitch 500) (glitch 500)

View File

@ -1,6 +1,12 @@
; Normalize photo colors ; Normalize photo colors
(open $path 0.5) (open $path 0.5)
;(pick (rect 0 0 100 100)) will return the average color
;of 100x100px rectangle.
;without extra arguments,
;pick grabs the whole canvas by default,
;and calculates the average color.
(def average-color (def average-color
(pick)) (pick))
(pixels normalize average-color) (pixels normalize average-color)

View File

@ -1,4 +1,4 @@
; saturate image ; saturate image
(open "../static/crystal.jpg") (open $path)
(pixels (pixels saturation 12
(frame) saturation 12) (get-frame))

View File

@ -2,7 +2,7 @@
; drag an image on the window ; drag an image on the window
(open $path) (open $path)
; ;
(pixels (pixels saturation 0
(rect 100 100 400 400) saturation 0) (rect 100 100 400 400))
(pixels (pixels contrast 0.5
(rect 300 300 400 400) contrast 0.5) (rect 300 300 400 400))

View File

@ -6,7 +6,7 @@
(if (if
(gt rec 0) (gt rec 0)
( (
(import "../static/crystal.jpg" (import $path
(rect (rect
(random 200) (random 200)
(random 200) (random 200)

View File

@ -18,63 +18,10 @@
(pick (pick
(guide (guide
(rect $xy unit unit)))) (rect $xy unit unit))))
(def color-2 (echo color-1)
(pick
(guide
(rect $xy unit unit))))
(def color-3
(pick
(guide
(rect $xy unit unit))))
(def color-4
(pick
(guide
(rect $xy unit unit))))
(def color-5
(pick
(guide
(rect $xy unit unit))))
(def color-6
(pick
(guide
(rect $xy unit unit))))
(def color-7
(pick
(guide
(rect $xy unit unit))))
(def color-8
(pick
(guide
(rect $xy unit unit))))
; display ; display
(fill (fill
(circle (circle
(mul 20 2) pos-row-1 18) color-1) (mul 20 2) pos-row-1 18) color-1)
(fill
(circle
(mul 20 4) pos-row-1 18) color-2)
(fill
(circle
(mul 20 6) pos-row-1 18) color-3)
(fill
(circle
(mul 20 8) pos-row-1 18) color-4)
(fill
(circle
(mul 20 3) pos-row-2 18) color-5)
(fill
(circle
(mul 20 5) pos-row-2 18) color-6)
(fill
(circle
(mul 20 7) pos-row-2 18) color-7)
(fill
(circle
(mul 20 9) pos-row-2 18) color-8)
;
(def res
(add color-1:hex ":" color-2:hex ":" color-3:hex ":" color-4:hex ":" color-5:hex ":" color-6:hex ":" color-7:hex ":" color-8:hex))
(echo
(add res ":" res))

View File

@ -1,4 +1,4 @@
(resize 600 200) (resize 600 800)
(clear) (clear)
@ -14,6 +14,9 @@
(guide (guide
(line 0 100 600 100)) colors)) (line 0 100 600 100)) colors))
;collect colors to prepared list,
;in particular points from the gradient
;marked by the guides
(each picked-colors (each picked-colors
(λ (λ
(color id) (color id)
@ -25,5 +28,33 @@
(mul id (mul id
(div 600 9)) 100))))))) (div 600 9)) 100)))))))
;show picked colors as swatches
(each picked-colors
(λ
(color id)
(
;swatch circle
(fill
(circle
20 (add (mul id
(div 600 9)) 300) 18) color)
"black")))
;show picked colors as text
(each picked-colors
(λ
(color id)
(
(fill
(text
12 (add (mul id
(div 600 9)) 300 5) 24
(concat id ": "
(get
(get picked-colors
(concat "" id)) "hex"))) "black"))))
;get the first color in different formats
(echo (echo
(get picked-colors:1 "hex")) (get picked-colors:0 "hex")
(get picked-colors:0 "rgba"))

View File

@ -7,5 +7,8 @@
(add (add
(mul index 40) 50) 40 name) "red")) (mul index 40) 50) 40 name) "red"))
;this will display the list of loaded files.
;drag a few pictures into Ronin, then eval
;this script again.
(each (each
(files) print-file) (files) print-file)

View File

@ -3,6 +3,8 @@
; ;
(clear) (clear)
(def frame (get-frame))
; ;
(def gradient-line (def gradient-line
(line frame:c 0 frame:c frame:h)) (line frame:c 0 frame:c frame:h))

134
examples/tests/lists.lisp Normal file
View File

@ -0,0 +1,134 @@
(test "Native last call - list of numbers"
(last
(1 2 3)) 3)
(test "Native last call - list of numbers, 1 element"
(last
(1)) 1)
(test "Native last call - list of numbers, no elements"
(last ()) undefined)
(test "Native last call - list of strings, 1 element"
(last
("abc")) "abc")
(test "Native last call - list of strings, many elements"
(last
("ala" "bla" "cla")) "cla")
;functions defined as defn/lambda args bodyinstrs...
(defn lastfn
(listarg) (debug "some other instruction")
(last listarg))
(test "Proxied last call - list of numbers"
(lastfn
(1 2 3)) 3)
(test "Proxied last call - list of numbers, 1 element"
(lastfn
(1)) 1)
(test "Proxied last call - list of numbers, no elements"
(lastfn ()) undefined)
(test "Proxied last call - list of strings, 1 element"
(lastfn
("abc")) "abc")
(test "Proxied last call - list of strings, many elements"
(lastfn
("ala" "bla" "cla")) "cla")
(test "Lambda last call - list of numbers"
((λ
(listarg) (debug "some other instruction")
(last listarg))
(1 2 3)) 3)
(test "Lambda last call - list of numbers, 1 element"
((λ
(listarg) (debug "some other instruction")
(last listarg))
(1)) 1)
(test "Lambda last call - list of numbers, no elements"
((λ
(listarg) (debug "some other instruction")
(last listarg)) ()) undefined)
(test "Lambda last call - list of strings, 1 element"
((λ
(listarg) (debug "some other instruction")
(last listarg))
("abc")) "abc")
(test "Lambda last call - list of strings, many elements"
((λ
(listarg) (debug "some other instruction")
(last listarg))
("ala" "bla" "cla")) "cla")
;functions defined as defn/lambda args bodyinstr
(defn lastfn2
(listarg)
(last listarg))
(test "Proxied last call - list of numbers"
(lastfn2
(1 2 3)) 3)
(test "Proxied last call - list of numbers, 1 element"
(lastfn2
(1)) 1)
(test "Proxied last call - list of numbers, no elements"
(lastfn2 ()) undefined)
(test "Proxied last call - list of strings, 1 element"
(lastfn2
("abc")) "abc")
(test "Proxied last call - list of strings, many elements"
(lastfn2
("ala" "bla" "cla")) "cla")
(test "Lambda last call - list of numbers"
((λ
(listarg)
(last listarg))
(1 2 3)) 3)
(test "Lambda last call - list of numbers, 1 element"
((λ
(listarg)
(last listarg))
(1)) 1)
(test "Lambda last call - list of numbers, no elements"
((λ
(listarg)
(last listarg)) ()) undefined)
(test "Lambda last call - list of strings, 1 element"
((λ
(listarg)
(last listarg))
("abc")) "abc")
(test "Lambda last call - list of strings, many elements"
((λ
(listarg)
(last listarg))
("ala" "bla" "cla")) "cla")

View File

@ -148,14 +148,16 @@ function Lain (lib = {}) {
const identifier = input[1].value const identifier = input[1].value
if (context.scope[identifier]) { console.warn('Lain', `Redefining function: ${identifier}`) } if (context.scope[identifier]) { console.warn('Lain', `Redefining function: ${identifier}`) }
const fnParams = input[2].type === TYPES.string && input[3] ? input[3] : input[2] const fnParams = input[2].type === TYPES.string && input[3] ? input[3] : input[2]
const fnBody = input[2].type === TYPES.string && input[4] ? input[4] : input[3] const fnBodyFirstIndex = input[2].type === TYPES.string && input[4] ? 4 : 3
const fnBody = input.slice(fnBodyFirstIndex)
context.scope[identifier] = function () { context.scope[identifier] = function () {
const lambdaArguments = arguments const lambdaArguments = arguments
const lambdaScope = fnParams.reduce(function (acc, x, i) { const lambdaScope = fnParams.reduce(function (acc, x, i) {
acc[x.value] = lambdaArguments[i] acc[x.value] = lambdaArguments[i]
return acc return acc
}, {}) }, {})
return interpret(fnBody, new Context(lambdaScope, context)) let result = interpret(fnBody, new Context(lambdaScope, context))
return getReturnValue(result)
} }
}, },
λ: function (input, context) { λ: function (input, context) {
@ -165,13 +167,20 @@ function Lain (lib = {}) {
acc[x.value] = lambdaArguments[i] acc[x.value] = lambdaArguments[i]
return acc return acc
}, {}) }, {})
return interpret(input[2], new Context(lambdaScope, context)) let result = interpret(input.slice(2), new Context(lambdaScope, context))
return getReturnValue(result)
} }
}, },
if: function (input, context) { if: function (input, context) {
return interpret(input[1], context) ? interpret(input[2], context) : input[3] ? interpret(input[3], context) : [] return interpret(input[1], context) ? interpret(input[2], context) : input[3] ? interpret(input[3], context) : []
} }
} }
const getReturnValue = function (interpretResult) {
if(!interpretResult || !(interpretResult instanceof Array) || !interpretResult.length){
return interpretResult
}
return interpretResult[interpretResult.length - 1]
}
const interpretList = function (input, context) { const interpretList = function (input, context) {
if (input.length > 0 && input[0].value in special) { if (input.length > 0 && input[0].value in special) {
return special[input[0].value](input, context) return special[input[0].value](input, context)
@ -1185,6 +1194,21 @@ function Library (client) {
} }
return Math.random() return Math.random()
} }
this.logand = (a, b) => {
return a & b
}
this.logior = (a, b) => {
return a | b
}
this.logxor = (a, b) => {
return a ^ b
}
this.lognot = (a) => {
return ~ a
}
this.ash = (a, b) => {
return a << b
}
this.gt = (a, b) => { // Returns true if a is greater than b, else false. this.gt = (a, b) => { // Returns true if a is greater than b, else false.
return a > b return a > b
} }
@ -1211,20 +1235,29 @@ function Library (client) {
} }
return args[args.length - 1] return args[args.length - 1]
} }
this.not = (a) => { this.not = (a) => { //Negation. Returns true if a is false. Returns false if a is true.
return !a return !a
} }
this.while = (fn, action) => { this.while = (fn, action) => { //While loop. Execute action for as long as fn is true.
while (fn()) { while (fn()) {
action() action()
} }
} }
this.apply = (fn, argslist) => {
let result = fn(...argslist);
return result;
}
this.each = (arr, fn) => { // Run a function for each element in a list. this.each = (arr, fn) => { // Run a function for each element in a list.
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
const arg = arr[i] const arg = arr[i]
fn(arg, i) fn(arg, i)
} }
} }
this.eachof = (arr, fn) => {
for(let elem of arr){
fn(elem);
}
}
this.map = (arr, fn) => { // Returns a new list with fn applied to each value. this.map = (arr, fn) => { // Returns a new list with fn applied to each value.
return arr.map(fn); return arr.map(fn);
} }
@ -1242,7 +1275,10 @@ function Library (client) {
this.len = (item) => { // Returns the length of a list. this.len = (item) => { // Returns the length of a list.
return item.length return item.length
} }
this.cons = (arr, ...items) => { // Retruns a new array with the items appended. this.list = (...items) => { // Returns a new array with the items
return items
}
this.cons = (arr, ...items) => { // Returns a new array with the items appended to arr.
return arr.concat(items) return arr.concat(items)
} }
this.push = (arr, ...items) => { // Appends the items into the existing list. this.push = (arr, ...items) => { // Appends the items into the existing list.
@ -1260,10 +1296,10 @@ function Library (client) {
this.last = (arr) => { // Returns the last this.last = (arr) => { // Returns the last
return arr[arr.length - 1] return arr[arr.length - 1]
} }
this.rest = ([_, ...arr]) => { this.rest = ([_, ...arr]) => { // Returns all arguments except the first
return arr return arr
} }
this.range = (start, end, step = 1) => { this.range = (start, end, step = 1) => { //Returns a list of numbers counting from start to end. Step defaults to 1.
const arr = [] const arr = []
if (step > 0) { if (step > 0) {
for (let i = start; i <= end; i += step) { for (let i = start; i <= end; i += step) {
@ -1277,7 +1313,7 @@ function Library (client) {
return arr return arr
} }
this.get = (item, key) => { // Gets an object's parameter with name. this.get = (item, key) => { // Gets an object's parameter with name.
return item && key ? item[key] : null return item && (key !== null && key !== undefined) ? item[key] : null
} }
this.set = (item, ...args) => { // Sets an object's parameter with name as value. this.set = (item, ...args) => { // Sets an object's parameter with name as value.
for (let i = 0; i < args.length; i += 2) { for (let i = 0; i < args.length; i += 2) {
@ -1305,6 +1341,9 @@ function Library (client) {
this.values = (item) => { // Returns a list of the object's values this.values = (item) => { // Returns a list of the object's values
return Object.values(item) return Object.values(item)
} }
this.entries = (item) => { // Returns a list of the object's properties, each in array of [key, value]
return Object.entries(item);
}
this.convolve = (kernel, rect = this['get-frame']()) => { this.convolve = (kernel, rect = this['get-frame']()) => {
const sigma = kernel.flat().reduce((a, x) => (a + x)) const sigma = kernel.flat().reduce((a, x) => (a + x))
const kw = kernel[0].length; const kh = kernel.length const kw = kernel[0].length; const kh = kernel.length
@ -1357,9 +1396,9 @@ function Library (client) {
client.log(args) client.log(args)
return args return args
} }
this.debug = (arg) => { // Print arguments to console. this.debug = (...args) => { // Print arguments to console.
console.log(arg) console.log(...args)
return arg return args
} }
this.time = (rate = 1) => { // Returns timestamp in milliseconds. this.time = (rate = 1) => { // Returns timestamp in milliseconds.
return (Date.now() * rate) return (Date.now() * rate)
@ -1367,10 +1406,13 @@ function Library (client) {
this.js = () => { // Javascript interop. this.js = () => { // Javascript interop.
return window return window
} }
this['js-bind'] = (fn, thisArg, ...args) => {
return fn.bind(thisArg, ...args)
}
this.on = (event, f) => { // Triggers on event. this.on = (event, f) => { // Triggers on event.
client.bind(event, f) client.bind(event, f)
} }
this.test = (name, a, b) => { this.test = (name, a, b) => { //nit test. Checks if a is equal to b, logs results to console.
if (`${a}` !== `${b}`) { if (`${a}` !== `${b}`) {
console.warn('failed ' + name, a, b) console.warn('failed ' + name, a, b)
} else { } else {
@ -1387,7 +1429,7 @@ function Library (client) {
this['get-theme'] = () => { // Get theme values. this['get-theme'] = () => { // Get theme values.
return client.theme.active return client.theme.active
} }
this['get-frame'] = () => { // Get theme values. this['get-frame'] = () => { // Get frame shape.
return client.surface.getFrame() return client.surface.getFrame()
} }
} }

View File

@ -40,15 +40,21 @@ function Lain (lib = {}) {
defn: function (input, context) { defn: function (input, context) {
const identifier = input[1].value const identifier = input[1].value
if (context.scope[identifier]) { console.warn('Lain', `Redefining function: ${identifier}`) } if (context.scope[identifier]) { console.warn('Lain', `Redefining function: ${identifier}`) }
const fnParams = input[2].type === TYPES.string && input[3] ? input[3] : input[2] const fnParams = input[2].type === TYPES.string && input[3] ? input[3] : input[2]
const fnBody = input[2].type === TYPES.string && input[4] ? input[4] : input[3] const fnBodyFirstIndex = input[2].type === TYPES.string && input[4] ? 4 : 3
const fnBody = input.slice(fnBodyFirstIndex)
context.scope[identifier] = function () { context.scope[identifier] = function () {
const lambdaArguments = arguments const lambdaArguments = arguments
const lambdaScope = fnParams.reduce(function (acc, x, i) { const lambdaScope = fnParams.reduce(function (acc, x, i) {
acc[x.value] = lambdaArguments[i] acc[x.value] = lambdaArguments[i]
return acc return acc
}, {}) }, {})
return interpret(fnBody, new Context(lambdaScope, context))
let result = interpret(fnBody, new Context(lambdaScope, context))
//lisp returns the return value of the last executed function, not a list of all results of all functions.
return getReturnValue(result)
} }
}, },
λ: function (input, context) { λ: function (input, context) {
@ -58,7 +64,10 @@ function Lain (lib = {}) {
acc[x.value] = lambdaArguments[i] acc[x.value] = lambdaArguments[i]
return acc return acc
}, {}) }, {})
return interpret(input[2], new Context(lambdaScope, context))
let result = interpret(input.slice(2), new Context(lambdaScope, context))
//lisp returns the return value of the last executed function, not a list of all results of all functions.
return getReturnValue(result)
} }
}, },
if: function (input, context) { if: function (input, context) {
@ -66,6 +75,15 @@ function Lain (lib = {}) {
} }
} }
const getReturnValue = function (interpretResult) {
//lisp returns the return value of the last executed function,
//not a list of all results of all functions.
if(!interpretResult || !(interpretResult instanceof Array) || !interpretResult.length){
return interpretResult
}
return interpretResult[interpretResult.length - 1]
}
const interpretList = function (input, context) { const interpretList = function (input, context) {
if (input.length > 0 && input[0].value in special) { if (input.length > 0 && input[0].value in special) {
return special[input[0].value](input, context) return special[input[0].value](input, context)

View File

@ -382,6 +382,28 @@ function Library (client) {
return Math.random() return Math.random()
} }
// Binary
this.logand = (a, b) => {
return a & b
}
this.logior = (a, b) => {
return a | b
}
this.logxor = (a, b) => {
return a ^ b
}
this.lognot = (a) => {
return ~ a
}
this.ash = (a, b) => {
return a << b
}
// Logic // Logic
this.gt = (a, b) => { // Returns true if a is greater than b, else false. this.gt = (a, b) => { // Returns true if a is greater than b, else false.
@ -415,18 +437,22 @@ function Library (client) {
return args[args.length - 1] return args[args.length - 1]
} }
this.not = (a) => { this.not = (a) => { //Negation. Returns true if a is false. Returns false if a is true.
return !a return !a
} }
// Arrays this.while = (fn, action) => { //While loop. Execute action for as long as fn is true.
this.while = (fn, action) => {
while (fn()) { while (fn()) {
action() action()
} }
} }
// Arrays
this.apply = (fn, argslist) => {
let result = fn(...argslist);
return result;
}
this.each = (arr, fn) => { // Run a function for each element in a list. this.each = (arr, fn) => { // Run a function for each element in a list.
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
const arg = arr[i] const arg = arr[i]
@ -434,6 +460,12 @@ function Library (client) {
} }
} }
this.eachof = (arr, fn) => {
for(let elem of arr){
fn(elem);
}
}
this.map = (arr, fn) => { // Returns a new list with fn applied to each value. this.map = (arr, fn) => { // Returns a new list with fn applied to each value.
return arr.map(fn); return arr.map(fn);
} }
@ -455,7 +487,11 @@ function Library (client) {
return item.length return item.length
} }
this.cons = (arr, ...items) => { // Retruns a new array with the items appended. this.list = (...items) => { // Returns a new array with the items
return items
}
this.cons = (arr, ...items) => { // Returns a new array with the items appended to arr.
return arr.concat(items) return arr.concat(items)
} }
@ -478,11 +514,11 @@ function Library (client) {
return arr[arr.length - 1] return arr[arr.length - 1]
} }
this.rest = ([_, ...arr]) => { this.rest = ([_, ...arr]) => { // Returns all arguments except the first
return arr return arr
} }
this.range = (start, end, step = 1) => { this.range = (start, end, step = 1) => { //Returns a list of numbers counting from start to end. Step defaults to 1.
const arr = [] const arr = []
if (step > 0) { if (step > 0) {
for (let i = start; i <= end; i += step) { for (let i = start; i <= end; i += step) {
@ -499,7 +535,7 @@ function Library (client) {
// Objects // Objects
this.get = (item, key) => { // Gets an object's parameter with name. this.get = (item, key) => { // Gets an object's parameter with name.
return item && key ? item[key] : null return item && (key !== null && key !== undefined) ? item[key] : null
} }
this.set = (item, ...args) => { // Sets an object's parameter with name as value. this.set = (item, ...args) => { // Sets an object's parameter with name as value.
@ -533,6 +569,10 @@ function Library (client) {
return Object.values(item) return Object.values(item)
} }
this.entries = (item) => { // Returns a list of the object's properties, each in array of [key, value]
return Object.entries(item);
}
// Convolve // Convolve
this.convolve = (kernel, rect = this['get-frame']()) => { this.convolve = (kernel, rect = this['get-frame']()) => {
@ -575,6 +615,8 @@ function Library (client) {
[-1, -1, -1]] [-1, -1, -1]]
} }
// Points
this.offset = (a, b) => { // Offsets pos a with pos b, returns a. this.offset = (a, b) => { // Offsets pos a with pos b, returns a.
a.x += b.x a.x += b.x
a.y += b.y a.y += b.y
@ -585,6 +627,8 @@ function Library (client) {
return Math.sqrt(((a.x - b.x) * (a.x - b.x)) + ((a.y - b.y) * (a.y - b.y))) return Math.sqrt(((a.x - b.x) * (a.x - b.x)) + ((a.y - b.y) * (a.y - b.y)))
} }
// Utilities
this.print = (value) => { this.print = (value) => {
client.source.write('ronin-print', 'txt', value, 'text/plain') client.source.write('ronin-print', 'txt', value, 'text/plain')
return value return value
@ -595,9 +639,9 @@ function Library (client) {
return args return args
} }
this.debug = (arg) => { // Print arguments to console. this.debug = (...args) => { // Print arguments to console.
console.log(arg) console.log(...args)
return arg return args
} }
this.time = (rate = 1) => { // Returns timestamp in milliseconds. this.time = (rate = 1) => { // Returns timestamp in milliseconds.
@ -608,11 +652,24 @@ function Library (client) {
return window return window
} }
// Returns a new function that
// Useful when executing JS functions that need a strict `this` context.
//
// An example:
// `(get (get (js) "navigator") "requestMIDIAccess")` in Ronin
// should be equivalent to `window.navigator.requestMIDIAccess` in JS.
// Executing such retrieved JS method will crash though - as the method
// needs an internal `this` context - in exemplary case, window.navigator.
//
this['js-bind'] = (fn, thisArg, ...args) => {
return fn.bind(thisArg, ...args)
}
this.on = (event, f) => { // Triggers on event. this.on = (event, f) => { // Triggers on event.
client.bind(event, f) client.bind(event, f)
} }
this.test = (name, a, b) => { this.test = (name, a, b) => { //nit test. Checks if a is equal to b, logs results to console.
if (`${a}` !== `${b}`) { if (`${a}` !== `${b}`) {
console.warn('failed ' + name, a, b) console.warn('failed ' + name, a, b)
} else { } else {
@ -634,7 +691,7 @@ function Library (client) {
return client.theme.active return client.theme.active
} }
this['get-frame'] = () => { // Get theme values. this['get-frame'] = () => { // Get frame shape.
return client.surface.getFrame() return client.surface.getFrame()
} }
} }