-
- Code
- Synthesis
collapsed:: true
- ```javascript
const Module = logseq.Module
const LS = Module.Logseq = logseq.api
const Kits = Module.Kits
Function.prototype.setProto = function setProto(func){
this.prototype[func.name] = func
return this
}
Function.setProto(function setStatic(func){
this[func.name] = func
return this
})
.setProto(function addHasInst(){
this.hasInst = Function.thisHasInst.bind(this)
return this
})
.setProto(function onType(func){
(this.forType ||= {})[func.name] = func
return this
})
.setStatic(function thisHasInst(inst){
return (inst instanceof this)
})
.addHasInst()
Array.setStatic(function appendOne(one){
this.push(one)
return this
})
Object.setStatic(function getOwn(obj, name){
return (Object.hasOwn(obj, name)) ? obj[name] : undefined
})
Promise.addHasInst()
const Syn = Module.setChild("Synthesis")
.setStatic(function error(msg, content){
return Syn.Error(msg, ": " + content + " , in " + this.constructor.name)
})
.setStatic(function htmlFor(obj){
if (obj === null || obj === undefined) return ["void"]
if (typeof obj === "string") {
if (obj.length > 1024 || obj.indexOf("\n", 1) < 0) {
if (!obj.startsWith("\n")) return [obj]
const c = obj[1]
return [(c === "│" || c === "├" || c === "└") ? obj + " " : obj]
}
} else {
if (obj.constructor === Object) {
if (Object.keys(obj).length < 3) {
return Syn.outFromObj(obj, htmlFor).flat()
}
} else if (Array.isArray(obj)) {
if (obj.length < 3) return Syn.outFromObj(obj, htmlFor).flat()
} else if (obj.dict?.constructor === Object) {
return htmlFor(obj.dict)
} else {
return [Syn.stringify(obj)]
}
}
return [buttonFromInfo("...", "dots", Syn.onDotsClicked.bind(null, obj))]
})
.setStatic(function onDotsClicked(obj, e){
const out = (typeof obj === "string") ? [obj]
: Syn.outFromObj(obj, Syn.safeHtmlFor).flat()
e.target.replaceWith(...out)
})
.setStatic(function outFromObj(obj, onItemGiven){
const isArray = Array.isArray(obj)
const arr = [isArray ? "[" : "{"]
for (const key in obj) {
if (!Object.hasOwn(obj, key)) break
const val = obj[key]
if (val === undefined) continue
if (arr.length > 1) arr.push(", ")
if (isArray) arr.push(onItemGiven(val))
else arr.push(key, ": ", onItemGiven(val))
}
arr.push(isArray ? "]" : "}")
return arr
})
.setStatic(function pageBy(by){
return LS.get_page(by)
})
.setStatic(function safeHtmlFor(obj){
try {
return Syn.htmlFor(obj)
} catch(er) {
return (er.name !== "RangeError") ? String(obj)
: [buttonFromInfo("...∞...", "dots",
Syn.onDotsClicked.bind(null, obj))]
}
})
.setStatic(function safeStringFor(obj){
try {
return Syn.stringFor(obj)
} catch(er) {
return (er.name === "RangeError") ? "[circular]" : String(obj)
}
})
.setStatic(function stringFor(obj){
return (typeof obj === "string" ? JSON : Syn).stringify(obj)
})
.setStatic(function stringForCode(code){
const index = code.indexOf("\n")
return code.slice(0, (index < 0 || index > 59) ? 59 : index)
})
.setStatic(function stringify(obj){
if (obj === null || obj === undefined) return "void"
const contor = obj.constructor
return (contor === Object) ? Syn.outFromObj(obj, Syn.stringFor).join("")
: (contor === Array) ? "[" + obj.map(Syn.stringFor).join(", ") + "]"
: (!Function.hasInst(obj)) ? String(obj)
: "<" + Syn.stringForCode(String(obj)) + "...>"
})
Syn.blocksTreeOfPage = LS.get_page_blocks_tree
Syn.constructor.setProto(function toString(){
return this._key
})
function buttonFromInfo(textContent, classname, clickHandler){
const button = document.createElement("button")
button.classList.add(classname)
button.textContent = textContent
if (clickHandler) button.addEventListener("click", clickHandler)
return button
}
const Tree = Syn.Tree = function Tree(){}
.setStatic(function button(textContent, clickHandler){
return buttonFromInfo(textContent, "tree-button", clickHandler)
})
.setStatic(function node(button, body){
const div = document.createElement("div")
div.classList.add("tree-node")
div.append(button, body)
return div
})
.setStatic(function toggleNode(e){
e.preventDefault()
e.stopPropagation()
const button = e.target
const isExpanded = (button.textContent === "-");
button.textContent = (isExpanded) ? "+" : "-"
button.parentElement.lastChild
.style.setProperty("display", (isExpanded ? "none" : "inline-table"))
})
const cells = document.getElementsByClassName("kit cell")
var eid = 0
var inEval = 0
const SpanEval = Syn.Span = function SpanEval(isRoot, span, type, what){
const f = Object.getOwn(SpanEval.prototype.next.forType, type)
if (!f) return // invalid
const divBlock = span.closest(".ls-block")
if (!divBlock) return // invalid
if (isRoot) {
if (inEval < 1) eid += 1
inEval += 1
}
const block = LS.get_block(divBlock.getAttribute("blockid"))
const container = span.closest(".block-main-container")
const cells = container.querySelectorAll(".cell")
const cellindex = Array.prototype.indexOf.call(cells, span)
const divOut = Kits.onParentEvalStarted(container)
divOut.classList.add("log")
const spanEval = {block, cellindex, divOut, eid, isRoot,
logger: Syn.Evaluation.Logger(Syn.dfltState),
span, type, what, __proto__: SpanEval.prototype}
queueMicrotask(spanEval.eval.bind(spanEval))
}
.setStatic(function onKeydown(e){
if (e.key === "Escape") {
const span = this.span
span.querySelector("input").value = span.syn.init
}
if (e.key === "Enter") return synthesis(this.span)
if (e.code === "KeyZ") return e.stopPropagation()
})
.setStatic(function setCell(span, eid, val){
span.syn.eid = eid
span.syn.val = val
span.querySelector("b").textContent = Syn.stringify(val)
})
.setProto(function eval(){
const res = Syn.Evaluation.eval(this)
if (this.type === "cell") {
const span = this.span
if (!span.syn) {
const init = span.querySelector("b").textContent
span.syn = {eid: 0, init, val: null}
const handler = SpanEval.onKeydown.bind(this)
span.querySelector("input").addEventListener("keydown", handler)
}
SpanEval.setCell(span, this.eid, res)
}
const isProm = Promise.hasInst(res)
if (!isProm) this.next(res)
else res.then(this.next.bind(this))
})
.setProto(function next(res){
const log = next.forType[this.type].call(this, res)
const logger = this.logger
if (logger.state.$log_target === Syn.Word("console")) logger.log("", log)
this.renderOut(logger.print() || [])
if (!this.isRoot) return // done
if (inEval > 0) inEval -= 1
if (inEval < 1) Array.prototype.forEach.call(cells, synthesis.evalSpan)
}
.onType(function cell(res){
const span = this.span
const input = span.querySelector("input")
span.syn.val = res
span.querySelector("b").textContent = Syn.stringify(res)
span.querySelector("input").setAttribute("value", span.dataset.what)
return span
})
.onType(function eval(res){
const Type = Syn.Type
const out = (Type.of(res) === Type.Html) ? Type.valueOf(res)
: Kits.createElementOfClass("span", "eval", ...this.strs(res))
this.span.replaceWith(out)
return out
})
.onType(function once(res){
const content = this.block.content
const macroName = this.span.closest(".macro").dataset.macroName
const macroStart = content.indexOf("{{" + macroName)
const premacro = content.slice(0, macroStart)
const postmacro = content.slice(content.indexOf("}}", macroStart) + 2)
LS.update_block(this.block.uuid, premacro + Syn.stringFor(res) + postmacro)
return res
})
)
.setProto(function renderOut(out){
const divOut = this.divOut
if (out.length < 8) return divOut.remove()
const args = out.map(Syn.safeHtmlFor).flat()
const spanOut = Kits.createElementOfClass("span", "out", ...args)
divOut.lastChild.replaceWith("=>", spanOut)
const expand = (this.logger.state.$log_visibility === Syn.Word("expand"))
const span = divOut.lastChild
if (!expand) span.style.setProperty("display", "none")
const sign = (expand) ? "-" : "+"
divOut.append(Tree.node(Tree.button(sign, Tree.toggleNode), span))
})
.setProto(function strs(res){
try {
return [Syn.stringify(res)]
} catch(er) {
return Syn.safeHtmlFor(res)
}
})
const synthesis = logseq.kits.synthesis = function synthesis(span){
logseq.kits.synthesis.evalSpan(span)
}
.setStatic(function evalSpan(span){
const isRoot = (this === synthesis)
if (!isRoot && span.syn?.eid === eid) return // done
const what = span.dataset.what
const type = span.dataset.type
if (type !== "parse") return SpanEval(isRoot, span, type, what)
const parsed = Syn.Expression.fromParsing(Syn.Expression("", what))
var str = Syn.stringFor(parsed)
if (str.startsWith("eval( ")) str = str.slice(6, -2)
span.replaceWith(what, "\n", str)
})
```
- Classes
- ```javascript
const Module = logseq.Module
Module.Clipboard = navigator.clipboard
Module.Date = Date
Module.Promise = Promise
Module.self = Module
const Syn = Module.Syn = Module.Synthesis
const Norm = Syn.Normal = function Norm(type, head = null,
body = null, meta = null){
return {type, head, body, meta, __proto__: Norm.prototype}
}
.addHasInst()
.setStatic(function forTypeOf(type, concept){
const norm = Norm.bind(null, type)
if (!concept) return norm
norm.isNorm = Norm.isType.bind(null, type)
return concept[type[0].toUpperCase() + type.slice(1)] = norm
})
.setStatic(function isType(type, obj){
return Norm.hasInst(obj) && obj.type === type
})
.setProto(function toJSON(){
const head = this.head
return this.type + (typeof head !== "string" ? "" : "{" + head + "}")
})
.setProto(function toString(){
const f = toString.forType[this.type]
return (f) ? f.call(this) : this.type + "{" + (this.head ?? this.body) + "}"
}
.onType(function error(){
return "$ERR{" + this.head + "}"
})
.onType(function typed(){
return (this.meta === ".word") ? this.body
: (typeof this.body === "number") ? this.body + " " + this.head
: this.head + "{" + Syn.stringFor(this.body) + "}"
})
)
const Error = Norm.forTypeOf("error", Syn)
.setStatic(function inListOrCallThis(list){
return list.find(Error.isNorm) || this(list)
})
const Typed = Norm.forTypeOf("typed", Syn)
const Word = Syn.Word = function Word(name){
const word = Object.getOwn(words, name)
return word || (words[name] = Typed("word", name, ".word"))
}
.setStatic(function isNorm(obj){
return Typed.isNorm(obj) && obj.meta === ".word"
})
const words = Syn.words = {}
const No = Word("no")
const Yes = Word("yes")
Syn.keywords = {
false: No,
let: Word("let"),
no: No,
null: Word("void"),
on: Word("on"),
rather: Word("rather"),
true: Yes,
void: Word("void"),
yes: Yes,
"etc.": Word("etc."),
do: Word("do"),
return: Word("return"),
word: Word("word")
}
Syn.dfltState = {
$log: Word("no"),
$log_visibility: Word("collapse"),
custom: {}
}
Syn.closing = {
'"': '"',
"$": "",
"'": "'",
"/": "",
"[": "]",
"{": "}"
}
Syn.pages = {}
Syn.types = {}
const JS = Syn.JS = function JS(){}
JS.console = console
JS.isNaN = function _isNaN(obj){ return isNaN(obj) ? Yes : No }
JS.new = function _new(_class, ...args){ return new _class(...args) }
const Type = Syn.Type = function Type(global, local){
return Norm("typed", "." + global, local || global, ".type")
}
.setStatic(function fromChain(chain){
const index = chain.lastIndexOf("-")
if (index < 0) return Syn.types[chain] ||= Type(chain)
const left = chain.slice(0, index)
const right = chain.slice(index + 1)
const isnum = !isNaN(Number(right))
return (isnum) ? Type(left, chain) : Type(right, left)
})
.setStatic(function of(arg){
if (arg === true || arg === false || arg === null || arg === undefined) {
return Type.Word
}
return (Array.isArray(arg)) ? Type.List :
(Typed.isNorm(arg)) ?
(arg.head.startsWith("html") ? Type.Html : Syn.types[arg.head]) :
(Error.isNorm(arg)) ? Syn.types.error :
Object.prototype.toString.call(arg) === "[object Date]" ? Type.Time :
(arg.constructor?.name.startsWith("HTML")) ?
Type.Html : Type.byjs[typeof arg]
})
.setStatic(function valueOf(arg){
return (Typed.isNorm(arg)) ? arg.body : arg
})
const types = ["dict", "error", "func", "html", "list",
"num", "text", "time", "value", "word"]
types.forEach( (name)=>{
Type[name[0].toUpperCase() + name.slice(1)] = Type.fromChain(name)
})
Type.byjs = {
function: Type.Func,
number: Type.Num,
object: Type.Dict,
string: Type.Text
}
```
- Expression
collapsed:: true
- ```javascript
const Module = logseq.Module
const Syn = Module.Synthesis
const Norm = Syn.Normal
const Word = Syn.Word
const Expr = Syn.Expression = Norm.forTypeOf("expr", Syn)
const That = Word("that")
const This = Word("this")
Norm.prototype.toString
.onType(function cell(){
const span = this.meta.DOM.DOM.l?.querySelector(".cell")
if (span) {
const text = span.querySelector("input").value
if (text !== " :cell") return "{ " + text + " }"
}
return Syn.stringFor(this.body)
})
.onType(function expr(){
return this.head + "{ " + this.body + " }"
})
.onType(function getter(){
return this.meta + "." + this.body
})
.onType(function holder(){
const simple = (this.head === "$" || !Expr.isNorm(this.meta))
return (simple) ? "$( " + Syn.stringFor(this.body) + " )"
: this.toString.forType.keyed.call(this, "on")
})
.onType(function keyarg(){
return this.toString.forType.keyed.call(this, "let")
})
.onType(function keyed(key){
return "( " + key + " " + this.head.replaceAll("_", "-") +
" " + Syn.stringFor(this.body) + " )"
})
.onType(function lookup(){
const head = this.head
return (head !== That && head !== This) ? head
: "( " + head.body + " " + this.body + " )"
})
.onType(function train(){
const head = this.head
const isCall = (typeof head === "string" && head.length &&
head[0] !== head[0].toLowerCase())
return (this.head || "") + "( " +
this.body.map(Syn.stringFor).join(isCall ? ", " : "; ") + " )"
})
.onType(function vector(){
const head = this.head
const isCaller = (head.type === "caller")
const args = (isCaller) ? this.body.map(Syn.stringFor)
: head.body.map(vector.kitWord, {body: this.body, i: 0})
const separator = (isCaller) ? ", " : " "
return (isCaller ? head.body : "") + "( " + args.join(separator) + " )"
}
.setStatic(function kitWord(word){
return (word.startsWith(".")) ? Syn.stringFor(this.body[this.i++]) : word
})
)
const Cell = Norm.forTypeOf("cell", Expr)
const BACKTICKS = "`".repeat(3)
const Code = Norm.forTypeOf("code", Expr)
.setStatic(function exec(code){
const head = code.head
const str = (head._key !== "javascript") ? code.body :
"const Syn = logseq.Module.Synthesis\n" + code.body
if (!head.eval) {
const prom = Module.Kits.loadDependencies( {key: head} )
head.eval = ()=>prom.then( ()=>head.eval(str) )
}
return head.eval(str)
})
.setStatic(function fromText(text){
const codeStart = text.indexOf(BACKTICKS) + 3
if (codeStart < 3) return // invalid
const strLang = text.slice(codeStart, text.search(/\w\W/) + 1)
var lang = logseq.Language[strLang]
if (!lang) return // invalid
const lineEnd = text.indexOf("\n", codeStart) + 1
const codeEnd = text.indexOf(BACKTICKS, lineEnd) - 1
if (codeEnd > -1) return Code(lang, text.slice(lineEnd, codeEnd))
})
Expr.setStatic(function fromParsing(expr){
const car = Expr.Vector.forTrain(Expr.Train("eval", [], null))
const literal = {acc: "", closing: null}
const parser = {car, dotpath: "", expr, literal, pron: null,
suffix: "", __proto__: Expr.Parser.prototype}
try {
var parsed = parser.parseExpr(expr)
const head = expr.meta?.head
if (head?.path === "$on") {
const train = (parsed.type === "train") ? parsed
: Expr.Train("on", [parsed], null)
const lookup = Expr.Lookup(That, "$it", expr)
train.body.unshift(Expr.Keyarg(head.key, lookup, expr))
parsed = Expr.Holder("$", train, expr)
}
return parsed
} catch(er) {
return parser.error("unexpected error", er)
}
})
.setStatic(function fromRight(right, cellindex, lineindex = -1){
const expr = (right === "") ? null
: (lineindex < 0 ? Expr("", right)
: Expr.Code.fromText(right))
return (cellindex > -1) ? expr.meta = Cell(cellindex, expr) : expr
})
Expr.Value = {}
Expr.Value.holds = {
AND: Word("AND"), OR: Word("OR"), // add XOR
else: Word("else"), then: Word("then")
}
Expr.Value.prons = {
that: That, those: That,
this: This, these: This
}
Object.assign(Syn.keywords, Expr.Value.holds, Expr.Value.prons)
```
- Expression Parser
- ```javascript
const Syn = logseq.Module.Synthesis
const Expr = Syn.Expression
const Norm = Syn.Normal
const Caller = Norm.forTypeOf("caller", Expr)
const Getter = Norm.forTypeOf("getter", Expr)
const Holds = Expr.Value.holds
const Let = Syn.keywords["let"].body
const On = Syn.keywords["on"].body
const Prons = Expr.Value.prons
const Rather = Syn.keywords["rather"].body
const Train = Norm.forTypeOf("train", Expr)
const Type = Syn.Type
const Word = Syn.Word
function fixDashes(name){
return (name.startsWith("$")) ? name.replaceAll("-", "_") : name
}
const Holder = Expr.Holder = function Holder(name, body, meta){
return Norm("holder", fixDashes(name), body, meta)
}
Holder.isNorm = Norm.isType.bind(null, "holder")
const Keyarg = Expr.Keyarg = function Keyarg(name, body, meta){
return Norm("keyarg", fixDashes(name), body, meta)
}
Keyarg.isNorm = Norm.isType.bind(null, "keyarg")
const Lookup = Expr.Lookup = function Lookup(head, name, meta){
const fix = (head === Word("that") &&
typeof name === "string" && name.startsWith("$."))
return Norm("lookup", head, (fix ? name.replaceAll("-", "_") : name), meta)
}
Lookup.isNorm = Norm.isType.bind(null, "lookup")
const Vector = Norm.forTypeOf("vector", Expr)
.setStatic(function forTrain(train){
return {args: [], keyarg: null, parts: [], train}
})
Expr.Parser = function ExprParser(){}
.setProto(Syn.error)
.setProto(function addMulti(arg){
const dotpath = this.dotpath
if (dotpath.length) {
arg = addMulti.processDotpath(dotpath, addMulti.kitAddGetter, arg)
}
if (this.suffix) this.beginTrain(this.suffix, arg)
else this.addPart(".value", arg)
this.suffix = ""
}
.setStatic(function kitAddGetter(node){
const num = Number(node)
const isWord = isNaN(num)
const type = (isWord || num >= 0) ? "index" : "rindex"
this.res = Getter(type, isWord ? node : num, this.res)
})
.setStatic(function processDotpath(dotpath, kitAddGetter, arg){
const kit = {res: arg}
dotpath.split(".").forEach(kitAddGetter, kit)
return kit.res
})
)
.setProto(function addPart(part, arg){
const car = this.car
const parts = car.parts
const word = (arg === undefined) ? part : arg.head
const keyarg = car.keyarg
if (keyarg === On) {
car.keyarg = Holder(word, On, this.expr)
return // done
} else if (keyarg === Let) {
car.keyarg = Keyarg(word, Let, this.expr)
return // done
} else if (!keyarg && parts.length < 1) {
if (word === Let || word === On) {
car.keyarg = word
return // done
} else if (word === Rather) {
car.keyarg = Holder(Rather, word, this.expr)
return // done
}
}
var pre = parts[parts.length - 1]
if (Object.getOwn(Prons, pre)) {
const name = (arg === undefined) ? part :
(Lookup.isNorm(arg) && arg.head.indexOf
&& arg.head.indexOf("-") > -1) ? arg.head : arg
arg = Lookup(Prons[pre], name, this.expr)
part = ".value"
parts.pop()
pre = parts[parts.length - 1]
}
parts.push(part)
if (arg === undefined) return // done
car.args.push(Holds[pre] ? Holder(pre, arg, this.expr) : arg)
})
.setProto(function assign(word){
return this.parseChain(Let) || this.parseChain(word)
})
.setProto(function attemptLiteral(chain){
const literal = this.literal
const closing = literal.closing
const acc = literal.acc
literal.acc = acc + (acc ? " " : "") + chain
if (literal.acc === '"') return // continue
if (chain[chain.length - 1] !== closing) {
if (chain.length < 2) return // continue
const suffix = chain.slice(-1)
const funcname = Expr.Parser.getFuncnameForSuffix(suffix)
if (!funcname) return // continue
const lit = literal.acc
literal.acc = acc
const found = this.attemptLiteral(chain.slice(0, -1))
if (found) return found
if (literal.acc.length) literal.acc = lit
else this.parseChain(suffix)
return // continue
}
const lit = attemptLiteral.closeAcc(closing, literal.acc)
if (lit === undefined) return // continue
this.addPart(Type.byjs[typeof lit].head, lit)
literal.acc = ""
}
.setStatic(function closeAcc(closing, acc){
if (closing === "'") return acc.slice(1, -1)
try { return JSON.parse(acc) } catch {}
})
)
.setProto(function beginTrain(word, head){
this.car = Vector.forTrain(Train(head, [], this.car))
})
.setProto(function carsFromCar(car){
return car.parts.map(carsFromCar.kitPart, {args: car.args, i: 0} )
}
.setStatic(function kitPart(part){
return (part.startsWith(".")) ? this.args[this.i++] : Word(part)
})
)
.setProto(function dfltSuffix(word, suffix){
return this.parseChain(word) || this.parseChain(suffix)
})
.setProto(function dfltWord(word){
return this.addPart(word)
})
.setProto(function endCar(car){
car.train.body.push(this.loadForCar(car))
})
.setProto(function endTrain(word, forLast){
const car = this.car
const train = car.train
const head = train.head
const isCall = head &&
(typeof head !== "string" || head[0] !== head[0].toLowerCase())
if (!isCall || train.body.length) this.endCar(car)
else train.body = this.carsFromCar(car)
var arg = (!isCall && train.body.length < 2) ? train.body[0]
: (!isCall) ? train
: Vector(Caller(train.body.length, train.head, this.expr),
train.body, this.expr)
if (head === "$") arg = Holder("$", arg, this.expr)
if (!train.meta) {
if (forLast) return arg
train.head = ""
train.meta = Vector.forTrain(Train("eval", [], null))
}
this.car = train.meta
this.addPart(".value", arg)
})
.setProto(function it(word){ return this.parseChain("?$it") })
.setProto(function loadForCar(car){
const keyarg = car.keyarg
const parts = car.parts
if (keyarg && parts.length < 1) this.parseChain("!" + keyarg.head)
const args = car.args
const expr = this.expr
const load = (parts.length !== 1) ?
Vector(Lookup(parts.join("-"), parts, expr), args, expr) :
(args.length > 0) ? args[0] :
(typeof parts[0] !== "string") ? parts[0] :
Lookup(parts[0], parts, this.expr)
if (!keyarg) return load
keyarg.body = load
return keyarg
})
.setProto(function nextCar(word){
this.endCar(this.car)
this.car = Vector.forTrain(this.car.train)
})
.setProto(function parseChain(chain){
if (!chain) return // continue // check negative
const literal = this.literal
if (literal.acc.length) return this.attemptLiteral(chain)
var funcname = Expr.Parser.getFuncnameForWord(chain)
if (funcname) return this[funcname](chain)
literal.closing = Syn.closing[chain[0]]
if (literal.closing) return this.attemptLiteral(chain)
const index = chain.indexOf("(")
if (index === 0) return this.parseChain("(") ||
this.parseChain(chain.slice(1))
if (index > 0) {
this.suffix = "("
return this.parseChain(chain.slice(0, index)) ||
this.parseChain(chain.slice(index + 1))
}
const suffix = chain.slice(-1)
funcname = Expr.Parser.getFuncnameForSuffix(suffix)
if (funcname) return this[funcname](chain.slice(0, -1), suffix)
if (chain.endsWith("'s")) return this.parseChain(chain.slice(0, -2)) ||
this.parseChain("its")
const prefix = chain.slice(0, 1)
const firstWord = Expr.Parser.prefixes[prefix]
if (firstWord) return this.parseChain(firstWord) ||
this.parseChain(chain.slice(1))
const num = Number(chain)
return (!isNaN(num)) ? this.addPart(".num", num)
: this.parseChainHead(this.parseChainDotpath(chain))
})
.setProto(function parseChainDotpath(chain){
this.dotpath = ""
var index = 0
while (true) {
index = chain.indexOf(".", index + 1)
if (index < 0) return chain
if (chain[index - 1] !== "-") break
}
this.dotpath = chain.slice(index + 1)
return chain.slice(0, index)
})
.setProto(function parseChainHead(chain){
const hasDash = (chain.length > 1 && chain.indexOf("-") > -1)
const load = (hasDash) ? Lookup(chain, [], this.expr) : chain
if (this.dotpath || (this.suffix !== "")) this.addMulti(load)
else if (hasDash) this.addPart(".value", load)
else this.addPart(load)
})
.setProto(function parseExpr(expr){
const found = expr.body.split(" ").find(this.parseChain, this)
if (this.literal.acc) return this.error("bad literal", this.literal.acc)
if (found) return this.error("invalid", found)
while (true) {
const last = this.endTrain(")", true)
if (last !== undefined) return last
}
})
Expr.Parser.getFuncnameForSuffix = Object.getOwn.bind(null, {
"=": "assign",
")": "dfltSuffix", ",": "dfltSuffix", ";": "dfltSuffix"
})
Expr.Parser.getFuncnameForWord = Object.getOwn.bind(null, {
"(": "beginTrain",
"=": "dfltWord", "<=": "dfltWord", ">=": "dfltWord",
")": "endTrain",
it: "it", them: "it",
")(": "nextCar", ",": "nextCar", ";": "nextCar"
})
Expr.Parser.prefixes = {
"!": Prons.this.body,
"?": Prons.that.body
}
```
-