From 791cd547ae6d266512276d1e57ea53d450e00ae5 Mon Sep 17 00:00:00 2001 From: Anish Lakhwara Date: Sun, 10 Nov 2024 02:15:10 -0800 Subject: [PATCH] feat: new command parser It's not fully hooked up to the react app, and it's not quite complete either, but it's a start. Currently, it will parse filter implicitly, and there's also the done and add command. For now, all commands must place the command name in the right location. Additionally, I've hooked up the react table's selection state to the command parser. Filter based on _ids_ explicitly does work. Manually selecting an ID will also place it in the input field for discoverability. It does not remove ids that were manually deselected yet, nor does filter based on any other attribute work. The command parser also parses times, but it does so very poorly currently. More work will need to go into that, for now it's basically a stub. I'll also need to convert the parser to Go. The react table conviently handles virtual tables based on it's internal `row.id`. I'm still wondering how to implement virtual ids, but at least on the web app, they are constantly updated since the view is always visible, which is different to the CLI (though perhaps the same as a TUI?) Notably, there's no semantics for the difference between selection and filtering. I think perhaps the implicit filter command should default to selection, but the explicit filter command should be handled differently, and actually execute filtering. I haven't yet looked into how the TanStack Table handles filtering, but I am aware that it does. I think for an MVP, once done is implemented, I can deploy this, the web socket server, and try and start using it. --- mast-react-vite/src/App.tsx | 76 +- .../src/components/ui/data-table.tsx | 13 + parser/command_js.peg | 143 ++ parser/command_parser.js | 1285 +++++++++++++++++ 4 files changed, 1508 insertions(+), 9 deletions(-) create mode 100644 parser/command_js.peg create mode 100644 parser/command_parser.js diff --git a/mast-react-vite/src/App.tsx b/mast-react-vite/src/App.tsx index 1183054..e30b1a2 100644 --- a/mast-react-vite/src/App.tsx +++ b/mast-react-vite/src/App.tsx @@ -8,6 +8,7 @@ import { } from "@tanstack/react-table" import { DataTable } from "@/components/ui/data-table" import * as addParser from '../../parser/add_parser.js'; +import * as commandParser from '../../parser/command_parser.js'; import { Checkbox } from "@/components/ui/checkbox" import { Input } from "@/components/ui/input" @@ -49,13 +50,31 @@ export const columns: ColumnDef[] = [ className="translate-y-[2px]" /> ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - className="translate-y-[2px]" - /> + cell: ({ row, table }) => ( +
+

{row.index+1}

+ { + row.toggleSelected(!!value); + // Get setNewText from table meta + const setNewText = table.options.meta?.setNewText; + const NewText = table.options.meta?.newText; + if (setNewText && value === true) { + const calculatedRowId = row.index+1 + setNewText(NewText+calculatedRowId+","); + } else { + // TODO: + // We need to _remove_ the value here + // That probably means taking the NewText + // Removing the first instance of "row.index," + // And then setting that with setNewText + } + }} + aria-label="Select row" + className="translate-y-[2px]" + /> +
), enableSorting: false, enableHiding: false, @@ -82,6 +101,7 @@ export const columns: ColumnDef[] = [ function App({ ctx }) { const todos = useQuery(ctx, "SELECT * FROM todos").data; const [newText, setNewText] = useState(""); + const [rowSelection, setRowSelection] = useState({}); const addTodo = (e) => { if (e.key === "Enter" && e.target.value.trim() !== "") { @@ -97,16 +117,54 @@ function App({ ctx }) { } } + const parseTodos = (e) => { + if (e.target.value.trim() !== "") { + try { + const parsed = commandParser.parse(e.target.value); + + console.log(parsed) + if (parsed.filters && parsed.filters.length > 0) { + const idFilters = parsed.filters.filter(f => f.type === "id"); + console.log(idFilters) + + // Create new selection state + const newSelection = {}; + idFilters.forEach(filter => { + filter.ids.forEach(id => { + newSelection[id-1] = true; + }) + }); + + setRowSelection(newSelection); + } + } catch (error) { + console.log("Unable to parse field") + return; + } + } else { + setRowSelection({}); + } + } + return ( <>
setNewText(e.target.value)} />
- +
) diff --git a/mast-react-vite/src/components/ui/data-table.tsx b/mast-react-vite/src/components/ui/data-table.tsx index 58fdcde..c1967e7 100644 --- a/mast-react-vite/src/components/ui/data-table.tsx +++ b/mast-react-vite/src/components/ui/data-table.tsx @@ -5,6 +5,7 @@ import { useState } from "react" import { ColumnDef, ColumnSizingState, + RowSelectionState, flexRender, getCoreRowModel, useReactTable, @@ -22,11 +23,17 @@ import { interface DataTableProps { columns: ColumnDef[] data: TData[] + rowSelection: {} + setRowSelection: (value: {}) => void } export function DataTable({ columns, data, + rowSelection, + setNewText, + newText, + setRowSelection, }: DataTableProps) { const [colSizing, setColSizing] = useState({}); const table = useReactTable({ @@ -36,9 +43,15 @@ export function DataTable({ columnResizeMode: "onChange", getCoreRowModel: getCoreRowModel(), onColumnSizingChange: setColSizing, + onRowSelectionChange: setRowSelection, state: { columnSizing: colSizing, + rowSelection: rowSelection, }, + meta: { + setNewText, + newText, + } }) return ( diff --git a/parser/command_js.peg b/parser/command_js.peg new file mode 100644 index 0000000..59655ff --- /dev/null +++ b/parser/command_js.peg @@ -0,0 +1,143 @@ +{ + function makeCommand(type, filters, parts) { + + const validParts = parts || []; + // Extract pure description (without attributes) + const description = validParts + .filter(part => typeof part === 'string') + .join(' ') + .trim(); + + // Collect all attributes + const attributes = validParts + .filter(part => typeof part === 'object') + .filter(part => part !== null); + + // Store original parts for reconstruction + const originalParts = validParts.filter(part => part !== null); + + + return { + type: type, + filters: filters.flat() || [], + description: description || [], + attributes: attributes || [], + parts: parts || [], + reconstruct: function() { + const filterStr = this.filters.map(f => f.reconstruct()).join(','); + const partsStr = this.parts.map(p => p.reconstruct()).join(' '); + return [filterStr, this.type, partsStr].filter(Boolean).join(' '); + } + }; + } +} + +Start = AddCommand / DoneCommand / ExplicitFilterCommand / ImplicitFilterCommand + +// ADD COMMAND +AddCommand = "add" _ parts:(Part / _)+ EOF { + return makeCommand('add', null, parts.filter(p => p !== null)); +} + +// DONE COMMAND +DoneCommand = filters:Filters _ "done" EOF { + return makeCommand('done', filters, null); +} + +ExplicitFilterCommand = filters:Filters _ "filter" _* moreFilters:Filters* EOF { + return makeCommand('filter', [...filters, ...moreFilters], null); +} + +ImplicitFilterCommand = filters:Filters+ EOF { + return makeCommand('filter', filters, null); +} + +Filters = first:Filter rest:(_ Filter)* { + return [first, ...rest.map(r => r[1])]; +} + + +IdRange = start:Integer "-" end:Integer { + const ids = []; + for (let i = start; i <= end; i++) { + ids.push(i); + } + return ids; +} + +SingleId = id:Integer { + return [id]; +} + +Filter = IdFilter / Tag / Project / Priority / Due + +IdFilter = first:(IdRange / SingleId) rest:("," (IdRange / SingleId))* trailing:"," ? { + const ids = [first, ...rest.map(r => r[1])].flat(); + return { + type: 'id', + ids: ids, + reconstruct: function() { return this.ids.join(','); } + }; +} + +Part = Attribute / TextPart + +TextPart = chars:Word { + return { + type: "text", + value: chars, + reconstruct: function() { return this.value; } + }; +} + +Attribute = Due / Tag / Project / Priority + +Due = "@" value:TimeValue { + return { + type: "due", + value: value, + reconstruct: function() { return `@${this.value}`; } + }; +} + +Project = ("pro:" / "project:" / "+") value:Word { + return { + type: "project", + value: value, + reconstruct: function() { return `+${this.value}`; } + }; +} + +Priority = "priority:" value:[HML] { + return { + type: "priority", + value: value, + reconstruct: function() { return `priority:${this.value}`; } + }; +} + +Tag = "#" value:Word { + return { + type: "tag", + value: value, + reconstruct: function() { return `#${this.value}`; } + }; +} + +Integer = digits:[0-9]+ { + return parseInt(digits.join(''), 10); +} + +TimeValue = chars:[0-9:]+ ("am" / "pm")? { + return chars.join('') + (text() || ''); +} + +Word = chars:[a-zA-Z0-9_-]+ { + return chars.join(''); +} + +_ = [ \t]+ { + return null; +} + +EOF = !. diff --git a/parser/command_parser.js b/parser/command_parser.js new file mode 100644 index 0000000..b8e04b3 --- /dev/null +++ b/parser/command_parser.js @@ -0,0 +1,1285 @@ +// @generated by Peggy 4.1.1. +// +// https://peggyjs.org/ + + +function peg$subclass(child, parent) { + function C() { this.constructor = child; } + C.prototype = parent.prototype; + child.prototype = new C(); +} + +function peg$SyntaxError(message, expected, found, location) { + var self = Error.call(this, message); + // istanbul ignore next Check is a necessary evil to support older environments + if (Object.setPrototypeOf) { + Object.setPrototypeOf(self, peg$SyntaxError.prototype); + } + self.expected = expected; + self.found = found; + self.location = location; + self.name = "SyntaxError"; + return self; +} + +peg$subclass(peg$SyntaxError, Error); + +function peg$padEnd(str, targetLength, padString) { + padString = padString || " "; + if (str.length > targetLength) { return str; } + targetLength -= str.length; + padString += padString.repeat(targetLength); + return str + padString.slice(0, targetLength); +} + +peg$SyntaxError.prototype.format = function(sources) { + var str = "Error: " + this.message; + if (this.location) { + var src = null; + var k; + for (k = 0; k < sources.length; k++) { + if (sources[k].source === this.location.source) { + src = sources[k].text.split(/\r\n|\n|\r/g); + break; + } + } + var s = this.location.start; + var offset_s = (this.location.source && (typeof this.location.source.offset === "function")) + ? this.location.source.offset(s) + : s; + var loc = this.location.source + ":" + offset_s.line + ":" + offset_s.column; + if (src) { + var e = this.location.end; + var filler = peg$padEnd("", offset_s.line.toString().length, ' '); + var line = src[s.line - 1]; + var last = s.line === e.line ? e.column : line.length + 1; + var hatLen = (last - s.column) || 1; + str += "\n --> " + loc + "\n" + + filler + " |\n" + + offset_s.line + " | " + line + "\n" + + filler + " | " + peg$padEnd("", s.column - 1, ' ') + + peg$padEnd("", hatLen, "^"); + } else { + str += "\n at " + loc; + } + } + return str; +}; + +peg$SyntaxError.buildMessage = function(expected, found) { + var DESCRIBE_EXPECTATION_FNS = { + literal: function(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + class: function(expectation) { + var escapedParts = expectation.parts.map(function(part) { + return Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part); + }); + + return "[" + (expectation.inverted ? "^" : "") + escapedParts.join("") + "]"; + }, + + any: function() { + return "any character"; + }, + + end: function() { + return "end of input"; + }, + + other: function(expectation) { + return expectation.description; + } + }; + + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + + function literalEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function classEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + var descriptions = expected.map(describeExpectation); + var i, j; + + descriptions.sort(); + + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; +}; + +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + + var peg$FAILED = {}; + var peg$source = options.grammarSource; + + var peg$startRuleFunctions = { Start: peg$parseStart }; + var peg$startRuleFunction = peg$parseStart; + + var peg$c0 = "add"; + var peg$c1 = "done"; + var peg$c2 = "filter"; + var peg$c3 = "-"; + var peg$c4 = ","; + var peg$c5 = "@"; + var peg$c6 = "pro:"; + var peg$c7 = "project:"; + var peg$c8 = "+"; + var peg$c9 = "priority:"; + var peg$c10 = "#"; + var peg$c11 = "am"; + var peg$c12 = "pm"; + + var peg$r0 = /^[HML]/; + var peg$r1 = /^[0-9]/; + var peg$r2 = /^[0-9:]/; + var peg$r3 = /^[a-zA-Z0-9_\-]/; + var peg$r4 = /^[ \t]/; + + var peg$e0 = peg$literalExpectation("add", false); + var peg$e1 = peg$literalExpectation("done", false); + var peg$e2 = peg$literalExpectation("filter", false); + var peg$e3 = peg$literalExpectation("-", false); + var peg$e4 = peg$literalExpectation(",", false); + var peg$e5 = peg$literalExpectation("@", false); + var peg$e6 = peg$literalExpectation("pro:", false); + var peg$e7 = peg$literalExpectation("project:", false); + var peg$e8 = peg$literalExpectation("+", false); + var peg$e9 = peg$literalExpectation("priority:", false); + var peg$e10 = peg$classExpectation(["H", "M", "L"], false, false); + var peg$e11 = peg$literalExpectation("#", false); + var peg$e12 = peg$classExpectation([["0", "9"]], false, false); + var peg$e13 = peg$classExpectation([["0", "9"], ":"], false, false); + var peg$e14 = peg$literalExpectation("am", false); + var peg$e15 = peg$literalExpectation("pm", false); + var peg$e16 = peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false); + var peg$e17 = peg$classExpectation([" ", "\t"], false, false); + var peg$e18 = peg$anyExpectation(); + + var peg$f0 = function(parts) { + return makeCommand('add', null, parts.filter(p => p !== null)); +}; + var peg$f1 = function(filters) { + return makeCommand('done', filters, null); +}; + var peg$f2 = function(filters, moreFilters) { + return makeCommand('filter', [...filters, ...moreFilters], null); +}; + var peg$f3 = function(filters) { + return makeCommand('filter', filters, null); +}; + var peg$f4 = function(first, rest) { + return [first, ...rest.map(r => r[1])]; +}; + var peg$f5 = function(start, end) { + const ids = []; + for (let i = start; i <= end; i++) { + ids.push(i); + } + return ids; +}; + var peg$f6 = function(id) { + return [id]; +}; + var peg$f7 = function(first, rest, trailing) { + const ids = [first, ...rest.map(r => r[1])].flat(); + return { + type: 'id', + ids: ids, + reconstruct: function() { return this.ids.join(','); } + }; +}; + var peg$f8 = function(chars) { + return { + type: "text", + value: chars, + reconstruct: function() { return this.value; } + }; +}; + var peg$f9 = function(value) { + return { + type: "due", + value: value, + reconstruct: function() { return `@${this.value}`; } + }; +}; + var peg$f10 = function(value) { + return { + type: "project", + value: value, + reconstruct: function() { return `+${this.value}`; } + }; +}; + var peg$f11 = function(value) { + return { + type: "priority", + value: value, + reconstruct: function() { return `priority:${this.value}`; } + }; +}; + var peg$f12 = function(value) { + return { + type: "tag", + value: value, + reconstruct: function() { return `#${this.value}`; } + }; +}; + var peg$f13 = function(digits) { + return parseInt(digits.join(''), 10); +}; + var peg$f14 = function(chars) { + return chars.join('') + (text() || ''); +}; + var peg$f15 = function(chars) { + return chars.join(''); +}; + var peg$f16 = function() { + return null; +}; + var peg$currPos = options.peg$currPos | 0; + var peg$savedPos = peg$currPos; + var peg$posDetailsCache = [{ line: 1, column: 1 }]; + var peg$maxFailPos = peg$currPos; + var peg$maxFailExpected = options.peg$maxFailExpected || []; + var peg$silentFails = options.peg$silentFails | 0; + + var peg$result; + + if (options.startRule) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function offset() { + return peg$savedPos; + } + + function range() { + return { + source: peg$source, + start: peg$savedPos, + end: peg$currPos + }; + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildSimpleError(message, location); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text: text, ignoreCase: ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase) { + return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description: description }; + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos]; + var p; + + if (details) { + return details; + } else { + if (pos >= peg$posDetailsCache.length) { + p = peg$posDetailsCache.length - 1; + } else { + p = pos; + while (!peg$posDetailsCache[--p]) {} + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + + return details; + } + } + + function peg$computeLocation(startPos, endPos, offset) { + var startPosDetails = peg$computePosDetails(startPos); + var endPosDetails = peg$computePosDetails(endPos); + + var res = { + source: peg$source, + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + if (offset && peg$source && (typeof peg$source.offset === "function")) { + res.start = peg$source.offset(res.start); + res.end = peg$source.offset(res.end); + } + return res; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parseStart() { + var s0; + + s0 = peg$parseAddCommand(); + if (s0 === peg$FAILED) { + s0 = peg$parseDoneCommand(); + if (s0 === peg$FAILED) { + s0 = peg$parseExplicitFilterCommand(); + if (s0 === peg$FAILED) { + s0 = peg$parseImplicitFilterCommand(); + } + } + } + + return s0; + } + + function peg$parseAddCommand() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 3) === peg$c0) { + s1 = peg$c0; + peg$currPos += 3; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parsePart(); + if (s4 === peg$FAILED) { + s4 = peg$parse_(); + } + if (s4 !== peg$FAILED) { + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parsePart(); + if (s4 === peg$FAILED) { + s4 = peg$parse_(); + } + } + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s4 = peg$parseEOF(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f0(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseDoneCommand() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parseFilters(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (s2 !== peg$FAILED) { + if (input.substr(peg$currPos, 4) === peg$c1) { + s3 = peg$c1; + peg$currPos += 4; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parseEOF(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f1(s1); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseExplicitFilterCommand() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + s1 = peg$parseFilters(); + if (s1 !== peg$FAILED) { + s2 = peg$parse_(); + if (s2 !== peg$FAILED) { + if (input.substr(peg$currPos, 6) === peg$c2) { + s3 = peg$c2; + peg$currPos += 6; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parse_(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parse_(); + } + s5 = []; + s6 = peg$parseFilters(); + while (s6 !== peg$FAILED) { + s5.push(s6); + s6 = peg$parseFilters(); + } + s6 = peg$parseEOF(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f2(s1, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseImplicitFilterCommand() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parseFilters(); + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseFilters(); + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseEOF(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f3(s1); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseFilters() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseFilter(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parse_(); + if (s4 !== peg$FAILED) { + s5 = peg$parseFilter(); + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parse_(); + if (s4 !== peg$FAILED) { + s5 = peg$parseFilter(); + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + peg$savedPos = s0; + s0 = peg$f4(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseIdRange() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parseInteger(); + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 45) { + s2 = peg$c3; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e3); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parseInteger(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f5(s1, s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseSingleId() { + var s0, s1; + + s0 = peg$currPos; + s1 = peg$parseInteger(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f6(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseFilter() { + var s0; + + s0 = peg$parseIdFilter(); + if (s0 === peg$FAILED) { + s0 = peg$parseTag(); + if (s0 === peg$FAILED) { + s0 = peg$parseProject(); + if (s0 === peg$FAILED) { + s0 = peg$parsePriority(); + if (s0 === peg$FAILED) { + s0 = peg$parseDue(); + } + } + } + } + + return s0; + } + + function peg$parseIdFilter() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseIdRange(); + if (s1 === peg$FAILED) { + s1 = peg$parseSingleId(); + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 44) { + s4 = peg$c4; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s4 !== peg$FAILED) { + s5 = peg$parseIdRange(); + if (s5 === peg$FAILED) { + s5 = peg$parseSingleId(); + } + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 44) { + s4 = peg$c4; + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s4 !== peg$FAILED) { + s5 = peg$parseIdRange(); + if (s5 === peg$FAILED) { + s5 = peg$parseSingleId(); + } + if (s5 !== peg$FAILED) { + s4 = [s4, s5]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + if (input.charCodeAt(peg$currPos) === 44) { + s3 = peg$c4; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s3 === peg$FAILED) { + s3 = null; + } + peg$savedPos = s0; + s0 = peg$f7(s1, s2, s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsePart() { + var s0; + + s0 = peg$parseAttribute(); + if (s0 === peg$FAILED) { + s0 = peg$parseTextPart(); + } + + return s0; + } + + function peg$parseTextPart() { + var s0, s1; + + s0 = peg$currPos; + s1 = peg$parseWord(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f8(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseAttribute() { + var s0; + + s0 = peg$parseDue(); + if (s0 === peg$FAILED) { + s0 = peg$parseTag(); + if (s0 === peg$FAILED) { + s0 = peg$parseProject(); + if (s0 === peg$FAILED) { + s0 = peg$parsePriority(); + } + } + } + + return s0; + } + + function peg$parseDue() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 64) { + s1 = peg$c5; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e5); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseTimeValue(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f9(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseProject() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c6) { + s1 = peg$c6; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + if (s1 === peg$FAILED) { + if (input.substr(peg$currPos, 8) === peg$c7) { + s1 = peg$c7; + peg$currPos += 8; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e7); } + } + if (s1 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 43) { + s1 = peg$c8; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e8); } + } + } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseWord(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f10(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsePriority() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 9) === peg$c9) { + s1 = peg$c9; + peg$currPos += 9; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e9); } + } + if (s1 !== peg$FAILED) { + s2 = input.charAt(peg$currPos); + if (peg$r0.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e10); } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f11(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseTag() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 35) { + s1 = peg$c10; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e11); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseWord(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f12(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseInteger() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = input.charAt(peg$currPos); + if (peg$r1.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e12); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = input.charAt(peg$currPos); + if (peg$r1.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e12); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f13(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseTimeValue() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = input.charAt(peg$currPos); + if (peg$r2.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e13); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = input.charAt(peg$currPos); + if (peg$r2.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e13); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + if (input.substr(peg$currPos, 2) === peg$c11) { + s2 = peg$c11; + peg$currPos += 2; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s2 === peg$FAILED) { + if (input.substr(peg$currPos, 2) === peg$c12) { + s2 = peg$c12; + peg$currPos += 2; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + } + if (s2 === peg$FAILED) { + s2 = null; + } + peg$savedPos = s0; + s0 = peg$f14(s1); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseWord() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = input.charAt(peg$currPos); + if (peg$r3.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = input.charAt(peg$currPos); + if (peg$r3.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f15(s1); + } + s0 = s1; + + return s0; + } + + function peg$parse_() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = input.charAt(peg$currPos); + if (peg$r4.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e17); } + } + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = input.charAt(peg$currPos); + if (peg$r4.test(s2)) { + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e17); } + } + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f16(); + } + s0 = s1; + + return s0; + } + + function peg$parseEOF() { + var s0, s1; + + s0 = peg$currPos; + peg$silentFails++; + if (input.length > peg$currPos) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e18); } + } + peg$silentFails--; + if (s1 === peg$FAILED) { + s0 = undefined; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + + function makeCommand(type, filters, parts) { + + const validParts = parts || []; + // Extract pure description (without attributes) + const description = validParts + .filter(part => typeof part === 'string') + .join(' ') + .trim(); + + // Collect all attributes + const attributes = validParts + .filter(part => typeof part === 'object') + .filter(part => part !== null); + + // Store original parts for reconstruction + const originalParts = validParts.filter(part => part !== null); + + + return { + type: type, + filters: filters.flat() || [], + description: description || [], + attributes: attributes || [], + parts: parts || [], + reconstruct: function() { + const filterStr = this.filters.map(f => f.reconstruct()).join(','); + const partsStr = this.parts.map(p => p.reconstruct()).join(' '); + return [filterStr, this.type, partsStr].filter(Boolean).join(' '); + } + }; + } + + peg$result = peg$startRuleFunction(); + + if (options.peg$library) { + return /** @type {any} */ ({ + peg$result, + peg$currPos, + peg$FAILED, + peg$maxFailExpected, + peg$maxFailPos + }); + } + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + + throw peg$buildStructuredError( + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } +} + +const peg$allowedStartRules = [ + "Start" +]; + +export { + peg$allowedStartRules as StartRules, + peg$SyntaxError as SyntaxError, + peg$parse as parse +};