[sheet] make query a primitive value

This commit is contained in:
Anantha Kumaran 2024-01-09 22:06:55 +05:30
parent fd3629851d
commit 6e1a57c105
13 changed files with 222 additions and 77 deletions

View File

@ -754,8 +754,7 @@ nav.level.grid-2 {
}
.sheet-result {
background-color: $white-ter;
color: $grey;
border-left: 1px solid $grey-lighter;
}
.sheet-editor .cm-editor {

View File

@ -15,22 +15,47 @@ import { functions } from "./sheet/functions";
import { Environment, buildAST } from "./sheet/interpreter";
import type { Posting } from "./utils";
function lint(editor: EditorView): Diagnostic[] {
const diagnostics: Diagnostic[] = [];
const tree = syntaxTree(editor.state);
function lint(env: Environment) {
return function (editor: EditorView): Diagnostic[] {
const diagnostics: Diagnostic[] = [];
const tree = syntaxTree(editor.state);
tree.cursor().iterate((node) => {
if (node.type.isError) {
diagnostics.push({
from: node.from,
to: node.to,
severity: "error",
message: "Invalid syntax"
tree.cursor().iterate((node) => {
if (node.type.isError) {
diagnostics.push({
from: node.from,
to: node.to,
severity: "error",
message: "Invalid syntax"
});
}
});
sheetEditorState.update((current) => {
if (!current.pendingEval) {
return current;
}
const startTime = performance.now();
let results = current.results;
try {
const ast = buildAST(tree.topNode, editor.state);
results = ast.evaluate(env);
} catch (e) {
console.log(e);
// ignore
}
const endTime = performance.now();
return _.assign({}, current, {
pendingEval: false,
evalDuration: endTime - startTime,
results
});
}
});
});
return diagnostics;
return diagnostics;
};
}
export function createEditor(
@ -45,6 +70,8 @@ export function createEditor(
env.scope = functions;
env.postings = postings;
let firstLoad = true;
return new EditorView({
extensions: [
keymap.of(opts.keybindings || []),
@ -53,28 +80,31 @@ export function createEditor(
closeBrackets(),
EditorView.contentAttributes.of({ "data-enable-grammarly": "false" }),
sheetExtension(),
linter(lint),
linter(lint(env), {
delay: 300,
needsRefresh: () => {
if (firstLoad) {
firstLoad = false;
return true;
}
return false;
}
}),
lintGutter(),
history(),
EditorView.updateListener.of((viewUpdate) => {
const doc = viewUpdate.state.doc.toString();
const currentLine = viewUpdate.state.doc.lineAt(viewUpdate.state.selection.main.head);
sheetEditorState.update((current) => {
let results = current.results;
let pendingEval = current.pendingEval;
if (current.doc !== doc) {
const tree = syntaxTree(viewUpdate.state);
try {
const ast = buildAST(tree.topNode, viewUpdate.state);
results = ast.evaluate(env);
} catch (e) {
console.log(e);
// ignore
}
pendingEval = true;
}
return _.assign({}, current, {
results: results,
doc: doc,
pendingEval,
doc,
currentLine: currentLine.number,
hasUnsavedChanges: current.hasUnsavedChanges || viewUpdate.docChanged,
undoDepth: undoDepth(viewUpdate.state),

View File

@ -66,7 +66,7 @@ Sheet(Line(Expression(FunctionCall(Identifier))))
# Postings Search Query
postings(`amount > 0`)
{amount > 0}
===>
@ -83,8 +83,24 @@ Sheet(Line(FunctionDefinition(Identifier,Parameters(Identifier),Expression(Binar
# Empty Postings Search Query
postings(``)
{}
===>
Sheet(Line(Expression(Postings(SearchQueryString(Query)))))
# Query composition AND
{amount > 0} AND {date > [2024]}
===>
Sheet(Line(Expression(BinaryExpression(Expression(Postings(SearchQueryString("{",Query(Clause(Condition(Property(Amount),Operator(">"),Value(Number)))),"}"))),BinaryOperator,Expression(Postings(SearchQueryString("{",Query(Clause(Condition(Property(Date),Operator(">"),Value(DateValue)))),"}")))))))
# Query composition OR
{amount > 0} OR {date > [2024]}
===>
Sheet(Line(Expression(BinaryExpression(Expression(Postings(SearchQueryString("{",Query(Clause(Condition(Property(Amount),Operator(">"),Value(Number)))),"}"))),BinaryOperator,Expression(Postings(SearchQueryString("{",Query(Clause(Condition(Property(Date),Operator(">"),Value(DateValue)))),"}")))))))

View File

@ -1,9 +1,52 @@
import type { Posting } from "$lib/utils";
import type { Environment } from "./interpreter";
import _ from "lodash";
import type { Environment, Query } from "./interpreter";
import { BigNumber } from "bignumber.js";
function cost(env: Environment, ps: Posting[]) {
type PostingsOrQuery = Posting[] | Query;
function cost(env: Environment, q: PostingsOrQuery): BigNumber {
const ps = toPostings(env, q);
return ps.reduce((acc, p) => acc.plus(new BigNumber(p.amount)), new BigNumber(0));
}
export const functions = { cost };
function fifo(env: Environment, q: PostingsOrQuery): Posting[] {
const ps = toPostings(env, q);
return _.chain(ps)
.groupBy((p) => [p.account, p.commodity].join(":"))
.map((ps) => {
ps = _.sortBy(ps, (p) => p.date);
const available: Posting[] = [];
while (ps.length > 0) {
const p = ps.shift();
if (p.quantity >= 0) {
available.push(p);
} else {
let quantity = -p.quantity;
while (quantity > 0 && available.length > 0) {
const a = available.shift();
if (a.quantity > quantity) {
const diff = a.quantity - quantity;
const price = a.amount / a.quantity;
available.unshift({ ...a, quantity: diff, amount: diff * price });
quantity = 0;
} else {
quantity -= a.quantity;
}
}
}
}
return available;
})
.flatten()
.value();
}
function toPostings(env: Environment, q: PostingsOrQuery) {
if (Array.isArray(q)) {
return q;
}
return q.resolve(env);
}
export const functions = { cost, fifo };

View File

@ -7,7 +7,7 @@ export const sheetHighlighting = styleTags({
"FunctionDefinition/Identifier": t.function(t.variableName),
"FunctionCall/Identifier": t.function(t.variableName),
Postings: t.special(t.variableName),
"`": t.heading,
"{ }": t.operator,
UnaryOperator: t.operator,
BinaryOperator: t.operator,
AssignmentOperator: t.operator

View File

@ -2,7 +2,7 @@ import type { SyntaxNode } from "@lezer/common";
import * as Terms from "./parser.terms";
import type { EditorState } from "@codemirror/state";
import { BigNumber } from "bignumber.js";
import { asTransaction, type Posting, type SheetLineResult } from "$lib/utils";
import { asTransaction, formatCurrency, type Posting, type SheetLineResult } from "$lib/utils";
import {
buildFilter,
buildAST as buildSearchAST,
@ -33,6 +33,38 @@ export class Environment {
}
}
export class Query {
predicate: TransactionPredicate;
result: Posting[] | null;
constructor(predicate: TransactionPredicate) {
this.predicate = predicate;
this.result = null;
}
resolve(env: Environment): Posting[] {
if (this.result === null) {
this.result = env.postings
.map(asTransaction)
.filter(this.predicate)
.map((t) => t.postings[0]);
}
return this.result;
}
and(query: Query): Query {
return new Query((t) => this.predicate(t) && query.predicate(t));
}
or(query: Query): Query {
return new Query((t) => this.predicate(t) || query.predicate(t));
}
toString(): string {
return "";
}
}
abstract class AST {
readonly id: number;
constructor(readonly node: SyntaxNode) {
@ -82,6 +114,8 @@ class UnaryExpressionAST extends AST {
switch (this.operator) {
case "-":
return (this.value.evaluate(env) as BigNumber).negated();
case "+":
return this.value.evaluate(env);
default:
throw new Error("Unexpected operator");
}
@ -114,6 +148,10 @@ class BinaryExpressionAST extends AST {
return (this.left.evaluate(env) as BigNumber).div(this.right.evaluate(env));
case "^":
return (this.left.evaluate(env) as BigNumber).exponentiatedBy(this.right.evaluate(env));
case "AND":
return this.left.evaluate(env).and(this.right.evaluate(env));
case "OR":
return this.left.evaluate(env).or(this.right.evaluate(env));
default:
throw new Error("Unexpected operator");
}
@ -147,11 +185,8 @@ class PostingsAST extends AST {
this.predicate = buildFilter(buildSearchAST(state, node.lastChild.firstChild.nextSibling));
}
evaluate(env: Environment): any {
return env.postings
.map(asTransaction)
.filter(this.predicate)
.map((t) => t.postings[0]);
evaluate(): Query {
return new Query(this.predicate);
}
}
@ -291,7 +326,7 @@ class LineAST extends AST {
evaluate(env: Environment): Record<string, any> {
let value = this.value.evaluate(env);
if (value instanceof BigNumber) {
value = value.toFixed(2);
value = formatCurrency(value.toNumber());
}
switch (this.valueId) {
case Terms.Assignment:
@ -300,7 +335,7 @@ class LineAST extends AST {
case Terms.FunctionDefinition:
return { result: "" };
case Terms.Header:
return { result: value?.toString() || "", align: "left", bold: true, underline: true };
return { result: value?.toString() || "", align: "left", bold: true };
default:
throw new Error("Unexpected node type");
}

View File

@ -5,8 +5,10 @@
unary @right,
factor @left,
term @left,
comparison @left
equality @left
comparison @left,
equality @left,
and @left,
or @left
}
lines { Line (newline+ Line)* newline* }
@ -21,14 +23,16 @@ UnaryExpression { !unary UnaryOperator ~unary Expression }
BinaryExpression {
Expression !factor BinaryOperator<"*" | "/"> Expression |
Expression !term BinaryOperator<"+" | "-"> ~unary Expression |
Expression !comparison BinaryOperator<"<" | "<=" | ">" | ">="> Expression
Expression !equality BinaryOperator<"==" | "!="> Expression
Expression !comparison BinaryOperator<"<" | "<=" | ">" | ">="> Expression |
Expression !equality BinaryOperator<"==" | "!="> Expression |
Expression !and BinaryOperator<@specialize<Identifier, "AND">> Expression |
Expression !or BinaryOperator<@specialize<Identifier, "OR">> Expression
}
Assignment { Identifier AssignmentOperator Expression }
FunctionCall { Identifier !call "(" Arguments? ")" }
Arguments { Expression ~call ("," Expression)* }
Postings { @specialize<Identifier, "postings"> "(" SearchQueryString ")" }
Postings { SearchQueryString }
FunctionDefinition { Identifier !call "(" Parameters? ")" "=" Expression }
Parameters { Identifier ~call ("," Identifier)* }
@ -54,13 +58,13 @@ BinaryOperator<expr> { expr }
}
@local tokens {
stringEnd[@name='`'] { '`' }
stringEnd[@name='}'] { '}' }
stringEscape { "\\" _ }
@else stringContent
}
@skip {} {
stringStart[@name='`'] { '`' }
stringStart[@name='{'] { '{' }
SearchQueryString { stringStart SearchQuery stringEnd }
SearchQuery { (stringContent | stringEscape)* }
}

View File

@ -14,7 +14,7 @@ export const sheetLanguage = LRLanguage.define({
})
}),
languageData: {
closeBrackets: { brackets: ["[", "(", "/", '"', "`"] }
closeBrackets: { brackets: ["[", "(", "/", '"', "`", "{"] }
}
});

View File

@ -1,21 +1,21 @@
// This file was generated by lezer-generator. You probably shouldn't edit it.
import {LRParser, LocalTokenGroup} from "@lezer/lr"
import {sheetHighlighting} from "./highlight"
const spec_Identifier = {__proto__:null,postings:102}
const spec_Identifier = {__proto__:null,AND:100, OR:102}
export const parser = LRParser.deserialize({
version: 14,
states: "+|QVQPOOOOQO'#Cw'#CwQVQPOOOOQO'#C`'#C`OOQO'#Cc'#CcOtQPO'#CbO!wQPO'#C^OtQPO'#CiO#sQPO'#C_O#}QPO'#CmOOQO'#C_'#C_OOQO'#C^'#C^O$SQPO'#DQQOQPOOOOQO-E6u-E6uOOQO,58|,58|O$[QPO'#C_OOQO'#Ce'#CeOOQO'#Cf'#CfOOQO'#Cg'#CgOtQPO,59OOtQPO,59OOtQPO,59OO$uQPO,59TO%bQPO,59VOOQO'#Cs'#CsOtQPO,59^O%lQPO,59XO%qQPO,59lO%xQPO,59lO&QQPO,59VOOQO1G.j1G.jO&sQPO1G.jO'dQPO1G.jOOQO1G.o1G.oO(_QPO'#ClO)WQPO'#C_O)hQPO1G.qO*]QPO1G.qO*bQPO1G.zO*gQPO1G.xOOOO'#Co'#CoO*qOSO'#CnO*|QPO1G.sOOQO,59g,59gO+RQPO1G/WOOQO-E6y-E6yOOQO1G.q1G.qO+YQPO'#CcO,WQQO7+$UO,bQQO'#C_OtQPO'#CxO,lQPO,59WO,tQPO'#CzO,yQPO,59aOtQPO7+$fOOQO7+$]7+$]O-RQPO7+$fOOOO'#Cy'#CyO-WOSO'#CpO-cOSO,59YOOQO7+$_7+$_PVQPO'#CwOOQO'#Ch'#ChOtQPO<<GpO-hQPO,59dOOQO-E6v-E6vOOQO,59f,59fOOQO-E6x-E6xO-rQPO<<HQOtQPO<<HQOOOO-E6w-E6wOOQO1G.t1G.tO-|QQO1G.jO.rQPOAN=[O/cQPOAN=lO/mQQOAN=[O/wQPO'#CbO/wQPO,59OO/wQPO,59OO/wQPO<<GpO,WQQO7+$UO'dQPO1G.jOtQPO,59O",
stateData: "0O~OrOS~OTRO^WOjZOsPOuSOvSOwSO!RVO!TXO~OTRO^`OuSOvSOwSO!RVO!TXO~OubOvbOxaOyaOzcO{cO|cO}cO~OpQXsQX~P!]OpRXsRXuRXvRXxRXyRXzRX{RX|RX}RX~O!RhO!XiO~P#RO!RkO~OsPOptX~O!RnO!QRXTRX^RXwRX!TRX!SRX~P#RO!QrO~P!]OTROuSOvSOwSO!RVO!TXO~O^tO!QuO~P$|O!UyO~Opta~PVOsPOpta~O!Q!PO~PtOxaOyaOuWivWizWi{Wi|Wi}Wi~OpWisWi!QWiTWi^WiwWi!RWi!TWi!SWi~P&XOTRO^!SOu!QOv!QOwSOxaOyaOzcO{cO|cO}cO!RVO!TXO~O!S!TO!Q`X~P!]O!RnOuRXvRXxRXyRXzRX{RX|RX}RX~O!S!VO!QRX!QiX!SRX~P(iO!X!XOp_is_iu_iv_ix_iy_iz_i{_i|_i}_i~O!Q!YO~O!Q!ZO~Opfisfi~P!]O!V![O!W![OedP~O!Q!_O~Opti~PVOTVXTYX^VX^YXuVXuYXvVXvYXwVXwYX!RVX!RYX!TVX!TYX~O!O!aO!P!aO~P!]O!ORX!PRX~P(iO!S!TO!Q`a~O^!eO~O!S!VO!Qia~O!X!hO~O!V![O!W![OedX~Oe!jO~O!Qla!Sla~P!]Ophyshy~P!]O!OWi!PWi~P&XOubOvbOxaOyaOzW!R{W!R|W!R}W!R~OpW!RsW!R!QW!RTW!R^W!RwW!R!RW!R!TW!R!SW!R~P.WOph!Rsh!R~P!]O!OW!R!PW!R~P.WO^!SO~P$|O",
goto: "'XuPPv!P#]P#]#w#]$e$x%Z%j#]P#]%p#]%t%w%zP%}&U%}&XP&[&k&q&w&}PPPP'TS[OQV|l}!`YUOQl}!`S_T!oQgVSod!pQpeQqfSshnQxjQ!RqQ!c!TQ!g!XQ!k!qQ!l!bQ!m!hQ!n!rQ!s!tR!t!u!RYOQTVdefhjlnq}!T!X!`!b!h!o!p!q!r!t!utTOQTVdefhjln}!T!X!`!b!h!u]!oq!o!p!q!r!tfdUgpqsx!c!g!l!m!tX!p!R!k!n!sdeUgqsx!c!g!l!m!tV!q!R!n!sbfUgqsx!c!g!m!tT!u!R!sQ!b!RR!r!sTvhnR{kRzkR!^zZZOQl}!`RjWRwhQQOW^Ql}!`Ql[R}mQ!UsR!d!UQ!]zR!i!]Q!WtR!f!WQm[R!OmT]OQ",
nodeNames: "⚠ Sheet Line Expression Literal Number UnaryExpression UnaryOperator BinaryExpression BinaryOperator BinaryOperator BinaryOperator BinaryOperator Grouping Identifier FunctionCall Arguments Postings SearchQueryString ` SearchQuery ` Assignment AssignmentOperator FunctionDefinition Parameters Header",
maxTerm: 55,
states: "*tQVQPOOOOQO'#Cy'#CyQVQPOOOOQO'#C`'#C`OOQO'#Cc'#CcOtQPO'#CbO#TQQO'#C^OtQPO'#ClO$]QQO'#C_OOOO'#Cq'#CqO$gOSO'#CpOOQO'#Co'#CoOOQO'#C_'#C_OOQO'#C^'#C^O$rQPO'#DSQOQPOOOOQO-E6w-E6wOOQO,58|,58|O$zQQO'#C_OOQO'#Ce'#CeOOQO'#Cf'#CfOOQO'#Cg'#CgOOQO'#Ch'#ChOOQO'#Ci'#CiOOQO'#Ck'#CkOtQPO,59OOtQPO,59OOtQPO,59OOtQPO,59OOtQPO,59OOtQPO,59OO%XQQO,59WO%tQPO,59XOOQO'#Cu'#CuOtQPO,59`OOOO'#C{'#C{O&OOSO'#CrO&ZOSO,59[O&`QPO,59nO&gQPO,59nO&oQPO,59XOOQO1G.j1G.jO'tQQO1G.jO(OQQO1G.jO)WQQO1G.jO)bQQO1G.jO*aQQO1G.jOOQO1G.r1G.rO*tQQO'#CnO+OQQO'#C_O,VQQO1G.sO-WQPO1G.sO-]QPO1G.|O-bQQO1G.zOOOO-E6y-E6yOOQO1G.v1G.vOOQO,59i,59iO-lQPO1G/YOOQO-E6{-E6{OOQO1G.s1G.sOtQPO'#CzO-sQPO,59YO-{QPO'#C|O.QQPO,59cOtQPO7+$hOOQO7+$_7+$_O.YQPO7+$hPVQPO'#CyO._QQO,59fOOQO-E6x-E6xOOQO,59h,59hOOQO-E6z-E6zO.iQQO<<HSOtQPO<<HSO.sQQOAN=n",
stateData: ".}~OtOS~OTRO^WOl]OuPOwSOxSOySO!VVO!XXO~OTRO^bOwSOxSOySO!VVO!XXO~OwdOxdOzcO{cO|eO}eO!OeO!PeO!QfO!RfO!SgO!ThO~OrQXuQX~P!]OrRXuRXwRXxRXzRX{RX|RX}RX!ORX!PRX!QRX!RRX!SRX!TRX~O!VpO![qO~P#_O!YsO!ZsOgfP~OuPOrvX~O!VxO!URX!WRX~P#_O!U!PO~P!]OTROwSOxSOySO!VVO!XXO~O^!RO!U!SO~P%`O!YsO!ZsOgfX~Og!XO~Orva~PVOuPOrva~O!U!]O~PtOzcO{cOrWiuWi|Wi}Wi!OWi!PWi!QWi!RWi!SWi!TWi!UWi!WWi~OwWixWi~P&vOwdOxdO~P&vOwdOxdOzcO{cO|eO}eO!OeO!PeOrWiuWi!SWi!TWi!UWi!WWi~O!QWi!RWi~P(YO!QfO!RfO~P(YOwdOxdOzcO{cO|eO}eO!OeO!PeO!QfO!RfO!SgO~OrWiuWi!TWi!UWi!WWi~P)lO!W!^O!UbX~P!]O!VxO!W!`OwRXxRXzRX{RX|RX}RX!ORX!PRX!QRX!RRX!SRX!TRX!URX!UkX!WRX~O![!bOraiuaiwaixaizai{ai|ai}ai!Oai!Pai!Qai!Rai!Sai!Tai~O!U!cO~O!U!dO~Orhiuhi~P!]Orvi~PVO!W!^O!Uba~O^!hO~O!W!`O!Uka~O![!kO~O!Una!Wna~P!]Orjyujy~P!]Orj!Ruj!R~P!]O",
goto: "'twPPx!R#SP#S#i#S$O$^$k$w%SP%^#S#S%g#S%k&Q&gP&j&q&j&tP&w'W'^'d'jPPPP'pS^OQV!Yv!Z!eYUOQv!Z!eQaTQoVQyiQzjQ{kQ|lQ}mQ!OnS!QpxQ!VrQ!f!^Q!j!bR!l!kw[OQTVijklmnprvx!Z!^!b!e!kwTOQTVijklmnprvx!Z!^!b!e!kiiUoz{|}!O!Q!V!f!j!lgjUo{|}!O!Q!V!f!j!lekUo|}!O!Q!V!f!j!lclUo}!O!Q!V!f!j!lamUo!O!Q!V!f!j!l_nUo!Q!V!f!j!lT!TpxwZOQTVijklmnprvx!Z!^!b!e!kwYOQTVijklmnprvx!Z!^!b!e!kRuYZ]OQv!Z!eRrWR!UpQQOW`Qv!Z!eQv^R!ZwQ!_!QR!g!_QtYR!WtQ!a!RR!i!aQw^R![wT_OQ",
nodeNames: "⚠ Sheet Line Expression Literal Number UnaryExpression UnaryOperator BinaryExpression BinaryOperator BinaryOperator BinaryOperator BinaryOperator BinaryOperator Identifier BinaryOperator Grouping FunctionCall Arguments Postings SearchQueryString { SearchQuery } Assignment AssignmentOperator FunctionDefinition Parameters Header",
maxTerm: 58,
propSources: [sheetHighlighting],
skippedNodes: [0],
repeatNodeCount: 5,
tokenData: "'e~RfXY!gYZ!l]^!lpq!gqr!qst#Oxy#gyz#lz{#q{|#v|}#{}!O$Q!P!Q$V!Q!R$[!R![%j!^!_%{!_!`&Y!`!a&g!c!}&t#R#S&t#S#T'`#T#o&t~!lOr~~!qOs~U!vPwQ!_!`!yS#OO!PS~#TSj~OY#OZ;'S#O;'S;=`#a<%lO#O~#dP;=`<%l#O~#lO!R~~#qO!Q~~#vOx~~#{Ou~~$QO!S~~$VOv~~$[Oy~~$aRT~!O!P$j!g!h%O#X#Y%O~$mP!Q![$p~$uRT~!Q![$p!g!h%O#X#Y%O~%RR{|%[}!O%[!Q![%b~%_P!Q![%b~%gPT~!Q![%b~%oST~!O!P$j!Q![%j!g!h%O#X#Y%O~&QPz~!_!`&T~&YO{~U&_P!XQ!_!`&bS&gO!OS~&lP|~!_!`&o~&tO}~~&yV^~!O!P&t!P!Q&t!Q![&t![!]&t!c!}&t#R#S&t#T#o&t~'eO!U~",
tokenizers: [1, 2, new LocalTokenGroup("x~RQ#O#PX#S#Tr~[RO;'Se;'S;=`j;=`Oe~jO!W~~oP!W~;=`<%le~wOe~~", 39, 53)],
tokenData: "'e~RfXY!gYZ!l]^!lpq!gqr!qst#Oxy#gyz#lz{#q{|#v|}#{}!O$Q!P!Q$V!Q!R$[!R![%j!^!_%{!_!`&Y!`!a&g!c!}&t#R#S&t#T#o&t#o#p'`~!lOt~~!qOu~U!vPyQ!_!`!yS#OO!RS~#TSl~OY#OZ;'S#O;'S;=`#a<%lO#O~#dP;=`<%l#O~#lO!V~~#qO!U~~#vOz~~#{Ow~~$QO!W~~$VOx~~$[O{~~$aRT~!O!P$j!g!h%O#X#Y%O~$mP!Q![$p~$uRT~!Q![$p!g!h%O#X#Y%O~%RR{|%[}!O%[!Q![%b~%_P!Q![%b~%gPT~!Q![%b~%oST~!O!P$j!Q![%j!g!h%O#X#Y%O~&QP|~!_!`&T~&YO}~~&_P![~!_!`&b~&gO!Q~~&lP!O~!_!`&o~&tO!P~~&yV^~!O!P&t!P!Q&t!Q![&t![!]&t!c!}&t#R#S&t#T#o&t~'eO!X~",
tokenizers: [1, 2, new LocalTokenGroup("x~RQ#O#PX#q#rr~[RO;'Se;'S;=`j;=`Oe~jO!Z~~oP!Z~;=`<%le~wOg~~", 39, 56)],
topRules: {"Sheet":[0,1]},
specialized: [{term: 14, get: (value) => spec_Identifier[value] || -1}],
tokenPrec: 0,
termNames: {"0":"⚠","1":"@top","2":"Line","3":"Expression","4":"Literal","5":"Number","6":"UnaryExpression","7":"UnaryOperator","8":"BinaryExpression","9":"BinaryOperator<\"*\" | \"/\">","10":"BinaryOperator<\"+\" | \"-\">","11":"BinaryOperator<\"<\" | \"<=\" | \">\" | \">=\">","12":"BinaryOperator<\"==\" | \"!=\">","13":"Grouping","14":"Identifier","15":"FunctionCall","16":"Arguments","17":"Postings","18":"SearchQueryString","19":"stringStart","20":"SearchQuery","21":"stringEnd","22":"Assignment","23":"AssignmentOperator","24":"FunctionDefinition","25":"Parameters","26":"Header","27":"newline+","28":"(\",\" Expression)+","29":"(stringContent | stringEscape)+","30":"(\",\" Identifier)+","31":"(newline+ Line)+","32":"␄","33":"%mainskip","34":"whitespace","35":"newline","36":"lines","37":"\"+\"","38":"\"-\"","39":"\"!\"","40":"\"*\"","41":"\"/\"","42":"\"<\"","43":"\"<=\"","44":"\">\"","45":"\">=\"","46":"\"==\"","47":"\"!=\"","48":"\")\"","49":"\"(\"","50":"\",\"","51":"Identifier/\"postings\"","52":"\"`\"","53":"stringContent","54":"stringEscape","55":"\"=\""}
termNames: {"0":"⚠","1":"@top","2":"Line","3":"Expression","4":"Literal","5":"Number","6":"UnaryExpression","7":"UnaryOperator","8":"BinaryExpression","9":"BinaryOperator<\"*\" | \"/\">","10":"BinaryOperator<\"+\" | \"-\">","11":"BinaryOperator<\"<\" | \"<=\" | \">\" | \">=\">","12":"BinaryOperator<\"==\" | \"!=\">","13":"BinaryOperator<@specialize[]<Identifier, \"AND\">>","14":"Identifier","15":"BinaryOperator<@specialize[]<Identifier, \"OR\">>","16":"Grouping","17":"FunctionCall","18":"Arguments","19":"Postings","20":"SearchQueryString","21":"stringStart","22":"SearchQuery","23":"stringEnd","24":"Assignment","25":"AssignmentOperator","26":"FunctionDefinition","27":"Parameters","28":"Header","29":"newline+","30":"(\",\" Expression)+","31":"(stringContent | stringEscape)+","32":"(\",\" Identifier)+","33":"(newline+ Line)+","34":"␄","35":"%mainskip","36":"whitespace","37":"newline","38":"lines","39":"\"+\"","40":"\"-\"","41":"\"!\"","42":"\"*\"","43":"\"/\"","44":"\"<\"","45":"\"<=\"","46":"\">\"","47":"\">=\"","48":"\"==\"","49":"\"!=\"","50":"Identifier/\"AND\"","51":"Identifier/\"OR\"","52":"\")\"","53":"\"(\"","54":"\",\"","55":"\"{\"","56":"stringContent","57":"stringEscape","58":"\"=\""}
})

View File

@ -8,17 +8,17 @@ export const
UnaryExpression = 6,
UnaryOperator = 7,
BinaryExpression = 8,
Grouping = 13,
Identifier = 14,
FunctionCall = 15,
Arguments = 16,
Postings = 17,
SearchQueryString = 18,
stringStart = 19,
SearchQuery = 20,
stringEnd = 21,
Assignment = 22,
AssignmentOperator = 23,
FunctionDefinition = 24,
Parameters = 25,
Header = 26
Grouping = 16,
FunctionCall = 17,
Arguments = 18,
Postings = 19,
SearchQueryString = 20,
stringStart = 21,
SearchQuery = 22,
stringEnd = 23,
Assignment = 24,
AssignmentOperator = 25,
FunctionDefinition = 26,
Parameters = 27,
Header = 28

View File

@ -1,7 +1,13 @@
<script lang="ts">
import { createEditor, sheetEditorState } from "$lib/sheet";
import { moveToEnd, moveToLine, updateContent } from "$lib/editor";
import { ajax, buildDirectoryTree, type Posting, type SheetFile } from "$lib/utils";
import { moveToLine, updateContent } from "$lib/editor";
import {
ajax,
buildDirectoryTree,
formatFloatUptoPrecision,
type Posting,
type SheetFile
} from "$lib/utils";
import { redo, undo } from "@codemirror/commands";
import type { KeyBinding } from "@codemirror/view";
import * as toast from "bulma-toast";
@ -140,8 +146,6 @@
}
moveToLine(editor, lineNumber, true);
lineNumber = 0;
} else {
moveToEnd(editor);
}
}
}
@ -292,6 +296,16 @@
>
</div>
{/if}
<div class="control ml-5">
<button
class:is-loading={$sheetEditorState.pendingEval}
class="is-loading button is-small pointer-events-none px-0"
style="border: none"
>
{formatFloatUptoPrecision($sheetEditorState.evalDuration, 2)}ms
</button>
</div>
</div>
</div>
</div>
@ -311,7 +325,7 @@
</div>
<div class="column is-9-widescreen is-10-fullhd is-8">
<div class="flex">
<div class="box box-r-none py-0 pr-1 mb-0 basis-[36rem] max-w-[36rem]">
<div class="box box-r-none py-0 pr-1 mb-0 basis-[36rem] max-w-[48rem]">
<div class="sheet-editor" bind:this={editorDom} />
</div>
<div

View File

@ -33,6 +33,8 @@ interface SheetEditorState {
undoDepth: number;
redoDepth: number;
doc: string;
pendingEval: boolean;
evalDuration: number;
currentLine: number;
errors: SheetFileError[];
results: SheetLineResult[];
@ -44,6 +46,8 @@ export const initialSheetEditorState: SheetEditorState = {
redoDepth: 0,
currentLine: 0,
doc: "",
pendingEval: false,
evalDuration: 0,
errors: [],
results: []
};

View File

@ -3,7 +3,7 @@
"compilerOptions": {
"types": ["bun-types"],
"allowJs": true,
"checkJs": true,
"checkJs": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,