commit
7a7914ddbe
26
README.md
26
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.
|
||||
|
@ -18,10 +18,10 @@
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
const {dialog,app} = require('electron').remote;
|
||||
const fs = require('fs');
|
||||
const app_path = app.getAppPath();
|
||||
const ronin = new Ronin();
|
||||
ronin.controller = new Controller();
|
||||
const fs = require('fs')
|
||||
const ronin = new Ronin()
|
||||
|
||||
ronin.controller = new Controller()
|
||||
ronin.controller.add("default","*","About",() => { require('electron').shell.openExternal('https://github.com/hundredrabbits/Dotgrid'); },"CmdOrCtrl+,");
|
||||
ronin.controller.add("default","*","Fullscreen",() => { app.toggleFullscreen() },"CmdOrCtrl+Enter");
|
||||
ronin.controller.add("default","*","Hide",() => { app.toggleVisible() },"CmdOrCtrl+H");
|
||||
@ -53,9 +53,10 @@
|
||||
ronin.controller.add("default","Theme","Reset Theme",() => { ronin.theme.reset() },"CmdOrCtrl+Shift+Backspace")
|
||||
ronin.controller.addSpacer('default', 'Theme', 'Download')
|
||||
ronin.controller.add("default","Theme","Download Themes..",() => { require('electron').shell.openExternal('https://github.com/hundredrabbits/Themes') })
|
||||
ronin.controller.commit();
|
||||
ronin.install(document.body);
|
||||
window.addEventListener('load', () => { ronin.start(); })
|
||||
ronin.controller.commit()
|
||||
|
||||
ronin.install(document.body)
|
||||
window.addEventListener('load', () => { ronin.start() })
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -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))))
|
@ -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-- }
|
||||
|
@ -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.
|
||||
|
@ -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<input.length ; i+=2) {
|
||||
obj[await interpret(input[i] ,context)] = await interpret(input[i+1], context)
|
||||
for (let i = 1; i < input.length; i += 2) {
|
||||
obj[await interpret(input[i], context)] = await interpret(input[i + 1], context)
|
||||
}
|
||||
return obj
|
||||
}
|
||||
@ -101,7 +102,7 @@ function Lisp (input, lib) {
|
||||
}
|
||||
|
||||
const interpret = async function (input, context) {
|
||||
if (!input) { console.warn('error', context.scope); return null }
|
||||
if (!input) { console.warn('Lisp', 'error', context.scope); return null }
|
||||
|
||||
if (context === undefined) {
|
||||
return interpret(input, new Context(lib))
|
||||
@ -152,27 +153,35 @@ function Lisp (input, lib) {
|
||||
}
|
||||
|
||||
const tokenize = function (input) {
|
||||
const i = input.replace(/^\;.*\n?/gm, '').split('"')
|
||||
return i.map(function (x, i) {
|
||||
return i % 2 === 0 ?
|
||||
x.replace(/\(/g, ' ( ')
|
||||
.replace(/\)/g, ' ) ')
|
||||
.replace(/' \( /g, ' \'( ') // '()
|
||||
.replace(/\{/g, ' { ') // {}
|
||||
.replace(/\}/g, ' } ') // {}
|
||||
: x.replace(/ /g, '!whitespace!')
|
||||
const i = input.replace(/^\;.*\n?/gm, '').replace('λ', 'lambda ').split('"')
|
||||
return i.map(function (x, i) {
|
||||
return i % 2 === 0
|
||||
? x.replace(/\(/g, ' ( ')
|
||||
.replace(/\)/g, ' ) ')
|
||||
.replace(/' \( /g, ' \'( ') // '()
|
||||
.replace(/\{/g, ' { ') // {}
|
||||
.replace(/\}/g, ' } ') // {}
|
||||
: x.replace(/ /g, '!whitespace!')
|
||||
})
|
||||
.join('"').trim().split(/\s+/)
|
||||
.map(function (x) { return x.replace(/!whitespace!/g, ' ') })
|
||||
.join('"').trim().split(/\s+/)
|
||||
.map(function (x) { return x.replace(/!whitespace!/g, ' ') })
|
||||
}
|
||||
|
||||
this.inc = function () {
|
||||
return includes.reduce((acc, item) => {
|
||||
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})`))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 () {
|
||||
|
@ -6,4 +6,8 @@
|
||||
|
||||
; define a function
|
||||
(defn add-two (a) (add 2 a))
|
||||
(echo (add-two 4))
|
||||
(echo (add-two 4))
|
||||
|
||||
; use a lambda
|
||||
(times 5
|
||||
(λ (a) (echo (concat "time:" a))))
|
@ -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))
|
||||
|
@ -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)
|
||||
)))
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user