@xaendar/compiler 0.0.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/package.json +19 -11
  2. package/xaendar-compiler.es.d.ts +199 -0
  3. package/xaendar-compiler.es.js +505 -0
  4. package/xaendar-compiler.es.js.map +1 -0
  5. package/src/costants/chars.constants.ts +0 -188
  6. package/src/costants/tags/base-tags.constants.ts +0 -106
  7. package/src/costants/tags/not-alllowed-tags.constants.ts +0 -13
  8. package/src/costants/tags/not-allowed-chars.constants.ts +0 -24
  9. package/src/lexer/lexer.ts +0 -91
  10. package/src/lexer/models/current-char.type.ts +0 -17
  11. package/src/lexer/models/current-position.type.ts +0 -13
  12. package/src/lexer/models/lexer-cursor.model.ts +0 -192
  13. package/src/lexer/models/lexer-state.enum.ts +0 -13
  14. package/src/lexer/models/token-type.enum.ts +0 -17
  15. package/src/lexer/models/token.type.ts +0 -62
  16. package/src/lexer/models/transition-function/transition-function-context.type.ts +0 -27
  17. package/src/lexer/models/transition-function/transition-function-return-type.type.ts +0 -48
  18. package/src/lexer/models/transition-function/transition-function.type.ts +0 -36
  19. package/src/lexer/states/attribute.state.ts +0 -47
  20. package/src/lexer/states/event.state.ts +0 -38
  21. package/src/lexer/states/interpolation-expression.state.ts +0 -68
  22. package/src/lexer/states/interpolation-literal.state.ts +0 -71
  23. package/src/lexer/states/interpolation.state.ts +0 -31
  24. package/src/lexer/states/tag-body.state.ts +0 -43
  25. package/src/lexer/states/tag-close.state.ts +0 -46
  26. package/src/lexer/states/tag-open-end.state.ts +0 -40
  27. package/src/lexer/states/tag-open-name.state.ts +0 -50
  28. package/src/lexer/states/text.state.ts +0 -61
  29. package/src/parser/models/ast.type.ts +0 -34
  30. package/src/parser/models/current-token.type.ts +0 -6
  31. package/src/parser/models/node.enum.ts +0 -5
  32. package/src/parser/models/parser-cursor.model.ts +0 -133
  33. package/src/parser/parser.ts +0 -225
  34. package/src/public-api.ts +0 -3
  35. package/src/render-generator/render-generator.model.ts +0 -73
  36. package/src/utils/chars.utils.ts +0 -70
  37. package/src/utils/tags.utils.ts +0 -36
  38. package/tsconfig.json +0 -3
  39. package/vite.config.ts +0 -4
