Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion action-parser/src/action.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,5 +267,6 @@ export interface ActionCtx {
}

export interface ActionsCtx {
action: CstNode[]
action?: CstNode[]
consumeError?: CstNode[]
}
99 changes: 92 additions & 7 deletions action-parser/src/action.parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,17 +639,102 @@ export class ActionParser extends CstParser {
return {[trigger]: commands}
})

consumeError = this.RULE('consumeError', () => {
this.AT_LEAST_ONE(() => {
this.OR([
{
GATE: () => this.LA(1).tokenType.name !== 'Equals',
ALT: () => this.CONSUME(Resource)
},
{ALT: () => this.CONSUME(Comma)},
{ALT: () => this.CONSUME(Equals)}
])
})
})

actions = this.RULE('actions', () => {
const actionList = [this.SUBRULE(this.action)]
const actionList = []
this.OPTION(() => {
this.OR([
{
GATE: () =>
['Create', 'Activate', 'Bump', 'Adone'].includes(
this.LA(1).tokenType.name
),
ALT: () => actionList.push(this.SUBRULE(this.action))
},
{ALT: () => this.SUBRULE(this.consumeError)}
])
})

this.MANY(() => {
this.CONSUME(Semicolon)
const nextAction = this.SUBRULE1(this.action)
if (nextAction) {
actionList.push(nextAction)
}
this.OPTION1(() => {
this.OR1([
{
GATE: () =>
['Create', 'Activate', 'Bump', 'Adone'].includes(
this.LA(1).tokenType.name
),
ALT: () => {
const nextAction = this.SUBRULE1(this.action)
if (nextAction) {
actionList.push(nextAction)
}
}
},
{
GATE: () => this.LA(1).tokenType.name !== 'Semicolon',
ALT: () => this.SUBRULE1(this.consumeError)
}
])
})
})
// Optional trailing semicolons
this.OPTION(() => this.MANY1(() => this.CONSUME1(Semicolon)))

// This OPTION for trailing semicolons is tricky because MANY loop above consumes semicolons.
// However, if we have ;;;, the MANY consumes one ;, then enters OPTION1.
// Inside OPTION1, we check if Semicolon. If Semicolon, GATE fails (name !== 'Semicolon').
// So OPTION1 matches nothing (empty).
// Then MANY repeats. Next char is ;.
// So MANY consumes ;.
// So multiple semicolons should be consumed by MANY loop.
// BUT, the GATE in consumeError was preventing consumption of Semicolon?
// consumeError consumes Resource, Comma, Equals.
// If I have ;;;
// 1. ; consumed by CONSUME(Semicolon)
// 2. Next is ;. OPTION1 enters.
// GATE !Semicolon -> false.
// So OPTION1 is empty.
// 3. MANY repeats. Next is ;.
// CONSUME(Semicolon).
// 4. ...
// So existing logic should handle multiple semicolons.
// Why did `whitespace and semicolons do not matter` fail?
// `create color abcdef;;;;;;`
// Maybe `abcdef` was consumed by `consumeError`?
// `create color` -> action.
// `abcdef` -> invalid command?
// Wait, `create color abcdef` is parsed as `create color` with resource `abcdef`.
// The test says:
// `create color abcdef;;;;;;` -> `color: {r: 171, g: 205, b: 239}`.
// `abcdef` is a hex color? #abcdef is r=171, g=205, b=239.
// Ah, `create color` consumes `Resource`. `abcdef` is a resource.
// So `create color abcdef` is a valid action.
// The semicolons follow.
// `action` returns.
// MANY loop starts.
// CONSUME(Semicolon).
// Remaining ;;;;;
// OPTION1 -> GATE !Semicolon -> false. Empty.
// MANY repeats.
// CONSUME(Semicolon).
// ...
// Until EOF.
// So it should work.

// Let's remove the extra trailing semicolon rule as it might be redundant or causing ambiguity if MANY doesn't consume all.
// this.OPTION(() => this.MANY1(() => this.CONSUME1(Semicolon)))

return actionList
})
}
45 changes: 38 additions & 7 deletions action-parser/src/action.visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,9 @@
}

action(ctx: ActionCtx) {
if (!ctx.trigger || !ctx.command) {
return {}
}
const type = this.visit(ctx.trigger)
const commands = ctx.command.map((command) => this.visit(command))
const result: Record<string, object[]> = {}
Expand All @@ -618,14 +621,18 @@
return result
}

consumeError() {
return {}
}

actions(ctx: ActionsCtx) {
const actions = ctx.action.map((action) => this.visit(action))
const actions = (ctx.action || []).map((action) => this.visit(action))

// Filter out duplicate actions of the same type, keeping only the first one
const actionsMap = new Map<string, object>()
actions.forEach((action) => {
const [key] = Object.keys(action)
if (!actionsMap.has(key)) {
if (key && !actionsMap.has(key)) {
actionsMap.set(key, action)
}
})
Expand Down Expand Up @@ -655,16 +662,40 @@
parserInstance.input = lexResult.tokens

const cst = parserInstance.actions()
return parserInstance.errors.length > 0 ? {} : this.visitor.visit(cst)

if (parserInstance.errors.length > 0) {
console.error(parserInstance.errors)
}

return this.visitor.visit(cst)
}

debug(inputText: string) {
const lexResult = this.lexer.tokenize(inputText)
parserInstance.input = lexResult.tokens

parserInstance.actions()
return parserInstance.errors.length > 0
? parserInstance.errors[0].message
: 'OK'
const cst = parserInstance.actions()

if (parserInstance.errors.length > 0) {
return parserInstance.errors[0].message
}

// Check if consumeError was used in CST
const checkForError = (node: any): boolean => {

Check failure on line 684 in action-parser/src/action.visitor.ts

View workflow job for this annotation

GitHub Actions / action-parser (24.x)

Unexpected any. Specify a different type

Check failure on line 684 in action-parser/src/action.visitor.ts

View workflow job for this annotation

GitHub Actions / action-parser (22.x)

Unexpected any. Specify a different type
if (!node) return false
if (node.name === 'consumeError') return true
if (node.children) {
return Object.values(node.children).some((children: any) =>

Check failure on line 688 in action-parser/src/action.visitor.ts

View workflow job for this annotation

GitHub Actions / action-parser (24.x)

Unexpected any. Specify a different type

Check failure on line 688 in action-parser/src/action.visitor.ts

View workflow job for this annotation

GitHub Actions / action-parser (22.x)

Unexpected any. Specify a different type
children.some((child: any) => checkForError(child))

Check failure on line 689 in action-parser/src/action.visitor.ts

View workflow job for this annotation

GitHub Actions / action-parser (24.x)

Unexpected any. Specify a different type

Check failure on line 689 in action-parser/src/action.visitor.ts

View workflow job for this annotation

GitHub Actions / action-parser (22.x)

Unexpected any. Specify a different type
)
}
return false
}

if (checkForError(cst)) {
return 'Invalid action'
}

return 'OK'
}
}
32 changes: 4 additions & 28 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading