diff --git a/README.md b/README.md index d49c62f..b1251db 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ npm start ## Library +Additional functions can be found in the [includes](https://github.com/hundredrabbits/Ronin/tree/master/desktop/sources/lisp), you can also look at the [examples](https://github.com/hundredrabbits/Ronin/tree/master/examples) to see them in action. + - `(import path rect)` Imports a graphic file with format. - `(export path ~format ~quality)` Exports a graphic file with format. - `(open path)` Imports a graphic file and resizes the frame. @@ -38,6 +40,17 @@ npm start - `(stroke ~shape)` Strokes a shape. - `(fill ~rect)` Fills a shape. - `(clear ~rect)` Clears a rect. +- `(frame)` Returns a rect of the frame. +- `(center)` Returns a position of the center of the frame. +- `(resize w h ~fit)` Resizes the canvas to target w and h, returns the rect. +- `(rescale w h)` Rescales the canvas to target ratio of w and h, returns the rect. +- `(crop rect)` Crop canvas to rect. +- `(clone a b)` +- `(theme variable ~el)` +- `(gradient [x1 y1 x2 y2] ~colors 'black'])` +- `(pixels rect fn q)` +- `(saturation pixel ~q)` +- `(contrast pixel ~q)` - `(concat ...items)` Concat multiple strings. - `(add ...args)` Adds values. - `(sub ...args)` Subtracts values. @@ -71,17 +84,8 @@ npm start - `(get item key)` Gets an object's parameter with name. - `(set item key val)` Sets an object's parameter with name as value. - `(of h ...keys)` Gets object parameters with names. -- `(frame)` Returns a rect of the frame. -- `(center)` Returns a position of the center of the frame. -- `(resize w h ~fit)` Resizes the canvas to target w and h, returns the rect. -- `(rescale w h)` Rescales the canvas to target ratio of w and h, returns the rect. -- `(crop rect)` Crop canvas to rect. -- `(clone a b)` -- `(theme variable ~el)` -- `(gradient [x1 y1 x2 y2] ~colors 'black'])` -- `(pixels rect fn q)` -- `(saturation pixel ~q)` -- `(contrast pixel ~q)` +- `(keys item)` Returns a list of the object's keys +- `(values item)` Returns a list of the object's values - `(dir ~path)` Returns the content of a directory. - `(file ~path)` Returns the content of a file. - `(dirpath ~path)` Returns the path of a directory. diff --git a/desktop/sources/index.html b/desktop/sources/index.html index 955ce4b..ce810ca 100644 --- a/desktop/sources/index.html +++ b/desktop/sources/index.html @@ -18,10 +18,10 @@ diff --git a/desktop/sources/lisp/prelude.lisp b/desktop/sources/lisp/prelude.lisp index 82c0999..ef8b670 100644 --- a/desktop/sources/lisp/prelude.lisp +++ b/desktop/sources/lisp/prelude.lisp @@ -1,11 +1,20 @@ +; (echo "Loading prelude.lisp") - +; translate (defn translate - (r p) - (clone - r - (rect - (of p :x) - (of p :y) - (of r :w) - (of r :h)))) + (r p) + (clone r + (rect + (of p :x) + (of p :y) + (of r :w) + (of r :h)))) +; times +(defn times + (v f) + ( + (f v) + (if + (gt v 1) + (times + (sub v 1) f)))) \ No newline at end of file diff --git a/desktop/sources/scripts/commander.js b/desktop/sources/scripts/commander.js index f42d1d1..852b2b9 100644 --- a/desktop/sources/scripts/commander.js +++ b/desktop/sources/scripts/commander.js @@ -33,8 +33,7 @@ function Commander (ronin) { this.run = (txt = this._input.value) => { if (txt.indexOf('$') > -1) { ronin.log('Present: $'); return } - const inter = new Lisp(txt, ronin.library) - inter.toPixels() + ronin.interpreter.run(txt) ronin.always === true && requestAnimationFrame(() => this.run(txt)) } @@ -47,6 +46,10 @@ function Commander (ronin) { this.reindent = function () { let val = this._input.value.replace(/\n/g, '').replace(/ +(?= )/g, '').replace(/\( \(/g, '((').replace(/\) \)/g, '))').trim() let depth = 0 + if (val.split('(').length !== val.split(')').length) { + ronin.log('Uneven number of parens.') + return + } for (let i = 0; i < val.length; i++) { const c = val.charAt(i) if (c === '(') { depth++ } else if (c === ')') { depth-- } diff --git a/desktop/sources/scripts/library.js b/desktop/sources/scripts/library.js index 0a8aa78..35b48b2 100644 --- a/desktop/sources/scripts/library.js +++ b/desktop/sources/scripts/library.js @@ -64,6 +64,83 @@ function Library (ronin) { return rect } + // Frame + + this.frame = () => { // Returns a rect of the frame. + return ronin.surface.getFrame() + } + + this.center = () => { // Returns a position of the center of the frame. + const rect = this.frame() + return this.pos(rect.w / 2, rect.h / 2) + } + + this.resize = async (w, h, fit = true) => { // Resizes the canvas to target w and h, returns the rect. + const rect = { x: 0, y: 0, w, h } + const a = document.createElement('img') + const b = document.createElement('img') + a.src = ronin.surface.el.toDataURL() + await ronin.surface.resizeImage(a, b) + ronin.surface.resize(rect, fit) + return ronin.surface.draw(b, rect) + } + + this.rescale = async (w, h) => { // Rescales the canvas to target ratio of w and h, returns the rect. + const rect = { x: 0, y: 0, w: this.frame().w * w, h: this.frame().h * h } + const a = document.createElement('img') + const b = document.createElement('img') + a.src = ronin.surface.el.toDataURL() + await ronin.surface.resizeImage(a, b) + ronin.surface.resize(rect, true) + return ronin.surface.draw(b, rect) + } + + this.crop = async (rect) => { // Crop canvas to rect. + return ronin.surface.crop(rect) + } + + this.clone = (a, b) => { + ronin.surface.clone(a, b) + return [a, b] + } + + this.theme = (variable, el = document.documentElement) => { + // ex. (theme "f_main") -> :root { --f_main: "#fff" } + return getComputedStyle(el).getPropertyValue(`--${variable}`) + } + + // Gradients + + this.gradient = ([x1, y1, x2, y2], colors = ['white', 'black']) => { + return ronin.surface.linearGradient(x1, y1, x2, y2, colors) + } + + // Pixels + + this.pixels = (rect, fn, q) => { + const img = ronin.surface.context.getImageData(0, 0, rect.w, rect.h) + for (let i = 0, loop = img.data.length; i < loop; i += 4) { + const pixel = { r: img.data[i], g: img.data[i + 1], b: img.data[i + 2], a: img.data[i + 3] } + const processed = fn(pixel, q) + img.data[i] = processed[0] + img.data[i + 1] = processed[1] + img.data[i + 2] = processed[2] + img.data[i + 3] = processed[3] + } + ronin.surface.context.putImageData(img, 0, 0) + return rect + } + + this.saturation = (pixel, q = 1) => { + const color = 0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b + return [(color * (1 - q)) + (pixel.r * q), (color * (1 - q)) + (pixel.g * q), (color * (1 - q)) + (pixel.b * q), pixel.a] + } + + this.contrast = (pixel, q = 1) => { + const intercept = 128 * (1 - q) + return [pixel.r * q + intercept, pixel.g * q + intercept, pixel.b * q + intercept, pixel.a] + } + // Strings this.concat = function (...items) { // Concat multiple strings. @@ -241,83 +318,6 @@ function Library (ronin) { return Object.values(item) } - // Frame - - this.frame = () => { // Returns a rect of the frame. - return ronin.surface.getFrame() - } - - this.center = () => { // Returns a position of the center of the frame. - const rect = this.frame() - return this.pos(rect.w / 2, rect.h / 2) - } - - this.resize = async (w, h, fit = true) => { // Resizes the canvas to target w and h, returns the rect. - const rect = { x: 0, y: 0, w, h } - const a = document.createElement('img') - const b = document.createElement('img') - a.src = ronin.surface.el.toDataURL() - await ronin.surface.resizeImage(a, b) - ronin.surface.resize(rect, fit) - return ronin.surface.draw(b, rect) - } - - this.rescale = async (w, h) => { // Rescales the canvas to target ratio of w and h, returns the rect. - const rect = { x: 0, y: 0, w: this.frame().w * w, h: this.frame().h * h } - const a = document.createElement('img') - const b = document.createElement('img') - a.src = ronin.surface.el.toDataURL() - await ronin.surface.resizeImage(a, b) - ronin.surface.resize(rect, true) - return ronin.surface.draw(b, rect) - } - - this.crop = async (rect) => { // Crop canvas to rect. - return ronin.surface.crop(rect) - } - - this.clone = (a, b) => { - ronin.surface.clone(a, b) - return [a, b] - } - - this.theme = (variable, el = document.documentElement) => { - // ex. (theme "f_main") -> :root { --f_main: "#fff" } - return getComputedStyle(el).getPropertyValue(`--${variable}`) - } - - // Gradients - - this.gradient = ([x1, y1, x2, y2], colors = ['white', 'black']) => { - return ronin.surface.linearGradient(x1, y1, x2, y2, colors) - } - - // Pixels - - this.pixels = (rect, fn, q) => { - const img = ronin.surface.context.getImageData(0, 0, rect.w, rect.h) - for (let i = 0, loop = img.data.length; i < loop; i += 4) { - const pixel = { r: img.data[i], g: img.data[i + 1], b: img.data[i + 2], a: img.data[i + 3] } - const processed = fn(pixel, q) - img.data[i] = processed[0] - img.data[i + 1] = processed[1] - img.data[i + 2] = processed[2] - img.data[i + 3] = processed[3] - } - ronin.surface.context.putImageData(img, 0, 0) - return rect - } - - this.saturation = (pixel, q = 1) => { - const color = 0.2126 * pixel.r + 0.7152 * pixel.g + 0.0722 * pixel.b - return [(color * (1 - q)) + (pixel.r * q), (color * (1 - q)) + (pixel.g * q), (color * (1 - q)) + (pixel.b * q), pixel.a] - } - - this.contrast = (pixel, q = 1) => { - const intercept = 128 * (1 - q) - return [pixel.r * q + intercept, pixel.g * q + intercept, pixel.b * q + intercept, pixel.a] - } - // File System this.dir = (path = this.dirpath()) => { // Returns the content of a directory. diff --git a/desktop/sources/scripts/lisp.js b/desktop/sources/scripts/lisp.js index d2a9541..1eebdd8 100644 --- a/desktop/sources/scripts/lisp.js +++ b/desktop/sources/scripts/lisp.js @@ -1,14 +1,15 @@ 'use strict' -function Lisp (input, lib) { +function Lisp (lib = {}, includes = []) { + console.log(includes) const path = require('path') const fs = require('fs') const TYPES = { identifier: 0, number: 1, string: 2, bool: 3 } + const Context = function (scope, parent) { this.scope = scope this.parent = parent - this.get = function (identifier) { if (identifier in this.scope) { return this.scope[identifier] @@ -20,7 +21,7 @@ function Lisp (input, lib) { const special = { include: (input, context) => { - if (!input[1].value || !fs.existsSync(input[1].value)) { console.warn('Source', input[1].value); return [] } + if (!input[1].value || !fs.existsSync(input[1].value)) { console.warn('Lisp', 'No file: ' + input[1].value); return [] } const file = fs.readFileSync(input[1].value, { encoding: 'utf-8' }) return interpret(this.parse(`(${file})`), context) }, @@ -69,7 +70,7 @@ function Lisp (input, lib) { __fn: function (input, context) { return async function () { const lambdaArguments = arguments - const keys = [...new Set(input.slice(2).flat(100).filter(i => + const keys = [...new Set(input.slice(2).flat(100).filter(i => i.type === TYPES.identifier && i.value[0] === '%' ).map(x => x.value).sort())] @@ -82,8 +83,8 @@ function Lisp (input, lib) { }, __obj: async function (input, context) { const obj = {} - for (let i = 1 ; i { + const p = path.join(__dirname, `lisp/${item}.lisp`) + if (!fs.existsSync(p)) { console.warn('Lisp', `Missing include: ${p}`); return acc } + return `${acc}(include "${p}") ` + }, '') } this.parse = function (input) { return parenthesize(tokenize(input)) } - this.toPixels = async function () { + this.run = async function (input) { return interpret(this.parse(`( - (include "./sources/lisp/prelude.lisp") - ${input})`)) + ${this.inc()} + ${input})`)) } } diff --git a/desktop/sources/scripts/ronin.js b/desktop/sources/scripts/ronin.js index 1e229c7..927d388 100644 --- a/desktop/sources/scripts/ronin.js +++ b/desktop/sources/scripts/ronin.js @@ -11,6 +11,8 @@ function Ronin () { b_inv: '#ffb545' } + this.includes = ['prelude'] + this.el = document.createElement('div') this.el.id = 'ronin' @@ -19,6 +21,7 @@ function Ronin () { this.commander = new Commander(this) this.surface = new Surface(this) this.library = new Library(this) + this.interpreter = new Lisp(this.library, this.includes) // Parameters diff --git a/desktop/sources/scripts/source.js b/desktop/sources/scripts/source.js index d0abb44..8ceec2e 100644 --- a/desktop/sources/scripts/source.js +++ b/desktop/sources/scripts/source.js @@ -126,12 +126,6 @@ function Source (ronin) { return `${str}` } - this.locate = function (name) { - if (!this.path) { return } - const loc = path.join(this.folder(), name) - return fs.existsSync(loc) ? loc : null - } - // Etc this.name = function () { diff --git a/examples/basics.lisp b/examples/basics.lisp index 997be5d..cbffb3e 100644 --- a/examples/basics.lisp +++ b/examples/basics.lisp @@ -6,4 +6,8 @@ ; define a function (defn add-two (a) (add 2 a)) -(echo (add-two 4)) \ No newline at end of file +(echo (add-two 4)) + +; use a lambda +(times 5 + (λ (a) (echo (concat "time:" a)))) \ No newline at end of file diff --git a/examples/benchmark.lisp b/examples/benchmark.lisp index e0b698e..03e1e51 100644 --- a/examples/benchmark.lisp +++ b/examples/benchmark.lisp @@ -35,14 +35,14 @@ (test "range simple" (range 0 4) (0 1 2 3 4)) (test "range with step" (range 0 4 2) (0 2 4)) (test "range with negative step" (range 0 -4 -1) (0 -1 -2 -3 -4)) - (test "map" (map (lambda (a) (add 1 a)) (1 2 3)) (2 3 4)) - (test "filter" (filter (lambda (a) (eq 0 (mod a 2))) (2 3 4 5 6)) (2 4 6)) - (test "reduce" (reduce (lambda (acc val) (add acc val)) (1 2 3) 4) 10) + (test "map" (map (λ (a) (add 1 a)) (1 2 3)) (2 3 4)) + (test "filter" (filter (λ (a) (eq 0 (mod a 2))) (2 3 4 5 6)) (2 4 6)) + (test "reduce" (reduce (λ (acc val) (add acc val)) (1 2 3) 4) 10) ; Scope (def aaa 123) - (def addOne (lambda (a) (add a 1))) + (def addOne (λ (a) (add a 1))) (test "def - value" aaa 123) (test "def - func" (addOne 4) 5) (defn addTwo (a) (add 2 a)) diff --git a/examples/dejong.lisp b/examples/dejong.lisp index 654271c..d373008 100644 --- a/examples/dejong.lisp +++ b/examples/dejong.lisp @@ -16,7 +16,7 @@ (defn dejong (r a b c d) (reduce - (lambda (acc val) + (λ (acc val) (first ( (_dejong (first acc) (last acc) a b c d) ))) diff --git a/examples/theme.lisp b/examples/theme.lisp index cba7b21..7a5db32 100644 --- a/examples/theme.lisp +++ b/examples/theme.lisp @@ -2,7 +2,7 @@ (clear) (def col - (lambda + (λ (i) (of ( @@ -16,7 +16,7 @@ (theme "b_inv")) (mod i 8)))) (def rec - (lambda + (λ (v i) (if (gt v 0)