@@ -1,34 +0,0 @@
1
- import { ASTNodeType } from './node.enum';
2
-
3
- export type ASTNode =
4
- | ElementNode
5
- | TextNode
6
- | InterpolationNode;
7
-
8
- export type ElementNode = {
9
- type: ASTNodeType.Element
10
- tagName: string;
11
- attributes: AttributeNode[];
12
- events: EventNode[];
13
- children: ASTNode[];
14
- }
15
-
16
- export type AttributeNode = {
17
- name: string;
18
- value: string | InterpolationNode;
19
- }
20
-
21
- export type EventNode = {
22
- name: string;
23
- handler: string | InterpolationNode;
24
- }
25
-
26
- export type TextNode = {
27
- type: ASTNodeType.Text
28
- value: string;
29
- }
30
-
31
- export type InterpolationNode = {
32
- type: ASTNodeType.Interpolation
33
- expression: string;
34
- }
@@ -1,6 +0,0 @@
1
- import { Token } from '../../lexer/models/token.type'
2
-
3
- export type CurrentToken = {
4
- value: Token
5
- index: number
6
- }
@@ -1,5 +0,0 @@
1
- export enum ASTNodeType {
2
- Element,
3
- Text,
4
- Interpolation
5
- }
@@ -1,133 +0,0 @@
1
- import { PositiveInteger, TupleOfLength } from '@xaendar/common';
2
- import { EOF } from '../../costants/chars.constants';
3
- import { TokenType } from '../../lexer/models/token-type.enum';
4
- import { Token } from '../../lexer/models/token.type';
5
- import { CurrentToken } from './current-token.type';
6
-
7
- /**
8
- * Cursor abstraction used by the Parser to navigate
9
- * through a sequence of tokens produced by the Lexer.
10
- *
11
- * Responsibilities:
12
- * - Sequential token consumption
13
- * - Lookahead (peek) operations without mutating state
14
- * - Handling end-of-file conditions
15
- *
16
- * This class does not perform parsing itself: it only
17
- * manages position and access to the token stream.
18
- */
19
- export class ParserCursor {
20
-
21
- /**
22
- * Representation of the current token.
23
- *
24
- * - `index`: absolute index within the token array
25
- * - `value`: current token object (or EOF token)
26
- *
27
- * An index of `-1` indicates that the cursor has not
28
- * yet consumed any token or has reached EOF.
29
- */
30
- private readonly _currentToken: CurrentToken = {
31
- value: { type: TokenType.EOF },
32
- index: -1
33
- };
34
-
35
- /**
36
- * Returns a read-only snapshot of the current token.
37
- */
38
- public get currentToken(): Readonly<CurrentToken> {
39
- return this._currentToken;
40
- }
41
-
42
- /**
43
- * Creates a new ParserCursor for the given token array.
44
- *
45
- * @param _tokens Array of tokens to navigate.
46
- */
47
- constructor(private readonly _tokens: Token[]) { }
48
-
49
- /**
50
- * Advances the cursor by the specified number of tokens.
51
- *
52
- * Updates the current token and index.
53
- *
54
- * @param chars Number of tokens to advance (must be >= 1)
55
- *
56
- * @throws Error with cause `EOF` when advancing past the end
57
- */
58
- public advance(chars = 1): void {
59
- if (chars < 1) {
60
- throw new Error(`${chars} is not a valid value. Please enter a number equal or greater than 1`);
61
- }
62
-
63
- const newIndex = this._currentToken.index + chars;
64
-
65
- if (newIndex >= this._tokens.length) {
66
- this._currentToken.value = { type: TokenType.EOF };
67
- this._currentToken.index = -1;
68
- this.throwEOFError();
69
- } else {
70
- this._currentToken.index = newIndex;
71
- this._currentToken.value = this._tokens[newIndex]!;
72
- }
73
- }
74
-
75
- /**
76
- * Peeks ahead in the token stream without advancing the cursor.
77
- *
78
- * Supports:
79
- * - Single-token lookahead
80
- * - Multi-token lookahead
81
- * - Optional offset from the current token
82
- *
83
- * @returns
84
- * - A single Token when peeking one token
85
- * - An array of Tokens when peeking multiple tokens
86
- *
87
- * @throws Error with cause `EOF` if the peek exceeds the token array
88
- */
89
- public peek(): Token;
90
- public peek<OffSet extends number>(options?: { offset?: PositiveInteger<OffSet> }): Token;
91
- public peek(chars: 1): Token;
92
- public peek<OffSet extends number>(chars: 1, options?: { offset?: PositiveInteger<OffSet> }): Token;
93
- public peek<ReadChars extends number>(chars: PositiveInteger<ReadChars>): TupleOfLength<ReadChars, Token>;
94
- public peek<ReadChars extends number, OffSet extends number>(chars: PositiveInteger<ReadChars>, options?: { offset?: PositiveInteger<OffSet> }): TupleOfLength<ReadChars, Token>;
95
- public peek(charsOrOptions?: number | { offset?: number }, options?: { offset?: number }): Token | Token[] {
96
- const tokens = typeof charsOrOptions === 'number' ? charsOrOptions : 1;
97
- const offset = (typeof charsOrOptions === 'object' ? charsOrOptions : options)?.offset ?? 0;
98
- return tokens === 1 ? this.peekOneToken(this._currentToken.index + offset + 1) : this.peekMany(tokens + offset);
99
- }
100
-
101
- /**
102
- * Peeks multiple tokens ahead.
103
- */
104
- private peekMany(chars: number): Token[] {
105
- const peekedTokens: Token[] = [];
106
- const nextTokenIndex = this._currentToken.index + 1;
107
-
108
- for (let i = nextTokenIndex; i < nextTokenIndex + chars; i++) {
109
- peekedTokens.push(this.peekOneToken(i));
110
- }
111
-
112
- return peekedTokens;
113
- }
114
-
115
- /**
116
- * Peeks a single token at the given absolute index.
117
- */
118
- private peekOneToken(index: number): Token {
119
- if (index >= this._tokens.length) {
120
- this.throwEOFError();
121
- }
122
-
123
- return this._tokens[index]!;
124
- }
125
-
126
- /**
127
- * Throws a standardized EOF error used by the parser
128
- * to terminate token consumption.
129
- */
130
- private throwEOFError(): never {
131
- throw new Error('', { cause: EOF });
132
- }
133
- }
@@ -1,225 +0,0 @@
1
- import { EOF } from '../costants/chars.constants';
2
- import { TokenType } from '../lexer/models/token-type.enum';
3
- import { AttributeToken, EventToken, InterpolationExpressionToken, InterpolationLiteralToken, TagOpenNameToken, TextToken, Token } from '../lexer/models/token.type';
4
- import { ASTNode, AttributeNode, ElementNode, EventNode, InterpolationNode, TextNode } from './models/ast.type';
5
- import { ASTNodeType } from './models/node.enum';
6
- import { ParserCursor } from './models/parser-cursor.model';
7
-
8
- /**
9
- * Parser class that transforms a stream of tokens (from the Lexer)
10
- * into an Abstract Syntax Tree (AST) representing the template structure.
11
- *
12
- * Responsibilities:
13
- * - Parse text nodes, elements, attributes, events, and interpolations
14
- * - Maintain cursor state for sequential token consumption
15
- * - Detect tag boundaries and nested structures
16
- *
17
- * The parser assumes that the token stream is syntactically valid according
18
- * to the Lexer rules. Parsing errors are thrown as exceptions.
19
- */
20
- export class Parser {
21
-
22
- /** Internal cursor for navigating tokens */
23
- private readonly _cursor: ParserCursor;
24
-
25
- /**
26
- * Creates a new Parser instance.
27
- *
28
- * @param tokens Array of tokens produced by the Lexer
29
- */
30
- constructor(private readonly tokens: Token[]) {
31
- this._cursor = new ParserCursor(this.tokens);
32
- }
33
-
34
- /**
35
- * Entry point for parsing the token stream into AST nodes.
36
- *
37
- * @returns Array of top-level AST nodes
38
- */
39
- public parse(): ASTNode[] {
40
- let eof = false;
41
- const nodes: ASTNode[] = [];
42
-
43
- while (!eof) {
44
- try {
45
- nodes.push(this.parseNode());
46
- } catch (err) {
47
- const error = err as Error;
48
- if (error.cause === EOF) {
49
- eof = true;
50
- } else {
51
- throw err;
52
- }
53
- }
54
- }
55
-
56
- return nodes;
57
- }
58
-
59
- /**
60
- * Parses the next AST node based on the current token.
61
- *
62
- * @returns Parsed AST node
63
- * @throws Error if an unexpected token is encountered
64
- */
65
- private parseNode(): ASTNode {
66
- const token = this._cursor.peek();
67
-
68
- switch (token.type) {
69
- case TokenType.TEXT:
70
- return this.parseText(token);
71
-
72
- case TokenType.INTERPOLATION_EXPRESSION:
73
- case TokenType.INTERPOLATION_LITERAL:
74
- return this.parseInterpolation(token);
75
-
76
- case TokenType.TAG_OPEN_NAME:
77
- return this.parseElement(token);
78
-
79
- default:
80
- throw this.error(`Unexpected token ${TokenType[token.type]}`);
81
- }
82
- }
83
-
84
- /**
85
- * Parses a text token into a TextNode.
86
- */
87
- private parseText(token: TextToken): TextNode {
88
- this._cursor.advance();
89
- return {
90
- type: ASTNodeType.Text,
91
- value: token.parts[0]
92
- };
93
- }
94
-
95
- /**
96
- * Parses an interpolation token into an InterpolationNode.
97
- */
98
- private parseInterpolation(token: InterpolationExpressionToken | InterpolationLiteralToken): InterpolationNode {
99
- this._cursor.advance();
100
- return {
101
- type: ASTNodeType.Interpolation,
102
- expression: token.parts[0]
103
- };
104
- }
105
-
106
- /**
107
- * Parses an element starting from a TAG_OPEN_NAME token.
108
- * Handles attributes, events, children, and tag closure.
109
- */
110
- private parseElement(token: TagOpenNameToken): ElementNode {
111
- this._cursor.advance();
112
- const tagName = token.parts[0];
113
-
114
- const attributes: AttributeNode[] = [];
115
- const events: EventNode[] = [];
116
-
117
- let read = true;
118
- while (read) {
119
- const token = this._cursor.peek();
120
- switch (token.type) {
121
- case TokenType.ATTRIBUTE:
122
- attributes.push(this.parseAttribute(token));
123
- break;
124
-
125
- case TokenType.EVENT:
126
- events.push(this.parseEvent(token));
127
- break;
128
-
129
- default:
130
- read = false;
131
- }
132
- }
133
-
134
- // Consume TAG_OPEN_END if present: <div>
135
- if (this._cursor.peek().type === TokenType.TAG_OPEN_END) {
136
- this._cursor.advance();
137
- }
138
-
139
- // Handle self-closing tags: <div />
140
- if (this._cursor.peek().type === TokenType.TAG_SELF_CLOSE) {
141
- this._cursor.advance();
142
- return {
143
- type: ASTNodeType.Element,
144
- tagName,
145
- attributes,
146
- events,
147
- children: []
148
- };
149
- }
150
-
151
- // Parse children recursively until closing tag
152
- const children: ASTNode[] = [];
153
- while (!this.isTagClose(tagName)) {
154
- children.push(this.parseNode());
155
- }
156
-
157
- // Consume closing tag </div>
158
- this._cursor.advance();
159
-
160
- return {
161
- type: ASTNodeType.Element,
162
- tagName,
163
- attributes,
164
- events,
165
- children
166
- };
167
- }
168
-
169
- /**
170
- * Parses an attribute token into an AttributeNode.
171
- * Supports literal values and interpolations as attribute values.
172
- */
173
- private parseAttribute(token: AttributeToken): AttributeNode {
174
- this._cursor.advance();
175
- const raw = token.parts[0];
176
-
177
- if (!raw.includes('=')) {
178
- return { name: raw, value: 'true' };
179
- }
180
-
181
- const [name, value] = raw.split('=');
182
-
183
- const nextToken = this._cursor.peek();
184
- if (nextToken.type === TokenType.INTERPOLATION_EXPRESSION || nextToken.type === TokenType.INTERPOLATION_LITERAL) {
185
- return {
186
- name: name!,
187
- value: this.parseInterpolation(nextToken)
188
- };
189
- }
190
-
191
- return {
192
- name: name!,
193
- value: value!.replace(/^['']|['']$/g, '')
194
- };
195
- }
196
-
197
- /**
198
- * Parses an event token into an EventNode.
199
- */
200
- private parseEvent(token: EventToken): EventNode {
201
- this._cursor.advance();
202
- const raw = token.parts[0];
203
- const [name, value] = raw.split('=');
204
-
205
- return {
206
- name: name!,
207
- handler: value!.replace(/^['']|['']$/g, '')
208
- };
209
- }
210
-
211
- /**
212
- * Checks whether the next token is a closing tag matching the given name.
213
- */
214
- private isTagClose(tagName: string): boolean {
215
- const nextToken = this._cursor.peek();
216
- return nextToken.type === TokenType.TAG_CLOSE_NAME && nextToken.parts[0] === tagName;
217
- }
218
-
219
- /**
220
- * Creates a parser error with a consistent prefix.
221
- */
222
- private error(message: string): Error {
223
- return new Error(`[Parser] ${message}`);
224
- }
225
- }
package/src/public-api.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from './lexer/lexer';
2
- export * from './parser/parser';
3
- export * from './render-generator/render-generator.model';
@@ -1,73 +0,0 @@
1
- import { ASTNode, TextNode, InterpolationNode, ElementNode, AttributeNode, EventNode } from '../parser/models/ast.type';
2
- import { ASTNodeType } from '../parser/models/node.enum';
3
-
4
- /**
5
- * Genera il codice TypeScript della funzione render
6
- * @param ast L'albero AST del template
7
- * @param componentVar Nome della variabile 'this' del componente (di solito `this`)
8
- * @returns Stringa contenente il corpo della funzione render
9
- */
10
- export function generateRenderFunction(ast: ASTNode[], componentVar = 'this'): string {
11
- const lines: string[] = [];
12
-
13
- // Apertura funzione
14
- lines.push(`const shadow = ${componentVar}.shadowRoot!;`);
15
-
16
- ast.forEach((node, i) => {
17
- lines.push(...processNode(node, `node${i}`, componentVar));
18
- lines.push(`shadow.appendChild(node${i});`);
19
- });
20
-
21
- return lines.join('\n');
22
- }
23
-
24
- /**
25
- * Genera il codice per creare un singolo nodo
26
- */
27
- function processNode(node: ASTNode, varName: string, componentVar: string): string[] {
28
- const code = new Array<string>;
29
-
30
- switch (node.type) {
31
- case ASTNodeType.Text:
32
- code.push(`const ${varName} = document.createTextNode(${JSON.stringify((node as TextNode).value)});`);
33
- break;
34
-
35
- case ASTNodeType.Interpolation:
36
- code.push(`const ${varName} = document.createTextNode(${componentVar}.${(node as InterpolationNode).expression});`);
37
- break;
38
-
39
- case ASTNodeType.Element:
40
- const elNode = node as ElementNode;
41
- code.push(`const ${varName} = document.createElement(${JSON.stringify(elNode.tagName)});`);
42
-
43
- // Attributi
44
- (elNode.attributes || []).forEach((attr: AttributeNode) => {
45
- if (typeof attr.value === 'string') {
46
- code.push(`${varName}.setAttribute(${JSON.stringify(attr.name)}, ${JSON.stringify(attr.value)});`);
47
- } else {
48
- // Interpolazione come value
49
- const interp = attr.value as InterpolationNode;
50
- code.push(`${varName}.setAttribute(${JSON.stringify(attr.name)}, ${componentVar}.${interp.expression});`);
51
- }
52
- });
53
-
54
- // Eventi
55
- (elNode.events || []).forEach((event: EventNode) => {
56
- if (event.name.startsWith('@')) {
57
- event.name = event.name.slice(1);
58
- }
59
- code.push(`${varName}.addEventListener(${JSON.stringify(event.name)}, ${componentVar}.${event.handler}.bind(${componentVar}));`);
60
- });
61
-
62
- // Children
63
- (elNode.children || []).forEach((child, idx) => {
64
- const childVar = `${varName}_child${idx}`;
65
- code.push(...processNode(child, childVar, componentVar));
66
- code.push(`${varName}.appendChild(${childVar});`);
67
- });
68
-
69
- break;
70
- }
71
-
72
- return code;
73
- }
@@ -1,70 +0,0 @@
1
- import { A, a, CR, LF, Z, z } from '../costants/chars.constants';
2
-
3
- /**
4
- * Check if char code is a Line Feed (\n) or Carriage Return (\r)
5
- * @param char Character to control
6
- * @returns True if character is LF or CR, false otherwise
7
- */
8
- export function isNewLine(char: number): boolean {
9
- return char === LF || char === CR;
10
- }
11
-
12
- /**
13
- * Check if char code is is a lower case letter
14
- * @param char Character to control
15
- * @returns True if character is a lowercase letter, false otherwise
16
- */
17
- export function isLowerCase(char: number): boolean {
18
- return char >= a && char <= z;
19
- }
20
-
21
- /**
22
- * Check if char code is is a upper case letter
23
- * @param char Character to control
24
- * @returns True if character is a upper case letter, false otherwise
25
- */
26
- export function isUpperCase(char: number): boolean {
27
- return char >= A && char <= Z;
28
- }
29
-
30
- /**
31
- * Check if the string contains at least one character different from
32
- * ' '
33
- * \n
34
- * \r
35
- * \t
36
- * \f
37
- * \v
38
- * @param str String to check
39
- * @returns True if string is not blank, false otherwise
40
- */
41
- export function isNotBlank(str: string): boolean {
42
- /*
43
- Differently from the approach of the other functions
44
- here we are working with string and not numbers.
45
-
46
- Number checks are usually faster when checking a character is
47
- included in a specific range.
48
- For this case we are checking if the string contains at least one char
49
- different from a list of non adiacent characters in the ASCII code, resulting
50
- in a very long condition with multiple OR
51
-
52
- This has been proven slower than using a regex
53
- */
54
- return /\S/.test(str)
55
- }
56
-
57
- /**
58
- * Check if given ascii code is a valid First Character
59
- * for Javasript Identifiers
60
- * @param code The ascii code to valuate
61
- * @returns True if is valid, false otherwise
62
- */
63
- export function isJSIdentifierStart(code: number): boolean {
64
- return (
65
- (code >= 65 && code <= 90) || // A-Z
66
- (code >= 97 && code <= 122) || // a-z
67
- code === 36 || // $
68
- code === 95 // _
69
- );
70
- }
@@ -1,36 +0,0 @@
1
- import { HTML_TAGS } from '../costants/tags/base-tags.constants';
2
- import { NOT_ALLOWED_TAGS } from '../costants/tags/not-alllowed-tags.constants';
3
- import { NOT_ALLOWED_CHARS_FOR_TAGS } from '../costants/tags/not-allowed-chars.constants';
4
-
5
- /**
6
- * Checks if a character is allowed in a custom element tag name.
7
- *
8
- * @param char - The character to check.
9
- * @returns `true` if the character is allowed, `false` if it is forbidden.
10
- */
11
- export function isAllowedCharForTag(char: string): boolean {
12
- return !NOT_ALLOWED_CHARS_FOR_TAGS.includes(char);
13
- }
14
-
15
- /**
16
- * Checks if a tag name is a native HTML tag.
17
- *
18
- * @param tagName - The tag name to check.
19
- * @returns `true` if the tag name is a standard HTML tag, `false` otherwise.
20
- */
21
- export function isNativeHTMLTag(tagName: string): boolean {
22
- return HTML_TAGS.includes(tagName);
23
- }
24
-
25
- /**
26
- * Checks if a tag name is reserved and cannot be used for a custom element.
27
- *
28
- * Reserved names include certain HTML, SVG, or other names that are forbidden by the specification.
29
- * https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
30
- *
31
- * @param tagName - The tag name to check.
32
- * @returns `true` if the tag name is reserved, `false` otherwise.
33
- */
34
- export function isReservedTagName(tagName: string): boolean {
35
- return NOT_ALLOWED_TAGS.includes(tagName);
36
- }
package/tsconfig.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- }
package/vite.config.ts DELETED
@@ -1,4 +0,0 @@
1
- import { defineConfig } from 'vite';
2
- import getViteConfig from '../../vite-config';
3
-
4
- export default defineConfig(getViteConfig('@xaendar/compiler', __dirname));