@jplorg/jpl
Version:
JPL interpreter
184 lines (171 loc) • 5.97 kB
JavaScript
import createSingleton from '../createSingleton';
import systemInterpreter from '../interpreter/system';
import { applyObject } from '../library';
import * as internals from './internals';
import * as native from './native';
const builtins = `
# There are additional builtins that are implemented directly in the corresponding languages.
# Also the functions in the "internals" namespace are not exposed to the public and are only to be used by the builtins below.
func map(f): ([.[] | f()])
| func mapValues(f): (.[] |= f())
| func select(f): (if f() then . else void() end)
| func reduce(f, s): (
l = length()
| func i(i, n, c): (if n < l then i(i, n + 1, f->(.[n], c, n)) else c end)
| i(i, 0, s)
)
| func while(cond, f): (
func i(i): (if cond() then ., i->(f(), i) else void() end)
| i(i)
)
| func until(cond, f): (
func i(i): (if cond() then . else i->(f(), i) end)
| i(i)
)
| func range(from, to, step): (
t = type->(from) | if t != "number" then error->("cannot use \\(t) as a number") end
| t = type->(to) | if t != "number" then error->("cannot use \\(t) as a number") end
| s = (step | if . != 0 and . != null then abs() else 1 end)
| while->(
from,
if from <= to then func (): (. < to) else func (): (. > to) end,
if from <= to then func (): (. + s) else func (): (. - s) end
)
)
| func hasContent(): (not contains->(["null", "function"], type()) and not contains->([[], {}, ""], .))
# type selectors:
| func arrays(): (select(func (): (type() == "array")))
| func objects(): (select(func (): (type() == "object")))
| func iterables(): (select(func (): (contains->(["array", "object"], type()))))
| func scalars(): (select(func (): (not contains->(["array", "object"], type()))))
| func booleans(): (select(func (): (type() == "boolean")))
| func numbers(): (select(func (): (type() == "number")))
| func strings(): (select(func (): (type() == "string")))
| func nulls(): (select(func (): (type() == "null")))
| func functions(): (select(func (): (type() == "function")))
| func nullLikes(): (select(func (): (contains->(["null", "function"], type()))))
| func values(): (select(func (): (not contains->(["null", "function"], type()))))
| func contents(): (select(hasContent))
| func toEntries(): (o = . | keys() | map(func (): ({ key: ., value: o[.] })))
| func fromEntries(): (
reduce(
func (s): (s + { (.key ?? .Key ?? .name ?? .Name): if has("value") then .value else .Value end }),
{}
)
)
| func withEntries(f): (toEntries() | map(f) | fromEntries())
| func add(): (reduce(func (sum): (sum + .)))
| func join(sep): (
reduce(
func (sum): (
if sum == null then "" else sum + sep end
+ (. | toString())
)
) ?? ""
)
| func sortBy(f): ([.[] | [[f()], .]] | internals.sortEntries() | [.[][1]])
| func sort(): (sortBy(func (): (.)))
| func groupBy(f): (
key = 0 | value = 1
| [.[] | [[f()], [.]]] | internals.sortEntries()
| if . == []
then .
else
reduce->(
.[1:],
func (sum): (if sum[-1][key] == .[key] then (sum)[-1][value] += .[value] else sum + [.] end),
.[:1]
)
| [.[][value]]
end
)
| func group(): (groupBy(func (): (.)))
| func uniqueBy(f): (groupBy(f) | map(func (): (.[0])))
| func unique(): (group() | map(func (): (.[0])))
| func recurseBy(f, cond): (
c = cond ?? func (): (. != null)
| func r(r): (., (f() | select(c) | r(r)))
| r(r)
)
| func recurse(cond): (recurseBy(func (): ((arrays(), objects()) | .[]), cond))
| func reverse(): ([.[length() - 1 - range(0, length())]])
| func minBy(f): (
key = 0 | value = 1
| [.[] | [[f()], .]]
| reduce->(.[1:], func (sum): (if .[key] < sum[key] then . else sum end), .[0])
| .[value]
)
| func min(): (reduce->(.[1:], func (sum): (if . < sum then . else sum end), .[0]))
| func maxBy(f): (
key = 0 | value = 1
| [.[] | [[f()], .]]
| reduce->(.[1:], func (sum): (if .[key] > sum[key] then . else sum end), .[0])
| .[value]
)
| func max(): (reduce->(.[1:], func (sum): (if . > sum then . else sum end), .[0]))
| func first(f): ([f()][0])
| func nth(which, f): (
[f()]
| .[if which | type() == "number" then which else length() | which() end]
)
| func last(f): ([f()][-1])
| func isEmpty(f): (first(func (): ((f() | false), true)))
| func allBy(f, cond): (isEmpty(func (): (f() | cond() and void())))
| func all(cond): (allBy(func (): (.[]), cond))
| func anyBy(f, cond): (not isEmpty(func (): (f() | cond() or void())))
| func any(cond): (anyBy(func (): (.[]), cond))
| func getPath(path): (
fields = (path | if type() == 'string' then . / '.' end)
| reduce->(fields, func (sum): sum[if type->(sum) == 'array' then toNumber() else toString() end], .)
)
| func updatePath(path, update): (
fields = (path | if type() == 'string' then (. / '.')[] |= (v = . | try toNumber() catch v) end)
| reduce->(
reverse->(fields),
func (sum): (
field = .
| func (): (
if type() == 'array'
then (
input = .
| try toNumber->(field) catch toString->(field)
| if type() == 'number'
then input[.] |= sum()
else {}[.] |= sum()
end
) elif type() == 'object'
then (
.[toString->(field)] |= sum()
) elif type->(field) == 'number'
then (
[][field] |= sum()
) else (
{}[toString->(field)] |= sum()
)
end
)
),
update
)->(.)
)
| func setPath(path, value): (
updatePath(path, func (): value)
)
`;
const getBuiltins = createSingleton(async () => {
await undefined;
let result = { ...native };
const program = await systemInterpreter.parse(builtins, {
runtime: {
vars: { internals, ...native },
adjustResult: (value, scope) => {
const { internals: _, ...vars } = scope.vars;
result = applyObject(result, Object.entries(vars));
return [];
},
},
});
await program.run([null]);
return result;
});
export default getBuiltins;