Skip to content

Commit

Permalink
Flatten logic
Browse files Browse the repository at this point in the history
  • Loading branch information
dy committed Feb 17, 2024
1 parent 506891b commit b607a26
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 49 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ fn({min: 5}) // min*60 + "sec" == "300sec"
_Subscript_ provides pluggable language [features](./features) and API to customize syntax:

* `unary(str, precedence, postfix=false)` − register unary operator, either prefix or postfix.
* `binary(str, precedence, rightAssoc=false)` − register binary operator, optionally right-associative.
* `nary(str, precedence)` − register n-ary (sequence) operator.
* `binary(str, precedence, rassoc=false)` − register binary operator, optionally right-associative.
* `nary(str, precedence)` − register n-ary (sequence) operator like `a;b;` or `a,b`, allows missing args.
* `group(str, precedence)` - register group, like `[]`, `{}`, `()`, optionally allow empty.
* `access(str, precedence)` - register access operator, like `a[b]`.
* `token(str, precedence, lnode => node)` − register custom token or literal. Callback takes left-side node and returns complete expression node.
Expand Down
3 changes: 2 additions & 1 deletion feature/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import { SPACE, STAR, PREC_TOKEN } from "../src/const.js"
import { token, skip, cur, idx, expr } from "../src/parse.js"

// /**/, //
token('/*', PREC_TOKEN, (a, prec) => (skip(c => c !== STAR && cur.charCodeAt(idx + 1) !== 47), skip(2), a || expr(prec) || ['']))
// FIXME: try replacing with group
token('/*', PREC_TOKEN, (a, prec) => (skip(c => c !== STAR && cur.charCodeAt(idx + 1) !== 47), skip(2), a || expr(prec) || []))
token('//', PREC_TOKEN, (a, prec) => (skip(c => c >= SPACE), a || expr(prec) || ['']))
14 changes: 5 additions & 9 deletions feature/logic.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
import { PREC_LOR, PREC_LAND, PREC_PREFIX } from '../src/const.js';
import { unary, nary } from "../src/parse.js"
import { unary, binary } from "../src/parse.js"
import { operator, compile } from "../src/compile.js"

unary('!', PREC_PREFIX), operator('!', (a, b) => !b && (a = compile(a), ctx => !a(ctx)))

nary('||', PREC_LOR), operator('||', (...args) => (
args = args.map(compile),
ctx => { let arg, res; for (arg of args) if (res = arg(ctx)) return res; return res }
))
binary('||', PREC_LOR)
operator('||', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) || b(ctx)))

nary('&&', PREC_LAND), operator('&&', (...args) => (
args = args.map(compile),
ctx => { let arg, res; for (arg of args) if (!(res = arg(ctx))) return res; return res }
))
binary('&&', PREC_LAND)
operator('&&', (a, b) => (a = compile(a), b = compile(b), ctx => a(ctx) && b(ctx)))
40 changes: 20 additions & 20 deletions test/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { parse, binary, nary, unary, token, skip } from '../subscript.js'
import { PREC_MULT } from '../src/const.js'

