diff --git a/README.txt b/README.txt index eddac97..c00d675 100644 --- a/README.txt +++ b/README.txt @@ -59,11 +59,16 @@ To save an image in memory, open an image file with Ronin, or drag an image file Library +Files + - (open name ~scale) Imports a graphic file with format. - (import name ~shape) Imports a graphic file with format. - (export ~format ~quality) Exports a graphic file with format. - (files) Returns the list of loaded files. - (print string) Exports string to file. + +Shapes + - (pos ~x ~y) Returns a position shape. - (line ax ay bx by) Returns a line shape. - (size w h) Returns a size shape. @@ -74,6 +79,9 @@ Library - (poly ...pos) Returns a poly shape. - (text x y p t ~a ~f) Returns a text shape. - (svg x y d) Returns a svg shape. + +Canvas operations + - (color r g b ~a) Returns a 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. @@ -92,6 +100,9 @@ Library - (clear ~rect) Clears a rect. - (gradient line ~colors 'black']) Defines a gradient color. - (guide shape color) Draws a shape on the guide layer. + +Pixel operations + - (pixels fn ~q ~rect) - (saturation pixel q) Change the saturation of pixels. - (contrast pixel q) Change the contrast of pixels. @@ -100,8 +111,14 @@ Library - (multiply pixel q) Change the color balance of pixels. - (normalize pixel q) Normalize the color of pixels with another color. - (lum color) Return the luminance of a color. + +Strings + - (concat ...items) Concat multiple strings. - (split string char) Split string at character. + +Math + - (add ...args) Adds values. - (sub ...args) Subtracts values. - (mul ...args) Multiplies values. @@ -125,38 +142,71 @@ Library - (PI) - (TWO_PI) - (random ...args) + +Logic + - (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. - (eq a b) Returns true if a is equal to b, else false. - (and ...args) Returns true if all conditions are 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. - (map arr fn) Run a function on each element in a list. - (filter arr fn) Remove from list, when function returns false. - (reduce arr fn acc) - (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. - (last arr) Returns the last -- (rest [_ ...arr]) -- (range start end ~step) +- (rest [_ ...arr]) Returns all arguments except the first +- (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. - (set item ...args) Sets an object's parameter with name as value. - (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 - (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. - (sharpen) Returns the sharpen kernel. - (edge) Returns the edge kernel. + +Points + - (offset a b) Offsets pos a with pos b, returns a. - (distance a b) Get distance between positions. + +Utilities + - (echo ...args) Print arguments to interface. - (debug arg) Print arguments to console. - (time ~rate) Returns timestamp in milliseconds. -- (js) Javascript interop. +- (js) Javascript interop. Returns window object. - (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. +- (get-theme) Returns an object with current theme colors. +- (get-frame) Returns a shape object describing the current canvas. Extras diff --git a/examples/basics/spiral.lisp b/examples/basics/spiral.lisp index dc8af66..4dae393 100644 --- a/examples/basics/spiral.lisp +++ b/examples/basics/spiral.lisp @@ -35,9 +35,9 @@ ; (defn redraw () - ( + (clear) - (rec 300))) + (rec 300)) ; (on "animate" redraw) \ No newline at end of file diff --git a/examples/basics/spire.lisp b/examples/basics/spire.lisp index f68569b..7fb581d 100644 --- a/examples/basics/spire.lisp +++ b/examples/basics/spire.lisp @@ -3,6 +3,9 @@ ; (clear) +(def frame + (get-frame)) + ; (def gradient-line (line frame:c 0 frame:c frame:h)) diff --git a/examples/basics/stars.lisp b/examples/basics/stars.lisp index 67d0e3a..3703eba 100644 --- a/examples/basics/stars.lisp +++ b/examples/basics/stars.lisp @@ -2,6 +2,9 @@ (clear) +(def frame + (get-frame)) + ; times @@ -26,13 +29,13 @@ ; position on a circle from angle (defn circle-pos - (cx cy r a) {:x + (cx cy r a) (object "x" (add cx (mul r - (cos a))) :y + (cos a))) "y" (add cy (mul r - (sin a)))}) + (sin a))))) ; draw @@ -42,10 +45,10 @@ ( (stroke (line cx cy - (:x - (circle-pos cx cy r a)) - (:y - (circle-pos cx cy r a))) "white" 2))) + (get + (circle-pos cx cy r a) "x") + (get + (circle-pos cx cy r a) "y")) "white" 2))) ; (defn draw-star diff --git a/examples/basics/theme.lisp b/examples/basics/theme.lisp index 7659232..945f24b 100644 --- a/examples/basics/theme.lisp +++ b/examples/basics/theme.lisp @@ -2,7 +2,8 @@ (def theme (get-theme)) - +(def frame + (get-frame)) ; ex: theme:f_high diff --git a/examples/basics/transform.branch.lisp b/examples/basics/transform.branch.lisp index f7e91f5..6c59354 100644 --- a/examples/basics/transform.branch.lisp +++ b/examples/basics/transform.branch.lisp @@ -1,5 +1,11 @@ (clear) +(def theme + (get-theme)) + +(def frame + (get-frame)) + (defn branch (v) (if diff --git a/examples/basics/transforms.lisp b/examples/basics/transforms.lisp index 0f5a15c..7bcbb10 100644 --- a/examples/basics/transforms.lisp +++ b/examples/basics/transforms.lisp @@ -2,6 +2,9 @@ (clear) +(def theme + (get-theme)) + (transform:move 150 150) (fill diff --git a/examples/events/webmidi.lisp b/examples/events/webmidi.lisp new file mode 100644 index 0000000..924dc00 --- /dev/null +++ b/examples/events/webmidi.lisp @@ -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) \ No newline at end of file diff --git a/examples/not-implemented-yet/README.md b/examples/not-implemented-yet/README.md new file mode 100644 index 0000000..2f3b9db --- /dev/null +++ b/examples/not-implemented-yet/README.md @@ -0,0 +1,3 @@ +# Not implemented (yet) + +This set of examples shows the limitations of Ronin's LISP. \ No newline at end of file diff --git a/examples/not-implemented-yet/object-symbols.lisp b/examples/not-implemented-yet/object-symbols.lisp new file mode 100644 index 0000000..95da3d8 --- /dev/null +++ b/examples/not-implemented-yet/object-symbols.lisp @@ -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") \ No newline at end of file diff --git a/examples/pixels/glitch.lisp b/examples/pixels/glitch.lisp index 064d5e1..fc46527 100644 --- a/examples/pixels/glitch.lisp +++ b/examples/pixels/glitch.lisp @@ -2,17 +2,31 @@ (clear) -(defn glitch - (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" +(import $path (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) \ No newline at end of file diff --git a/examples/pixels/normalize.lisp b/examples/pixels/normalize.lisp index 0f4bb11..522d49c 100644 --- a/examples/pixels/normalize.lisp +++ b/examples/pixels/normalize.lisp @@ -1,6 +1,12 @@ ; Normalize photo colors (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 (pick)) (pixels normalize average-color) \ No newline at end of file diff --git a/examples/pixels/open.lisp b/examples/pixels/open.lisp index af80858..90aa480 100644 --- a/examples/pixels/open.lisp +++ b/examples/pixels/open.lisp @@ -1,4 +1,4 @@ ; saturate image -(open "../static/crystal.jpg") -(pixels - (frame) saturation 12) \ No newline at end of file +(open $path) +(pixels saturation 12 + (get-frame)) \ No newline at end of file diff --git a/examples/pixels/pixels.lisp b/examples/pixels/pixels.lisp index b1cb522..4486600 100644 --- a/examples/pixels/pixels.lisp +++ b/examples/pixels/pixels.lisp @@ -2,7 +2,7 @@ ; drag an image on the window (open $path) ; -(pixels - (rect 100 100 400 400) saturation 0) -(pixels - (rect 300 300 400 400) contrast 0.5) \ No newline at end of file +(pixels saturation 0 + (rect 100 100 400 400)) +(pixels contrast 0.5 + (rect 300 300 400 400)) \ No newline at end of file diff --git a/examples/pixels/random.lisp b/examples/pixels/random.lisp index f6cdf0d..38ce8ff 100644 --- a/examples/pixels/random.lisp +++ b/examples/pixels/random.lisp @@ -6,7 +6,7 @@ (if (gt rec 0) ( - (import "../static/crystal.jpg" + (import $path (rect (random 200) (random 200) diff --git a/examples/projects/elementary-theme.lisp b/examples/projects/elementary-theme.lisp index b879346..06a835c 100644 --- a/examples/projects/elementary-theme.lisp +++ b/examples/projects/elementary-theme.lisp @@ -18,63 +18,10 @@ (pick (guide (rect $xy unit unit)))) -(def color-2 - (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)))) +(echo color-1) ; display (fill (circle (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)) \ No newline at end of file diff --git a/examples/projects/lambda.lisp b/examples/projects/lambda.lisp index 4c993d9..77dceec 100644 --- a/examples/projects/lambda.lisp +++ b/examples/projects/lambda.lisp @@ -1,4 +1,4 @@ -(resize 600 200) +(resize 600 800) (clear) @@ -14,6 +14,9 @@ (guide (line 0 100 600 100)) colors)) +;collect colors to prepared list, +;in particular points from the gradient +;marked by the guides (each picked-colors (λ (color id) @@ -25,5 +28,33 @@ (mul id (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 - (get picked-colors:1 "hex")) \ No newline at end of file + (get picked-colors:0 "hex") + (get picked-colors:0 "rgba")) \ No newline at end of file diff --git a/examples/projects/print-files.lisp b/examples/projects/print-files.lisp index 988f280..85909af 100644 --- a/examples/projects/print-files.lisp +++ b/examples/projects/print-files.lisp @@ -7,5 +7,8 @@ (add (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 (files) print-file) \ No newline at end of file diff --git a/examples/projects/spire.lisp b/examples/projects/spire.lisp index f68569b..38fcb79 100644 --- a/examples/projects/spire.lisp +++ b/examples/projects/spire.lisp @@ -3,6 +3,8 @@ ; (clear) +(def frame (get-frame)) + ; (def gradient-line (line frame:c 0 frame:c frame:h)) diff --git a/examples/projects/benchmark.lisp b/examples/tests/benchmark.lisp similarity index 100% rename from examples/projects/benchmark.lisp rename to examples/tests/benchmark.lisp diff --git a/examples/tests/lists.lisp b/examples/tests/lists.lisp new file mode 100644 index 0000000..07b81e9 --- /dev/null +++ b/examples/tests/lists.lisp @@ -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") \ No newline at end of file diff --git a/index.html b/index.html index 117b145..ce41bb9 100644 --- a/index.html +++ b/index.html @@ -148,14 +148,16 @@ function Lain (lib = {}) { const identifier = input[1].value if (context.scope[identifier]) { console.warn('Lain', `Redefining function: ${identifier}`) } 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 () { const lambdaArguments = arguments const lambdaScope = fnParams.reduce(function (acc, x, i) { acc[x.value] = lambdaArguments[i] return acc }, {}) - return interpret(fnBody, new Context(lambdaScope, context)) + let result = interpret(fnBody, new Context(lambdaScope, context)) + return getReturnValue(result) } }, λ: function (input, context) { @@ -165,13 +167,20 @@ function Lain (lib = {}) { acc[x.value] = lambdaArguments[i] 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) { 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) { if (input.length > 0 && input[0].value in special) { return special[input[0].value](input, context) @@ -1185,6 +1194,21 @@ function Library (client) { } 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. return a > b } @@ -1211,20 +1235,29 @@ function Library (client) { } 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 } - this.while = (fn, action) => { + this.while = (fn, action) => { //While loop. Execute action for as long as fn is true. while (fn()) { action() } } + this.apply = (fn, argslist) => { + let result = fn(...argslist); + return result; + } this.each = (arr, fn) => { // Run a function for each element in a list. for (let i = 0; i < arr.length; i++) { const arg = arr[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. return arr.map(fn); } @@ -1242,7 +1275,10 @@ function Library (client) { this.len = (item) => { // Returns the length of a list. 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) } this.push = (arr, ...items) => { // Appends the items into the existing list. @@ -1260,10 +1296,10 @@ function Library (client) { this.last = (arr) => { // Returns the last return arr[arr.length - 1] } - this.rest = ([_, ...arr]) => { + this.rest = ([_, ...arr]) => { // Returns all arguments except the first 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 = [] if (step > 0) { for (let i = start; i <= end; i += step) { @@ -1277,7 +1313,7 @@ function Library (client) { return arr } 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. 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 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']()) => { const sigma = kernel.flat().reduce((a, x) => (a + x)) const kw = kernel[0].length; const kh = kernel.length @@ -1357,9 +1396,9 @@ function Library (client) { client.log(args) return args } - this.debug = (arg) => { // Print arguments to console. - console.log(arg) - return arg + this.debug = (...args) => { // Print arguments to console. + console.log(...args) + return args } this.time = (rate = 1) => { // Returns timestamp in milliseconds. return (Date.now() * rate) @@ -1367,10 +1406,13 @@ function Library (client) { this.js = () => { // Javascript interop. return window } + this['js-bind'] = (fn, thisArg, ...args) => { + return fn.bind(thisArg, ...args) + } this.on = (event, f) => { // Triggers on event. 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}`) { console.warn('failed ' + name, a, b) } else { @@ -1387,7 +1429,7 @@ function Library (client) { this['get-theme'] = () => { // Get theme values. return client.theme.active } - this['get-frame'] = () => { // Get theme values. + this['get-frame'] = () => { // Get frame shape. return client.surface.getFrame() } } diff --git a/scripts/lib/lain.js b/scripts/lib/lain.js index 660fca4..485bdca 100644 --- a/scripts/lib/lain.js +++ b/scripts/lib/lain.js @@ -40,15 +40,21 @@ function Lain (lib = {}) { defn: function (input, context) { const identifier = input[1].value if (context.scope[identifier]) { console.warn('Lain', `Redefining function: ${identifier}`) } + 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 () { const lambdaArguments = arguments const lambdaScope = fnParams.reduce(function (acc, x, i) { acc[x.value] = lambdaArguments[i] 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) { @@ -58,7 +64,10 @@ function Lain (lib = {}) { acc[x.value] = lambdaArguments[i] 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) { @@ -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) { if (input.length > 0 && input[0].value in special) { return special[input[0].value](input, context) diff --git a/scripts/library.js b/scripts/library.js index e5aa02a..b0e60cc 100644 --- a/scripts/library.js +++ b/scripts/library.js @@ -382,6 +382,28 @@ function Library (client) { 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 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] } - this.not = (a) => { + this.not = (a) => { //Negation. Returns true if a is false. Returns false if a is true. return !a } - // Arrays - - this.while = (fn, action) => { + this.while = (fn, action) => { //While loop. Execute action for as long as fn is true. while (fn()) { 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. for (let i = 0; i < arr.length; 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. return arr.map(fn); } @@ -455,7 +487,11 @@ function Library (client) { 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) } @@ -478,11 +514,11 @@ function Library (client) { return arr[arr.length - 1] } - this.rest = ([_, ...arr]) => { + this.rest = ([_, ...arr]) => { // Returns all arguments except the first 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 = [] if (step > 0) { for (let i = start; i <= end; i += step) { @@ -499,7 +535,7 @@ function Library (client) { // Objects 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. @@ -533,6 +569,10 @@ function Library (client) { 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 this.convolve = (kernel, rect = this['get-frame']()) => { @@ -575,6 +615,8 @@ function Library (client) { [-1, -1, -1]] } + // Points + this.offset = (a, b) => { // Offsets pos a with pos b, returns a. a.x += b.x 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))) } + // Utilities + this.print = (value) => { client.source.write('ronin-print', 'txt', value, 'text/plain') return value @@ -595,9 +639,9 @@ function Library (client) { return args } - this.debug = (arg) => { // Print arguments to console. - console.log(arg) - return arg + this.debug = (...args) => { // Print arguments to console. + console.log(...args) + return args } this.time = (rate = 1) => { // Returns timestamp in milliseconds. @@ -608,11 +652,24 @@ function Library (client) { 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. 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}`) { console.warn('failed ' + name, a, b) } else { @@ -634,7 +691,7 @@ function Library (client) { return client.theme.active } - this['get-frame'] = () => { // Get theme values. + this['get-frame'] = () => { // Get frame shape. return client.surface.getFrame() } }