From 1b1caafbf649e95b27cf6201c8ba8cfedb9e3a93 Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Thu, 2 Apr 2026 15:24:38 +0200 Subject: [PATCH 1/7] feat: improve ABNF grammar clarity and inline all secondary grammars Restructure the ABNF grammar to use explicit, typed reference rules in the primary grammar instead of relying on secondary grammars with two-pass parsing. This improves grammar clarity and aligns with the proposed spec changes in OAI/Arazzo-Specification#454. Key changes: - Add $self expression support - Add $inputs/$outputs JSON Pointer support (e.g., $inputs.customer#/firstName) - Inline all secondary grammars into the primary grammar - Extract shared identifier and identifier-strict rules - Adapt json-pointer to exclude { and } from unescaped for unambiguous embedded expression parsing, fixing the body expression extract limitation - Require explicit component types (parameters/successActions/failureActions) - Update README with current grammar and examples Resolves: OAI/Arazzo-Specification#424, OAI/Arazzo-Specification#425, OAI/Arazzo-Specification#426, OAI/Arazzo-Specification#428 Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 109 +- src/grammar.bnf | 94 +- src/grammar.js | 586 +++++----- .../translators/ASTTranslator/transformers.js | 137 +-- src/parse/translators/CSTTranslator.js | 28 +- test/extract.js | 12 +- test/fixtures/expressions-valid.js | 14 + test/parse/__snapshots__/ast-corpus.js.snap | 196 +++- test/parse/__snapshots__/cst-corpus.js.snap | 1013 +++++++++++++++-- .../__snapshots__/ASTTranslator.js.snap | 1 - .../__snapshots__/CSTTranslator.js.snap | 19 +- test/test.js | 12 +- 12 files changed, 1643 insertions(+), 578 deletions(-) diff --git a/README.md b/README.md index b3b5867..297acdb 100644 --- a/README.md +++ b/README.md @@ -88,11 +88,6 @@ test(expressions[0]); // => true parse(expressions[0]); // => { result, tree } ``` -**Known limitation:** `$request.body#/...` and `$response.body#/...` expressions with JSON pointers cannot be reliably -extracted from `{expression}` syntax. This is because [RFC 6901](https://datatracker.ietf.org/doc/html/rfc6901) -(JSON Pointer) allows the `}` character in pointer paths, making it impossible to determine where the expression ends. -Use `parse()` directly on the raw expression for these cases. - #### Parsing Parsing a Runtime Expression is as simple as importing the **parse** function and calling it. @@ -266,52 +261,66 @@ The runtime expression is defined by the following [ABNF](https://tools.ietf.org ```abnf ; Arazzo runtime expression ABNF syntax -expression = ( "$url" / "$method" / "$statusCode" / "$request." source / "$response." source / "$inputs." name / "$outputs." name / "$steps." name / "$workflows." name / "$sourceDescriptions." name / "$components." name ) -source = ( header-reference / query-reference / path-reference / body-reference ) -header-reference = "header." token -query-reference = "query." name -path-reference = "path." name -body-reference = "body" ["#" json-pointer ] -name = *( CHAR ) +expression = ( + "$url" / + "$method" / + "$statusCode" / + "$request." source / + "$response." source / + "$inputs." inputs-reference / + "$outputs." outputs-reference / + "$steps." steps-reference / + "$workflows." workflows-reference / + "$sourceDescriptions." source-reference / + "$components." components-reference / + "$self" + ) +; Request/Response sources +source = ( header-reference / query-reference / path-reference / body-reference ) +header-reference = "header." token +query-reference = "query." name +path-reference = "path." name +body-reference = "body" ["#" json-pointer ] + +; Input/Output references +inputs-reference = inputs-name ["#" json-pointer] +inputs-name = identifier +outputs-reference = outputs-name ["#" json-pointer] +outputs-name = identifier + +; Steps expressions +steps-reference = steps-id ".outputs." outputs-name ["#" json-pointer] +steps-id = identifier-strict + +; Workflows expressions +workflows-reference = workflows-id "." workflows-field "." workflows-field-name ["#" json-pointer] +workflows-id = identifier-strict +workflows-field = "inputs" / "outputs" +workflows-field-name = identifier + +; Source descriptions expressions +source-reference = source-descriptions-name "." source-descriptions-reference +source-descriptions-name = identifier-strict +source-descriptions-reference = 1*CHAR + +; Components expressions +components-reference = components-type "." components-name +components-type = "parameters" / "successActions" / "failureActions" +components-name = identifier + +name = *( CHAR ) ; Grammar for parsing template strings with embedded expressions expression-string = *( literal-char / embedded-expression ) embedded-expression = "{" expression "}" literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) and } (%x7D) -; Secondary grammar for parsing $steps name part -; Format: {stepId}.{field}.{subField}[#/{jsonPointer}] -steps-name = steps-id "." steps-field "." steps-sub-field ["#" json-pointer] -steps-id = 1*(ALPHA / DIGIT / "_" / "-") -steps-field = "outputs" -steps-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_") - -; Secondary grammar for parsing $workflows name part -; Format: {workflowId}.{field}.{subField}[#/{jsonPointer}] -workflows-name = workflows-id "." workflows-field "." workflows-sub-field ["#" json-pointer] -workflows-id = 1*(ALPHA / DIGIT / "_" / "-") -workflows-field = "inputs" / "outputs" -workflows-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_") - -; Secondary grammar for parsing $sourceDescriptions name part -; Format: {sourceName}.{reference} -; reference can be operationId (unconstrained) or workflowId (constrained) -source-descriptions-name = source-descriptions-source-name "." source-descriptions-reference -source-descriptions-source-name = 1*(ALPHA / DIGIT / "_" / "-") -source-descriptions-reference = 1*CHAR - -; Secondary grammar for parsing $components name part -; Format: {field}.{subField} -; Allowed fields: parameters, successActions, failureActions -components-name = components-field "." components-sub-field -components-field = "parameters" / "successActions" / "failureActions" -components-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_") - -; https://datatracker.ietf.org/doc/html/rfc6901#section-3 +; JSON Pointer (RFC 6901, adapted) +; { (%x7B) and } (%x7D) are excluded from 'unescaped' for unambiguous embedded expression parsing json-pointer = *( "/" reference-token ) reference-token = *( unescaped / escaped ) -unescaped = %x00-2E / %x30-7D / %x7F-10FFFF - ; %x2F ('/') and %x7E ('~') are excluded from 'unescaped' +unescaped = %x00-2E / %x30-7A / %x7C / %x7E-10FFFF + ; %x2F ('/'), %x7E ('~'), %x7B ('{'), %x7D ('}') are excluded escaped = "~" ( "0" / "1" ) ; representing '~' and '/', respectively @@ -338,6 +347,12 @@ escape = %x5C ; \ unescape = %x20-21 / %x23-5B / %x5D-7A / %x7C / %x7E-10FFFF ; %x7B ('{') and %x7D ('}') are excluded from 'unescape' +; Identifier rules +identifier = 1*(ALPHA / DIGIT / "." / "-" / "_") + ; Alphanumeric with dots, hyphens, underscores +identifier-strict = 1*(ALPHA / DIGIT / "_" / "-") + ; Alphanumeric with hyphens, underscores (no dots) + ; https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1 HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" DIGIT = %x30-39 ; 0-9 @@ -358,11 +373,15 @@ Request parameter | `$request.path.id` | Request parameters MUST be Request body property | `$request.body#/user/uuid` | In operations which accept payloads, references may be made to portions of the `requestBody` or the entire body. Request URL | `$url` | Response value | `$response.body#/status` | In operations which return payloads, references may be made to portions of the response body or the entire body. -Response header | `$response.header.Server` | Single header values only are available -workflow input | `$inputs.username` or `$workflows.foo.inputs.username` | Single input values only are available +Response header | `$response.header.Server` | Single header values only are available. +Self URI | `$self` | References the canonical URI of the current Arazzo Description. +Workflow input | `$inputs.username` | Single input values only are available. +Workflow input property | `$inputs.customer#/firstName` | To access nested properties within an input object, use JSON Pointer syntax. Step output value | `$steps.someStepId.outputs.pets` | In situations where the output named property return payloads, references may be made to portions of the response body (e.g., `$steps.someStepId.outputs.pets#/0/id`) or the entire body. Workflow output value | `$outputs.bar` or `$workflows.foo.outputs.bar` | In situations where the output named property return payloads, references may be made to portions of the response body (e.g., `$workflows.foo.outputs.mappedResponse#/name`) or the entire body. +Source description reference | `$sourceDescriptions.petstore.getPetById` | References an operationId or workflowId from the named source description. Components parameter | `$components.parameters.foo` | Accesses a foo parameter defined within the Components Object. +Components action | `$components.successActions.bar` | Accesses a success or failure action defined within the Components Object. Runtime expressions preserve the type of the referenced value. Expressions can be embedded into string values by surrounding the expression with `{}` curly braces. diff --git a/src/grammar.bnf b/src/grammar.bnf index d6d616e..fe06af4 100644 --- a/src/grammar.bnf +++ b/src/grammar.bnf @@ -1,50 +1,64 @@ ; Arazzo runtime expression ABNF syntax -expression = ( "$url" / "$method" / "$statusCode" / "$request." source / "$response." source / "$inputs." name / "$outputs." name / "$steps." name / "$workflows." name / "$sourceDescriptions." name / "$components." name ) -source = ( header-reference / query-reference / path-reference / body-reference ) -header-reference = "header." token -query-reference = "query." name -path-reference = "path." name -body-reference = "body" ["#" json-pointer ] -name = *( CHAR ) +expression = ( + "$url" / + "$method" / + "$statusCode" / + "$request." source / + "$response." source / + "$inputs." inputs-reference / + "$outputs." outputs-reference / + "$steps." steps-reference / + "$workflows." workflows-reference / + "$sourceDescriptions." source-reference / + "$components." components-reference / + "$self" + ) +; Request/Response sources +source = ( header-reference / query-reference / path-reference / body-reference ) +header-reference = "header." token +query-reference = "query." name +path-reference = "path." name +body-reference = "body" ["#" json-pointer ] -; Grammar for parsing template strings with embedded expressions -expression-string = *( literal-char / embedded-expression ) -embedded-expression = "{" expression "}" -literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) and } (%x7D) +; Input/Output references +inputs-reference = inputs-name ["#" json-pointer] +inputs-name = identifier +outputs-reference = outputs-name ["#" json-pointer] +outputs-name = identifier -; Secondary grammar for parsing $steps name part -; Format: {stepId}.{field}.{subField}[#/{jsonPointer}] -steps-name = steps-id "." steps-field "." steps-sub-field ["#" json-pointer] -steps-id = 1*(ALPHA / DIGIT / "_" / "-") -steps-field = "outputs" -steps-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_") +; Steps expressions +steps-reference = steps-id ".outputs." outputs-name ["#" json-pointer] +steps-id = identifier-strict -; Secondary grammar for parsing $workflows name part -; Format: {workflowId}.{field}.{subField}[#/{jsonPointer}] -workflows-name = workflows-id "." workflows-field "." workflows-sub-field ["#" json-pointer] -workflows-id = 1*(ALPHA / DIGIT / "_" / "-") -workflows-field = "inputs" / "outputs" -workflows-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_") +; Workflows expressions +workflows-reference = workflows-id "." workflows-field "." workflows-field-name ["#" json-pointer] +workflows-id = identifier-strict +workflows-field = "inputs" / "outputs" +workflows-field-name = identifier -; Secondary grammar for parsing $sourceDescriptions name part -; Format: {sourceName}.{reference} -; reference can be operationId (unconstrained) or workflowId (constrained) -source-descriptions-name = source-descriptions-source-name "." source-descriptions-reference -source-descriptions-source-name = 1*(ALPHA / DIGIT / "_" / "-") +; Source descriptions expressions +source-reference = source-descriptions-name "." source-descriptions-reference +source-descriptions-name = identifier-strict source-descriptions-reference = 1*CHAR -; Secondary grammar for parsing $components name part -; Format: {field}.{subField} -; Allowed fields: parameters, successActions, failureActions -components-name = components-field "." components-sub-field -components-field = "parameters" / "successActions" / "failureActions" -components-sub-field = 1*(ALPHA / DIGIT / "." / "-" / "_") +; Components expressions +components-reference = components-type "." components-name +components-type = "parameters" / "successActions" / "failureActions" +components-name = identifier + +name = *( CHAR ) -; https://datatracker.ietf.org/doc/html/rfc6901#section-3 +; Grammar for parsing template strings with embedded expressions +expression-string = *( literal-char / embedded-expression ) +embedded-expression = "{" expression "}" +literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) and } (%x7D) + +; JSON Pointer (RFC 6901, adapted) +; { (%x7B) and } (%x7D) are excluded from 'unescaped' for unambiguous embedded expression parsing json-pointer = *( "/" reference-token ) reference-token = *( unescaped / escaped ) -unescaped = %x00-2E / %x30-7D / %x7F-10FFFF - ; %x2F ('/') and %x7E ('~') are excluded from 'unescaped' +unescaped = %x00-2E / %x30-7A / %x7C / %x7E-10FFFF + ; %x2F ('/'), %x7E ('~'), %x7B ('{'), %x7D ('}') are excluded escaped = "~" ( "0" / "1" ) ; representing '~' and '/', respectively @@ -71,6 +85,12 @@ escape = %x5C ; \ unescape = %x20-21 / %x23-5B / %x5D-7A / %x7C / %x7E-10FFFF ; %x7B ('{') and %x7D ('}') are excluded from 'unescape' +; Identifier rules +identifier = 1*(ALPHA / DIGIT / "." / "-" / "_") + ; Alphanumeric with dots, hyphens, underscores +identifier-strict = 1*(ALPHA / DIGIT / "_" / "-") + ; Alphanumeric with hyphens, underscores (no dots) + ; https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1 HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" DIGIT = %x30-39 ; 0-9 diff --git a/src/grammar.js b/src/grammar.js index bf7fb55..3da4c52 100644 --- a/src/grammar.js +++ b/src/grammar.js @@ -5,16 +5,16 @@ export default function grammar(){ // ``` // SUMMARY - // rules = 36 + // rules = 40 // udts = 0 - // opcodes = 212 + // opcodes = 204 // --- ABNF original opcodes - // ALT = 21 - // CAT = 24 - // REP = 16 - // RNM = 55 - // TLS = 72 - // TBS = 12 + // ALT = 17 + // CAT = 28 + // REP = 14 + // RNM = 57 + // TLS = 63 + // TBS = 13 // TRG = 12 // --- SABNF superset opcodes // UDT = 0 @@ -33,36 +33,40 @@ export default function grammar(){ this.rules[3] = { name: 'query-reference', lower: 'query-reference', index: 3, isBkr: false }; this.rules[4] = { name: 'path-reference', lower: 'path-reference', index: 4, isBkr: false }; this.rules[5] = { name: 'body-reference', lower: 'body-reference', index: 5, isBkr: false }; - this.rules[6] = { name: 'name', lower: 'name', index: 6, isBkr: false }; - this.rules[7] = { name: 'expression-string', lower: 'expression-string', index: 7, isBkr: false }; - this.rules[8] = { name: 'embedded-expression', lower: 'embedded-expression', index: 8, isBkr: false }; - this.rules[9] = { name: 'literal-char', lower: 'literal-char', index: 9, isBkr: false }; - this.rules[10] = { name: 'steps-name', lower: 'steps-name', index: 10, isBkr: false }; + this.rules[6] = { name: 'inputs-reference', lower: 'inputs-reference', index: 6, isBkr: false }; + this.rules[7] = { name: 'inputs-name', lower: 'inputs-name', index: 7, isBkr: false }; + this.rules[8] = { name: 'outputs-reference', lower: 'outputs-reference', index: 8, isBkr: false }; + this.rules[9] = { name: 'outputs-name', lower: 'outputs-name', index: 9, isBkr: false }; + this.rules[10] = { name: 'steps-reference', lower: 'steps-reference', index: 10, isBkr: false }; this.rules[11] = { name: 'steps-id', lower: 'steps-id', index: 11, isBkr: false }; - this.rules[12] = { name: 'steps-field', lower: 'steps-field', index: 12, isBkr: false }; - this.rules[13] = { name: 'steps-sub-field', lower: 'steps-sub-field', index: 13, isBkr: false }; - this.rules[14] = { name: 'workflows-name', lower: 'workflows-name', index: 14, isBkr: false }; - this.rules[15] = { name: 'workflows-id', lower: 'workflows-id', index: 15, isBkr: false }; - this.rules[16] = { name: 'workflows-field', lower: 'workflows-field', index: 16, isBkr: false }; - this.rules[17] = { name: 'workflows-sub-field', lower: 'workflows-sub-field', index: 17, isBkr: false }; - this.rules[18] = { name: 'source-descriptions-name', lower: 'source-descriptions-name', index: 18, isBkr: false }; - this.rules[19] = { name: 'source-descriptions-source-name', lower: 'source-descriptions-source-name', index: 19, isBkr: false }; - this.rules[20] = { name: 'source-descriptions-reference', lower: 'source-descriptions-reference', index: 20, isBkr: false }; + this.rules[12] = { name: 'workflows-reference', lower: 'workflows-reference', index: 12, isBkr: false }; + this.rules[13] = { name: 'workflows-id', lower: 'workflows-id', index: 13, isBkr: false }; + this.rules[14] = { name: 'workflows-field', lower: 'workflows-field', index: 14, isBkr: false }; + this.rules[15] = { name: 'workflows-field-name', lower: 'workflows-field-name', index: 15, isBkr: false }; + this.rules[16] = { name: 'source-reference', lower: 'source-reference', index: 16, isBkr: false }; + this.rules[17] = { name: 'source-descriptions-name', lower: 'source-descriptions-name', index: 17, isBkr: false }; + this.rules[18] = { name: 'source-descriptions-reference', lower: 'source-descriptions-reference', index: 18, isBkr: false }; + this.rules[19] = { name: 'components-reference', lower: 'components-reference', index: 19, isBkr: false }; + this.rules[20] = { name: 'components-type', lower: 'components-type', index: 20, isBkr: false }; this.rules[21] = { name: 'components-name', lower: 'components-name', index: 21, isBkr: false }; - this.rules[22] = { name: 'components-field', lower: 'components-field', index: 22, isBkr: false }; - this.rules[23] = { name: 'components-sub-field', lower: 'components-sub-field', index: 23, isBkr: false }; - this.rules[24] = { name: 'json-pointer', lower: 'json-pointer', index: 24, isBkr: false }; - this.rules[25] = { name: 'reference-token', lower: 'reference-token', index: 25, isBkr: false }; - this.rules[26] = { name: 'unescaped', lower: 'unescaped', index: 26, isBkr: false }; - this.rules[27] = { name: 'escaped', lower: 'escaped', index: 27, isBkr: false }; - this.rules[28] = { name: 'token', lower: 'token', index: 28, isBkr: false }; - this.rules[29] = { name: 'tchar', lower: 'tchar', index: 29, isBkr: false }; - this.rules[30] = { name: 'CHAR', lower: 'char', index: 30, isBkr: false }; - this.rules[31] = { name: 'escape', lower: 'escape', index: 31, isBkr: false }; - this.rules[32] = { name: 'unescape', lower: 'unescape', index: 32, isBkr: false }; - this.rules[33] = { name: 'HEXDIG', lower: 'hexdig', index: 33, isBkr: false }; - this.rules[34] = { name: 'DIGIT', lower: 'digit', index: 34, isBkr: false }; - this.rules[35] = { name: 'ALPHA', lower: 'alpha', index: 35, isBkr: false }; + this.rules[22] = { name: 'name', lower: 'name', index: 22, isBkr: false }; + this.rules[23] = { name: 'expression-string', lower: 'expression-string', index: 23, isBkr: false }; + this.rules[24] = { name: 'embedded-expression', lower: 'embedded-expression', index: 24, isBkr: false }; + this.rules[25] = { name: 'literal-char', lower: 'literal-char', index: 25, isBkr: false }; + this.rules[26] = { name: 'json-pointer', lower: 'json-pointer', index: 26, isBkr: false }; + this.rules[27] = { name: 'reference-token', lower: 'reference-token', index: 27, isBkr: false }; + this.rules[28] = { name: 'unescaped', lower: 'unescaped', index: 28, isBkr: false }; + this.rules[29] = { name: 'escaped', lower: 'escaped', index: 29, isBkr: false }; + this.rules[30] = { name: 'token', lower: 'token', index: 30, isBkr: false }; + this.rules[31] = { name: 'tchar', lower: 'tchar', index: 31, isBkr: false }; + this.rules[32] = { name: 'CHAR', lower: 'char', index: 32, isBkr: false }; + this.rules[33] = { name: 'escape', lower: 'escape', index: 33, isBkr: false }; + this.rules[34] = { name: 'unescape', lower: 'unescape', index: 34, isBkr: false }; + this.rules[35] = { name: 'identifier', lower: 'identifier', index: 35, isBkr: false }; + this.rules[36] = { name: 'identifier-strict', lower: 'identifier-strict', index: 36, isBkr: false }; + this.rules[37] = { name: 'HEXDIG', lower: 'hexdig', index: 37, isBkr: false }; + this.rules[38] = { name: 'DIGIT', lower: 'digit', index: 38, isBkr: false }; + this.rules[39] = { name: 'ALPHA', lower: 'alpha', index: 39, isBkr: false }; /* UDTS */ this.udts = []; @@ -70,7 +74,7 @@ export default function grammar(){ /* OPCODES */ /* expression */ this.rules[0].opcodes = []; - this.rules[0].opcodes[0] = { type: 1, children: [1,2,3,4,7,10,13,16,19,22,25] };// ALT + this.rules[0].opcodes[0] = { type: 1, children: [1,2,3,4,7,10,13,16,19,22,25,28] };// ALT this.rules[0].opcodes[1] = { type: 7, string: [36,117,114,108] };// TLS this.rules[0].opcodes[2] = { type: 7, string: [36,109,101,116,104,111,100] };// TLS this.rules[0].opcodes[3] = { type: 7, string: [36,115,116,97,116,117,115,99,111,100,101] };// TLS @@ -82,22 +86,23 @@ export default function grammar(){ this.rules[0].opcodes[9] = { type: 4, index: 1 };// RNM(source) this.rules[0].opcodes[10] = { type: 2, children: [11,12] };// CAT this.rules[0].opcodes[11] = { type: 7, string: [36,105,110,112,117,116,115,46] };// TLS - this.rules[0].opcodes[12] = { type: 4, index: 6 };// RNM(name) + this.rules[0].opcodes[12] = { type: 4, index: 6 };// RNM(inputs-reference) this.rules[0].opcodes[13] = { type: 2, children: [14,15] };// CAT this.rules[0].opcodes[14] = { type: 7, string: [36,111,117,116,112,117,116,115,46] };// TLS - this.rules[0].opcodes[15] = { type: 4, index: 6 };// RNM(name) + this.rules[0].opcodes[15] = { type: 4, index: 8 };// RNM(outputs-reference) this.rules[0].opcodes[16] = { type: 2, children: [17,18] };// CAT this.rules[0].opcodes[17] = { type: 7, string: [36,115,116,101,112,115,46] };// TLS - this.rules[0].opcodes[18] = { type: 4, index: 6 };// RNM(name) + this.rules[0].opcodes[18] = { type: 4, index: 10 };// RNM(steps-reference) this.rules[0].opcodes[19] = { type: 2, children: [20,21] };// CAT this.rules[0].opcodes[20] = { type: 7, string: [36,119,111,114,107,102,108,111,119,115,46] };// TLS - this.rules[0].opcodes[21] = { type: 4, index: 6 };// RNM(name) + this.rules[0].opcodes[21] = { type: 4, index: 12 };// RNM(workflows-reference) this.rules[0].opcodes[22] = { type: 2, children: [23,24] };// CAT this.rules[0].opcodes[23] = { type: 7, string: [36,115,111,117,114,99,101,100,101,115,99,114,105,112,116,105,111,110,115,46] };// TLS - this.rules[0].opcodes[24] = { type: 4, index: 6 };// RNM(name) + this.rules[0].opcodes[24] = { type: 4, index: 16 };// RNM(source-reference) this.rules[0].opcodes[25] = { type: 2, children: [26,27] };// CAT this.rules[0].opcodes[26] = { type: 7, string: [36,99,111,109,112,111,110,101,110,116,115,46] };// TLS - this.rules[0].opcodes[27] = { type: 4, index: 6 };// RNM(name) + this.rules[0].opcodes[27] = { type: 4, index: 19 };// RNM(components-reference) + this.rules[0].opcodes[28] = { type: 7, string: [36,115,101,108,102] };// TLS /* source */ this.rules[1].opcodes = []; @@ -111,19 +116,19 @@ export default function grammar(){ this.rules[2].opcodes = []; this.rules[2].opcodes[0] = { type: 2, children: [1,2] };// CAT this.rules[2].opcodes[1] = { type: 7, string: [104,101,97,100,101,114,46] };// TLS - this.rules[2].opcodes[2] = { type: 4, index: 28 };// RNM(token) + this.rules[2].opcodes[2] = { type: 4, index: 30 };// RNM(token) /* query-reference */ this.rules[3].opcodes = []; this.rules[3].opcodes[0] = { type: 2, children: [1,2] };// CAT this.rules[3].opcodes[1] = { type: 7, string: [113,117,101,114,121,46] };// TLS - this.rules[3].opcodes[2] = { type: 4, index: 6 };// RNM(name) + this.rules[3].opcodes[2] = { type: 4, index: 22 };// RNM(name) /* path-reference */ this.rules[4].opcodes = []; this.rules[4].opcodes[0] = { type: 2, children: [1,2] };// CAT this.rules[4].opcodes[1] = { type: 7, string: [112,97,116,104,46] };// TLS - this.rules[4].opcodes[2] = { type: 4, index: 6 };// RNM(name) + this.rules[4].opcodes[2] = { type: 4, index: 22 };// RNM(name) /* body-reference */ this.rules[5].opcodes = []; @@ -132,312 +137,329 @@ export default function grammar(){ this.rules[5].opcodes[2] = { type: 3, min: 0, max: 1 };// REP this.rules[5].opcodes[3] = { type: 2, children: [4,5] };// CAT this.rules[5].opcodes[4] = { type: 7, string: [35] };// TLS - this.rules[5].opcodes[5] = { type: 4, index: 24 };// RNM(json-pointer) + this.rules[5].opcodes[5] = { type: 4, index: 26 };// RNM(json-pointer) - /* name */ + /* inputs-reference */ this.rules[6].opcodes = []; - this.rules[6].opcodes[0] = { type: 3, min: 0, max: Infinity };// REP - this.rules[6].opcodes[1] = { type: 4, index: 30 };// RNM(CHAR) - - /* expression-string */ + this.rules[6].opcodes[0] = { type: 2, children: [1,2] };// CAT + this.rules[6].opcodes[1] = { type: 4, index: 7 };// RNM(inputs-name) + this.rules[6].opcodes[2] = { type: 3, min: 0, max: 1 };// REP + this.rules[6].opcodes[3] = { type: 2, children: [4,5] };// CAT + this.rules[6].opcodes[4] = { type: 7, string: [35] };// TLS + this.rules[6].opcodes[5] = { type: 4, index: 26 };// RNM(json-pointer) + + /* inputs-name */ this.rules[7].opcodes = []; - this.rules[7].opcodes[0] = { type: 3, min: 0, max: Infinity };// REP - this.rules[7].opcodes[1] = { type: 1, children: [2,3] };// ALT - this.rules[7].opcodes[2] = { type: 4, index: 9 };// RNM(literal-char) - this.rules[7].opcodes[3] = { type: 4, index: 8 };// RNM(embedded-expression) + this.rules[7].opcodes[0] = { type: 4, index: 35 };// RNM(identifier) - /* embedded-expression */ + /* outputs-reference */ this.rules[8].opcodes = []; - this.rules[8].opcodes[0] = { type: 2, children: [1,2,3] };// CAT - this.rules[8].opcodes[1] = { type: 7, string: [123] };// TLS - this.rules[8].opcodes[2] = { type: 4, index: 0 };// RNM(expression) - this.rules[8].opcodes[3] = { type: 7, string: [125] };// TLS - - /* literal-char */ + this.rules[8].opcodes[0] = { type: 2, children: [1,2] };// CAT + this.rules[8].opcodes[1] = { type: 4, index: 9 };// RNM(outputs-name) + this.rules[8].opcodes[2] = { type: 3, min: 0, max: 1 };// REP + this.rules[8].opcodes[3] = { type: 2, children: [4,5] };// CAT + this.rules[8].opcodes[4] = { type: 7, string: [35] };// TLS + this.rules[8].opcodes[5] = { type: 4, index: 26 };// RNM(json-pointer) + + /* outputs-name */ this.rules[9].opcodes = []; - this.rules[9].opcodes[0] = { type: 1, children: [1,2,3] };// ALT - this.rules[9].opcodes[1] = { type: 5, min: 0, max: 122 };// TRG - this.rules[9].opcodes[2] = { type: 6, string: [124] };// TBS - this.rules[9].opcodes[3] = { type: 5, min: 126, max: 1114111 };// TRG + this.rules[9].opcodes[0] = { type: 4, index: 35 };// RNM(identifier) - /* steps-name */ + /* steps-reference */ this.rules[10].opcodes = []; - this.rules[10].opcodes[0] = { type: 2, children: [1,2,3,4,5,6] };// CAT + this.rules[10].opcodes[0] = { type: 2, children: [1,2,3,4] };// CAT this.rules[10].opcodes[1] = { type: 4, index: 11 };// RNM(steps-id) - this.rules[10].opcodes[2] = { type: 7, string: [46] };// TLS - this.rules[10].opcodes[3] = { type: 4, index: 12 };// RNM(steps-field) - this.rules[10].opcodes[4] = { type: 7, string: [46] };// TLS - this.rules[10].opcodes[5] = { type: 4, index: 13 };// RNM(steps-sub-field) - this.rules[10].opcodes[6] = { type: 3, min: 0, max: 1 };// REP - this.rules[10].opcodes[7] = { type: 2, children: [8,9] };// CAT - this.rules[10].opcodes[8] = { type: 7, string: [35] };// TLS - this.rules[10].opcodes[9] = { type: 4, index: 24 };// RNM(json-pointer) + this.rules[10].opcodes[2] = { type: 7, string: [46,111,117,116,112,117,116,115,46] };// TLS + this.rules[10].opcodes[3] = { type: 4, index: 9 };// RNM(outputs-name) + this.rules[10].opcodes[4] = { type: 3, min: 0, max: 1 };// REP + this.rules[10].opcodes[5] = { type: 2, children: [6,7] };// CAT + this.rules[10].opcodes[6] = { type: 7, string: [35] };// TLS + this.rules[10].opcodes[7] = { type: 4, index: 26 };// RNM(json-pointer) /* steps-id */ this.rules[11].opcodes = []; - this.rules[11].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP - this.rules[11].opcodes[1] = { type: 1, children: [2,3,4,5] };// ALT - this.rules[11].opcodes[2] = { type: 4, index: 35 };// RNM(ALPHA) - this.rules[11].opcodes[3] = { type: 4, index: 34 };// RNM(DIGIT) - this.rules[11].opcodes[4] = { type: 7, string: [95] };// TLS - this.rules[11].opcodes[5] = { type: 7, string: [45] };// TLS - - /* steps-field */ + this.rules[11].opcodes[0] = { type: 4, index: 36 };// RNM(identifier-strict) + + /* workflows-reference */ this.rules[12].opcodes = []; - this.rules[12].opcodes[0] = { type: 7, string: [111,117,116,112,117,116,115] };// TLS + this.rules[12].opcodes[0] = { type: 2, children: [1,2,3,4,5,6] };// CAT + this.rules[12].opcodes[1] = { type: 4, index: 13 };// RNM(workflows-id) + this.rules[12].opcodes[2] = { type: 7, string: [46] };// TLS + this.rules[12].opcodes[3] = { type: 4, index: 14 };// RNM(workflows-field) + this.rules[12].opcodes[4] = { type: 7, string: [46] };// TLS + this.rules[12].opcodes[5] = { type: 4, index: 15 };// RNM(workflows-field-name) + this.rules[12].opcodes[6] = { type: 3, min: 0, max: 1 };// REP + this.rules[12].opcodes[7] = { type: 2, children: [8,9] };// CAT + this.rules[12].opcodes[8] = { type: 7, string: [35] };// TLS + this.rules[12].opcodes[9] = { type: 4, index: 26 };// RNM(json-pointer) - /* steps-sub-field */ + /* workflows-id */ this.rules[13].opcodes = []; - this.rules[13].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP - this.rules[13].opcodes[1] = { type: 1, children: [2,3,4,5,6] };// ALT - this.rules[13].opcodes[2] = { type: 4, index: 35 };// RNM(ALPHA) - this.rules[13].opcodes[3] = { type: 4, index: 34 };// RNM(DIGIT) - this.rules[13].opcodes[4] = { type: 7, string: [46] };// TLS - this.rules[13].opcodes[5] = { type: 7, string: [45] };// TLS - this.rules[13].opcodes[6] = { type: 7, string: [95] };// TLS - - /* workflows-name */ + this.rules[13].opcodes[0] = { type: 4, index: 36 };// RNM(identifier-strict) + + /* workflows-field */ this.rules[14].opcodes = []; - this.rules[14].opcodes[0] = { type: 2, children: [1,2,3,4,5,6] };// CAT - this.rules[14].opcodes[1] = { type: 4, index: 15 };// RNM(workflows-id) - this.rules[14].opcodes[2] = { type: 7, string: [46] };// TLS - this.rules[14].opcodes[3] = { type: 4, index: 16 };// RNM(workflows-field) - this.rules[14].opcodes[4] = { type: 7, string: [46] };// TLS - this.rules[14].opcodes[5] = { type: 4, index: 17 };// RNM(workflows-sub-field) - this.rules[14].opcodes[6] = { type: 3, min: 0, max: 1 };// REP - this.rules[14].opcodes[7] = { type: 2, children: [8,9] };// CAT - this.rules[14].opcodes[8] = { type: 7, string: [35] };// TLS - this.rules[14].opcodes[9] = { type: 4, index: 24 };// RNM(json-pointer) + this.rules[14].opcodes[0] = { type: 1, children: [1,2] };// ALT + this.rules[14].opcodes[1] = { type: 7, string: [105,110,112,117,116,115] };// TLS + this.rules[14].opcodes[2] = { type: 7, string: [111,117,116,112,117,116,115] };// TLS - /* workflows-id */ + /* workflows-field-name */ this.rules[15].opcodes = []; - this.rules[15].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP - this.rules[15].opcodes[1] = { type: 1, children: [2,3,4,5] };// ALT - this.rules[15].opcodes[2] = { type: 4, index: 35 };// RNM(ALPHA) - this.rules[15].opcodes[3] = { type: 4, index: 34 };// RNM(DIGIT) - this.rules[15].opcodes[4] = { type: 7, string: [95] };// TLS - this.rules[15].opcodes[5] = { type: 7, string: [45] };// TLS + this.rules[15].opcodes[0] = { type: 4, index: 35 };// RNM(identifier) - /* workflows-field */ + /* source-reference */ this.rules[16].opcodes = []; - this.rules[16].opcodes[0] = { type: 1, children: [1,2] };// ALT - this.rules[16].opcodes[1] = { type: 7, string: [105,110,112,117,116,115] };// TLS - this.rules[16].opcodes[2] = { type: 7, string: [111,117,116,112,117,116,115] };// TLS + this.rules[16].opcodes[0] = { type: 2, children: [1,2,3] };// CAT + this.rules[16].opcodes[1] = { type: 4, index: 17 };// RNM(source-descriptions-name) + this.rules[16].opcodes[2] = { type: 7, string: [46] };// TLS + this.rules[16].opcodes[3] = { type: 4, index: 18 };// RNM(source-descriptions-reference) - /* workflows-sub-field */ + /* source-descriptions-name */ this.rules[17].opcodes = []; - this.rules[17].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP - this.rules[17].opcodes[1] = { type: 1, children: [2,3,4,5,6] };// ALT - this.rules[17].opcodes[2] = { type: 4, index: 35 };// RNM(ALPHA) - this.rules[17].opcodes[3] = { type: 4, index: 34 };// RNM(DIGIT) - this.rules[17].opcodes[4] = { type: 7, string: [46] };// TLS - this.rules[17].opcodes[5] = { type: 7, string: [45] };// TLS - this.rules[17].opcodes[6] = { type: 7, string: [95] };// TLS + this.rules[17].opcodes[0] = { type: 4, index: 36 };// RNM(identifier-strict) - /* source-descriptions-name */ + /* source-descriptions-reference */ this.rules[18].opcodes = []; - this.rules[18].opcodes[0] = { type: 2, children: [1,2,3] };// CAT - this.rules[18].opcodes[1] = { type: 4, index: 19 };// RNM(source-descriptions-source-name) - this.rules[18].opcodes[2] = { type: 7, string: [46] };// TLS - this.rules[18].opcodes[3] = { type: 4, index: 20 };// RNM(source-descriptions-reference) + this.rules[18].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP + this.rules[18].opcodes[1] = { type: 4, index: 32 };// RNM(CHAR) - /* source-descriptions-source-name */ + /* components-reference */ this.rules[19].opcodes = []; - this.rules[19].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP - this.rules[19].opcodes[1] = { type: 1, children: [2,3,4,5] };// ALT - this.rules[19].opcodes[2] = { type: 4, index: 35 };// RNM(ALPHA) - this.rules[19].opcodes[3] = { type: 4, index: 34 };// RNM(DIGIT) - this.rules[19].opcodes[4] = { type: 7, string: [95] };// TLS - this.rules[19].opcodes[5] = { type: 7, string: [45] };// TLS + this.rules[19].opcodes[0] = { type: 2, children: [1,2,3] };// CAT + this.rules[19].opcodes[1] = { type: 4, index: 20 };// RNM(components-type) + this.rules[19].opcodes[2] = { type: 7, string: [46] };// TLS + this.rules[19].opcodes[3] = { type: 4, index: 21 };// RNM(components-name) - /* source-descriptions-reference */ + /* components-type */ this.rules[20].opcodes = []; - this.rules[20].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP - this.rules[20].opcodes[1] = { type: 4, index: 30 };// RNM(CHAR) + this.rules[20].opcodes[0] = { type: 1, children: [1,2,3] };// ALT + this.rules[20].opcodes[1] = { type: 7, string: [112,97,114,97,109,101,116,101,114,115] };// TLS + this.rules[20].opcodes[2] = { type: 7, string: [115,117,99,99,101,115,115,97,99,116,105,111,110,115] };// TLS + this.rules[20].opcodes[3] = { type: 7, string: [102,97,105,108,117,114,101,97,99,116,105,111,110,115] };// TLS /* components-name */ this.rules[21].opcodes = []; - this.rules[21].opcodes[0] = { type: 2, children: [1,2,3] };// CAT - this.rules[21].opcodes[1] = { type: 4, index: 22 };// RNM(components-field) - this.rules[21].opcodes[2] = { type: 7, string: [46] };// TLS - this.rules[21].opcodes[3] = { type: 4, index: 23 };// RNM(components-sub-field) + this.rules[21].opcodes[0] = { type: 4, index: 35 };// RNM(identifier) - /* components-field */ + /* name */ this.rules[22].opcodes = []; - this.rules[22].opcodes[0] = { type: 1, children: [1,2,3] };// ALT - this.rules[22].opcodes[1] = { type: 7, string: [112,97,114,97,109,101,116,101,114,115] };// TLS - this.rules[22].opcodes[2] = { type: 7, string: [115,117,99,99,101,115,115,97,99,116,105,111,110,115] };// TLS - this.rules[22].opcodes[3] = { type: 7, string: [102,97,105,108,117,114,101,97,99,116,105,111,110,115] };// TLS + this.rules[22].opcodes[0] = { type: 3, min: 0, max: Infinity };// REP + this.rules[22].opcodes[1] = { type: 4, index: 32 };// RNM(CHAR) - /* components-sub-field */ + /* expression-string */ this.rules[23].opcodes = []; - this.rules[23].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP - this.rules[23].opcodes[1] = { type: 1, children: [2,3,4,5,6] };// ALT - this.rules[23].opcodes[2] = { type: 4, index: 35 };// RNM(ALPHA) - this.rules[23].opcodes[3] = { type: 4, index: 34 };// RNM(DIGIT) - this.rules[23].opcodes[4] = { type: 7, string: [46] };// TLS - this.rules[23].opcodes[5] = { type: 7, string: [45] };// TLS - this.rules[23].opcodes[6] = { type: 7, string: [95] };// TLS + this.rules[23].opcodes[0] = { type: 3, min: 0, max: Infinity };// REP + this.rules[23].opcodes[1] = { type: 1, children: [2,3] };// ALT + this.rules[23].opcodes[2] = { type: 4, index: 25 };// RNM(literal-char) + this.rules[23].opcodes[3] = { type: 4, index: 24 };// RNM(embedded-expression) - /* json-pointer */ + /* embedded-expression */ this.rules[24].opcodes = []; - this.rules[24].opcodes[0] = { type: 3, min: 0, max: Infinity };// REP - this.rules[24].opcodes[1] = { type: 2, children: [2,3] };// CAT - this.rules[24].opcodes[2] = { type: 7, string: [47] };// TLS - this.rules[24].opcodes[3] = { type: 4, index: 25 };// RNM(reference-token) + this.rules[24].opcodes[0] = { type: 2, children: [1,2,3] };// CAT + this.rules[24].opcodes[1] = { type: 7, string: [123] };// TLS + this.rules[24].opcodes[2] = { type: 4, index: 0 };// RNM(expression) + this.rules[24].opcodes[3] = { type: 7, string: [125] };// TLS - /* reference-token */ + /* literal-char */ this.rules[25].opcodes = []; - this.rules[25].opcodes[0] = { type: 3, min: 0, max: Infinity };// REP - this.rules[25].opcodes[1] = { type: 1, children: [2,3] };// ALT - this.rules[25].opcodes[2] = { type: 4, index: 26 };// RNM(unescaped) - this.rules[25].opcodes[3] = { type: 4, index: 27 };// RNM(escaped) + this.rules[25].opcodes[0] = { type: 1, children: [1,2,3] };// ALT + this.rules[25].opcodes[1] = { type: 5, min: 0, max: 122 };// TRG + this.rules[25].opcodes[2] = { type: 6, string: [124] };// TBS + this.rules[25].opcodes[3] = { type: 5, min: 126, max: 1114111 };// TRG - /* unescaped */ + /* json-pointer */ this.rules[26].opcodes = []; - this.rules[26].opcodes[0] = { type: 1, children: [1,2,3] };// ALT - this.rules[26].opcodes[1] = { type: 5, min: 0, max: 46 };// TRG - this.rules[26].opcodes[2] = { type: 5, min: 48, max: 125 };// TRG - this.rules[26].opcodes[3] = { type: 5, min: 127, max: 1114111 };// TRG + this.rules[26].opcodes[0] = { type: 3, min: 0, max: Infinity };// REP + this.rules[26].opcodes[1] = { type: 2, children: [2,3] };// CAT + this.rules[26].opcodes[2] = { type: 7, string: [47] };// TLS + this.rules[26].opcodes[3] = { type: 4, index: 27 };// RNM(reference-token) - /* escaped */ + /* reference-token */ this.rules[27].opcodes = []; - this.rules[27].opcodes[0] = { type: 2, children: [1,2] };// CAT - this.rules[27].opcodes[1] = { type: 7, string: [126] };// TLS - this.rules[27].opcodes[2] = { type: 1, children: [3,4] };// ALT - this.rules[27].opcodes[3] = { type: 7, string: [48] };// TLS - this.rules[27].opcodes[4] = { type: 7, string: [49] };// TLS + this.rules[27].opcodes[0] = { type: 3, min: 0, max: Infinity };// REP + this.rules[27].opcodes[1] = { type: 1, children: [2,3] };// ALT + this.rules[27].opcodes[2] = { type: 4, index: 28 };// RNM(unescaped) + this.rules[27].opcodes[3] = { type: 4, index: 29 };// RNM(escaped) - /* token */ + /* unescaped */ this.rules[28].opcodes = []; - this.rules[28].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP - this.rules[28].opcodes[1] = { type: 4, index: 29 };// RNM(tchar) + this.rules[28].opcodes[0] = { type: 1, children: [1,2,3,4] };// ALT + this.rules[28].opcodes[1] = { type: 5, min: 0, max: 46 };// TRG + this.rules[28].opcodes[2] = { type: 5, min: 48, max: 122 };// TRG + this.rules[28].opcodes[3] = { type: 6, string: [124] };// TBS + this.rules[28].opcodes[4] = { type: 5, min: 126, max: 1114111 };// TRG - /* tchar */ + /* escaped */ this.rules[29].opcodes = []; - this.rules[29].opcodes[0] = { type: 1, children: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17] };// ALT - this.rules[29].opcodes[1] = { type: 7, string: [33] };// TLS - this.rules[29].opcodes[2] = { type: 7, string: [35] };// TLS - this.rules[29].opcodes[3] = { type: 7, string: [36] };// TLS - this.rules[29].opcodes[4] = { type: 7, string: [37] };// TLS - this.rules[29].opcodes[5] = { type: 7, string: [38] };// TLS - this.rules[29].opcodes[6] = { type: 7, string: [39] };// TLS - this.rules[29].opcodes[7] = { type: 7, string: [42] };// TLS - this.rules[29].opcodes[8] = { type: 7, string: [43] };// TLS - this.rules[29].opcodes[9] = { type: 7, string: [45] };// TLS - this.rules[29].opcodes[10] = { type: 7, string: [46] };// TLS - this.rules[29].opcodes[11] = { type: 7, string: [94] };// TLS - this.rules[29].opcodes[12] = { type: 7, string: [95] };// TLS - this.rules[29].opcodes[13] = { type: 7, string: [96] };// TLS - this.rules[29].opcodes[14] = { type: 7, string: [124] };// TLS - this.rules[29].opcodes[15] = { type: 7, string: [126] };// TLS - this.rules[29].opcodes[16] = { type: 4, index: 34 };// RNM(DIGIT) - this.rules[29].opcodes[17] = { type: 4, index: 35 };// RNM(ALPHA) + this.rules[29].opcodes[0] = { type: 2, children: [1,2] };// CAT + this.rules[29].opcodes[1] = { type: 7, string: [126] };// TLS + this.rules[29].opcodes[2] = { type: 1, children: [3,4] };// ALT + this.rules[29].opcodes[3] = { type: 7, string: [48] };// TLS + this.rules[29].opcodes[4] = { type: 7, string: [49] };// TLS - /* CHAR */ + /* token */ this.rules[30].opcodes = []; - this.rules[30].opcodes[0] = { type: 1, children: [1,2] };// ALT - this.rules[30].opcodes[1] = { type: 4, index: 32 };// RNM(unescape) - this.rules[30].opcodes[2] = { type: 2, children: [3,4] };// CAT - this.rules[30].opcodes[3] = { type: 4, index: 31 };// RNM(escape) - this.rules[30].opcodes[4] = { type: 1, children: [5,6,7,8,9,10,11,12,13] };// ALT - this.rules[30].opcodes[5] = { type: 6, string: [34] };// TBS - this.rules[30].opcodes[6] = { type: 6, string: [92] };// TBS - this.rules[30].opcodes[7] = { type: 6, string: [47] };// TBS - this.rules[30].opcodes[8] = { type: 6, string: [98] };// TBS - this.rules[30].opcodes[9] = { type: 6, string: [102] };// TBS - this.rules[30].opcodes[10] = { type: 6, string: [110] };// TBS - this.rules[30].opcodes[11] = { type: 6, string: [114] };// TBS - this.rules[30].opcodes[12] = { type: 6, string: [116] };// TBS - this.rules[30].opcodes[13] = { type: 2, children: [14,15] };// CAT - this.rules[30].opcodes[14] = { type: 6, string: [117] };// TBS - this.rules[30].opcodes[15] = { type: 3, min: 4, max: 4 };// REP - this.rules[30].opcodes[16] = { type: 4, index: 33 };// RNM(HEXDIG) + this.rules[30].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP + this.rules[30].opcodes[1] = { type: 4, index: 31 };// RNM(tchar) - /* escape */ + /* tchar */ this.rules[31].opcodes = []; - this.rules[31].opcodes[0] = { type: 6, string: [92] };// TBS + this.rules[31].opcodes[0] = { type: 1, children: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17] };// ALT + this.rules[31].opcodes[1] = { type: 7, string: [33] };// TLS + this.rules[31].opcodes[2] = { type: 7, string: [35] };// TLS + this.rules[31].opcodes[3] = { type: 7, string: [36] };// TLS + this.rules[31].opcodes[4] = { type: 7, string: [37] };// TLS + this.rules[31].opcodes[5] = { type: 7, string: [38] };// TLS + this.rules[31].opcodes[6] = { type: 7, string: [39] };// TLS + this.rules[31].opcodes[7] = { type: 7, string: [42] };// TLS + this.rules[31].opcodes[8] = { type: 7, string: [43] };// TLS + this.rules[31].opcodes[9] = { type: 7, string: [45] };// TLS + this.rules[31].opcodes[10] = { type: 7, string: [46] };// TLS + this.rules[31].opcodes[11] = { type: 7, string: [94] };// TLS + this.rules[31].opcodes[12] = { type: 7, string: [95] };// TLS + this.rules[31].opcodes[13] = { type: 7, string: [96] };// TLS + this.rules[31].opcodes[14] = { type: 7, string: [124] };// TLS + this.rules[31].opcodes[15] = { type: 7, string: [126] };// TLS + this.rules[31].opcodes[16] = { type: 4, index: 38 };// RNM(DIGIT) + this.rules[31].opcodes[17] = { type: 4, index: 39 };// RNM(ALPHA) - /* unescape */ + /* CHAR */ this.rules[32].opcodes = []; - this.rules[32].opcodes[0] = { type: 1, children: [1,2,3,4,5] };// ALT - this.rules[32].opcodes[1] = { type: 5, min: 32, max: 33 };// TRG - this.rules[32].opcodes[2] = { type: 5, min: 35, max: 91 };// TRG - this.rules[32].opcodes[3] = { type: 5, min: 93, max: 122 };// TRG - this.rules[32].opcodes[4] = { type: 6, string: [124] };// TBS - this.rules[32].opcodes[5] = { type: 5, min: 126, max: 1114111 };// TRG + this.rules[32].opcodes[0] = { type: 1, children: [1,2] };// ALT + this.rules[32].opcodes[1] = { type: 4, index: 34 };// RNM(unescape) + this.rules[32].opcodes[2] = { type: 2, children: [3,4] };// CAT + this.rules[32].opcodes[3] = { type: 4, index: 33 };// RNM(escape) + this.rules[32].opcodes[4] = { type: 1, children: [5,6,7,8,9,10,11,12,13] };// ALT + this.rules[32].opcodes[5] = { type: 6, string: [34] };// TBS + this.rules[32].opcodes[6] = { type: 6, string: [92] };// TBS + this.rules[32].opcodes[7] = { type: 6, string: [47] };// TBS + this.rules[32].opcodes[8] = { type: 6, string: [98] };// TBS + this.rules[32].opcodes[9] = { type: 6, string: [102] };// TBS + this.rules[32].opcodes[10] = { type: 6, string: [110] };// TBS + this.rules[32].opcodes[11] = { type: 6, string: [114] };// TBS + this.rules[32].opcodes[12] = { type: 6, string: [116] };// TBS + this.rules[32].opcodes[13] = { type: 2, children: [14,15] };// CAT + this.rules[32].opcodes[14] = { type: 6, string: [117] };// TBS + this.rules[32].opcodes[15] = { type: 3, min: 4, max: 4 };// REP + this.rules[32].opcodes[16] = { type: 4, index: 37 };// RNM(HEXDIG) - /* HEXDIG */ + /* escape */ this.rules[33].opcodes = []; - this.rules[33].opcodes[0] = { type: 1, children: [1,2,3,4,5,6,7] };// ALT - this.rules[33].opcodes[1] = { type: 4, index: 34 };// RNM(DIGIT) - this.rules[33].opcodes[2] = { type: 7, string: [97] };// TLS - this.rules[33].opcodes[3] = { type: 7, string: [98] };// TLS - this.rules[33].opcodes[4] = { type: 7, string: [99] };// TLS - this.rules[33].opcodes[5] = { type: 7, string: [100] };// TLS - this.rules[33].opcodes[6] = { type: 7, string: [101] };// TLS - this.rules[33].opcodes[7] = { type: 7, string: [102] };// TLS + this.rules[33].opcodes[0] = { type: 6, string: [92] };// TBS - /* DIGIT */ + /* unescape */ this.rules[34].opcodes = []; - this.rules[34].opcodes[0] = { type: 5, min: 48, max: 57 };// TRG + this.rules[34].opcodes[0] = { type: 1, children: [1,2,3,4,5] };// ALT + this.rules[34].opcodes[1] = { type: 5, min: 32, max: 33 };// TRG + this.rules[34].opcodes[2] = { type: 5, min: 35, max: 91 };// TRG + this.rules[34].opcodes[3] = { type: 5, min: 93, max: 122 };// TRG + this.rules[34].opcodes[4] = { type: 6, string: [124] };// TBS + this.rules[34].opcodes[5] = { type: 5, min: 126, max: 1114111 };// TRG + + /* identifier */ + this.rules[35].opcodes = []; + this.rules[35].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP + this.rules[35].opcodes[1] = { type: 1, children: [2,3,4,5,6] };// ALT + this.rules[35].opcodes[2] = { type: 4, index: 39 };// RNM(ALPHA) + this.rules[35].opcodes[3] = { type: 4, index: 38 };// RNM(DIGIT) + this.rules[35].opcodes[4] = { type: 7, string: [46] };// TLS + this.rules[35].opcodes[5] = { type: 7, string: [45] };// TLS + this.rules[35].opcodes[6] = { type: 7, string: [95] };// TLS + + /* identifier-strict */ + this.rules[36].opcodes = []; + this.rules[36].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP + this.rules[36].opcodes[1] = { type: 1, children: [2,3,4,5] };// ALT + this.rules[36].opcodes[2] = { type: 4, index: 39 };// RNM(ALPHA) + this.rules[36].opcodes[3] = { type: 4, index: 38 };// RNM(DIGIT) + this.rules[36].opcodes[4] = { type: 7, string: [95] };// TLS + this.rules[36].opcodes[5] = { type: 7, string: [45] };// TLS + + /* HEXDIG */ + this.rules[37].opcodes = []; + this.rules[37].opcodes[0] = { type: 1, children: [1,2,3,4,5,6,7] };// ALT + this.rules[37].opcodes[1] = { type: 4, index: 38 };// RNM(DIGIT) + this.rules[37].opcodes[2] = { type: 7, string: [97] };// TLS + this.rules[37].opcodes[3] = { type: 7, string: [98] };// TLS + this.rules[37].opcodes[4] = { type: 7, string: [99] };// TLS + this.rules[37].opcodes[5] = { type: 7, string: [100] };// TLS + this.rules[37].opcodes[6] = { type: 7, string: [101] };// TLS + this.rules[37].opcodes[7] = { type: 7, string: [102] };// TLS + + /* DIGIT */ + this.rules[38].opcodes = []; + this.rules[38].opcodes[0] = { type: 5, min: 48, max: 57 };// TRG /* ALPHA */ - this.rules[35].opcodes = []; - this.rules[35].opcodes[0] = { type: 1, children: [1,2] };// ALT - this.rules[35].opcodes[1] = { type: 5, min: 65, max: 90 };// TRG - this.rules[35].opcodes[2] = { type: 5, min: 97, max: 122 };// TRG + this.rules[39].opcodes = []; + this.rules[39].opcodes[0] = { type: 1, children: [1,2] };// ALT + this.rules[39].opcodes[1] = { type: 5, min: 65, max: 90 };// TRG + this.rules[39].opcodes[2] = { type: 5, min: 97, max: 122 };// TRG // The `toString()` function will display the original grammar file(s) that produced these opcodes. this.toString = function toString(){ let str = ""; str += "; Arazzo runtime expression ABNF syntax\n"; - str += "expression = ( \"$url\" / \"$method\" / \"$statusCode\" / \"$request.\" source / \"$response.\" source / \"$inputs.\" name / \"$outputs.\" name / \"$steps.\" name / \"$workflows.\" name / \"$sourceDescriptions.\" name / \"$components.\" name )\n"; - str += "source = ( header-reference / query-reference / path-reference / body-reference )\n"; - str += "header-reference = \"header.\" token\n"; - str += "query-reference = \"query.\" name\n"; - str += "path-reference = \"path.\" name\n"; - str += "body-reference = \"body\" [\"#\" json-pointer ]\n"; - str += "name = *( CHAR )\n"; + str += "expression = (\n"; + str += " \"$url\" /\n"; + str += " \"$method\" /\n"; + str += " \"$statusCode\" /\n"; + str += " \"$request.\" source /\n"; + str += " \"$response.\" source /\n"; + str += " \"$inputs.\" inputs-reference /\n"; + str += " \"$outputs.\" outputs-reference /\n"; + str += " \"$steps.\" steps-reference /\n"; + str += " \"$workflows.\" workflows-reference /\n"; + str += " \"$sourceDescriptions.\" source-reference /\n"; + str += " \"$components.\" components-reference /\n"; + str += " \"$self\"\n"; + str += " )\n"; + str += "; Request/Response sources\n"; + str += "source = ( header-reference / query-reference / path-reference / body-reference )\n"; + str += "header-reference = \"header.\" token\n"; + str += "query-reference = \"query.\" name\n"; + str += "path-reference = \"path.\" name\n"; + str += "body-reference = \"body\" [\"#\" json-pointer ]\n"; str += "\n"; - str += "; Grammar for parsing template strings with embedded expressions\n"; - str += "expression-string = *( literal-char / embedded-expression )\n"; - str += "embedded-expression = \"{\" expression \"}\"\n"; - str += "literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) and } (%x7D)\n"; + str += "; Input/Output references\n"; + str += "inputs-reference = inputs-name [\"#\" json-pointer]\n"; + str += "inputs-name = identifier\n"; + str += "outputs-reference = outputs-name [\"#\" json-pointer]\n"; + str += "outputs-name = identifier\n"; str += "\n"; - str += "; Secondary grammar for parsing $steps name part\n"; - str += "; Format: {stepId}.{field}.{subField}[#/{jsonPointer}]\n"; - str += "steps-name = steps-id \".\" steps-field \".\" steps-sub-field [\"#\" json-pointer]\n"; - str += "steps-id = 1*(ALPHA / DIGIT / \"_\" / \"-\")\n"; - str += "steps-field = \"outputs\"\n"; - str += "steps-sub-field = 1*(ALPHA / DIGIT / \".\" / \"-\" / \"_\")\n"; + str += "; Steps expressions\n"; + str += "steps-reference = steps-id \".outputs.\" outputs-name [\"#\" json-pointer]\n"; + str += "steps-id = identifier-strict\n"; str += "\n"; - str += "; Secondary grammar for parsing $workflows name part\n"; - str += "; Format: {workflowId}.{field}.{subField}[#/{jsonPointer}]\n"; - str += "workflows-name = workflows-id \".\" workflows-field \".\" workflows-sub-field [\"#\" json-pointer]\n"; - str += "workflows-id = 1*(ALPHA / DIGIT / \"_\" / \"-\")\n"; - str += "workflows-field = \"inputs\" / \"outputs\"\n"; - str += "workflows-sub-field = 1*(ALPHA / DIGIT / \".\" / \"-\" / \"_\")\n"; + str += "; Workflows expressions\n"; + str += "workflows-reference = workflows-id \".\" workflows-field \".\" workflows-field-name [\"#\" json-pointer]\n"; + str += "workflows-id = identifier-strict\n"; + str += "workflows-field = \"inputs\" / \"outputs\"\n"; + str += "workflows-field-name = identifier\n"; str += "\n"; - str += "; Secondary grammar for parsing $sourceDescriptions name part\n"; - str += "; Format: {sourceName}.{reference}\n"; - str += "; reference can be operationId (unconstrained) or workflowId (constrained)\n"; - str += "source-descriptions-name = source-descriptions-source-name \".\" source-descriptions-reference\n"; - str += "source-descriptions-source-name = 1*(ALPHA / DIGIT / \"_\" / \"-\")\n"; + str += "; Source descriptions expressions\n"; + str += "source-reference = source-descriptions-name \".\" source-descriptions-reference\n"; + str += "source-descriptions-name = identifier-strict\n"; str += "source-descriptions-reference = 1*CHAR\n"; str += "\n"; - str += "; Secondary grammar for parsing $components name part\n"; - str += "; Format: {field}.{subField}\n"; - str += "; Allowed fields: parameters, successActions, failureActions\n"; - str += "components-name = components-field \".\" components-sub-field\n"; - str += "components-field = \"parameters\" / \"successActions\" / \"failureActions\"\n"; - str += "components-sub-field = 1*(ALPHA / DIGIT / \".\" / \"-\" / \"_\")\n"; + str += "; Components expressions\n"; + str += "components-reference = components-type \".\" components-name\n"; + str += "components-type = \"parameters\" / \"successActions\" / \"failureActions\"\n"; + str += "components-name = identifier\n"; str += "\n"; - str += "; https://datatracker.ietf.org/doc/html/rfc6901#section-3\n"; + str += "name = *( CHAR )\n"; + str += "\n"; + str += "; Grammar for parsing template strings with embedded expressions\n"; + str += "expression-string = *( literal-char / embedded-expression )\n"; + str += "embedded-expression = \"{\" expression \"}\"\n"; + str += "literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) and } (%x7D)\n"; + str += "\n"; + str += "; JSON Pointer (RFC 6901, adapted)\n"; + str += "; { (%x7B) and } (%x7D) are excluded from 'unescaped' for unambiguous embedded expression parsing\n"; str += "json-pointer = *( \"/\" reference-token )\n"; str += "reference-token = *( unescaped / escaped )\n"; - str += "unescaped = %x00-2E / %x30-7D / %x7F-10FFFF\n"; - str += " ; %x2F ('/') and %x7E ('~') are excluded from 'unescaped'\n"; + str += "unescaped = %x00-2E / %x30-7A / %x7C / %x7E-10FFFF\n"; + str += " ; %x2F ('/'), %x7E ('~'), %x7B ('{'), %x7D ('}') are excluded\n"; str += "escaped = \"~\" ( \"0\" / \"1\" )\n"; str += " ; representing '~' and '/', respectively\n"; str += "\n"; @@ -464,6 +486,12 @@ export default function grammar(){ str += "unescape = %x20-21 / %x23-5B / %x5D-7A / %x7C / %x7E-10FFFF\n"; str += " ; %x7B ('{') and %x7D ('}') are excluded from 'unescape'\n"; str += "\n"; + str += "; Identifier rules\n"; + str += "identifier = 1*(ALPHA / DIGIT / \".\" / \"-\" / \"_\")\n"; + str += " ; Alphanumeric with dots, hyphens, underscores\n"; + str += "identifier-strict = 1*(ALPHA / DIGIT / \"_\" / \"-\")\n"; + str += " ; Alphanumeric with hyphens, underscores (no dots)\n"; + str += "\n"; str += "; https://datatracker.ietf.org/doc/html/rfc5234#appendix-B.1\n"; str += "HEXDIG = DIGIT / \"A\" / \"B\" / \"C\" / \"D\" / \"E\" / \"F\"\n"; str += "DIGIT = %x30-39 ; 0-9\n"; diff --git a/src/parse/translators/ASTTranslator/transformers.js b/src/parse/translators/ASTTranslator/transformers.js index 9134796..f6161b7 100644 --- a/src/parse/translators/ASTTranslator/transformers.js +++ b/src/parse/translators/ASTTranslator/transformers.js @@ -1,50 +1,3 @@ -import { Parser } from 'apg-lite'; - -import Grammar from '../../../grammar.js'; -import CSTTranslator from '../CSTTranslator.js'; - -const grammar = new Grammar(); - -const parseStepsName = (stepsName) => { - const parser = new Parser(); - parser.ast = new CSTTranslator(); - const result = parser.parse(grammar, 'steps-name', stepsName); - if (!result.success) { - throw new Error(`Invalid steps expression name: ${stepsName}`); - } - return parser.ast.getTree(); -}; - -const parseWorkflowsName = (workflowsName) => { - const parser = new Parser(); - parser.ast = new CSTTranslator(); - const result = parser.parse(grammar, 'workflows-name', workflowsName); - if (!result.success) { - throw new Error(`Invalid workflows expression name: ${workflowsName}`); - } - return parser.ast.getTree(); -}; - -const parseSourceDescriptionsName = (sourceDescriptionsName) => { - const parser = new Parser(); - parser.ast = new CSTTranslator(); - const result = parser.parse(grammar, 'source-descriptions-name', sourceDescriptionsName); - if (!result.success) { - throw new Error(`Invalid source descriptions expression name: ${sourceDescriptionsName}`); - } - return parser.ast.getTree(); -}; - -const parseComponentsName = (componentsName) => { - const parser = new Parser(); - parser.ast = new CSTTranslator(); - const result = parser.parse(grammar, 'components-name', componentsName); - if (!result.success) { - throw new Error(`Invalid components expression name: ${componentsName}`); - } - return parser.ast.getTree(); -}; - export const transformCSTtoAST = (node, transformerMap) => { const transformer = transformerMap[node.type]; if (!transformer) { @@ -61,6 +14,7 @@ const transformers = { if (text === '$url') return { type: 'UrlExpression' }; if (text === '$method') return { type: 'MethodExpression' }; if (text === '$statusCode') return { type: 'StatusCodeExpression' }; + if (text === '$self') return { type: 'SelfExpression' }; // Source expressions (request/response) if (text.startsWith('$request.')) { @@ -78,29 +32,30 @@ const transformers = { }; } - // Named expressions (raw name) - const nameNode = node.children.find((c) => c.type === 'name'); + // Reference expressions (secondary grammar parsing) if (text.startsWith('$inputs.')) { - return { type: 'InputsExpression', name: nameNode.text }; + const refNode = node.children.find((c) => c.type === 'inputs-reference'); + return transformCSTtoAST(refNode, transformers); } if (text.startsWith('$outputs.')) { - return { type: 'OutputsExpression', name: nameNode.text }; + const refNode = node.children.find((c) => c.type === 'outputs-reference'); + return transformCSTtoAST(refNode, transformers); } if (text.startsWith('$steps.')) { - const stepsNameCST = parseStepsName(nameNode.text); - return transformCSTtoAST(stepsNameCST, transformers); + const refNode = node.children.find((c) => c.type === 'steps-reference'); + return transformCSTtoAST(refNode, transformers); } if (text.startsWith('$workflows.')) { - const workflowsNameCST = parseWorkflowsName(nameNode.text); - return transformCSTtoAST(workflowsNameCST, transformers); + const refNode = node.children.find((c) => c.type === 'workflows-reference'); + return transformCSTtoAST(refNode, transformers); } if (text.startsWith('$sourceDescriptions.')) { - const sourceDescriptionsNameCST = parseSourceDescriptionsName(nameNode.text); - return transformCSTtoAST(sourceDescriptionsNameCST, transformers); + const refNode = node.children.find((c) => c.type === 'source-reference'); + return transformCSTtoAST(refNode, transformers); } if (text.startsWith('$components.')) { - const componentsNameCST = parseComponentsName(nameNode.text); - return transformCSTtoAST(componentsNameCST, transformers); + const refNode = node.children.find((c) => c.type === 'components-reference'); + return transformCSTtoAST(refNode, transformers); } }, @@ -153,18 +108,47 @@ const transformers = { return { type: 'ReferenceToken', value: node.text }; }, - // steps-name secondary grammar transformers - ['steps-name'](node) { + ['inputs-reference'](node) { + const inputNameNode = node.children.find((c) => c.type === 'inputs-name'); + const jsonPointerNode = node.children.find((c) => c.type === 'json-pointer'); + + const result = { + type: 'InputsExpression', + name: inputNameNode.text, + }; + + if (jsonPointerNode) { + result.jsonPointer = transformCSTtoAST(jsonPointerNode, transformers); + } + + return result; + }, + + ['outputs-reference'](node) { + const outputNameNode = node.children.find((c) => c.type === 'outputs-name'); + const jsonPointerNode = node.children.find((c) => c.type === 'json-pointer'); + + const result = { + type: 'OutputsExpression', + name: outputNameNode.text, + }; + + if (jsonPointerNode) { + result.jsonPointer = transformCSTtoAST(jsonPointerNode, transformers); + } + + return result; + }, + + ['steps-reference'](node) { const stepIdNode = node.children.find((c) => c.type === 'steps-id'); - const fieldNode = node.children.find((c) => c.type === 'steps-field'); - const subFieldNode = node.children.find((c) => c.type === 'steps-sub-field'); + const outputNameNode = node.children.find((c) => c.type === 'outputs-name'); const jsonPointerNode = node.children.find((c) => c.type === 'json-pointer'); const result = { type: 'StepsExpression', stepId: stepIdNode.text, - field: fieldNode.text, - outputName: subFieldNode.text, + outputName: outputNameNode.text, }; if (jsonPointerNode) { @@ -174,18 +158,17 @@ const transformers = { return result; }, - // workflows-name secondary grammar transformers - ['workflows-name'](node) { + ['workflows-reference'](node) { const workflowIdNode = node.children.find((c) => c.type === 'workflows-id'); const fieldNode = node.children.find((c) => c.type === 'workflows-field'); - const subFieldNode = node.children.find((c) => c.type === 'workflows-sub-field'); + const fieldNameNode = node.children.find((c) => c.type === 'workflows-field-name'); const jsonPointerNode = node.children.find((c) => c.type === 'json-pointer'); const result = { type: 'WorkflowsExpression', workflowId: workflowIdNode.text, field: fieldNode.text, - subField: subFieldNode.text, + fieldName: fieldNameNode.text, }; if (jsonPointerNode) { @@ -195,9 +178,8 @@ const transformers = { return result; }, - // source-descriptions-name secondary grammar transformers - ['source-descriptions-name'](node) { - const sourceNameNode = node.children.find((c) => c.type === 'source-descriptions-source-name'); + ['source-reference'](node) { + const sourceNameNode = node.children.find((c) => c.type === 'source-descriptions-name'); const referenceNode = node.children.find((c) => c.type === 'source-descriptions-reference'); return { @@ -207,15 +189,14 @@ const transformers = { }; }, - // components-name secondary grammar transformers - ['components-name'](node) { - const fieldNode = node.children.find((c) => c.type === 'components-field'); - const subFieldNode = node.children.find((c) => c.type === 'components-sub-field'); + ['components-reference'](node) { + const typeNode = node.children.find((c) => c.type === 'components-type'); + const nameNode = node.children.find((c) => c.type === 'components-name'); return { type: 'ComponentsExpression', - field: fieldNode.text, - subField: subFieldNode.text, + componentType: typeNode.text, + componentName: nameNode.text, }; }, }; diff --git a/src/parse/translators/CSTTranslator.js b/src/parse/translators/CSTTranslator.js index 487a068..20c3daa 100644 --- a/src/parse/translators/CSTTranslator.js +++ b/src/parse/translators/CSTTranslator.js @@ -19,26 +19,24 @@ class CSTTranslator extends AST { this.callbacks['body-reference'] = cstCallback('body-reference'); this.callbacks['json-pointer'] = cstCallback('json-pointer'); this.callbacks['reference-token'] = cstCallback('reference-token'); - this.callbacks['name'] = cstCallback('name'); - this.callbacks['token'] = cstCallback('token'); - // steps-name secondary grammar rules - this.callbacks['steps-name'] = cstCallback('steps-name'); - this.callbacks['steps-id'] = cstCallback('steps-id'); - this.callbacks['steps-field'] = cstCallback('steps-field'); - this.callbacks['steps-sub-field'] = cstCallback('steps-sub-field'); - // workflows-name secondary grammar rules - this.callbacks['workflows-name'] = cstCallback('workflows-name'); + this.callbacks['inputs-reference'] = cstCallback('inputs-reference'); + this.callbacks['inputs-name'] = cstCallback('inputs-name'); + this.callbacks['outputs-reference'] = cstCallback('outputs-reference'); + this.callbacks['outputs-name'] = cstCallback('outputs-name'); + this.callbacks['steps-reference'] = cstCallback('steps-reference'); + this.callbacks['workflows-reference'] = cstCallback('workflows-reference'); this.callbacks['workflows-id'] = cstCallback('workflows-id'); this.callbacks['workflows-field'] = cstCallback('workflows-field'); - this.callbacks['workflows-sub-field'] = cstCallback('workflows-sub-field'); - // source-descriptions-name secondary grammar rules + this.callbacks['workflows-field-name'] = cstCallback('workflows-field-name'); + this.callbacks['source-reference'] = cstCallback('source-reference'); this.callbacks['source-descriptions-name'] = cstCallback('source-descriptions-name'); - this.callbacks['source-descriptions-source-name'] = cstCallback('source-descriptions-source-name'); this.callbacks['source-descriptions-reference'] = cstCallback('source-descriptions-reference'); - // components-name secondary grammar rules + this.callbacks['components-reference'] = cstCallback('components-reference'); + this.callbacks['components-type'] = cstCallback('components-type'); this.callbacks['components-name'] = cstCallback('components-name'); - this.callbacks['components-field'] = cstCallback('components-field'); - this.callbacks['components-sub-field'] = cstCallback('components-sub-field'); + this.callbacks['name'] = cstCallback('name'); + this.callbacks['token'] = cstCallback('token'); + this.callbacks['steps-id'] = cstCallback('steps-id'); } getTree() { diff --git a/test/extract.js b/test/extract.js index 8ef9f5b..f5c67be 100644 --- a/test/extract.js +++ b/test/extract.js @@ -73,17 +73,13 @@ describe('extract', function () { * Workaround: Use parse() directly on the raw expression (without {} delimiters) * for $request.body and $response.body expressions containing JSON pointers. */ - it('should return empty array for body expressions with JSON pointers (known limitation)', function () { - // RFC 6901 allows } in JSON pointer paths, so extract() cannot determine - // where the expression ends for body-reference expressions - assert.deepEqual(extract('{$request.body#/url}'), []); - assert.deepEqual(extract('{$response.body#/status}'), []); - assert.deepEqual(extract('{$request.body#/user/uuid}'), []); + it('should handle body expressions with JSON pointers', function () { + assert.deepEqual(extract('{$request.body#/url}'), ['$request.body#/url']); + assert.deepEqual(extract('{$response.body#/status}'), ['$response.body#/status']); + assert.deepEqual(extract('{$request.body#/user/uuid}'), ['$request.body#/user/uuid']); }); it('should handle expressions with JSON pointers in name part', function () { - // $steps., $inputs., $outputs., etc. use the `name` rule which excludes } - // so they work correctly even with JSON pointer-like paths const result = extract('pet={$steps.someStepId.outputs.pets#/0/id}'); assert.deepEqual(result, ['$steps.someStepId.outputs.pets#/0/id']); }); diff --git a/test/fixtures/expressions-valid.js b/test/fixtures/expressions-valid.js index aaeda4e..19449d7 100644 --- a/test/fixtures/expressions-valid.js +++ b/test/fixtures/expressions-valid.js @@ -3,6 +3,7 @@ const validExpressions = [ '$url', '$method', '$statusCode', + '$self', // Request expressions - header '$request.header.accept', @@ -40,11 +41,24 @@ const validExpressions = [ '$inputs.username', '$inputs.password', '$inputs.apiKey', + '$inputs.a', + '$inputs.my-input', + '$inputs.my_input', + '$inputs.my.nested.name', + '$inputs.customer#/firstName', + '$inputs.customer#/address/city', // Outputs expression '$outputs.result', '$outputs.token', '$outputs.data', + '$outputs.a', + '$outputs.my-output', + '$outputs.my_output', + '$outputs.my.nested.name', + '$outputs.response#/name', + '$outputs.mappedResponse#/data/id', + '$outputs.result#/items/0/name', // Steps expression '$steps.stepId.outputs.value', diff --git a/test/parse/__snapshots__/ast-corpus.js.snap b/test/parse/__snapshots__/ast-corpus.js.snap index 6515d2c..80a35ca 100644 --- a/test/parse/__snapshots__/ast-corpus.js.snap +++ b/test/parse/__snapshots__/ast-corpus.js.snap @@ -3,32 +3,31 @@ exports[`parse ast-corpus $components.failureActions.logError 1`] = ` { "type": "ComponentsExpression", - "field": "failureActions", - "subField": "logError", + "componentType": "failureActions", + "componentName": "logError", } `; -exports[`parse ast-corpus $components.inputs.username 1`] = ` +exports[`parse ast-corpus $components.parameters.petId 1`] = ` { "type": "ComponentsExpression", - "field": "inputs", - "subField": "username", + "componentType": "parameters", + "componentName": "petId", } `; -exports[`parse ast-corpus $components.parameters.petId 1`] = ` +exports[`parse ast-corpus $components.successActions.notifyUser 1`] = ` { "type": "ComponentsExpression", - "field": "parameters", - "subField": "petId", + "componentType": "successActions", + "componentName": "notifyUser", } `; -exports[`parse ast-corpus $components.successActions.notifyUser 1`] = ` +exports[`parse ast-corpus $inputs.a 1`] = ` { - "type": "ComponentsExpression", - "field": "successActions", - "subField": "notifyUser", + "type": "InputsExpression", + "name": "a", } `; @@ -39,6 +38,65 @@ exports[`parse ast-corpus $inputs.apiKey 1`] = ` } `; +exports[`parse ast-corpus $inputs.customer#/address/city 1`] = ` +{ + "type": "InputsExpression", + "name": "customer", + "jsonPointer": { + "type": "JsonPointer", + "value": "/address/city", + "referenceTokens": [ + { + "type": "ReferenceToken", + "value": "address", + }, + { + "type": "ReferenceToken", + "value": "city", + }, + ], + }, +} +`; + +exports[`parse ast-corpus $inputs.customer#/firstName 1`] = ` +{ + "type": "InputsExpression", + "name": "customer", + "jsonPointer": { + "type": "JsonPointer", + "value": "/firstName", + "referenceTokens": [ + { + "type": "ReferenceToken", + "value": "firstName", + }, + ], + }, +} +`; + +exports[`parse ast-corpus $inputs.my.nested.name 1`] = ` +{ + "type": "InputsExpression", + "name": "my.nested.name", +} +`; + +exports[`parse ast-corpus $inputs.my_input 1`] = ` +{ + "type": "InputsExpression", + "name": "my_input", +} +`; + +exports[`parse ast-corpus $inputs.my-input 1`] = ` +{ + "type": "InputsExpression", + "name": "my-input", +} +`; + exports[`parse ast-corpus $inputs.password 1`] = ` { "type": "InputsExpression", @@ -59,6 +117,13 @@ exports[`parse ast-corpus $method 1`] = ` } `; +exports[`parse ast-corpus $outputs.a 1`] = ` +{ + "type": "OutputsExpression", + "name": "a", +} +`; + exports[`parse ast-corpus $outputs.data 1`] = ` { "type": "OutputsExpression", @@ -66,6 +131,65 @@ exports[`parse ast-corpus $outputs.data 1`] = ` } `; +exports[`parse ast-corpus $outputs.mappedResponse#/data/id 1`] = ` +{ + "type": "OutputsExpression", + "name": "mappedResponse", + "jsonPointer": { + "type": "JsonPointer", + "value": "/data/id", + "referenceTokens": [ + { + "type": "ReferenceToken", + "value": "data", + }, + { + "type": "ReferenceToken", + "value": "id", + }, + ], + }, +} +`; + +exports[`parse ast-corpus $outputs.my.nested.name 1`] = ` +{ + "type": "OutputsExpression", + "name": "my.nested.name", +} +`; + +exports[`parse ast-corpus $outputs.my_output 1`] = ` +{ + "type": "OutputsExpression", + "name": "my_output", +} +`; + +exports[`parse ast-corpus $outputs.my-output 1`] = ` +{ + "type": "OutputsExpression", + "name": "my-output", +} +`; + +exports[`parse ast-corpus $outputs.response#/name 1`] = ` +{ + "type": "OutputsExpression", + "name": "response", + "jsonPointer": { + "type": "JsonPointer", + "value": "/name", + "referenceTokens": [ + { + "type": "ReferenceToken", + "value": "name", + }, + ], + }, +} +`; + exports[`parse ast-corpus $outputs.result 1`] = ` { "type": "OutputsExpression", @@ -73,6 +197,31 @@ exports[`parse ast-corpus $outputs.result 1`] = ` } `; +exports[`parse ast-corpus $outputs.result#/items/0/name 1`] = ` +{ + "type": "OutputsExpression", + "name": "result", + "jsonPointer": { + "type": "JsonPointer", + "value": "/items/0/name", + "referenceTokens": [ + { + "type": "ReferenceToken", + "value": "items", + }, + { + "type": "ReferenceToken", + "value": "0", + }, + { + "type": "ReferenceToken", + "value": "name", + }, + ], + }, +} +`; + exports[`parse ast-corpus $outputs.token 1`] = ` { "type": "OutputsExpression", @@ -433,6 +582,12 @@ exports[`parse ast-corpus $response.header.content-type 1`] = ` } `; +exports[`parse ast-corpus $self 1`] = ` +{ + "type": "SelfExpression", +} +`; + exports[`parse ast-corpus $sourceDescriptions.externalService.url 1`] = ` { "type": "SourceDescriptionsExpression", @@ -467,7 +622,6 @@ exports[`parse ast-corpus $steps.createUser.outputs.userId 1`] = ` { "type": "StepsExpression", "stepId": "createUser", - "field": "outputs", "outputName": "userId", } `; @@ -476,7 +630,6 @@ exports[`parse ast-corpus $steps.loginStep.outputs.sessionToken 1`] = ` { "type": "StepsExpression", "stepId": "loginStep", - "field": "outputs", "outputName": "sessionToken", } `; @@ -485,7 +638,6 @@ exports[`parse ast-corpus $steps.loginStep.outputs.user#/name 1`] = ` { "type": "StepsExpression", "stepId": "loginStep", - "field": "outputs", "outputName": "user", "jsonPointer": { "type": "JsonPointer", @@ -504,7 +656,6 @@ exports[`parse ast-corpus $steps.someStepId.outputs.pets#/0/id 1`] = ` { "type": "StepsExpression", "stepId": "someStepId", - "field": "outputs", "outputName": "pets", "jsonPointer": { "type": "JsonPointer", @@ -527,7 +678,6 @@ exports[`parse ast-corpus $steps.stepId.outputs.value 1`] = ` { "type": "StepsExpression", "stepId": "stepId", - "field": "outputs", "outputName": "value", } `; @@ -543,7 +693,7 @@ exports[`parse ast-corpus $workflows.authWorkflow.outputs.token 1`] = ` "type": "WorkflowsExpression", "workflowId": "authWorkflow", "field": "outputs", - "subField": "token", + "fieldName": "token", } `; @@ -552,7 +702,7 @@ exports[`parse ast-corpus $workflows.authWorkflow.outputs.token#/accessToken 1`] "type": "WorkflowsExpression", "workflowId": "authWorkflow", "field": "outputs", - "subField": "token", + "fieldName": "token", "jsonPointer": { "type": "JsonPointer", "value": "/accessToken", @@ -571,7 +721,7 @@ exports[`parse ast-corpus $workflows.mainProcess.outputs.data 1`] = ` "type": "WorkflowsExpression", "workflowId": "mainProcess", "field": "outputs", - "subField": "data", + "fieldName": "data", } `; @@ -580,7 +730,7 @@ exports[`parse ast-corpus $workflows.myWorkflow.inputs.username 1`] = ` "type": "WorkflowsExpression", "workflowId": "myWorkflow", "field": "inputs", - "subField": "username", + "fieldName": "username", } `; @@ -589,7 +739,7 @@ exports[`parse ast-corpus $workflows.myWorkflow.outputs.result 1`] = ` "type": "WorkflowsExpression", "workflowId": "myWorkflow", "field": "outputs", - "subField": "result", + "fieldName": "result", } `; @@ -598,7 +748,7 @@ exports[`parse ast-corpus $workflows.myWorkflow.outputs.result#/value 1`] = ` "type": "WorkflowsExpression", "workflowId": "myWorkflow", "field": "outputs", - "subField": "result", + "fieldName": "result", "jsonPointer": { "type": "JsonPointer", "value": "/value", diff --git a/test/parse/__snapshots__/cst-corpus.js.snap b/test/parse/__snapshots__/cst-corpus.js.snap index 90e8a68..a3241b3 100644 --- a/test/parse/__snapshots__/cst-corpus.js.snap +++ b/test/parse/__snapshots__/cst-corpus.js.snap @@ -8,29 +8,26 @@ exports[`parse cst-corpus $components.failureActions.logError 1`] = ` "length": 35, "children": [ { - "type": "name", + "type": "components-reference", "text": "failureActions.logError", "start": 12, "length": 23, - "children": [], - }, - ], -} -`; - -exports[`parse cst-corpus $components.inputs.username 1`] = ` -{ - "type": "expression", - "text": "$components.inputs.username", - "start": 0, - "length": 27, - "children": [ - { - "type": "name", - "text": "inputs.username", - "start": 12, - "length": 15, - "children": [], + "children": [ + { + "type": "components-type", + "text": "failureActions", + "start": 12, + "length": 14, + "children": [], + }, + { + "type": "components-name", + "text": "logError", + "start": 27, + "length": 8, + "children": [], + }, + ], }, ], } @@ -44,11 +41,26 @@ exports[`parse cst-corpus $components.parameters.petId 1`] = ` "length": 28, "children": [ { - "type": "name", + "type": "components-reference", "text": "parameters.petId", "start": 12, "length": 16, - "children": [], + "children": [ + { + "type": "components-type", + "text": "parameters", + "start": 12, + "length": 10, + "children": [], + }, + { + "type": "components-name", + "text": "petId", + "start": 23, + "length": 5, + "children": [], + }, + ], }, ], } @@ -62,11 +74,52 @@ exports[`parse cst-corpus $components.successActions.notifyUser 1`] = ` "length": 37, "children": [ { - "type": "name", + "type": "components-reference", "text": "successActions.notifyUser", "start": 12, "length": 25, - "children": [], + "children": [ + { + "type": "components-type", + "text": "successActions", + "start": 12, + "length": 14, + "children": [], + }, + { + "type": "components-name", + "text": "notifyUser", + "start": 27, + "length": 10, + "children": [], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $inputs.a 1`] = ` +{ + "type": "expression", + "text": "$inputs.a", + "start": 0, + "length": 9, + "children": [ + { + "type": "inputs-reference", + "text": "a", + "start": 8, + "length": 1, + "children": [ + { + "type": "inputs-name", + "text": "a", + "start": 8, + "length": 1, + "children": [], + }, + ], }, ], } @@ -80,11 +133,186 @@ exports[`parse cst-corpus $inputs.apiKey 1`] = ` "length": 14, "children": [ { - "type": "name", + "type": "inputs-reference", "text": "apiKey", "start": 8, "length": 6, - "children": [], + "children": [ + { + "type": "inputs-name", + "text": "apiKey", + "start": 8, + "length": 6, + "children": [], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $inputs.customer#/address/city 1`] = ` +{ + "type": "expression", + "text": "$inputs.customer#/address/city", + "start": 0, + "length": 30, + "children": [ + { + "type": "inputs-reference", + "text": "customer#/address/city", + "start": 8, + "length": 22, + "children": [ + { + "type": "inputs-name", + "text": "customer", + "start": 8, + "length": 8, + "children": [], + }, + { + "type": "json-pointer", + "text": "/address/city", + "start": 17, + "length": 13, + "children": [ + { + "type": "reference-token", + "text": "address", + "start": 18, + "length": 7, + "children": [], + }, + { + "type": "reference-token", + "text": "city", + "start": 26, + "length": 4, + "children": [], + }, + ], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $inputs.customer#/firstName 1`] = ` +{ + "type": "expression", + "text": "$inputs.customer#/firstName", + "start": 0, + "length": 27, + "children": [ + { + "type": "inputs-reference", + "text": "customer#/firstName", + "start": 8, + "length": 19, + "children": [ + { + "type": "inputs-name", + "text": "customer", + "start": 8, + "length": 8, + "children": [], + }, + { + "type": "json-pointer", + "text": "/firstName", + "start": 17, + "length": 10, + "children": [ + { + "type": "reference-token", + "text": "firstName", + "start": 18, + "length": 9, + "children": [], + }, + ], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $inputs.my.nested.name 1`] = ` +{ + "type": "expression", + "text": "$inputs.my.nested.name", + "start": 0, + "length": 22, + "children": [ + { + "type": "inputs-reference", + "text": "my.nested.name", + "start": 8, + "length": 14, + "children": [ + { + "type": "inputs-name", + "text": "my.nested.name", + "start": 8, + "length": 14, + "children": [], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $inputs.my_input 1`] = ` +{ + "type": "expression", + "text": "$inputs.my_input", + "start": 0, + "length": 16, + "children": [ + { + "type": "inputs-reference", + "text": "my_input", + "start": 8, + "length": 8, + "children": [ + { + "type": "inputs-name", + "text": "my_input", + "start": 8, + "length": 8, + "children": [], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $inputs.my-input 1`] = ` +{ + "type": "expression", + "text": "$inputs.my-input", + "start": 0, + "length": 16, + "children": [ + { + "type": "inputs-reference", + "text": "my-input", + "start": 8, + "length": 8, + "children": [ + { + "type": "inputs-name", + "text": "my-input", + "start": 8, + "length": 8, + "children": [], + }, + ], }, ], } @@ -98,11 +326,19 @@ exports[`parse cst-corpus $inputs.password 1`] = ` "length": 16, "children": [ { - "type": "name", + "type": "inputs-reference", "text": "password", "start": 8, "length": 8, - "children": [], + "children": [ + { + "type": "inputs-name", + "text": "password", + "start": 8, + "length": 8, + "children": [], + }, + ], }, ], } @@ -116,57 +352,329 @@ exports[`parse cst-corpus $inputs.username 1`] = ` "length": 16, "children": [ { - "type": "name", + "type": "inputs-reference", "text": "username", "start": 8, "length": 8, - "children": [], + "children": [ + { + "type": "inputs-name", + "text": "username", + "start": 8, + "length": 8, + "children": [], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $method 1`] = ` +{ + "type": "expression", + "text": "$method", + "start": 0, + "length": 7, + "children": [], +} +`; + +exports[`parse cst-corpus $outputs.a 1`] = ` +{ + "type": "expression", + "text": "$outputs.a", + "start": 0, + "length": 10, + "children": [ + { + "type": "outputs-reference", + "text": "a", + "start": 9, + "length": 1, + "children": [ + { + "type": "outputs-name", + "text": "a", + "start": 9, + "length": 1, + "children": [], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $outputs.data 1`] = ` +{ + "type": "expression", + "text": "$outputs.data", + "start": 0, + "length": 13, + "children": [ + { + "type": "outputs-reference", + "text": "data", + "start": 9, + "length": 4, + "children": [ + { + "type": "outputs-name", + "text": "data", + "start": 9, + "length": 4, + "children": [], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $outputs.mappedResponse#/data/id 1`] = ` +{ + "type": "expression", + "text": "$outputs.mappedResponse#/data/id", + "start": 0, + "length": 32, + "children": [ + { + "type": "outputs-reference", + "text": "mappedResponse#/data/id", + "start": 9, + "length": 23, + "children": [ + { + "type": "outputs-name", + "text": "mappedResponse", + "start": 9, + "length": 14, + "children": [], + }, + { + "type": "json-pointer", + "text": "/data/id", + "start": 24, + "length": 8, + "children": [ + { + "type": "reference-token", + "text": "data", + "start": 25, + "length": 4, + "children": [], + }, + { + "type": "reference-token", + "text": "id", + "start": 30, + "length": 2, + "children": [], + }, + ], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $outputs.my.nested.name 1`] = ` +{ + "type": "expression", + "text": "$outputs.my.nested.name", + "start": 0, + "length": 23, + "children": [ + { + "type": "outputs-reference", + "text": "my.nested.name", + "start": 9, + "length": 14, + "children": [ + { + "type": "outputs-name", + "text": "my.nested.name", + "start": 9, + "length": 14, + "children": [], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $outputs.my_output 1`] = ` +{ + "type": "expression", + "text": "$outputs.my_output", + "start": 0, + "length": 18, + "children": [ + { + "type": "outputs-reference", + "text": "my_output", + "start": 9, + "length": 9, + "children": [ + { + "type": "outputs-name", + "text": "my_output", + "start": 9, + "length": 9, + "children": [], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $outputs.my-output 1`] = ` +{ + "type": "expression", + "text": "$outputs.my-output", + "start": 0, + "length": 18, + "children": [ + { + "type": "outputs-reference", + "text": "my-output", + "start": 9, + "length": 9, + "children": [ + { + "type": "outputs-name", + "text": "my-output", + "start": 9, + "length": 9, + "children": [], + }, + ], + }, + ], +} +`; + +exports[`parse cst-corpus $outputs.response#/name 1`] = ` +{ + "type": "expression", + "text": "$outputs.response#/name", + "start": 0, + "length": 23, + "children": [ + { + "type": "outputs-reference", + "text": "response#/name", + "start": 9, + "length": 14, + "children": [ + { + "type": "outputs-name", + "text": "response", + "start": 9, + "length": 8, + "children": [], + }, + { + "type": "json-pointer", + "text": "/name", + "start": 18, + "length": 5, + "children": [ + { + "type": "reference-token", + "text": "name", + "start": 19, + "length": 4, + "children": [], + }, + ], + }, + ], }, ], } `; -exports[`parse cst-corpus $method 1`] = ` -{ - "type": "expression", - "text": "$method", - "start": 0, - "length": 7, - "children": [], -} -`; - -exports[`parse cst-corpus $outputs.data 1`] = ` +exports[`parse cst-corpus $outputs.result 1`] = ` { "type": "expression", - "text": "$outputs.data", + "text": "$outputs.result", "start": 0, - "length": 13, + "length": 15, "children": [ { - "type": "name", - "text": "data", + "type": "outputs-reference", + "text": "result", "start": 9, - "length": 4, - "children": [], + "length": 6, + "children": [ + { + "type": "outputs-name", + "text": "result", + "start": 9, + "length": 6, + "children": [], + }, + ], }, ], } `; -exports[`parse cst-corpus $outputs.result 1`] = ` +exports[`parse cst-corpus $outputs.result#/items/0/name 1`] = ` { "type": "expression", - "text": "$outputs.result", + "text": "$outputs.result#/items/0/name", "start": 0, - "length": 15, + "length": 29, "children": [ { - "type": "name", - "text": "result", + "type": "outputs-reference", + "text": "result#/items/0/name", "start": 9, - "length": 6, - "children": [], + "length": 20, + "children": [ + { + "type": "outputs-name", + "text": "result", + "start": 9, + "length": 6, + "children": [], + }, + { + "type": "json-pointer", + "text": "/items/0/name", + "start": 16, + "length": 13, + "children": [ + { + "type": "reference-token", + "text": "items", + "start": 17, + "length": 5, + "children": [], + }, + { + "type": "reference-token", + "text": "0", + "start": 23, + "length": 1, + "children": [], + }, + { + "type": "reference-token", + "text": "name", + "start": 25, + "length": 4, + "children": [], + }, + ], + }, + ], }, ], } @@ -180,11 +688,19 @@ exports[`parse cst-corpus $outputs.token 1`] = ` "length": 14, "children": [ { - "type": "name", + "type": "outputs-reference", "text": "token", "start": 9, "length": 5, - "children": [], + "children": [ + { + "type": "outputs-name", + "text": "token", + "start": 9, + "length": 5, + "children": [], + }, + ], }, ], } @@ -966,6 +1482,16 @@ exports[`parse cst-corpus $response.header.content-type 1`] = ` } `; +exports[`parse cst-corpus $self 1`] = ` +{ + "type": "expression", + "text": "$self", + "start": 0, + "length": 5, + "children": [], +} +`; + exports[`parse cst-corpus $sourceDescriptions.externalService.url 1`] = ` { "type": "expression", @@ -974,11 +1500,26 @@ exports[`parse cst-corpus $sourceDescriptions.externalService.url 1`] = ` "length": 39, "children": [ { - "type": "name", + "type": "source-reference", "text": "externalService.url", "start": 20, "length": 19, - "children": [], + "children": [ + { + "type": "source-descriptions-name", + "text": "externalService", + "start": 20, + "length": 15, + "children": [], + }, + { + "type": "source-descriptions-reference", + "text": "url", + "start": 36, + "length": 3, + "children": [], + }, + ], }, ], } @@ -992,11 +1533,26 @@ exports[`parse cst-corpus $sourceDescriptions.mainApi.createUser 1`] = ` "length": 38, "children": [ { - "type": "name", + "type": "source-reference", "text": "mainApi.createUser", "start": 20, "length": 18, - "children": [], + "children": [ + { + "type": "source-descriptions-name", + "text": "mainApi", + "start": 20, + "length": 7, + "children": [], + }, + { + "type": "source-descriptions-reference", + "text": "createUser", + "start": 28, + "length": 10, + "children": [], + }, + ], }, ], } @@ -1010,11 +1566,26 @@ exports[`parse cst-corpus $sourceDescriptions.petStore.getPets 1`] = ` "length": 36, "children": [ { - "type": "name", + "type": "source-reference", "text": "petStore.getPets", "start": 20, "length": 16, - "children": [], + "children": [ + { + "type": "source-descriptions-name", + "text": "petStore", + "start": 20, + "length": 8, + "children": [], + }, + { + "type": "source-descriptions-reference", + "text": "getPets", + "start": 29, + "length": 7, + "children": [], + }, + ], }, ], } @@ -1038,11 +1609,26 @@ exports[`parse cst-corpus $steps.createUser.outputs.userId 1`] = ` "length": 32, "children": [ { - "type": "name", + "type": "steps-reference", "text": "createUser.outputs.userId", "start": 7, "length": 25, - "children": [], + "children": [ + { + "type": "steps-id", + "text": "createUser", + "start": 7, + "length": 10, + "children": [], + }, + { + "type": "outputs-name", + "text": "userId", + "start": 26, + "length": 6, + "children": [], + }, + ], }, ], } @@ -1056,11 +1642,26 @@ exports[`parse cst-corpus $steps.loginStep.outputs.sessionToken 1`] = ` "length": 37, "children": [ { - "type": "name", + "type": "steps-reference", "text": "loginStep.outputs.sessionToken", "start": 7, "length": 30, - "children": [], + "children": [ + { + "type": "steps-id", + "text": "loginStep", + "start": 7, + "length": 9, + "children": [], + }, + { + "type": "outputs-name", + "text": "sessionToken", + "start": 25, + "length": 12, + "children": [], + }, + ], }, ], } @@ -1074,11 +1675,41 @@ exports[`parse cst-corpus $steps.loginStep.outputs.user#/name 1`] = ` "length": 35, "children": [ { - "type": "name", + "type": "steps-reference", "text": "loginStep.outputs.user#/name", "start": 7, "length": 28, - "children": [], + "children": [ + { + "type": "steps-id", + "text": "loginStep", + "start": 7, + "length": 9, + "children": [], + }, + { + "type": "outputs-name", + "text": "user", + "start": 25, + "length": 4, + "children": [], + }, + { + "type": "json-pointer", + "text": "/name", + "start": 30, + "length": 5, + "children": [ + { + "type": "reference-token", + "text": "name", + "start": 31, + "length": 4, + "children": [], + }, + ], + }, + ], }, ], } @@ -1092,11 +1723,48 @@ exports[`parse cst-corpus $steps.someStepId.outputs.pets#/0/id 1`] = ` "length": 36, "children": [ { - "type": "name", + "type": "steps-reference", "text": "someStepId.outputs.pets#/0/id", "start": 7, "length": 29, - "children": [], + "children": [ + { + "type": "steps-id", + "text": "someStepId", + "start": 7, + "length": 10, + "children": [], + }, + { + "type": "outputs-name", + "text": "pets", + "start": 26, + "length": 4, + "children": [], + }, + { + "type": "json-pointer", + "text": "/0/id", + "start": 31, + "length": 5, + "children": [ + { + "type": "reference-token", + "text": "0", + "start": 32, + "length": 1, + "children": [], + }, + { + "type": "reference-token", + "text": "id", + "start": 34, + "length": 2, + "children": [], + }, + ], + }, + ], }, ], } @@ -1110,11 +1778,26 @@ exports[`parse cst-corpus $steps.stepId.outputs.value 1`] = ` "length": 27, "children": [ { - "type": "name", + "type": "steps-reference", "text": "stepId.outputs.value", "start": 7, "length": 20, - "children": [], + "children": [ + { + "type": "steps-id", + "text": "stepId", + "start": 7, + "length": 6, + "children": [], + }, + { + "type": "outputs-name", + "text": "value", + "start": 22, + "length": 5, + "children": [], + }, + ], }, ], } @@ -1138,11 +1821,33 @@ exports[`parse cst-corpus $workflows.authWorkflow.outputs.token 1`] = ` "length": 37, "children": [ { - "type": "name", + "type": "workflows-reference", "text": "authWorkflow.outputs.token", "start": 11, "length": 26, - "children": [], + "children": [ + { + "type": "workflows-id", + "text": "authWorkflow", + "start": 11, + "length": 12, + "children": [], + }, + { + "type": "workflows-field", + "text": "outputs", + "start": 24, + "length": 7, + "children": [], + }, + { + "type": "workflows-field-name", + "text": "token", + "start": 32, + "length": 5, + "children": [], + }, + ], }, ], } @@ -1156,11 +1861,48 @@ exports[`parse cst-corpus $workflows.authWorkflow.outputs.token#/accessToken 1`] "length": 50, "children": [ { - "type": "name", + "type": "workflows-reference", "text": "authWorkflow.outputs.token#/accessToken", "start": 11, "length": 39, - "children": [], + "children": [ + { + "type": "workflows-id", + "text": "authWorkflow", + "start": 11, + "length": 12, + "children": [], + }, + { + "type": "workflows-field", + "text": "outputs", + "start": 24, + "length": 7, + "children": [], + }, + { + "type": "workflows-field-name", + "text": "token", + "start": 32, + "length": 5, + "children": [], + }, + { + "type": "json-pointer", + "text": "/accessToken", + "start": 38, + "length": 12, + "children": [ + { + "type": "reference-token", + "text": "accessToken", + "start": 39, + "length": 11, + "children": [], + }, + ], + }, + ], }, ], } @@ -1174,11 +1916,33 @@ exports[`parse cst-corpus $workflows.mainProcess.outputs.data 1`] = ` "length": 35, "children": [ { - "type": "name", + "type": "workflows-reference", "text": "mainProcess.outputs.data", "start": 11, "length": 24, - "children": [], + "children": [ + { + "type": "workflows-id", + "text": "mainProcess", + "start": 11, + "length": 11, + "children": [], + }, + { + "type": "workflows-field", + "text": "outputs", + "start": 23, + "length": 7, + "children": [], + }, + { + "type": "workflows-field-name", + "text": "data", + "start": 31, + "length": 4, + "children": [], + }, + ], }, ], } @@ -1192,11 +1956,33 @@ exports[`parse cst-corpus $workflows.myWorkflow.inputs.username 1`] = ` "length": 37, "children": [ { - "type": "name", + "type": "workflows-reference", "text": "myWorkflow.inputs.username", "start": 11, "length": 26, - "children": [], + "children": [ + { + "type": "workflows-id", + "text": "myWorkflow", + "start": 11, + "length": 10, + "children": [], + }, + { + "type": "workflows-field", + "text": "inputs", + "start": 22, + "length": 6, + "children": [], + }, + { + "type": "workflows-field-name", + "text": "username", + "start": 29, + "length": 8, + "children": [], + }, + ], }, ], } @@ -1210,11 +1996,33 @@ exports[`parse cst-corpus $workflows.myWorkflow.outputs.result 1`] = ` "length": 36, "children": [ { - "type": "name", + "type": "workflows-reference", "text": "myWorkflow.outputs.result", "start": 11, "length": 25, - "children": [], + "children": [ + { + "type": "workflows-id", + "text": "myWorkflow", + "start": 11, + "length": 10, + "children": [], + }, + { + "type": "workflows-field", + "text": "outputs", + "start": 22, + "length": 7, + "children": [], + }, + { + "type": "workflows-field-name", + "text": "result", + "start": 30, + "length": 6, + "children": [], + }, + ], }, ], } @@ -1228,11 +2036,48 @@ exports[`parse cst-corpus $workflows.myWorkflow.outputs.result#/value 1`] = ` "length": 43, "children": [ { - "type": "name", + "type": "workflows-reference", "text": "myWorkflow.outputs.result#/value", "start": 11, "length": 32, - "children": [], + "children": [ + { + "type": "workflows-id", + "text": "myWorkflow", + "start": 11, + "length": 10, + "children": [], + }, + { + "type": "workflows-field", + "text": "outputs", + "start": 22, + "length": 7, + "children": [], + }, + { + "type": "workflows-field-name", + "text": "result", + "start": 30, + "length": 6, + "children": [], + }, + { + "type": "json-pointer", + "text": "/value", + "start": 37, + "length": 6, + "children": [ + { + "type": "reference-token", + "text": "value", + "start": 38, + "length": 5, + "children": [], + }, + ], + }, + ], }, ], } diff --git a/test/parse/translators/__snapshots__/ASTTranslator.js.snap b/test/parse/translators/__snapshots__/ASTTranslator.js.snap index f109da7..890879f 100644 --- a/test/parse/translators/__snapshots__/ASTTranslator.js.snap +++ b/test/parse/translators/__snapshots__/ASTTranslator.js.snap @@ -30,7 +30,6 @@ exports[`parse translators ASTTranslator should translate $steps.login.outputs.t { "type": "StepsExpression", "stepId": "login", - "field": "outputs", "outputName": "token", } `; diff --git a/test/parse/translators/__snapshots__/CSTTranslator.js.snap b/test/parse/translators/__snapshots__/CSTTranslator.js.snap index 6220854..e82f1ff 100644 --- a/test/parse/translators/__snapshots__/CSTTranslator.js.snap +++ b/test/parse/translators/__snapshots__/CSTTranslator.js.snap @@ -57,11 +57,26 @@ exports[`parse translators CSTTranslator should translate $steps.login.outputs.t "length": 26, "children": [ { - "type": "name", + "type": "steps-reference", "text": "login.outputs.token", "start": 7, "length": 19, - "children": [], + "children": [ + { + "type": "steps-id", + "text": "login", + "start": 7, + "length": 5, + "children": [], + }, + { + "type": "outputs-name", + "text": "token", + "start": 21, + "length": 5, + "children": [], + }, + ], }, ], } diff --git a/test/test.js b/test/test.js index 437914b..13fa654 100644 --- a/test/test.js +++ b/test/test.js @@ -50,11 +50,11 @@ describe('test', function () { assert.isFalse(test('$inputs.{foo}')); }); - it('should accept { and } in JSON pointer paths (RFC 6901 compliant)', function () { - // RFC 6901 allows { and } in JSON pointer paths (unescaped rule) - // This is standards-compliant behavior - assert.isTrue(test('$request.body#/foo}')); - assert.isTrue(test('$request.body#/foo{')); - assert.isTrue(test('$request.body#/{foo}')); + it('should reject { and } in JSON pointer paths', function () { + // { and } are excluded from json-pointer-safe to allow unambiguous + // parsing of embedded expressions like {$request.body#/status} + assert.isFalse(test('$request.body#/foo}')); + assert.isFalse(test('$request.body#/foo{')); + assert.isFalse(test('$request.body#/{foo}')); }); }); From 79212964fdf03a3a8d78d241f9e553ce2e6dd3fe Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Thu, 2 Apr 2026 15:29:11 +0200 Subject: [PATCH 2/7] docs(grammar): add comment explaining the name rule Co-Authored-By: Claude Opus 4.6 (1M context) --- src/grammar.bnf | 1 + src/grammar.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/grammar.bnf b/src/grammar.bnf index fe06af4..cf4aa5a 100644 --- a/src/grammar.bnf +++ b/src/grammar.bnf @@ -46,6 +46,7 @@ components-reference = components-type "." components-name components-type = "parameters" / "successActions" / "failureActions" components-name = identifier +; Unconstrained name rule for query/path references and source description references name = *( CHAR ) ; Grammar for parsing template strings with embedded expressions diff --git a/src/grammar.js b/src/grammar.js index 3da4c52..6100b83 100644 --- a/src/grammar.js +++ b/src/grammar.js @@ -447,6 +447,7 @@ export default function grammar(){ str += "components-type = \"parameters\" / \"successActions\" / \"failureActions\"\n"; str += "components-name = identifier\n"; str += "\n"; + str += "; Unconstrained name rule for query/path references and source description references\n"; str += "name = *( CHAR )\n"; str += "\n"; str += "; Grammar for parsing template strings with embedded expressions\n"; From 7211c7456610a7ffc98136cc803e0b0c1654bd83 Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Fri, 3 Apr 2026 13:21:04 +0200 Subject: [PATCH 3/7] fix(grammar): exclude ~ (%x7E) from json-pointer unescaped rule The unescaped range incorrectly started at %x7E instead of %x7F, re-introducing ~ which RFC 6901 requires to only appear as part of escape sequences (~0 / ~1). Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 2 +- src/grammar.bnf | 2 +- src/grammar.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 297acdb..2ae56a4 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) ; { (%x7B) and } (%x7D) are excluded from 'unescaped' for unambiguous embedded expression parsing json-pointer = *( "/" reference-token ) reference-token = *( unescaped / escaped ) -unescaped = %x00-2E / %x30-7A / %x7C / %x7E-10FFFF +unescaped = %x00-2E / %x30-7A / %x7C / %x7F-10FFFF ; %x2F ('/'), %x7E ('~'), %x7B ('{'), %x7D ('}') are excluded escaped = "~" ( "0" / "1" ) ; representing '~' and '/', respectively diff --git a/src/grammar.bnf b/src/grammar.bnf index cf4aa5a..86629dc 100644 --- a/src/grammar.bnf +++ b/src/grammar.bnf @@ -58,7 +58,7 @@ literal-char = %x00-7A / %x7C / %x7E-10FFFF ; anything except { (%x7B) ; { (%x7B) and } (%x7D) are excluded from 'unescaped' for unambiguous embedded expression parsing json-pointer = *( "/" reference-token ) reference-token = *( unescaped / escaped ) -unescaped = %x00-2E / %x30-7A / %x7C / %x7E-10FFFF +unescaped = %x00-2E / %x30-7A / %x7C / %x7F-10FFFF ; %x2F ('/'), %x7E ('~'), %x7B ('{'), %x7D ('}') are excluded escaped = "~" ( "0" / "1" ) ; representing '~' and '/', respectively diff --git a/src/grammar.js b/src/grammar.js index 6100b83..7c822a7 100644 --- a/src/grammar.js +++ b/src/grammar.js @@ -287,7 +287,7 @@ export default function grammar(){ this.rules[28].opcodes[1] = { type: 5, min: 0, max: 46 };// TRG this.rules[28].opcodes[2] = { type: 5, min: 48, max: 122 };// TRG this.rules[28].opcodes[3] = { type: 6, string: [124] };// TBS - this.rules[28].opcodes[4] = { type: 5, min: 126, max: 1114111 };// TRG + this.rules[28].opcodes[4] = { type: 5, min: 127, max: 1114111 };// TRG /* escaped */ this.rules[29].opcodes = []; @@ -459,7 +459,7 @@ export default function grammar(){ str += "; { (%x7B) and } (%x7D) are excluded from 'unescaped' for unambiguous embedded expression parsing\n"; str += "json-pointer = *( \"/\" reference-token )\n"; str += "reference-token = *( unescaped / escaped )\n"; - str += "unescaped = %x00-2E / %x30-7A / %x7C / %x7E-10FFFF\n"; + str += "unescaped = %x00-2E / %x30-7A / %x7C / %x7F-10FFFF\n"; str += " ; %x2F ('/'), %x7E ('~'), %x7B ('{'), %x7D ('}') are excluded\n"; str += "escaped = \"~\" ( \"0\" / \"1\" )\n"; str += " ; representing '~' and '/', respectively\n"; From 2061931be995352b31cb7f7401a02f292637df5d Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Fri, 3 Apr 2026 13:32:06 +0200 Subject: [PATCH 4/7] fix: update TypeScript types and restore backward compatibility - Add SelfExpression type to ASTNode union - Add optional jsonPointer to InputsExpression and OutputsExpression - Restore field: 'outputs' on StepsExpression - Add fieldName to WorkflowsExpression, deprecate subField - Add componentType/componentName to ComponentsExpression, deprecate field/subField - Fix stale json-pointer-safe reference in test comment Co-Authored-By: Claude Opus 4.6 (1M context) --- .../translators/ASTTranslator/transformers.js | 4 ++++ test/parse/__snapshots__/ast-corpus.js.snap | 17 +++++++++++++++ .../__snapshots__/ASTTranslator.js.snap | 1 + test/test.js | 2 +- types/index.d.ts | 21 +++++++++++++++++-- 5 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/parse/translators/ASTTranslator/transformers.js b/src/parse/translators/ASTTranslator/transformers.js index f6161b7..3236b53 100644 --- a/src/parse/translators/ASTTranslator/transformers.js +++ b/src/parse/translators/ASTTranslator/transformers.js @@ -148,6 +148,7 @@ const transformers = { const result = { type: 'StepsExpression', stepId: stepIdNode.text, + field: 'outputs', outputName: outputNameNode.text, }; @@ -169,6 +170,7 @@ const transformers = { workflowId: workflowIdNode.text, field: fieldNode.text, fieldName: fieldNameNode.text, + subField: fieldNameNode.text, }; if (jsonPointerNode) { @@ -197,6 +199,8 @@ const transformers = { type: 'ComponentsExpression', componentType: typeNode.text, componentName: nameNode.text, + field: typeNode.text, + subField: nameNode.text, }; }, }; diff --git a/test/parse/__snapshots__/ast-corpus.js.snap b/test/parse/__snapshots__/ast-corpus.js.snap index 80a35ca..41d6cfb 100644 --- a/test/parse/__snapshots__/ast-corpus.js.snap +++ b/test/parse/__snapshots__/ast-corpus.js.snap @@ -5,6 +5,8 @@ exports[`parse ast-corpus $components.failureActions.logError 1`] = ` "type": "ComponentsExpression", "componentType": "failureActions", "componentName": "logError", + "field": "failureActions", + "subField": "logError", } `; @@ -13,6 +15,8 @@ exports[`parse ast-corpus $components.parameters.petId 1`] = ` "type": "ComponentsExpression", "componentType": "parameters", "componentName": "petId", + "field": "parameters", + "subField": "petId", } `; @@ -21,6 +25,8 @@ exports[`parse ast-corpus $components.successActions.notifyUser 1`] = ` "type": "ComponentsExpression", "componentType": "successActions", "componentName": "notifyUser", + "field": "successActions", + "subField": "notifyUser", } `; @@ -622,6 +628,7 @@ exports[`parse ast-corpus $steps.createUser.outputs.userId 1`] = ` { "type": "StepsExpression", "stepId": "createUser", + "field": "outputs", "outputName": "userId", } `; @@ -630,6 +637,7 @@ exports[`parse ast-corpus $steps.loginStep.outputs.sessionToken 1`] = ` { "type": "StepsExpression", "stepId": "loginStep", + "field": "outputs", "outputName": "sessionToken", } `; @@ -638,6 +646,7 @@ exports[`parse ast-corpus $steps.loginStep.outputs.user#/name 1`] = ` { "type": "StepsExpression", "stepId": "loginStep", + "field": "outputs", "outputName": "user", "jsonPointer": { "type": "JsonPointer", @@ -656,6 +665,7 @@ exports[`parse ast-corpus $steps.someStepId.outputs.pets#/0/id 1`] = ` { "type": "StepsExpression", "stepId": "someStepId", + "field": "outputs", "outputName": "pets", "jsonPointer": { "type": "JsonPointer", @@ -678,6 +688,7 @@ exports[`parse ast-corpus $steps.stepId.outputs.value 1`] = ` { "type": "StepsExpression", "stepId": "stepId", + "field": "outputs", "outputName": "value", } `; @@ -694,6 +705,7 @@ exports[`parse ast-corpus $workflows.authWorkflow.outputs.token 1`] = ` "workflowId": "authWorkflow", "field": "outputs", "fieldName": "token", + "subField": "token", } `; @@ -703,6 +715,7 @@ exports[`parse ast-corpus $workflows.authWorkflow.outputs.token#/accessToken 1`] "workflowId": "authWorkflow", "field": "outputs", "fieldName": "token", + "subField": "token", "jsonPointer": { "type": "JsonPointer", "value": "/accessToken", @@ -722,6 +735,7 @@ exports[`parse ast-corpus $workflows.mainProcess.outputs.data 1`] = ` "workflowId": "mainProcess", "field": "outputs", "fieldName": "data", + "subField": "data", } `; @@ -731,6 +745,7 @@ exports[`parse ast-corpus $workflows.myWorkflow.inputs.username 1`] = ` "workflowId": "myWorkflow", "field": "inputs", "fieldName": "username", + "subField": "username", } `; @@ -740,6 +755,7 @@ exports[`parse ast-corpus $workflows.myWorkflow.outputs.result 1`] = ` "workflowId": "myWorkflow", "field": "outputs", "fieldName": "result", + "subField": "result", } `; @@ -749,6 +765,7 @@ exports[`parse ast-corpus $workflows.myWorkflow.outputs.result#/value 1`] = ` "workflowId": "myWorkflow", "field": "outputs", "fieldName": "result", + "subField": "result", "jsonPointer": { "type": "JsonPointer", "value": "/value", diff --git a/test/parse/translators/__snapshots__/ASTTranslator.js.snap b/test/parse/translators/__snapshots__/ASTTranslator.js.snap index 890879f..f109da7 100644 --- a/test/parse/translators/__snapshots__/ASTTranslator.js.snap +++ b/test/parse/translators/__snapshots__/ASTTranslator.js.snap @@ -30,6 +30,7 @@ exports[`parse translators ASTTranslator should translate $steps.login.outputs.t { "type": "StepsExpression", "stepId": "login", + "field": "outputs", "outputName": "token", } `; diff --git a/test/test.js b/test/test.js index 13fa654..2cdc27e 100644 --- a/test/test.js +++ b/test/test.js @@ -51,7 +51,7 @@ describe('test', function () { }); it('should reject { and } in JSON pointer paths', function () { - // { and } are excluded from json-pointer-safe to allow unambiguous + // { and } are excluded from json-pointer grammar to allow unambiguous // parsing of embedded expressions like {$request.body#/status} assert.isFalse(test('$request.body#/foo}')); assert.isFalse(test('$request.body#/foo{')); diff --git a/types/index.d.ts b/types/index.d.ts index 37599f3..e0dc204 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -63,6 +63,14 @@ export interface StatusCodeExpression { readonly type: 'StatusCodeExpression'; } +/** + * $self - The canonical URI of the current Arazzo Description + * https://spec.openapis.org/arazzo/latest.html#runtime-expressions + */ +export interface SelfExpression { + readonly type: 'SelfExpression'; +} + /** * JSON Pointer reference token * https://datatracker.ietf.org/doc/html/rfc6901#section-3 @@ -153,6 +161,7 @@ export interface ResponseExpression { export interface InputsExpression { readonly type: 'InputsExpression'; readonly name: string; + readonly jsonPointer?: JsonPointer; } /** @@ -162,6 +171,7 @@ export interface InputsExpression { export interface OutputsExpression { readonly type: 'OutputsExpression'; readonly name: string; + readonly jsonPointer?: JsonPointer; } /** @@ -177,13 +187,15 @@ export interface StepsExpression { } /** - * $workflows.{workflowId}.{field}.{subField} - Workflows expression + * $workflows.{workflowId}.{field}.{fieldName} - Workflows expression * https://spec.openapis.org/arazzo/latest.html#runtime-expressions */ export interface WorkflowsExpression { readonly type: 'WorkflowsExpression'; readonly workflowId: string; readonly field: 'inputs' | 'outputs'; + readonly fieldName: string; + /** @deprecated Use `fieldName` instead. */ readonly subField: string; readonly jsonPointer?: JsonPointer; } @@ -199,12 +211,16 @@ export interface SourceDescriptionsExpression { } /** - * $components.{field}.{subField} - Components expression + * $components.{componentType}.{componentName} - Components expression * https://spec.openapis.org/arazzo/latest.html#runtime-expressions */ export interface ComponentsExpression { readonly type: 'ComponentsExpression'; + readonly componentType: 'parameters' | 'successActions' | 'failureActions'; + readonly componentName: string; + /** @deprecated Use `componentType` instead. */ readonly field: 'parameters' | 'successActions' | 'failureActions'; + /** @deprecated Use `componentName` instead. */ readonly subField: string; } @@ -215,6 +231,7 @@ export type ASTNode = | UrlExpression | MethodExpression | StatusCodeExpression + | SelfExpression | RequestExpression | ResponseExpression | InputsExpression From 4f9912a311429111cf6108afe90e4294a7adaa18 Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Fri, 3 Apr 2026 13:38:20 +0200 Subject: [PATCH 5/7] docs(README): sync grammar block with grammar.bnf Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2ae56a4..bbc28ba 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,7 @@ components-reference = components-type "." components-name components-type = "parameters" / "successActions" / "failureActions" components-name = identifier +; Unconstrained name rule for query/path references and source description references name = *( CHAR ) ; Grammar for parsing template strings with embedded expressions From b1949200b52051461599fb6e3b31411dad48f99c Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Mon, 13 Apr 2026 12:18:39 +0200 Subject: [PATCH 6/7] refactor!: align grammar naming with Arazzo spec PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: CST node type values changed to match the proposed Arazzo 1.1 spec naming convention. AST deprecated fields removed. Grammar rule renames: - inputs-name → input-name - outputs-name → output-name - steps-id → step-id - workflows-id → workflow-id - workflows-field → workflow-field - workflows-field-name → workflow-field-name - source-descriptions-name → source-name - source-descriptions-reference → source-reference-id - components-type → component-type - components-name → component-name AST deprecated fields removed: - WorkflowsExpression.subField (use fieldName) - ComponentsExpression.field (use componentType) - ComponentsExpression.subField (use componentName) Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 32 ++--- src/grammar.bnf | 32 ++--- src/grammar.js | 94 +++++++------- .../translators/ASTTranslator/transformers.js | 25 ++-- src/parse/translators/CSTTranslator.js | 20 +-- test/parse/__snapshots__/ast-corpus.js.snap | 12 -- test/parse/__snapshots__/cst-corpus.js.snap | 118 +++++++++--------- .../__snapshots__/CSTTranslator.js.snap | 4 +- types/index.d.ts | 6 - 9 files changed, 161 insertions(+), 182 deletions(-) diff --git a/README.md b/README.md index bbc28ba..4a638b7 100644 --- a/README.md +++ b/README.md @@ -283,30 +283,30 @@ path-reference = "path." name body-reference = "body" ["#" json-pointer ] ; Input/Output references -inputs-reference = inputs-name ["#" json-pointer] -inputs-name = identifier -outputs-reference = outputs-name ["#" json-pointer] -outputs-name = identifier +inputs-reference = input-name ["#" json-pointer] +input-name = identifier +outputs-reference = output-name ["#" json-pointer] +output-name = identifier ; Steps expressions -steps-reference = steps-id ".outputs." outputs-name ["#" json-pointer] -steps-id = identifier-strict +steps-reference = step-id ".outputs." output-name ["#" json-pointer] +step-id = identifier-strict ; Workflows expressions -workflows-reference = workflows-id "." workflows-field "." workflows-field-name ["#" json-pointer] -workflows-id = identifier-strict -workflows-field = "inputs" / "outputs" -workflows-field-name = identifier +workflows-reference = workflow-id "." workflow-field "." workflow-field-name ["#" json-pointer] +workflow-id = identifier-strict +workflow-field = "inputs" / "outputs" +workflow-field-name = identifier ; Source descriptions expressions -source-reference = source-descriptions-name "." source-descriptions-reference -source-descriptions-name = identifier-strict -source-descriptions-reference = 1*CHAR +source-reference = source-name "." source-reference-id +source-name = identifier-strict +source-reference-id = 1*CHAR ; Components expressions -components-reference = components-type "." components-name -components-type = "parameters" / "successActions" / "failureActions" -components-name = identifier +components-reference = component-type "." component-name +component-type = "parameters" / "successActions" / "failureActions" +component-name = identifier ; Unconstrained name rule for query/path references and source description references name = *( CHAR ) diff --git a/src/grammar.bnf b/src/grammar.bnf index 86629dc..e3eb716 100644 --- a/src/grammar.bnf +++ b/src/grammar.bnf @@ -21,30 +21,30 @@ path-reference = "path." name body-reference = "body" ["#" json-pointer ] ; Input/Output references -inputs-reference = inputs-name ["#" json-pointer] -inputs-name = identifier -outputs-reference = outputs-name ["#" json-pointer] -outputs-name = identifier +inputs-reference = input-name ["#" json-pointer] +input-name = identifier +outputs-reference = output-name ["#" json-pointer] +output-name = identifier ; Steps expressions -steps-reference = steps-id ".outputs." outputs-name ["#" json-pointer] -steps-id = identifier-strict +steps-reference = step-id ".outputs." output-name ["#" json-pointer] +step-id = identifier-strict ; Workflows expressions -workflows-reference = workflows-id "." workflows-field "." workflows-field-name ["#" json-pointer] -workflows-id = identifier-strict -workflows-field = "inputs" / "outputs" -workflows-field-name = identifier +workflows-reference = workflow-id "." workflow-field "." workflow-field-name ["#" json-pointer] +workflow-id = identifier-strict +workflow-field = "inputs" / "outputs" +workflow-field-name = identifier ; Source descriptions expressions -source-reference = source-descriptions-name "." source-descriptions-reference -source-descriptions-name = identifier-strict -source-descriptions-reference = 1*CHAR +source-reference = source-name "." source-reference-id +source-name = identifier-strict +source-reference-id = 1*CHAR ; Components expressions -components-reference = components-type "." components-name -components-type = "parameters" / "successActions" / "failureActions" -components-name = identifier +components-reference = component-type "." component-name +component-type = "parameters" / "successActions" / "failureActions" +component-name = identifier ; Unconstrained name rule for query/path references and source description references name = *( CHAR ) diff --git a/src/grammar.js b/src/grammar.js index 7c822a7..2a023fe 100644 --- a/src/grammar.js +++ b/src/grammar.js @@ -34,21 +34,21 @@ export default function grammar(){ this.rules[4] = { name: 'path-reference', lower: 'path-reference', index: 4, isBkr: false }; this.rules[5] = { name: 'body-reference', lower: 'body-reference', index: 5, isBkr: false }; this.rules[6] = { name: 'inputs-reference', lower: 'inputs-reference', index: 6, isBkr: false }; - this.rules[7] = { name: 'inputs-name', lower: 'inputs-name', index: 7, isBkr: false }; + this.rules[7] = { name: 'input-name', lower: 'input-name', index: 7, isBkr: false }; this.rules[8] = { name: 'outputs-reference', lower: 'outputs-reference', index: 8, isBkr: false }; - this.rules[9] = { name: 'outputs-name', lower: 'outputs-name', index: 9, isBkr: false }; + this.rules[9] = { name: 'output-name', lower: 'output-name', index: 9, isBkr: false }; this.rules[10] = { name: 'steps-reference', lower: 'steps-reference', index: 10, isBkr: false }; - this.rules[11] = { name: 'steps-id', lower: 'steps-id', index: 11, isBkr: false }; + this.rules[11] = { name: 'step-id', lower: 'step-id', index: 11, isBkr: false }; this.rules[12] = { name: 'workflows-reference', lower: 'workflows-reference', index: 12, isBkr: false }; - this.rules[13] = { name: 'workflows-id', lower: 'workflows-id', index: 13, isBkr: false }; - this.rules[14] = { name: 'workflows-field', lower: 'workflows-field', index: 14, isBkr: false }; - this.rules[15] = { name: 'workflows-field-name', lower: 'workflows-field-name', index: 15, isBkr: false }; + this.rules[13] = { name: 'workflow-id', lower: 'workflow-id', index: 13, isBkr: false }; + this.rules[14] = { name: 'workflow-field', lower: 'workflow-field', index: 14, isBkr: false }; + this.rules[15] = { name: 'workflow-field-name', lower: 'workflow-field-name', index: 15, isBkr: false }; this.rules[16] = { name: 'source-reference', lower: 'source-reference', index: 16, isBkr: false }; - this.rules[17] = { name: 'source-descriptions-name', lower: 'source-descriptions-name', index: 17, isBkr: false }; - this.rules[18] = { name: 'source-descriptions-reference', lower: 'source-descriptions-reference', index: 18, isBkr: false }; + this.rules[17] = { name: 'source-name', lower: 'source-name', index: 17, isBkr: false }; + this.rules[18] = { name: 'source-reference-id', lower: 'source-reference-id', index: 18, isBkr: false }; this.rules[19] = { name: 'components-reference', lower: 'components-reference', index: 19, isBkr: false }; - this.rules[20] = { name: 'components-type', lower: 'components-type', index: 20, isBkr: false }; - this.rules[21] = { name: 'components-name', lower: 'components-name', index: 21, isBkr: false }; + this.rules[20] = { name: 'component-type', lower: 'component-type', index: 20, isBkr: false }; + this.rules[21] = { name: 'component-name', lower: 'component-name', index: 21, isBkr: false }; this.rules[22] = { name: 'name', lower: 'name', index: 22, isBkr: false }; this.rules[23] = { name: 'expression-string', lower: 'expression-string', index: 23, isBkr: false }; this.rules[24] = { name: 'embedded-expression', lower: 'embedded-expression', index: 24, isBkr: false }; @@ -142,83 +142,83 @@ export default function grammar(){ /* inputs-reference */ this.rules[6].opcodes = []; this.rules[6].opcodes[0] = { type: 2, children: [1,2] };// CAT - this.rules[6].opcodes[1] = { type: 4, index: 7 };// RNM(inputs-name) + this.rules[6].opcodes[1] = { type: 4, index: 7 };// RNM(input-name) this.rules[6].opcodes[2] = { type: 3, min: 0, max: 1 };// REP this.rules[6].opcodes[3] = { type: 2, children: [4,5] };// CAT this.rules[6].opcodes[4] = { type: 7, string: [35] };// TLS this.rules[6].opcodes[5] = { type: 4, index: 26 };// RNM(json-pointer) - /* inputs-name */ + /* input-name */ this.rules[7].opcodes = []; this.rules[7].opcodes[0] = { type: 4, index: 35 };// RNM(identifier) /* outputs-reference */ this.rules[8].opcodes = []; this.rules[8].opcodes[0] = { type: 2, children: [1,2] };// CAT - this.rules[8].opcodes[1] = { type: 4, index: 9 };// RNM(outputs-name) + this.rules[8].opcodes[1] = { type: 4, index: 9 };// RNM(output-name) this.rules[8].opcodes[2] = { type: 3, min: 0, max: 1 };// REP this.rules[8].opcodes[3] = { type: 2, children: [4,5] };// CAT this.rules[8].opcodes[4] = { type: 7, string: [35] };// TLS this.rules[8].opcodes[5] = { type: 4, index: 26 };// RNM(json-pointer) - /* outputs-name */ + /* output-name */ this.rules[9].opcodes = []; this.rules[9].opcodes[0] = { type: 4, index: 35 };// RNM(identifier) /* steps-reference */ this.rules[10].opcodes = []; this.rules[10].opcodes[0] = { type: 2, children: [1,2,3,4] };// CAT - this.rules[10].opcodes[1] = { type: 4, index: 11 };// RNM(steps-id) + this.rules[10].opcodes[1] = { type: 4, index: 11 };// RNM(step-id) this.rules[10].opcodes[2] = { type: 7, string: [46,111,117,116,112,117,116,115,46] };// TLS - this.rules[10].opcodes[3] = { type: 4, index: 9 };// RNM(outputs-name) + this.rules[10].opcodes[3] = { type: 4, index: 9 };// RNM(output-name) this.rules[10].opcodes[4] = { type: 3, min: 0, max: 1 };// REP this.rules[10].opcodes[5] = { type: 2, children: [6,7] };// CAT this.rules[10].opcodes[6] = { type: 7, string: [35] };// TLS this.rules[10].opcodes[7] = { type: 4, index: 26 };// RNM(json-pointer) - /* steps-id */ + /* step-id */ this.rules[11].opcodes = []; this.rules[11].opcodes[0] = { type: 4, index: 36 };// RNM(identifier-strict) /* workflows-reference */ this.rules[12].opcodes = []; this.rules[12].opcodes[0] = { type: 2, children: [1,2,3,4,5,6] };// CAT - this.rules[12].opcodes[1] = { type: 4, index: 13 };// RNM(workflows-id) + this.rules[12].opcodes[1] = { type: 4, index: 13 };// RNM(workflow-id) this.rules[12].opcodes[2] = { type: 7, string: [46] };// TLS - this.rules[12].opcodes[3] = { type: 4, index: 14 };// RNM(workflows-field) + this.rules[12].opcodes[3] = { type: 4, index: 14 };// RNM(workflow-field) this.rules[12].opcodes[4] = { type: 7, string: [46] };// TLS - this.rules[12].opcodes[5] = { type: 4, index: 15 };// RNM(workflows-field-name) + this.rules[12].opcodes[5] = { type: 4, index: 15 };// RNM(workflow-field-name) this.rules[12].opcodes[6] = { type: 3, min: 0, max: 1 };// REP this.rules[12].opcodes[7] = { type: 2, children: [8,9] };// CAT this.rules[12].opcodes[8] = { type: 7, string: [35] };// TLS this.rules[12].opcodes[9] = { type: 4, index: 26 };// RNM(json-pointer) - /* workflows-id */ + /* workflow-id */ this.rules[13].opcodes = []; this.rules[13].opcodes[0] = { type: 4, index: 36 };// RNM(identifier-strict) - /* workflows-field */ + /* workflow-field */ this.rules[14].opcodes = []; this.rules[14].opcodes[0] = { type: 1, children: [1,2] };// ALT this.rules[14].opcodes[1] = { type: 7, string: [105,110,112,117,116,115] };// TLS this.rules[14].opcodes[2] = { type: 7, string: [111,117,116,112,117,116,115] };// TLS - /* workflows-field-name */ + /* workflow-field-name */ this.rules[15].opcodes = []; this.rules[15].opcodes[0] = { type: 4, index: 35 };// RNM(identifier) /* source-reference */ this.rules[16].opcodes = []; this.rules[16].opcodes[0] = { type: 2, children: [1,2,3] };// CAT - this.rules[16].opcodes[1] = { type: 4, index: 17 };// RNM(source-descriptions-name) + this.rules[16].opcodes[1] = { type: 4, index: 17 };// RNM(source-name) this.rules[16].opcodes[2] = { type: 7, string: [46] };// TLS - this.rules[16].opcodes[3] = { type: 4, index: 18 };// RNM(source-descriptions-reference) + this.rules[16].opcodes[3] = { type: 4, index: 18 };// RNM(source-reference-id) - /* source-descriptions-name */ + /* source-name */ this.rules[17].opcodes = []; this.rules[17].opcodes[0] = { type: 4, index: 36 };// RNM(identifier-strict) - /* source-descriptions-reference */ + /* source-reference-id */ this.rules[18].opcodes = []; this.rules[18].opcodes[0] = { type: 3, min: 1, max: Infinity };// REP this.rules[18].opcodes[1] = { type: 4, index: 32 };// RNM(CHAR) @@ -226,18 +226,18 @@ export default function grammar(){ /* components-reference */ this.rules[19].opcodes = []; this.rules[19].opcodes[0] = { type: 2, children: [1,2,3] };// CAT - this.rules[19].opcodes[1] = { type: 4, index: 20 };// RNM(components-type) + this.rules[19].opcodes[1] = { type: 4, index: 20 };// RNM(component-type) this.rules[19].opcodes[2] = { type: 7, string: [46] };// TLS - this.rules[19].opcodes[3] = { type: 4, index: 21 };// RNM(components-name) + this.rules[19].opcodes[3] = { type: 4, index: 21 };// RNM(component-name) - /* components-type */ + /* component-type */ this.rules[20].opcodes = []; this.rules[20].opcodes[0] = { type: 1, children: [1,2,3] };// ALT this.rules[20].opcodes[1] = { type: 7, string: [112,97,114,97,109,101,116,101,114,115] };// TLS this.rules[20].opcodes[2] = { type: 7, string: [115,117,99,99,101,115,115,97,99,116,105,111,110,115] };// TLS this.rules[20].opcodes[3] = { type: 7, string: [102,97,105,108,117,114,101,97,99,116,105,111,110,115] };// TLS - /* components-name */ + /* component-name */ this.rules[21].opcodes = []; this.rules[21].opcodes[0] = { type: 4, index: 35 };// RNM(identifier) @@ -422,30 +422,30 @@ export default function grammar(){ str += "body-reference = \"body\" [\"#\" json-pointer ]\n"; str += "\n"; str += "; Input/Output references\n"; - str += "inputs-reference = inputs-name [\"#\" json-pointer]\n"; - str += "inputs-name = identifier\n"; - str += "outputs-reference = outputs-name [\"#\" json-pointer]\n"; - str += "outputs-name = identifier\n"; + str += "inputs-reference = input-name [\"#\" json-pointer]\n"; + str += "input-name = identifier\n"; + str += "outputs-reference = output-name [\"#\" json-pointer]\n"; + str += "output-name = identifier\n"; str += "\n"; str += "; Steps expressions\n"; - str += "steps-reference = steps-id \".outputs.\" outputs-name [\"#\" json-pointer]\n"; - str += "steps-id = identifier-strict\n"; + str += "steps-reference = step-id \".outputs.\" output-name [\"#\" json-pointer]\n"; + str += "step-id = identifier-strict\n"; str += "\n"; str += "; Workflows expressions\n"; - str += "workflows-reference = workflows-id \".\" workflows-field \".\" workflows-field-name [\"#\" json-pointer]\n"; - str += "workflows-id = identifier-strict\n"; - str += "workflows-field = \"inputs\" / \"outputs\"\n"; - str += "workflows-field-name = identifier\n"; + str += "workflows-reference = workflow-id \".\" workflow-field \".\" workflow-field-name [\"#\" json-pointer]\n"; + str += "workflow-id = identifier-strict\n"; + str += "workflow-field = \"inputs\" / \"outputs\"\n"; + str += "workflow-field-name = identifier\n"; str += "\n"; str += "; Source descriptions expressions\n"; - str += "source-reference = source-descriptions-name \".\" source-descriptions-reference\n"; - str += "source-descriptions-name = identifier-strict\n"; - str += "source-descriptions-reference = 1*CHAR\n"; + str += "source-reference = source-name \".\" source-reference-id\n"; + str += "source-name = identifier-strict\n"; + str += "source-reference-id = 1*CHAR\n"; str += "\n"; str += "; Components expressions\n"; - str += "components-reference = components-type \".\" components-name\n"; - str += "components-type = \"parameters\" / \"successActions\" / \"failureActions\"\n"; - str += "components-name = identifier\n"; + str += "components-reference = component-type \".\" component-name\n"; + str += "component-type = \"parameters\" / \"successActions\" / \"failureActions\"\n"; + str += "component-name = identifier\n"; str += "\n"; str += "; Unconstrained name rule for query/path references and source description references\n"; str += "name = *( CHAR )\n"; diff --git a/src/parse/translators/ASTTranslator/transformers.js b/src/parse/translators/ASTTranslator/transformers.js index 3236b53..df071bb 100644 --- a/src/parse/translators/ASTTranslator/transformers.js +++ b/src/parse/translators/ASTTranslator/transformers.js @@ -109,7 +109,7 @@ const transformers = { }, ['inputs-reference'](node) { - const inputNameNode = node.children.find((c) => c.type === 'inputs-name'); + const inputNameNode = node.children.find((c) => c.type === 'input-name'); const jsonPointerNode = node.children.find((c) => c.type === 'json-pointer'); const result = { @@ -125,7 +125,7 @@ const transformers = { }, ['outputs-reference'](node) { - const outputNameNode = node.children.find((c) => c.type === 'outputs-name'); + const outputNameNode = node.children.find((c) => c.type === 'output-name'); const jsonPointerNode = node.children.find((c) => c.type === 'json-pointer'); const result = { @@ -141,8 +141,8 @@ const transformers = { }, ['steps-reference'](node) { - const stepIdNode = node.children.find((c) => c.type === 'steps-id'); - const outputNameNode = node.children.find((c) => c.type === 'outputs-name'); + const stepIdNode = node.children.find((c) => c.type === 'step-id'); + const outputNameNode = node.children.find((c) => c.type === 'output-name'); const jsonPointerNode = node.children.find((c) => c.type === 'json-pointer'); const result = { @@ -160,9 +160,9 @@ const transformers = { }, ['workflows-reference'](node) { - const workflowIdNode = node.children.find((c) => c.type === 'workflows-id'); - const fieldNode = node.children.find((c) => c.type === 'workflows-field'); - const fieldNameNode = node.children.find((c) => c.type === 'workflows-field-name'); + const workflowIdNode = node.children.find((c) => c.type === 'workflow-id'); + const fieldNode = node.children.find((c) => c.type === 'workflow-field'); + const fieldNameNode = node.children.find((c) => c.type === 'workflow-field-name'); const jsonPointerNode = node.children.find((c) => c.type === 'json-pointer'); const result = { @@ -170,7 +170,6 @@ const transformers = { workflowId: workflowIdNode.text, field: fieldNode.text, fieldName: fieldNameNode.text, - subField: fieldNameNode.text, }; if (jsonPointerNode) { @@ -181,8 +180,8 @@ const transformers = { }, ['source-reference'](node) { - const sourceNameNode = node.children.find((c) => c.type === 'source-descriptions-name'); - const referenceNode = node.children.find((c) => c.type === 'source-descriptions-reference'); + const sourceNameNode = node.children.find((c) => c.type === 'source-name'); + const referenceNode = node.children.find((c) => c.type === 'source-reference-id'); return { type: 'SourceDescriptionsExpression', @@ -192,15 +191,13 @@ const transformers = { }, ['components-reference'](node) { - const typeNode = node.children.find((c) => c.type === 'components-type'); - const nameNode = node.children.find((c) => c.type === 'components-name'); + const typeNode = node.children.find((c) => c.type === 'component-type'); + const nameNode = node.children.find((c) => c.type === 'component-name'); return { type: 'ComponentsExpression', componentType: typeNode.text, componentName: nameNode.text, - field: typeNode.text, - subField: nameNode.text, }; }, }; diff --git a/src/parse/translators/CSTTranslator.js b/src/parse/translators/CSTTranslator.js index 20c3daa..a2eda6f 100644 --- a/src/parse/translators/CSTTranslator.js +++ b/src/parse/translators/CSTTranslator.js @@ -20,23 +20,23 @@ class CSTTranslator extends AST { this.callbacks['json-pointer'] = cstCallback('json-pointer'); this.callbacks['reference-token'] = cstCallback('reference-token'); this.callbacks['inputs-reference'] = cstCallback('inputs-reference'); - this.callbacks['inputs-name'] = cstCallback('inputs-name'); + this.callbacks['input-name'] = cstCallback('input-name'); this.callbacks['outputs-reference'] = cstCallback('outputs-reference'); - this.callbacks['outputs-name'] = cstCallback('outputs-name'); + this.callbacks['output-name'] = cstCallback('output-name'); this.callbacks['steps-reference'] = cstCallback('steps-reference'); this.callbacks['workflows-reference'] = cstCallback('workflows-reference'); - this.callbacks['workflows-id'] = cstCallback('workflows-id'); - this.callbacks['workflows-field'] = cstCallback('workflows-field'); - this.callbacks['workflows-field-name'] = cstCallback('workflows-field-name'); + this.callbacks['workflow-id'] = cstCallback('workflow-id'); + this.callbacks['workflow-field'] = cstCallback('workflow-field'); + this.callbacks['workflow-field-name'] = cstCallback('workflow-field-name'); this.callbacks['source-reference'] = cstCallback('source-reference'); - this.callbacks['source-descriptions-name'] = cstCallback('source-descriptions-name'); - this.callbacks['source-descriptions-reference'] = cstCallback('source-descriptions-reference'); + this.callbacks['source-name'] = cstCallback('source-name'); + this.callbacks['source-reference-id'] = cstCallback('source-reference-id'); this.callbacks['components-reference'] = cstCallback('components-reference'); - this.callbacks['components-type'] = cstCallback('components-type'); - this.callbacks['components-name'] = cstCallback('components-name'); + this.callbacks['component-type'] = cstCallback('component-type'); + this.callbacks['component-name'] = cstCallback('component-name'); this.callbacks['name'] = cstCallback('name'); this.callbacks['token'] = cstCallback('token'); - this.callbacks['steps-id'] = cstCallback('steps-id'); + this.callbacks['step-id'] = cstCallback('step-id'); } getTree() { diff --git a/test/parse/__snapshots__/ast-corpus.js.snap b/test/parse/__snapshots__/ast-corpus.js.snap index 41d6cfb..543c219 100644 --- a/test/parse/__snapshots__/ast-corpus.js.snap +++ b/test/parse/__snapshots__/ast-corpus.js.snap @@ -5,8 +5,6 @@ exports[`parse ast-corpus $components.failureActions.logError 1`] = ` "type": "ComponentsExpression", "componentType": "failureActions", "componentName": "logError", - "field": "failureActions", - "subField": "logError", } `; @@ -15,8 +13,6 @@ exports[`parse ast-corpus $components.parameters.petId 1`] = ` "type": "ComponentsExpression", "componentType": "parameters", "componentName": "petId", - "field": "parameters", - "subField": "petId", } `; @@ -25,8 +21,6 @@ exports[`parse ast-corpus $components.successActions.notifyUser 1`] = ` "type": "ComponentsExpression", "componentType": "successActions", "componentName": "notifyUser", - "field": "successActions", - "subField": "notifyUser", } `; @@ -705,7 +699,6 @@ exports[`parse ast-corpus $workflows.authWorkflow.outputs.token 1`] = ` "workflowId": "authWorkflow", "field": "outputs", "fieldName": "token", - "subField": "token", } `; @@ -715,7 +708,6 @@ exports[`parse ast-corpus $workflows.authWorkflow.outputs.token#/accessToken 1`] "workflowId": "authWorkflow", "field": "outputs", "fieldName": "token", - "subField": "token", "jsonPointer": { "type": "JsonPointer", "value": "/accessToken", @@ -735,7 +727,6 @@ exports[`parse ast-corpus $workflows.mainProcess.outputs.data 1`] = ` "workflowId": "mainProcess", "field": "outputs", "fieldName": "data", - "subField": "data", } `; @@ -745,7 +736,6 @@ exports[`parse ast-corpus $workflows.myWorkflow.inputs.username 1`] = ` "workflowId": "myWorkflow", "field": "inputs", "fieldName": "username", - "subField": "username", } `; @@ -755,7 +745,6 @@ exports[`parse ast-corpus $workflows.myWorkflow.outputs.result 1`] = ` "workflowId": "myWorkflow", "field": "outputs", "fieldName": "result", - "subField": "result", } `; @@ -765,7 +754,6 @@ exports[`parse ast-corpus $workflows.myWorkflow.outputs.result#/value 1`] = ` "workflowId": "myWorkflow", "field": "outputs", "fieldName": "result", - "subField": "result", "jsonPointer": { "type": "JsonPointer", "value": "/value", diff --git a/test/parse/__snapshots__/cst-corpus.js.snap b/test/parse/__snapshots__/cst-corpus.js.snap index a3241b3..3dc72e7 100644 --- a/test/parse/__snapshots__/cst-corpus.js.snap +++ b/test/parse/__snapshots__/cst-corpus.js.snap @@ -14,14 +14,14 @@ exports[`parse cst-corpus $components.failureActions.logError 1`] = ` "length": 23, "children": [ { - "type": "components-type", + "type": "component-type", "text": "failureActions", "start": 12, "length": 14, "children": [], }, { - "type": "components-name", + "type": "component-name", "text": "logError", "start": 27, "length": 8, @@ -47,14 +47,14 @@ exports[`parse cst-corpus $components.parameters.petId 1`] = ` "length": 16, "children": [ { - "type": "components-type", + "type": "component-type", "text": "parameters", "start": 12, "length": 10, "children": [], }, { - "type": "components-name", + "type": "component-name", "text": "petId", "start": 23, "length": 5, @@ -80,14 +80,14 @@ exports[`parse cst-corpus $components.successActions.notifyUser 1`] = ` "length": 25, "children": [ { - "type": "components-type", + "type": "component-type", "text": "successActions", "start": 12, "length": 14, "children": [], }, { - "type": "components-name", + "type": "component-name", "text": "notifyUser", "start": 27, "length": 10, @@ -113,7 +113,7 @@ exports[`parse cst-corpus $inputs.a 1`] = ` "length": 1, "children": [ { - "type": "inputs-name", + "type": "input-name", "text": "a", "start": 8, "length": 1, @@ -139,7 +139,7 @@ exports[`parse cst-corpus $inputs.apiKey 1`] = ` "length": 6, "children": [ { - "type": "inputs-name", + "type": "input-name", "text": "apiKey", "start": 8, "length": 6, @@ -165,7 +165,7 @@ exports[`parse cst-corpus $inputs.customer#/address/city 1`] = ` "length": 22, "children": [ { - "type": "inputs-name", + "type": "input-name", "text": "customer", "start": 8, "length": 8, @@ -213,7 +213,7 @@ exports[`parse cst-corpus $inputs.customer#/firstName 1`] = ` "length": 19, "children": [ { - "type": "inputs-name", + "type": "input-name", "text": "customer", "start": 8, "length": 8, @@ -254,7 +254,7 @@ exports[`parse cst-corpus $inputs.my.nested.name 1`] = ` "length": 14, "children": [ { - "type": "inputs-name", + "type": "input-name", "text": "my.nested.name", "start": 8, "length": 14, @@ -280,7 +280,7 @@ exports[`parse cst-corpus $inputs.my_input 1`] = ` "length": 8, "children": [ { - "type": "inputs-name", + "type": "input-name", "text": "my_input", "start": 8, "length": 8, @@ -306,7 +306,7 @@ exports[`parse cst-corpus $inputs.my-input 1`] = ` "length": 8, "children": [ { - "type": "inputs-name", + "type": "input-name", "text": "my-input", "start": 8, "length": 8, @@ -332,7 +332,7 @@ exports[`parse cst-corpus $inputs.password 1`] = ` "length": 8, "children": [ { - "type": "inputs-name", + "type": "input-name", "text": "password", "start": 8, "length": 8, @@ -358,7 +358,7 @@ exports[`parse cst-corpus $inputs.username 1`] = ` "length": 8, "children": [ { - "type": "inputs-name", + "type": "input-name", "text": "username", "start": 8, "length": 8, @@ -394,7 +394,7 @@ exports[`parse cst-corpus $outputs.a 1`] = ` "length": 1, "children": [ { - "type": "outputs-name", + "type": "output-name", "text": "a", "start": 9, "length": 1, @@ -420,7 +420,7 @@ exports[`parse cst-corpus $outputs.data 1`] = ` "length": 4, "children": [ { - "type": "outputs-name", + "type": "output-name", "text": "data", "start": 9, "length": 4, @@ -446,7 +446,7 @@ exports[`parse cst-corpus $outputs.mappedResponse#/data/id 1`] = ` "length": 23, "children": [ { - "type": "outputs-name", + "type": "output-name", "text": "mappedResponse", "start": 9, "length": 14, @@ -494,7 +494,7 @@ exports[`parse cst-corpus $outputs.my.nested.name 1`] = ` "length": 14, "children": [ { - "type": "outputs-name", + "type": "output-name", "text": "my.nested.name", "start": 9, "length": 14, @@ -520,7 +520,7 @@ exports[`parse cst-corpus $outputs.my_output 1`] = ` "length": 9, "children": [ { - "type": "outputs-name", + "type": "output-name", "text": "my_output", "start": 9, "length": 9, @@ -546,7 +546,7 @@ exports[`parse cst-corpus $outputs.my-output 1`] = ` "length": 9, "children": [ { - "type": "outputs-name", + "type": "output-name", "text": "my-output", "start": 9, "length": 9, @@ -572,7 +572,7 @@ exports[`parse cst-corpus $outputs.response#/name 1`] = ` "length": 14, "children": [ { - "type": "outputs-name", + "type": "output-name", "text": "response", "start": 9, "length": 8, @@ -613,7 +613,7 @@ exports[`parse cst-corpus $outputs.result 1`] = ` "length": 6, "children": [ { - "type": "outputs-name", + "type": "output-name", "text": "result", "start": 9, "length": 6, @@ -639,7 +639,7 @@ exports[`parse cst-corpus $outputs.result#/items/0/name 1`] = ` "length": 20, "children": [ { - "type": "outputs-name", + "type": "output-name", "text": "result", "start": 9, "length": 6, @@ -694,7 +694,7 @@ exports[`parse cst-corpus $outputs.token 1`] = ` "length": 5, "children": [ { - "type": "outputs-name", + "type": "output-name", "text": "token", "start": 9, "length": 5, @@ -1506,14 +1506,14 @@ exports[`parse cst-corpus $sourceDescriptions.externalService.url 1`] = ` "length": 19, "children": [ { - "type": "source-descriptions-name", + "type": "source-name", "text": "externalService", "start": 20, "length": 15, "children": [], }, { - "type": "source-descriptions-reference", + "type": "source-reference-id", "text": "url", "start": 36, "length": 3, @@ -1539,14 +1539,14 @@ exports[`parse cst-corpus $sourceDescriptions.mainApi.createUser 1`] = ` "length": 18, "children": [ { - "type": "source-descriptions-name", + "type": "source-name", "text": "mainApi", "start": 20, "length": 7, "children": [], }, { - "type": "source-descriptions-reference", + "type": "source-reference-id", "text": "createUser", "start": 28, "length": 10, @@ -1572,14 +1572,14 @@ exports[`parse cst-corpus $sourceDescriptions.petStore.getPets 1`] = ` "length": 16, "children": [ { - "type": "source-descriptions-name", + "type": "source-name", "text": "petStore", "start": 20, "length": 8, "children": [], }, { - "type": "source-descriptions-reference", + "type": "source-reference-id", "text": "getPets", "start": 29, "length": 7, @@ -1615,14 +1615,14 @@ exports[`parse cst-corpus $steps.createUser.outputs.userId 1`] = ` "length": 25, "children": [ { - "type": "steps-id", + "type": "step-id", "text": "createUser", "start": 7, "length": 10, "children": [], }, { - "type": "outputs-name", + "type": "output-name", "text": "userId", "start": 26, "length": 6, @@ -1648,14 +1648,14 @@ exports[`parse cst-corpus $steps.loginStep.outputs.sessionToken 1`] = ` "length": 30, "children": [ { - "type": "steps-id", + "type": "step-id", "text": "loginStep", "start": 7, "length": 9, "children": [], }, { - "type": "outputs-name", + "type": "output-name", "text": "sessionToken", "start": 25, "length": 12, @@ -1681,14 +1681,14 @@ exports[`parse cst-corpus $steps.loginStep.outputs.user#/name 1`] = ` "length": 28, "children": [ { - "type": "steps-id", + "type": "step-id", "text": "loginStep", "start": 7, "length": 9, "children": [], }, { - "type": "outputs-name", + "type": "output-name", "text": "user", "start": 25, "length": 4, @@ -1729,14 +1729,14 @@ exports[`parse cst-corpus $steps.someStepId.outputs.pets#/0/id 1`] = ` "length": 29, "children": [ { - "type": "steps-id", + "type": "step-id", "text": "someStepId", "start": 7, "length": 10, "children": [], }, { - "type": "outputs-name", + "type": "output-name", "text": "pets", "start": 26, "length": 4, @@ -1784,14 +1784,14 @@ exports[`parse cst-corpus $steps.stepId.outputs.value 1`] = ` "length": 20, "children": [ { - "type": "steps-id", + "type": "step-id", "text": "stepId", "start": 7, "length": 6, "children": [], }, { - "type": "outputs-name", + "type": "output-name", "text": "value", "start": 22, "length": 5, @@ -1827,21 +1827,21 @@ exports[`parse cst-corpus $workflows.authWorkflow.outputs.token 1`] = ` "length": 26, "children": [ { - "type": "workflows-id", + "type": "workflow-id", "text": "authWorkflow", "start": 11, "length": 12, "children": [], }, { - "type": "workflows-field", + "type": "workflow-field", "text": "outputs", "start": 24, "length": 7, "children": [], }, { - "type": "workflows-field-name", + "type": "workflow-field-name", "text": "token", "start": 32, "length": 5, @@ -1867,21 +1867,21 @@ exports[`parse cst-corpus $workflows.authWorkflow.outputs.token#/accessToken 1`] "length": 39, "children": [ { - "type": "workflows-id", + "type": "workflow-id", "text": "authWorkflow", "start": 11, "length": 12, "children": [], }, { - "type": "workflows-field", + "type": "workflow-field", "text": "outputs", "start": 24, "length": 7, "children": [], }, { - "type": "workflows-field-name", + "type": "workflow-field-name", "text": "token", "start": 32, "length": 5, @@ -1922,21 +1922,21 @@ exports[`parse cst-corpus $workflows.mainProcess.outputs.data 1`] = ` "length": 24, "children": [ { - "type": "workflows-id", + "type": "workflow-id", "text": "mainProcess", "start": 11, "length": 11, "children": [], }, { - "type": "workflows-field", + "type": "workflow-field", "text": "outputs", "start": 23, "length": 7, "children": [], }, { - "type": "workflows-field-name", + "type": "workflow-field-name", "text": "data", "start": 31, "length": 4, @@ -1962,21 +1962,21 @@ exports[`parse cst-corpus $workflows.myWorkflow.inputs.username 1`] = ` "length": 26, "children": [ { - "type": "workflows-id", + "type": "workflow-id", "text": "myWorkflow", "start": 11, "length": 10, "children": [], }, { - "type": "workflows-field", + "type": "workflow-field", "text": "inputs", "start": 22, "length": 6, "children": [], }, { - "type": "workflows-field-name", + "type": "workflow-field-name", "text": "username", "start": 29, "length": 8, @@ -2002,21 +2002,21 @@ exports[`parse cst-corpus $workflows.myWorkflow.outputs.result 1`] = ` "length": 25, "children": [ { - "type": "workflows-id", + "type": "workflow-id", "text": "myWorkflow", "start": 11, "length": 10, "children": [], }, { - "type": "workflows-field", + "type": "workflow-field", "text": "outputs", "start": 22, "length": 7, "children": [], }, { - "type": "workflows-field-name", + "type": "workflow-field-name", "text": "result", "start": 30, "length": 6, @@ -2042,21 +2042,21 @@ exports[`parse cst-corpus $workflows.myWorkflow.outputs.result#/value 1`] = ` "length": 32, "children": [ { - "type": "workflows-id", + "type": "workflow-id", "text": "myWorkflow", "start": 11, "length": 10, "children": [], }, { - "type": "workflows-field", + "type": "workflow-field", "text": "outputs", "start": 22, "length": 7, "children": [], }, { - "type": "workflows-field-name", + "type": "workflow-field-name", "text": "result", "start": 30, "length": 6, diff --git a/test/parse/translators/__snapshots__/CSTTranslator.js.snap b/test/parse/translators/__snapshots__/CSTTranslator.js.snap index e82f1ff..654dbed 100644 --- a/test/parse/translators/__snapshots__/CSTTranslator.js.snap +++ b/test/parse/translators/__snapshots__/CSTTranslator.js.snap @@ -63,14 +63,14 @@ exports[`parse translators CSTTranslator should translate $steps.login.outputs.t "length": 19, "children": [ { - "type": "steps-id", + "type": "step-id", "text": "login", "start": 7, "length": 5, "children": [], }, { - "type": "outputs-name", + "type": "output-name", "text": "token", "start": 21, "length": 5, diff --git a/types/index.d.ts b/types/index.d.ts index e0dc204..062cc0b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -195,8 +195,6 @@ export interface WorkflowsExpression { readonly workflowId: string; readonly field: 'inputs' | 'outputs'; readonly fieldName: string; - /** @deprecated Use `fieldName` instead. */ - readonly subField: string; readonly jsonPointer?: JsonPointer; } @@ -218,10 +216,6 @@ export interface ComponentsExpression { readonly type: 'ComponentsExpression'; readonly componentType: 'parameters' | 'successActions' | 'failureActions'; readonly componentName: string; - /** @deprecated Use `componentType` instead. */ - readonly field: 'parameters' | 'successActions' | 'failureActions'; - /** @deprecated Use `componentName` instead. */ - readonly subField: string; } /** From ddba7424c609d6f94d40cb7b6879fca2ecc7f902 Mon Sep 17 00:00:00 2001 From: Vladimir Gorej Date: Mon, 13 Apr 2026 12:42:29 +0200 Subject: [PATCH 7/7] fix: update stale comments and add empty name tests - Fix stale "secondary grammar parsing" comment in transformers - Fix stale "source description references" in name rule comment - Add tests for empty query/path parameter names (valid per spec) Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 2 +- src/grammar.bnf | 2 +- src/grammar.js | 2 +- src/parse/translators/ASTTranslator/transformers.js | 2 +- test/test.js | 6 ++++++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4a638b7..62c45de 100644 --- a/README.md +++ b/README.md @@ -308,7 +308,7 @@ components-reference = component-type "." component-name component-type = "parameters" / "successActions" / "failureActions" component-name = identifier -; Unconstrained name rule for query/path references and source description references +; Unconstrained name rule for query/path references name = *( CHAR ) ; Grammar for parsing template strings with embedded expressions diff --git a/src/grammar.bnf b/src/grammar.bnf index e3eb716..e352a56 100644 --- a/src/grammar.bnf +++ b/src/grammar.bnf @@ -46,7 +46,7 @@ components-reference = component-type "." component-name component-type = "parameters" / "successActions" / "failureActions" component-name = identifier -; Unconstrained name rule for query/path references and source description references +; Unconstrained name rule for query/path references name = *( CHAR ) ; Grammar for parsing template strings with embedded expressions diff --git a/src/grammar.js b/src/grammar.js index 2a023fe..a5d7122 100644 --- a/src/grammar.js +++ b/src/grammar.js @@ -447,7 +447,7 @@ export default function grammar(){ str += "component-type = \"parameters\" / \"successActions\" / \"failureActions\"\n"; str += "component-name = identifier\n"; str += "\n"; - str += "; Unconstrained name rule for query/path references and source description references\n"; + str += "; Unconstrained name rule for query/path references\n"; str += "name = *( CHAR )\n"; str += "\n"; str += "; Grammar for parsing template strings with embedded expressions\n"; diff --git a/src/parse/translators/ASTTranslator/transformers.js b/src/parse/translators/ASTTranslator/transformers.js index df071bb..879e052 100644 --- a/src/parse/translators/ASTTranslator/transformers.js +++ b/src/parse/translators/ASTTranslator/transformers.js @@ -32,7 +32,7 @@ const transformers = { }; } - // Reference expressions (secondary grammar parsing) + // Reference expressions if (text.startsWith('$inputs.')) { const refNode = node.children.find((c) => c.type === 'inputs-reference'); return transformCSTtoAST(refNode, transformers); diff --git a/test/test.js b/test/test.js index 2cdc27e..37220c6 100644 --- a/test/test.js +++ b/test/test.js @@ -50,6 +50,12 @@ describe('test', function () { assert.isFalse(test('$inputs.{foo}')); }); + it('should accept empty name in query and path references', function () { + // name = *( CHAR ) allows zero characters, matching the OpenAPI spec + assert.isTrue(test('$request.query.')); + assert.isTrue(test('$request.path.')); + }); + it('should reject { and } in JSON pointer paths', function () { // { and } are excluded from json-pointer grammar to allow unambiguous // parsing of embedded expressions like {$request.body#/status}