test('parse: basic', t => {
is(parse('a()'), ['(', 'a', ''])
is(parse('a()'), ['(', 'a', ,])
// is(parse('1 + 2 + 3'), ['+', '1', '2', '3'])
is(parse('1 + 2 + 3'), ['+', ['+', [, 1], [, 2]], [, 3]])
is(parse('a + b * c'), ['+', 'a', ['*', 'b', 'c']])
Expand All @@ -29,25 +29,25 @@ test('parse: basic', t => {
is(parse(`-a.b+a`), ['+', ['-', ['.', 'a', 'b']], 'a'])
is(parse(`-a-b`), ['-', ['-', 'a'], 'b'])
is(parse(`+-a.b+-!a`), ['+', ['+', ['-', ['.', 'a', 'b']]], ['-', ['!', 'a']]])
is(parse(`1.0`), ['', 1])
is(parse(`1.0`), [, 1])

// is(parse(` .1 + -1.0 - 2.ce+1 `), ['-', ['+', '.1', ['-', '1']], '2c'])
is(parse(`( a, b )`), ['()', [',', 'a', 'b']])
is(parse(`a * c / b`), ['/', ['*', 'a', 'c'], 'b'])
is(parse('a(b)(c)'), ['(', ['(', 'a', 'b'], 'c'])
is(parse(`"abcd" + "efgh"`), ['+', ['', 'abcd'], ['', 'efgh']])
is(parse('0 + 1 + 2.0'), ['+', ['+', ['', 0], ['', 1]], ['', 2.0]])
is(parse(`"abcd" + "efgh"`), ['+', [, 'abcd'], [, 'efgh']])
is(parse('0 + 1 + 2.0'), ['+', ['+', [, 0], [, 1]], [, 2.0]])
is(parse('0 * 1 * 2 / 1 / 2 * 1'), ["*", ["/", ["/", ["*", ["*", ["", 0], ["", 1]], ["", 2]], ["", 1]], ["", 2]], ["", 1]])

// NOTE: these cases target tree mappers, rather than direct ops
is(parse('a()()()'), ['(', ['(', ['(', 'a', ''], ''], ''])
is(parse('a()()()'), ['(', ['(', ['(', 'a', ,], ,], ,])
is(parse(`a ( ccc. d, -+1.0 )`), ["(", "a", [",", [".", "ccc", "d"], ["-", ["+", ["", 1]]]]])
is(parse(`(a + 2) * 3 / 2 + b * 2 - 1`), ["-", ["+", ["/", ["*", ["()", ["+", "a", ["", 2]]], ["", 3]], ["", 2]], ["*", "b", ["", 2]]], ["", 1]])

// is(parse('1 + 2 * 3 ** 4 + 5'), ['+', '1', ['*', '2', ['**', '3', '4']], '5'])
is(parse(`a + b * c ** d | e`), ['|', ['+', 'a', ['*', 'b', ['**', 'c', 'd']]], 'e'])

is(parse('x(a + 3)'), ['(', 'x', ['+', 'a', ['', 3]]])
is(parse('x(a + 3)'), ['(', 'x', ['+', 'a', [, 3]]])
// is(parse('1 + x(a.b + 3.5)'), ['+', '1', ['(', 'x', ['+', ['.', 'a', 'b'], '3.5']]])
is(parse('a[b]'), ['[', 'a', 'b'])
// is(parse('(a(b) + 3.5)'), ['(', ['+', ['(', 'a', 'b'], '3.5']])
Expand All @@ -70,7 +70,7 @@ test('parse: strings', t => {
throws(() => parse('"a'), 'bad string')
is(parse('a + b'), ['+', 'a', 'b'])
throws(() => parse('"a" + "b'), 'bad string')
is(parse('"a" + ("1" + "2")'), ['+', ['', 'a'], ['()', ['+', ['', '1'], ['', '2']]]])
is(parse('"a" + ("1" + "2")'), ['+', [, 'a'], ['()', ['+', [, '1'], [, '2']]]])

// parse.quote['<?']='?>'
// is(parse('"abc" + <?js\nxyz?>'), ['+','"abc','<?js\nxyz?>'])
Expand All @@ -80,7 +80,7 @@ test('parse: strings', t => {
})

test('parse: bad number', t => {
is(parse('-1.23e-2'), ['-', ['', 1.23e-2]])
is(parse('-1.23e-2'), ['-', [, 1.23e-2]])
throws(t => parse('.e-1'))
})

Expand All @@ -101,8 +101,8 @@ test('parse: signs', t => {
is(parse('a+-x'), ['+', 'a', ['-', 'x']])
// is(parse('1+-1.23e-2-1.12'),['-',['+','1',['-','1.23e-2']], '1.12'])
is(parse('-+(x)'), ['-', ['+', ['()', 'x']]])
is(parse('+1.12-+-a+-(+x)'), ['+', ['-', ['+', ['', 1.12]], ['+', ['-', 'a']]], ['-', ['()', ['+', 'x']]]])
is(parse('+1.12-+-a[+x]'), ['-', ['+', ['', 1.12]], ['+', ['-', ['[', 'a', ['+', 'x']]]]])
is(parse('+1.12-+-a+-(+x)'), ['+', ['-', ['+', [, 1.12]], ['+', ['-', 'a']]], ['-', ['()', ['+', 'x']]]])
is(parse('+1.12-+-a[+x]'), ['-', ['+', [, 1.12]], ['+', ['-', ['[', 'a', ['+', 'x']]]]])
is(parse('+x-+-x'), ['-', ['+', 'x'], ['+', ['-', 'x']]])
is(parse('-a[x]'), ['-', ['[', 'a', 'x']])
is(parse('-a.b[x](y)'), ['-', ['(', ['[', ['.', 'a', 'b'], 'x'], 'y']])
Expand All @@ -124,21 +124,21 @@ test('parse: unaries', t => {
is(parse('a * -a'), ['*', 'a', ['-', 'a']])
})
test('parse: postfix unaries', t => {
is(parse('a--'), ['+', ['--', 'a'], ['', 1]])
is(parse('a++'), ['-', ['++', 'a'], ['', 1]])
is(parse('a--'), ['+', ['--', 'a'], [, 1]])
is(parse('a++'), ['-', ['++', 'a'], [, 1]])
// is(parse('1++(b)'),[['++',1],'b']) // NOTE: not supported by JS btw
})

test('parse: prop access', t => {
is(parse('a["b"]["c"][0]'), ['[', ['[', ['[', 'a', ['', 'b']], ['', 'c']], ['', 0]])
is(parse('a.b.c.0'), ['.', ['.', ['.', 'a', 'b'], 'c'], ['', 0]])
is(parse('a["b"]["c"][0]'), ['[', ['[', ['[', 'a', [, 'b']], [, 'c']], [, 0]])
is(parse('a.b.c.0'), ['.', ['.', ['.', 'a', 'b'], 'c'], [, 0]])
// is(evaluate(['.','a','b',new String('c'),0], {a:{b:{c:[2]}}}), 2)
// is(evaluate(['.',['.',['.','a','b'],new String('c')],0], {a:{b:{c:[2]}}}), 2)
})

test('parse: parens', t => {
is(parse('x+(b)()'), ['+', 'x', ['(', ['()', 'b'], '']])
is(parse('(x)+-b()'), ['+', ['()', 'x'], ['-', ['(', 'b', '']]])
is(parse('x+(b)()'), ['+', 'x', ['(', ['()', 'b'], undefined]])
is(parse('(x)+-b()'), ['+', ['()', 'x'], ['-', ['(', 'b', undefined]]])
is(parse('x+a(b)'), ['+', 'x', ['(', 'a', 'b']])
is(parse('x+(b)'), ['+', 'x', ['()', 'b']])
is(parse('x+-(b)'), ['+', 'x', ['-', ['()', 'b']]])
Expand All @@ -159,7 +159,7 @@ test('parse: parens', t => {
})

test('parse: functions', t => {
is(parse('a()'), ['(', 'a', ''])
is(parse('a()'), ['(', 'a', undefined])
is(parse('(c,d)'), ['()', [',', 'c', 'd']])
is(parse('a(b)(d)'), ['(', ['(', 'a', 'b'], 'd'])
is(parse('a(b,c)(d)'), ['(', ['(', 'a', [',', 'b', 'c']], 'd'])
Expand Down Expand Up @@ -217,9 +217,9 @@ test('ext: justin', async t => {
is(parse('a;b'), [';', 'a', 'b'])
is(parse('a;b;'), [';', 'a', 'b', ,])
is(parse('b;'), [';', 'b', ,])
is(parse(`"abcd" + 'efgh'`), ['+', ['', 'abcd'], ['', 'efgh']])
is(parse('{x:~1, "y":2**2}["x"]'), ['[', ['{}', [',', [':', 'x', ['~', ['', 1]]], [':', ['', 'y'], ['**', ['', 2], ['', 2]]]]], ['', 'x']])
is(parse('a((1 + 2), (e > 0 ? f : g))'), ['(', 'a', [',', ['()', ['+', ['', 1], ['', 2]]], ['()', ['?', ['>', 'e', ['', 0]], 'f', 'g']]]])
is(parse(`"abcd" + 'efgh'`), ['+', [, 'abcd'], [, 'efgh']])
is(parse('{x:~1, "y":2**2}["x"]'), ['[', ['{}', [',', [':', 'x', ['~', [, 1]]], [':', [, 'y'], ['**', [, 2], [, 2]]]]], [, 'x']])
is(parse('a((1 + 2), (e > 0 ? f : g))'), ['(', 'a', [',', ['()', ['+', [, 1], [, 2]]], ['()', ['?', ['>', 'e', [, 0]], 'f', 'g']]]])
})

test('parse: unfinished sequences', async t => {
Expand Down
23 changes: 6 additions & 17 deletions test/subscript.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ test('readme', t => {
sameAsJs(`min * 60 + "sec"`, { min: 5 })

binary('|', 6), operator('|', (a, b) => (a = compile(a), b = compile(b), (ctx) => a(ctx)?.pipe?.(b(ctx)) || (a(ctx) | b(ctx))))
token('=>', 2, (args, body) => (body = expr(), ctx => (...args) => body())) // single-arg arrow function parser

let evaluate = subscript(`
interval(350)
Expand Down Expand Up @@ -336,14 +335,12 @@ test('ext: in operator', async t => {
throws(() => subscript('b inc'))
})

test('ext: list', t => {
token('[', 20, a => !a && ['[', expr(0, 93) || ''])
operator('[', (a, b) => !b && (
!a ? ctx => [] : // []
a[0] === ',' ? (a = a.slice(1).map(compile), ctx => a.map(a => a(ctx))) : // [a,b,c]
(a = compile(a), ctx => [a(ctx)]) // [a]
)
)
test('ext: list', async t => {
await import('../feature/array.js')

is(subscript('[]')(), [])
is(subscript('[ 1 ]')(), [1])
is(subscript('[ 1, 2+3, "4" ]')(), [1, 5, '4'])

is(subscript('[1,2,3,4,5,6]')(), [1, 2, 3, 4, 5, 6])
is(subscript('[1,2,3,4,5]')(), [1, 2, 3, 4, 5])
Expand Down Expand Up @@ -410,14 +407,6 @@ test('ext: object', async t => {
sameAsJs('{b:true, c:d}', { d: true })
})

test('ext: array', async t => {
await import('../feature/array.js')

is(subscript('[]')(), [])
is(subscript('[ 1 ]')(), [1])
is(subscript('[ 1, 2+3, "4" ]')(), [1, 5, '4'])
})

test('ext: arrow', async t => {
await import('../feature/arrow.js')

Expand Down

0 comments on commit b607a26

Please sign in to comment.