From 9c4ed77f1f93510270fbbb07354e5dcbda247ec8 Mon Sep 17 00:00:00 2001 From: Osa Date: Thu, 25 Sep 2025 02:38:02 -0400 Subject: [PATCH 1/2] Removing string exception from number parser --- packages/core/src/compiler/parser.ts | 4 ---- packages/core/src/index.ts | 33 +++++++++++++++++++++++----- test.mirrow | 0 3 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 test.mirrow diff --git a/packages/core/src/compiler/parser.ts b/packages/core/src/compiler/parser.ts index 4d3cada..509cb7c 100644 --- a/packages/core/src/compiler/parser.ts +++ b/packages/core/src/compiler/parser.ts @@ -92,10 +92,6 @@ const ATTRIBUTE_VALIDATORS: { return; } - if (value.type === "StringLiteral") { - return; - } - failValidation(context, "expects a numeric value", value.position); }, string: (context, spec, value) => { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 05dfe6d..1f9cc8b 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -40,16 +40,39 @@ process.on("uncaughtException", (err) => { function runExample(): void { const code = ` - svg { +svg { + // Internal coordinate space: viewBox x y w h + box: (0, 0, 200, 200) + + // Rendered size on the page + size: (200px, 200px) + + // How to map box to size + preserve: (xMidYMid, meet) + circle { - r: 40 - fill: #fff + id: "pulse" + at: (100, 100) + r: "hello" + fill: "hotpink" - animate {} + animate { + prop: "r" + from: 40 + to: 60 + dur: 2s + repeat: indefinite + } } - + @hover, @active { + #pulse { + cy: 150px + r: 60px + } + } } + `; console.log(compile(code)); } diff --git a/test.mirrow b/test.mirrow new file mode 100644 index 0000000..e69de29 From d5e4e7d34dd5dfa7345d3805746b64366b2fb8ba Mon Sep 17 00:00:00 2001 From: Osa Date: Thu, 25 Sep 2025 03:10:29 -0400 Subject: [PATCH 2/2] Fixing major fall through issues, assigning fellThrough variable to class and attribute array as class constructor item --- packages/core/src/compiler/parser.ts | 237 ++++++++++++++------------- packages/core/src/index.ts | 6 +- 2 files changed, 125 insertions(+), 118 deletions(-) diff --git a/packages/core/src/compiler/parser.ts b/packages/core/src/compiler/parser.ts index 509cb7c..afd55f7 100644 --- a/packages/core/src/compiler/parser.ts +++ b/packages/core/src/compiler/parser.ts @@ -77,134 +77,139 @@ const failValidation = ( ); }; -const ATTRIBUTE_VALIDATORS: { - number: AttributeValidator; - string: AttributeValidator; - boolean: AttributeValidator; - tuple: AttributeValidator; -} = { - number: (context, spec, value) => { - if (value.type === "NumberLiteral") { - const numberValue = value as NumberLiteral; - if (spec.validator && !spec.validator(numberValue.value)) { - failValidation(context, "failed validation", value.position); - } - return; - } - - failValidation(context, "expects a numeric value", value.position); - }, - string: (context, spec, value) => { - if (value.type !== "StringLiteral") { - failValidation( - context, - `expects a string value got '${value.type}'`, - value.position - ); - } - const stringValue = value as StringLiteral; - if (spec.validator && !spec.validator(stringValue.value)) { - failValidation(context, "failed validation", value.position); - } - }, - boolean: (context, spec, value) => { - if (value.type !== "IdentifierLiteral") { - failValidation(context, "expects 'true' or 'false'", value.position); - } - const booleanValue = value as IdentifierLiteral; - const normalized = booleanValue.name.toLowerCase(); - if (normalized !== "true" && normalized !== "false") { - failValidation(context, "expects 'true' or 'false'", value.position); - } - if (spec.validator && !spec.validator(normalized === "true")) { - failValidation(context, "failed validation", value.position); - } - }, - tuple: (context, spec, value) => { - if (value.type !== "TupleLiteral") { - failValidation(context, "expects a tuple", value.position); - } - const tupleValue = value as TupleLiteral; - if (tupleValue.values.length !== spec.length) { - failValidation( - context, - `expects a tuple of length ${spec.length}`, - value.position - ); - } - const itemTypes = spec.itemTypes; - const collected: Array = []; - let shouldRunValidator = true; - - for (let index = 0; index < tupleValue.values.length; index++) { - const entry = tupleValue.values[index]!; - const expectedType = itemTypes?.[index] ?? itemTypes?.[0] ?? "number"; +class Parser { + private index = 0; + private tokens: Token[]; + private readonly keywordCatalog = getSvgKeywords(); + private newGenTokens: Token[] = []; + private fellThrough: boolean = false; + private ATTRIBUTE_VALIDATORS: { + number: AttributeValidator; + string: AttributeValidator; + boolean: AttributeValidator; + tuple: AttributeValidator; + } - if (expectedType === "number") { - if (entry.type === "NumberLiteral") { - collected.push((entry as NumberLiteral).value); - continue; + constructor(tokens: Token[]) { + this.tokens = tokens; + this.ATTRIBUTE_VALIDATORS = { + number: (context, spec, value) => { + if (value.type === "NumberLiteral") { + const numberValue = value as NumberLiteral; + if (spec.validator && !spec.validator(numberValue.value)) { + failValidation(context, "failed validation", value.position); + } + return; } - if (entry.type === "StringLiteral") { - collected.push((entry as StringLiteral).value); - shouldRunValidator = false; - continue; + if (value.type === "StringLiteral" && this.fellThrough) { + return; } - failValidation( - context, - "expects numeric or string tuple items", - entry.position - ); - continue; - } - - if (expectedType === "string") { - if (entry.type !== "StringLiteral") { - failValidation(context, "expects string tuple items", entry.position); + failValidation(context, "expects a numeric value", value.position); + }, + string: (context, spec, value) => { + if (value.type !== "StringLiteral") { + failValidation( + context, + `expects a string value got '${value.type}'`, + value.position + ); } - collected.push((entry as StringLiteral).value); - continue; - } - - if (expectedType === "identifier") { - if (entry.type !== "IdentifierLiteral") { + const stringValue = value as StringLiteral; + if (spec.validator && !spec.validator(stringValue.value)) { + failValidation(context, "failed validation", value.position); + } + }, + boolean: (context, spec, value) => { + if (value.type !== "IdentifierLiteral") { + failValidation(context, "expects 'true' or 'false'", value.position); + } + const booleanValue = value as IdentifierLiteral; + const normalized = booleanValue.name.toLowerCase(); + if (normalized !== "true" && normalized !== "false") { + failValidation(context, "expects 'true' or 'false'", value.position); + } + if (spec.validator && !spec.validator(normalized === "true")) { + failValidation(context, "failed validation", value.position); + } + }, + tuple: (context, spec, value) => { + if (value.type !== "TupleLiteral") { + failValidation(context, "expects a tuple", value.position); + } + const tupleValue = value as TupleLiteral; + if (tupleValue.values.length !== spec.length) { failValidation( context, - "expects identifier tuple items", - entry.position + `expects a tuple of length ${spec.length}`, + value.position ); } - collected.push((entry as IdentifierLiteral).name); - continue; - } + const itemTypes = spec.itemTypes; + const collected: Array = []; + let shouldRunValidator = true; + + for (let index = 0; index < tupleValue.values.length; index++) { + const entry = tupleValue.values[index]!; + const expectedType = itemTypes?.[index] ?? itemTypes?.[0] ?? "number"; + + if (expectedType === "number") { + if (entry.type === "NumberLiteral") { + collected.push((entry as NumberLiteral).value); + continue; + } + + if (entry.type === "StringLiteral") { + collected.push((entry as StringLiteral).value); + shouldRunValidator = false; + continue; + } + + failValidation( + context, + "expects numeric or string tuple items", + entry.position + ); + continue; + } - failValidation( - context, - "has unsupported tuple item type", - entry.position - ); - } + if (expectedType === "string") { + if (entry.type !== "StringLiteral") { + failValidation(context, "expects string tuple items", entry.position); + } + collected.push((entry as StringLiteral).value); + continue; + } - if ( - shouldRunValidator && - spec.validator && - !spec.validator(collected as never) - ) { - failValidation(context, "failed validation", value.position); - } - }, -}; + if (expectedType === "identifier") { + if (entry.type !== "IdentifierLiteral") { + failValidation( + context, + "expects identifier tuple items", + entry.position + ); + } + collected.push((entry as IdentifierLiteral).name); + continue; + } -class Parser { - private index = 0; - private tokens: Token[]; - private readonly keywordCatalog = getSvgKeywords(); - private newGenTokens: Token[] = []; + failValidation( + context, + "has unsupported tuple item type", + entry.position + ); + } - constructor(tokens: Token[]) { - this.tokens = tokens; + if ( + shouldRunValidator && + spec.validator && + !spec.validator(collected as never) + ) { + failValidation(context, "failed validation", value.position); + } + }, + }; } parse(): [RootNode, SpecialBlock[]] { @@ -346,6 +351,7 @@ class Parser { ); } this.validateAttributeValue(parentKeyword, nameToken.value, spec, value); + this.fellThrough = false; seen.add(nameToken.value); return { type: "Attribute", @@ -587,6 +593,7 @@ class Parser { private parseAttributeValue(): LiteralValue { const fall = this.fallThrough(); if (fall) { + this.fellThrough = true; return fall; } const token = this.peek(); @@ -705,7 +712,7 @@ class Parser { spec: T, value: LiteralValue ): void { - const handler = ATTRIBUTE_VALIDATORS[spec.type] as AttributeValidator; + const handler = this.ATTRIBUTE_VALIDATORS[spec.type] as AttributeValidator; handler({ keyword, attributeName }, spec, value); } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1f9cc8b..512238c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -53,13 +53,13 @@ svg { circle { id: "pulse" at: (100, 100) - r: "hello" + r: 40 fill: "hotpink" animate { prop: "r" - from: 40 - to: 60 + from: 40px + to: 60px dur: 2s repeat: indefinite }