@prisma-next/psl-parser 0.12.0 → 0.13.0-dev.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"syntax.mjs","names":["#lastSegment","#penultimateSegment","#stack"],"sources":["../src/syntax/red.ts","../src/syntax/ast-helpers.ts","../src/syntax/ast/identifier.ts","../src/syntax/ast/expressions.ts","../src/syntax/ast/attributes.ts","../src/syntax/ast/type-annotation.ts","../src/syntax/ast/declarations.ts","../src/syntax/green.ts","../src/syntax/green-builder.ts"],"sourcesContent":["import type { Token } from '../tokenizer';\nimport type { GreenElement, GreenNode } from './green';\nimport type { SyntaxKind } from './syntax-kind';\n\n/**\n * A token in the red tree. Unlike the green-layer {@link Token} (kind + text\n * only), a red token also carries its absolute `offset` within the source,\n * computed lazily as the tree is walked.\n */\nexport interface SyntaxToken extends Token {\n readonly offset: number;\n}\n\nexport type SyntaxElement = SyntaxNode | SyntaxToken;\n\nexport class SyntaxNode {\n readonly green: GreenNode;\n readonly offset: number;\n readonly parent: SyntaxNode | undefined;\n\n constructor(green: GreenNode, offset: number, parent: SyntaxNode | undefined) {\n this.green = green;\n this.offset = offset;\n this.parent = parent;\n }\n\n get kind(): SyntaxKind {\n return this.green.kind;\n }\n\n get textLength(): number {\n return this.green.textLength;\n }\n\n get firstChild(): SyntaxElement | undefined {\n return childAt(this, 0);\n }\n\n get lastChild(): SyntaxElement | undefined {\n const len = this.green.children.length;\n if (len === 0) return undefined;\n return childAt(this, len - 1);\n }\n\n get nextSibling(): SyntaxElement | undefined {\n if (!this.parent) return undefined;\n const siblings = this.parent.green.children;\n let offset = this.parent.offset;\n let found = false;\n for (const child of siblings) {\n if (found) {\n return wrapElement(child, offset, this.parent);\n }\n const childLen = elementTextLength(child);\n if (child.type === 'node' && offset === this.offset && child === this.green) {\n found = true;\n }\n offset += childLen;\n }\n return undefined;\n }\n\n get prevSibling(): SyntaxElement | undefined {\n if (!this.parent) return undefined;\n const siblings = this.parent.green.children;\n let offset = this.parent.offset;\n let prev: { green: GreenElement; offset: number } | undefined;\n for (const child of siblings) {\n if (child.type === 'node' && offset === this.offset && child === this.green) {\n if (!prev) return undefined;\n return wrapElement(prev.green, prev.offset, this.parent);\n }\n prev = { green: child, offset };\n offset += elementTextLength(child);\n }\n return undefined;\n }\n\n *children(): Iterable<SyntaxElement> {\n let offset = this.offset;\n for (const child of this.green.children) {\n yield wrapElement(child, offset, this);\n offset += elementTextLength(child);\n }\n }\n\n *childNodes(): Iterable<SyntaxNode> {\n for (const child of this.children()) {\n if (child instanceof SyntaxNode) yield child;\n }\n }\n\n *ancestors(): Iterable<SyntaxNode> {\n let current: SyntaxNode | undefined = this.parent;\n while (current) {\n yield current;\n current = current.parent;\n }\n }\n\n *descendants(): Iterable<SyntaxElement> {\n const stack: SyntaxElement[] = [this];\n for (let el = stack.pop(); el !== undefined; el = stack.pop()) {\n yield el;\n if (el instanceof SyntaxNode) {\n const children = Array.from(el.children());\n for (let i = children.length - 1; i >= 0; i--) {\n const child = children[i];\n if (child !== undefined) {\n stack.push(child);\n }\n }\n }\n }\n }\n\n *tokens(): Iterable<SyntaxToken> {\n for (const el of this.descendants()) {\n if (!(el instanceof SyntaxNode)) {\n yield el;\n }\n }\n }\n}\n\nfunction elementTextLength(el: GreenElement): number {\n return el.type === 'token' ? el.text.length : el.textLength;\n}\n\nfunction wrapElement(green: GreenElement, offset: number, parent: SyntaxNode): SyntaxElement {\n if (green.type === 'token') {\n const token: SyntaxToken = { kind: green.kind, text: green.text, offset };\n return token;\n }\n return new SyntaxNode(green, offset, parent);\n}\n\nfunction childAt(node: SyntaxNode, index: number): SyntaxElement | undefined {\n const children = node.green.children;\n const target = children[index];\n if (target === undefined) return undefined;\n let offset = node.offset;\n for (let i = 0; i < index; i++) {\n const child = children[i];\n if (child !== undefined) {\n offset += elementTextLength(child);\n }\n }\n return wrapElement(target, offset, node);\n}\n\nexport function createSyntaxTree(green: GreenNode): SyntaxNode {\n return new SyntaxNode(green, 0, undefined);\n}\n","import type { Token, TokenKind } from '../tokenizer';\nimport { SyntaxNode } from './red';\n\nexport interface AstNode {\n readonly syntax: SyntaxNode;\n}\n\nexport function findChildToken(node: SyntaxNode, kind: TokenKind): Token | undefined {\n for (const child of node.children()) {\n if (!(child instanceof SyntaxNode) && child.kind === kind) {\n return child;\n }\n }\n return undefined;\n}\n\nexport function findFirstChild<T>(\n node: SyntaxNode,\n cast: (node: SyntaxNode) => T | undefined,\n): T | undefined {\n for (const child of node.childNodes()) {\n const result = cast(child);\n if (result !== undefined) return result;\n }\n return undefined;\n}\n\nexport function* filterChildren<T>(\n node: SyntaxNode,\n cast: (node: SyntaxNode) => T | undefined,\n): Iterable<T> {\n for (const child of node.childNodes()) {\n const result = cast(child);\n if (result !== undefined) yield result;\n }\n}\n","import type { Token } from '../../tokenizer';\nimport type { AstNode } from '../ast-helpers';\nimport { findChildToken } from '../ast-helpers';\nimport type { SyntaxNode } from '../red';\n\nexport class IdentifierAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n token(): Token | undefined {\n return findChildToken(this.syntax, 'Ident');\n }\n\n static cast(node: SyntaxNode): IdentifierAst | undefined {\n return node.kind === 'Identifier' ? new IdentifierAst(node) : undefined;\n }\n}\n","import type { Token } from '../../tokenizer';\nimport type { AstNode } from '../ast-helpers';\nimport { filterChildren, findChildToken, findFirstChild } from '../ast-helpers';\nimport { SyntaxNode } from '../red';\nimport { IdentifierAst } from './identifier';\n\nexport class FunctionCallAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n name(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n lparen(): Token | undefined {\n return findChildToken(this.syntax, 'LParen');\n }\n\n rparen(): Token | undefined {\n return findChildToken(this.syntax, 'RParen');\n }\n\n *args(): Iterable<AttributeArgAst> {\n yield* filterChildren(this.syntax, AttributeArgAst.cast);\n }\n\n static cast(node: SyntaxNode): FunctionCallAst | undefined {\n return node.kind === 'FunctionCall' ? new FunctionCallAst(node) : undefined;\n }\n}\n\nexport class ArrayLiteralAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n lbracket(): Token | undefined {\n return findChildToken(this.syntax, 'LBracket');\n }\n\n rbracket(): Token | undefined {\n return findChildToken(this.syntax, 'RBracket');\n }\n\n *elements(): Iterable<ExpressionAst> {\n yield* filterChildren(this.syntax, castExpression);\n }\n\n static cast(node: SyntaxNode): ArrayLiteralAst | undefined {\n return node.kind === 'ArrayLiteral' ? new ArrayLiteralAst(node) : undefined;\n }\n}\n\nexport class StringLiteralExprAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n token(): Token | undefined {\n return findChildToken(this.syntax, 'StringLiteral');\n }\n\n value(): string | undefined {\n const tok = this.token();\n if (!tok) return undefined;\n const raw = tok.text.slice(1, -1);\n return raw.replace(/\\\\(.)/g, (_match, char: string) => {\n switch (char) {\n case 'n':\n return '\\n';\n case 'r':\n return '\\r';\n case 't':\n return '\\t';\n case '\"':\n return '\"';\n case '\\\\':\n return '\\\\';\n default:\n return `\\\\${char}`;\n }\n });\n }\n\n static cast(node: SyntaxNode): StringLiteralExprAst | undefined {\n return node.kind === 'StringLiteralExpr' ? new StringLiteralExprAst(node) : undefined;\n }\n}\n\nexport class NumberLiteralExprAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n token(): Token | undefined {\n return findChildToken(this.syntax, 'NumberLiteral');\n }\n\n value(): number | undefined {\n const tok = this.token();\n if (!tok) return undefined;\n return Number(tok.text);\n }\n\n static cast(node: SyntaxNode): NumberLiteralExprAst | undefined {\n return node.kind === 'NumberLiteralExpr' ? new NumberLiteralExprAst(node) : undefined;\n }\n}\n\nexport class BooleanLiteralExprAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n token(): Token | undefined {\n return findChildToken(this.syntax, 'Ident');\n }\n\n value(): boolean | undefined {\n const tok = this.token();\n if (!tok) return undefined;\n if (tok.text === 'true') return true;\n if (tok.text === 'false') return false;\n return undefined;\n }\n\n static cast(node: SyntaxNode): BooleanLiteralExprAst | undefined {\n return node.kind === 'BooleanLiteralExpr' ? new BooleanLiteralExprAst(node) : undefined;\n }\n}\n\nexport type ExpressionAst =\n | FunctionCallAst\n | ArrayLiteralAst\n | StringLiteralExprAst\n | NumberLiteralExprAst\n | BooleanLiteralExprAst\n | IdentifierAst;\n\nexport function castExpression(node: SyntaxNode): ExpressionAst | undefined {\n return (\n FunctionCallAst.cast(node) ??\n ArrayLiteralAst.cast(node) ??\n StringLiteralExprAst.cast(node) ??\n NumberLiteralExprAst.cast(node) ??\n BooleanLiteralExprAst.cast(node) ??\n IdentifierAst.cast(node)\n );\n}\n\nexport class AttributeArgAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n name(): IdentifierAst | undefined {\n if (!this.colon()) return undefined;\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n colon(): Token | undefined {\n return findChildToken(this.syntax, 'Colon');\n }\n\n value(): ExpressionAst | undefined {\n if (this.colon()) {\n let pastColon = false;\n for (const child of this.syntax.children()) {\n if (!(child instanceof SyntaxNode)) {\n if (child.kind === 'Colon') pastColon = true;\n continue;\n }\n if (pastColon) {\n const expr = castExpression(child);\n if (expr) return expr;\n }\n }\n return undefined;\n }\n return findFirstChild(this.syntax, castExpression);\n }\n\n static cast(node: SyntaxNode): AttributeArgAst | undefined {\n return node.kind === 'AttributeArg' ? new AttributeArgAst(node) : undefined;\n }\n}\n","import type { Token } from '../../tokenizer';\nimport type { AstNode } from '../ast-helpers';\nimport { filterChildren, findChildToken, findFirstChild } from '../ast-helpers';\nimport type { SyntaxNode } from '../red';\nimport { AttributeArgAst } from './expressions';\nimport { IdentifierAst } from './identifier';\n\nexport class AttributeArgListAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n lparen(): Token | undefined {\n return findChildToken(this.syntax, 'LParen');\n }\n\n rparen(): Token | undefined {\n return findChildToken(this.syntax, 'RParen');\n }\n\n *args(): Iterable<AttributeArgAst> {\n yield* filterChildren(this.syntax, AttributeArgAst.cast);\n }\n\n static cast(node: SyntaxNode): AttributeArgListAst | undefined {\n return node.kind === 'AttributeArgList' ? new AttributeArgListAst(node) : undefined;\n }\n}\n\nexport class FieldAttributeAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n at(): Token | undefined {\n return findChildToken(this.syntax, 'At');\n }\n\n name(): IdentifierAst | undefined {\n if (this.dot()) {\n let count = 0;\n for (const child of this.syntax.childNodes()) {\n if (child.kind === 'Identifier') {\n count++;\n if (count === 2) return new IdentifierAst(child);\n }\n }\n return undefined;\n }\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n dot(): Token | undefined {\n return findChildToken(this.syntax, 'Dot');\n }\n\n namespaceName(): IdentifierAst | undefined {\n if (!this.dot()) return undefined;\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n argList(): AttributeArgListAst | undefined {\n return findFirstChild(this.syntax, AttributeArgListAst.cast);\n }\n\n static cast(node: SyntaxNode): FieldAttributeAst | undefined {\n return node.kind === 'FieldAttribute' ? new FieldAttributeAst(node) : undefined;\n }\n}\n\nexport class ModelAttributeAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n doubleAt(): Token | undefined {\n return findChildToken(this.syntax, 'DoubleAt');\n }\n\n name(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n argList(): AttributeArgListAst | undefined {\n return findFirstChild(this.syntax, AttributeArgListAst.cast);\n }\n\n static cast(node: SyntaxNode): ModelAttributeAst | undefined {\n return node.kind === 'ModelAttribute' ? new ModelAttributeAst(node) : undefined;\n }\n}\n","import type { Token } from '../../tokenizer';\nimport type { AstNode } from '../ast-helpers';\nimport { filterChildren, findChildToken, findFirstChild } from '../ast-helpers';\nimport type { SyntaxNode } from '../red';\nimport { FunctionCallAst } from './expressions';\nimport { IdentifierAst } from './identifier';\n\nexport class TypeAnnotationAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n #lastSegment(): IdentifierAst | undefined {\n let last: IdentifierAst | undefined;\n for (const segment of filterChildren(this.syntax, IdentifierAst.cast)) {\n last = segment;\n }\n return last;\n }\n\n #penultimateSegment(): IdentifierAst | undefined {\n let last: IdentifierAst | undefined;\n let penultimate: IdentifierAst | undefined;\n for (const segment of filterChildren(this.syntax, IdentifierAst.cast)) {\n penultimate = last;\n last = segment;\n }\n return penultimate;\n }\n\n name(): IdentifierAst | undefined {\n return this.#lastSegment();\n }\n\n colon(): Token | undefined {\n return findChildToken(this.syntax, 'Colon');\n }\n\n dot(): Token | undefined {\n return findChildToken(this.syntax, 'Dot');\n }\n\n spaceName(): IdentifierAst | undefined {\n if (!this.colon()) return undefined;\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n namespaceName(): IdentifierAst | undefined {\n if (!this.dot()) return undefined;\n return this.#penultimateSegment();\n }\n\n constructorCall(): FunctionCallAst | undefined {\n return findFirstChild(this.syntax, FunctionCallAst.cast);\n }\n\n lbracket(): Token | undefined {\n return findChildToken(this.syntax, 'LBracket');\n }\n\n rbracket(): Token | undefined {\n return findChildToken(this.syntax, 'RBracket');\n }\n\n questionMark(): Token | undefined {\n return findChildToken(this.syntax, 'Question');\n }\n\n isList(): boolean {\n return this.lbracket() !== undefined;\n }\n\n isOptional(): boolean {\n return this.questionMark() !== undefined;\n }\n\n static cast(node: SyntaxNode): TypeAnnotationAst | undefined {\n return node.kind === 'TypeAnnotation' ? new TypeAnnotationAst(node) : undefined;\n }\n}\n","import type { Token } from '../../tokenizer';\nimport type { AstNode } from '../ast-helpers';\nimport { filterChildren, findChildToken, findFirstChild } from '../ast-helpers';\nimport { SyntaxNode } from '../red';\nimport { FieldAttributeAst, ModelAttributeAst } from './attributes';\nimport type { ExpressionAst } from './expressions';\nimport { castExpression } from './expressions';\nimport { IdentifierAst } from './identifier';\nimport { TypeAnnotationAst } from './type-annotation';\n\n/**\n * What may appear inside a `namespace` block: models, enums, composite types,\n * and extension (block) declarations. Mirrors what `parsePslDocument` accepts\n * in a namespace body. `types {}` blocks and nested `namespace` blocks are\n * document-only, so they are not namespace members.\n */\nexport type NamespaceMemberAst =\n | ModelDeclarationAst\n | EnumDeclarationAst\n | CompositeTypeDeclarationAst\n | BlockDeclarationAst;\n\nfunction castNamespaceMember(node: SyntaxNode): NamespaceMemberAst | undefined {\n return (\n ModelDeclarationAst.cast(node) ??\n EnumDeclarationAst.cast(node) ??\n CompositeTypeDeclarationAst.cast(node) ??\n BlockDeclarationAst.cast(node)\n );\n}\n\nexport class DocumentAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n *declarations(): Iterable<NamespaceMemberAst | TypesBlockAst | NamespaceDeclarationAst> {\n yield* filterChildren(\n this.syntax,\n (node) =>\n castNamespaceMember(node) ?? TypesBlockAst.cast(node) ?? NamespaceDeclarationAst.cast(node),\n );\n }\n\n static cast(node: SyntaxNode): DocumentAst | undefined {\n return node.kind === 'Document' ? new DocumentAst(node) : undefined;\n }\n}\n\nexport class ModelDeclarationAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n keyword(): Token | undefined {\n return findChildToken(this.syntax, 'Ident');\n }\n\n name(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n lbrace(): Token | undefined {\n return findChildToken(this.syntax, 'LBrace');\n }\n\n rbrace(): Token | undefined {\n return findChildToken(this.syntax, 'RBrace');\n }\n\n *fields(): Iterable<FieldDeclarationAst> {\n yield* filterChildren(this.syntax, FieldDeclarationAst.cast);\n }\n\n *attributes(): Iterable<ModelAttributeAst> {\n yield* filterChildren(this.syntax, ModelAttributeAst.cast);\n }\n\n static cast(node: SyntaxNode): ModelDeclarationAst | undefined {\n return node.kind === 'ModelDeclaration' ? new ModelDeclarationAst(node) : undefined;\n }\n}\n\nexport class EnumDeclarationAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n keyword(): Token | undefined {\n return findChildToken(this.syntax, 'Ident');\n }\n\n name(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n lbrace(): Token | undefined {\n return findChildToken(this.syntax, 'LBrace');\n }\n\n rbrace(): Token | undefined {\n return findChildToken(this.syntax, 'RBrace');\n }\n\n *values(): Iterable<EnumValueDeclarationAst> {\n yield* filterChildren(this.syntax, EnumValueDeclarationAst.cast);\n }\n\n *attributes(): Iterable<ModelAttributeAst> {\n yield* filterChildren(this.syntax, ModelAttributeAst.cast);\n }\n\n static cast(node: SyntaxNode): EnumDeclarationAst | undefined {\n return node.kind === 'EnumDeclaration' ? new EnumDeclarationAst(node) : undefined;\n }\n}\n\nexport class CompositeTypeDeclarationAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n keyword(): Token | undefined {\n return findChildToken(this.syntax, 'Ident');\n }\n\n name(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n lbrace(): Token | undefined {\n return findChildToken(this.syntax, 'LBrace');\n }\n\n rbrace(): Token | undefined {\n return findChildToken(this.syntax, 'RBrace');\n }\n\n *fields(): Iterable<FieldDeclarationAst> {\n yield* filterChildren(this.syntax, FieldDeclarationAst.cast);\n }\n\n *attributes(): Iterable<ModelAttributeAst> {\n yield* filterChildren(this.syntax, ModelAttributeAst.cast);\n }\n\n static cast(node: SyntaxNode): CompositeTypeDeclarationAst | undefined {\n return node.kind === 'CompositeTypeDeclaration'\n ? new CompositeTypeDeclarationAst(node)\n : undefined;\n }\n}\n\nexport class NamespaceDeclarationAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n keyword(): Token | undefined {\n return findChildToken(this.syntax, 'Ident');\n }\n\n name(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n lbrace(): Token | undefined {\n return findChildToken(this.syntax, 'LBrace');\n }\n\n rbrace(): Token | undefined {\n return findChildToken(this.syntax, 'RBrace');\n }\n\n *declarations(): Iterable<NamespaceMemberAst> {\n yield* filterChildren(this.syntax, castNamespaceMember);\n }\n\n static cast(node: SyntaxNode): NamespaceDeclarationAst | undefined {\n return node.kind === 'Namespace' ? new NamespaceDeclarationAst(node) : undefined;\n }\n}\n\nexport class TypesBlockAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n keyword(): Token | undefined {\n return findChildToken(this.syntax, 'Ident');\n }\n\n lbrace(): Token | undefined {\n return findChildToken(this.syntax, 'LBrace');\n }\n\n rbrace(): Token | undefined {\n return findChildToken(this.syntax, 'RBrace');\n }\n\n *declarations(): Iterable<NamedTypeDeclarationAst> {\n yield* filterChildren(this.syntax, NamedTypeDeclarationAst.cast);\n }\n\n static cast(node: SyntaxNode): TypesBlockAst | undefined {\n return node.kind === 'TypesBlock' ? new TypesBlockAst(node) : undefined;\n }\n}\n\nexport class BlockDeclarationAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n keyword(): Token | undefined {\n return findChildToken(this.syntax, 'Ident');\n }\n\n name(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n lbrace(): Token | undefined {\n return findChildToken(this.syntax, 'LBrace');\n }\n\n rbrace(): Token | undefined {\n return findChildToken(this.syntax, 'RBrace');\n }\n\n *entries(): Iterable<KeyValuePairAst> {\n yield* filterChildren(this.syntax, KeyValuePairAst.cast);\n }\n\n static cast(node: SyntaxNode): BlockDeclarationAst | undefined {\n return node.kind === 'BlockDeclaration' ? new BlockDeclarationAst(node) : undefined;\n }\n}\n\nexport class KeyValuePairAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n key(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n equals(): Token | undefined {\n return findChildToken(this.syntax, 'Equals');\n }\n\n value(): ExpressionAst | undefined {\n let pastEquals = false;\n for (const child of this.syntax.children()) {\n if (!(child instanceof SyntaxNode)) {\n if (child.kind === 'Equals') pastEquals = true;\n continue;\n }\n if (pastEquals) {\n const expr = castExpression(child);\n if (expr) return expr;\n }\n }\n return undefined;\n }\n\n static cast(node: SyntaxNode): KeyValuePairAst | undefined {\n return node.kind === 'KeyValuePair' ? new KeyValuePairAst(node) : undefined;\n }\n}\n\nexport class FieldDeclarationAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n name(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n typeAnnotation(): TypeAnnotationAst | undefined {\n return findFirstChild(this.syntax, TypeAnnotationAst.cast);\n }\n\n *attributes(): Iterable<FieldAttributeAst> {\n yield* filterChildren(this.syntax, FieldAttributeAst.cast);\n }\n\n static cast(node: SyntaxNode): FieldDeclarationAst | undefined {\n return node.kind === 'FieldDeclaration' ? new FieldDeclarationAst(node) : undefined;\n }\n}\n\nexport class EnumValueDeclarationAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n name(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n *attributes(): Iterable<FieldAttributeAst> {\n yield* filterChildren(this.syntax, FieldAttributeAst.cast);\n }\n\n static cast(node: SyntaxNode): EnumValueDeclarationAst | undefined {\n return node.kind === 'EnumValueDeclaration' ? new EnumValueDeclarationAst(node) : undefined;\n }\n}\n\nexport class NamedTypeDeclarationAst implements AstNode {\n readonly syntax: SyntaxNode;\n\n constructor(syntax: SyntaxNode) {\n this.syntax = syntax;\n }\n\n name(): IdentifierAst | undefined {\n return findFirstChild(this.syntax, IdentifierAst.cast);\n }\n\n equals(): Token | undefined {\n return findChildToken(this.syntax, 'Equals');\n }\n\n typeAnnotation(): TypeAnnotationAst | undefined {\n return findFirstChild(this.syntax, TypeAnnotationAst.cast);\n }\n\n *attributes(): Iterable<FieldAttributeAst> {\n yield* filterChildren(this.syntax, FieldAttributeAst.cast);\n }\n\n static cast(node: SyntaxNode): NamedTypeDeclarationAst | undefined {\n return node.kind === 'NamedTypeDeclaration' ? new NamedTypeDeclarationAst(node) : undefined;\n }\n}\n","import type { TokenKind } from '../tokenizer';\nimport type { SyntaxKind } from './syntax-kind';\n\nexport interface GreenToken {\n readonly type: 'token';\n readonly kind: TokenKind;\n readonly text: string;\n}\n\nexport interface GreenNode {\n readonly type: 'node';\n readonly kind: SyntaxKind;\n readonly children: ReadonlyArray<GreenElement>;\n readonly textLength: number;\n}\n\nexport type GreenElement = GreenNode | GreenToken;\n\nexport function greenToken(kind: TokenKind, text: string): GreenToken {\n return { type: 'token', kind, text };\n}\n\nexport function greenNode(kind: SyntaxKind, children: ReadonlyArray<GreenElement>): GreenNode {\n let textLength = 0;\n for (const child of children) {\n textLength += child.type === 'token' ? child.text.length : child.textLength;\n }\n return { type: 'node', kind, children, textLength };\n}\n","import type { TokenKind } from '../tokenizer';\nimport type { GreenElement, GreenNode } from './green';\nimport { greenNode, greenToken } from './green';\nimport type { SyntaxKind } from './syntax-kind';\n\nexport class GreenNodeBuilder {\n readonly #stack: Array<{ kind: SyntaxKind; children: GreenElement[] }> = [];\n\n startNode(kind: SyntaxKind): void {\n this.#stack.push({ kind, children: [] });\n }\n\n token(kind: TokenKind, text: string): void {\n const current = this.#stack.at(-1);\n if (!current) {\n throw new Error('GreenNodeBuilder: token() called with no open node');\n }\n current.children.push(greenToken(kind, text));\n }\n\n finishNode(): GreenNode {\n const completed = this.#stack.pop();\n if (!completed) {\n throw new Error('GreenNodeBuilder: finishNode() called with no open node');\n }\n const node = greenNode(completed.kind, completed.children);\n const parent = this.#stack.at(-1);\n if (parent) {\n parent.children.push(node);\n }\n return node;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ //#region src/tokenizer.d.ts
2
+ type TokenKind = 'Ident' | 'StringLiteral' | 'NumberLiteral' | 'At' | 'DoubleAt' | 'LBrace' | 'RBrace' | 'LParen' | 'RParen' | 'LBracket' | 'RBracket' | 'Equals' | 'Question' | 'Dot' | 'Comma' | 'Colon' | 'Whitespace' | 'Newline' | 'Comment' | 'Invalid' | 'Eof';
3
+ interface Token {
4
+ readonly kind: TokenKind;
5
+ readonly text: string;
6
+ }
7
+ declare class Tokenizer {
8
+ #private;
9
+ constructor(source: string);
10
+ next(): Token;
11
+ peek(offset?: number): Token;
12
+ }
13
+ //#endregion
14
+ export { TokenKind as n, Tokenizer as r, Token as t };
15
+ //# sourceMappingURL=tokenizer-DcYI0Xrq.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenizer-DcYI0Xrq.d.mts","names":[],"sources":["../src/tokenizer.ts"],"mappings":";KAAY,SAAA;AAAA,UAuBK,KAAA;EAAA,SACN,IAAA,EAAM,SAAS;EAAA,SACf,IAAA;AAAA;AAAA,cAGE,SAAA;EAAA;cAKC,MAAA;EAMZ,IAAA,IAAQ,KAAA;EAQR,IAAA,CAAK,MAAA,YAAa,KAAK;AAAA"}
@@ -1,15 +1,2 @@
1
- //#region src/tokenizer.d.ts
2
- type TokenKind = 'Ident' | 'StringLiteral' | 'NumberLiteral' | 'At' | 'DoubleAt' | 'LBrace' | 'RBrace' | 'LParen' | 'RParen' | 'LBracket' | 'RBracket' | 'Equals' | 'Question' | 'Dot' | 'Comma' | 'Colon' | 'Whitespace' | 'Newline' | 'Comment' | 'Invalid' | 'Eof';
3
- interface Token {
4
- readonly kind: TokenKind;
5
- readonly text: string;
6
- }
7
- declare class Tokenizer {
8
- #private;
9
- constructor(source: string);
10
- next(): Token;
11
- peek(offset?: number): Token;
12
- }
13
- //#endregion
14
- export { type Token, type TokenKind, Tokenizer };
15
- //# sourceMappingURL=tokenizer.d.mts.map
1
+ import { n as TokenKind, r as Tokenizer, t as Token } from "./tokenizer-DcYI0Xrq.mjs";
2
+ export { type Token, type TokenKind, Tokenizer };
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@prisma-next/psl-parser",
3
- "version": "0.12.0",
3
+ "version": "0.13.0-dev.2",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "Reusable parser for Prisma Schema Language (PSL)",
8
8
  "dependencies": {
9
- "@prisma-next/framework-components": "0.12.0",
10
- "@prisma-next/utils": "0.12.0"
9
+ "@prisma-next/framework-components": "0.13.0-dev.2",
10
+ "@prisma-next/utils": "0.13.0-dev.2"
11
11
  },
12
12
  "devDependencies": {
13
- "@prisma-next/tsconfig": "0.12.0",
14
- "@prisma-next/tsdown": "0.12.0",
15
- "tsdown": "0.22.0",
13
+ "@prisma-next/tsconfig": "0.13.0-dev.2",
14
+ "@prisma-next/tsdown": "0.13.0-dev.2",
15
+ "tsdown": "0.22.1",
16
16
  "typescript": "5.9.3",
17
- "vitest": "4.1.6"
17
+ "vitest": "4.1.8"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "typescript": ">=5.9"
@@ -32,6 +32,7 @@
32
32
  "exports": {
33
33
  ".": "./dist/index.mjs",
34
34
  "./parser": "./dist/parser.mjs",
35
+ "./syntax": "./dist/syntax.mjs",
35
36
  "./tokenizer": "./dist/tokenizer.mjs",
36
37
  "./package.json": "./package.json"
37
38
  },
@@ -0,0 +1,41 @@
1
+ export {
2
+ AttributeArgListAst,
3
+ FieldAttributeAst,
4
+ ModelAttributeAst,
5
+ } from '../syntax/ast/attributes';
6
+ export type { NamespaceMemberAst } from '../syntax/ast/declarations';
7
+ export {
8
+ BlockDeclarationAst,
9
+ CompositeTypeDeclarationAst,
10
+ DocumentAst,
11
+ EnumDeclarationAst,
12
+ EnumValueDeclarationAst,
13
+ FieldDeclarationAst,
14
+ KeyValuePairAst,
15
+ ModelDeclarationAst,
16
+ NamedTypeDeclarationAst,
17
+ NamespaceDeclarationAst,
18
+ TypesBlockAst,
19
+ } from '../syntax/ast/declarations';
20
+ export type { ExpressionAst } from '../syntax/ast/expressions';
21
+ export {
22
+ ArrayLiteralAst,
23
+ AttributeArgAst,
24
+ BooleanLiteralExprAst,
25
+ castExpression,
26
+ FunctionCallAst,
27
+ NumberLiteralExprAst,
28
+ StringLiteralExprAst,
29
+ } from '../syntax/ast/expressions';
30
+ // AST wrappers
31
+ export { IdentifierAst } from '../syntax/ast/identifier';
32
+ export { TypeAnnotationAst } from '../syntax/ast/type-annotation';
33
+ export type { AstNode } from '../syntax/ast-helpers';
34
+ export { filterChildren, findChildToken, findFirstChild } from '../syntax/ast-helpers';
35
+ export type { GreenElement, GreenNode, GreenToken } from '../syntax/green';
36
+ export { greenNode, greenToken } from '../syntax/green';
37
+ export { GreenNodeBuilder } from '../syntax/green-builder';
38
+ // Red layer
39
+ export type { SyntaxElement, SyntaxToken } from '../syntax/red';
40
+ export { createSyntaxTree, SyntaxNode } from '../syntax/red';
41
+ export type { SyntaxKind } from '../syntax/syntax-kind';
package/src/parser.ts CHANGED
@@ -1,3 +1,9 @@
1
+ import type {
2
+ AuthoringPslBlockDescriptor,
3
+ AuthoringPslBlockDescriptorNamespace,
4
+ } from '@prisma-next/framework-components/authoring';
5
+ import { isAuthoringPslBlockDescriptor } from '@prisma-next/framework-components/authoring';
6
+ import { emptyCodecLookup } from '@prisma-next/framework-components/codec';
1
7
  import type {
2
8
  ParsePslDocumentInput,
3
9
  ParsePslDocumentResult,
@@ -10,6 +16,8 @@ import type {
10
16
  PslDocumentAst,
11
17
  PslEnum,
12
18
  PslEnumValue,
19
+ PslExtensionBlock,
20
+ PslExtensionBlockParamValue,
13
21
  PslField,
14
22
  PslFieldAttribute,
15
23
  PslModel,
@@ -21,7 +29,13 @@ import type {
21
29
  PslTypeConstructorCall,
22
30
  PslTypesBlock,
23
31
  } from '@prisma-next/framework-components/psl-ast';
24
- import { UNSPECIFIED_PSL_NAMESPACE_ID } from '@prisma-next/framework-components/psl-ast';
32
+ import {
33
+ makePslNamespace,
34
+ makePslNamespaceEntries,
35
+ namespacePslExtensionBlocks,
36
+ UNSPECIFIED_PSL_NAMESPACE_ID,
37
+ validateExtensionBlock,
38
+ } from '@prisma-next/framework-components/psl-ast';
25
39
  import { ifDefined } from '@prisma-next/utils/defined';
26
40
 
27
41
  const SCALAR_TYPES = new Set([
@@ -68,6 +82,7 @@ export function parsePslDocument(input: ParsePslDocumentInput): ParsePslDocument
68
82
  models: PslModel[];
69
83
  enums: PslEnum[];
70
84
  compositeTypes: PslCompositeType[];
85
+ extensionBlocks: PslExtensionBlock[];
71
86
  span: PslSpan | undefined;
72
87
  }
73
88
 
@@ -79,7 +94,14 @@ export function parsePslDocument(input: ParsePslDocumentInput): ParsePslDocument
79
94
  ): NamespaceAccumulator => {
80
95
  let acc = namespacesByName.get(name);
81
96
  if (!acc) {
82
- acc = { name, models: [], enums: [], compositeTypes: [], span: spanIfNew };
97
+ acc = {
98
+ name,
99
+ models: [],
100
+ enums: [],
101
+ compositeTypes: [],
102
+ extensionBlocks: [],
103
+ span: spanIfNew,
104
+ };
83
105
  namespacesByName.set(name, acc);
84
106
  namespaceOrder.push(name);
85
107
  }
@@ -87,6 +109,7 @@ export function parsePslDocument(input: ParsePslDocumentInput): ParsePslDocument
87
109
  };
88
110
 
89
111
  let typesBlock: PslTypesBlock | undefined;
112
+ const pslBlockNamespace: AuthoringPslBlockDescriptorNamespace = input.pslBlockDescriptors ?? {};
90
113
 
91
114
  // Walk a contiguous range of lines, routing top-level declarations into the
92
115
  // active namespace bucket. Called once for the whole document and once per
@@ -198,6 +221,26 @@ export function parsePslDocument(input: ParsePslDocumentInput): ParsePslDocument
198
221
  continue;
199
222
  }
200
223
 
224
+ const extensionBlockMatch = line.match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*)\s*\{$/);
225
+ if (extensionBlockMatch) {
226
+ const keyword = extensionBlockMatch[1] ?? '';
227
+ const blockName = extensionBlockMatch[2] ?? '';
228
+ const descriptor = lookupExtensionBlockDescriptor(pslBlockNamespace, keyword);
229
+ if (descriptor) {
230
+ const bounds = findBlockBounds(context, lineIndex);
231
+ if (blockName.length > 0) {
232
+ const acc = getOrCreateNamespace(
233
+ currentNamespaceName,
234
+ createTrimmedLineSpan(context, lineIndex),
235
+ );
236
+ const node = parseExtensionBlock(context, descriptor, blockName, lineIndex, bounds);
237
+ acc.extensionBlocks.push(node);
238
+ }
239
+ lineIndex = bounds.endLine + 1;
240
+ continue;
241
+ }
242
+ }
243
+
201
244
  if (line.includes('{')) {
202
245
  const blockName = line.split(/\s+/)[0] ?? 'block';
203
246
  pushDiagnostic(context, {
@@ -288,7 +331,8 @@ export function parsePslDocument(input: ParsePslDocumentInput): ParsePslDocument
288
331
  name === UNSPECIFIED_PSL_NAMESPACE_ID &&
289
332
  acc.models.length === 0 &&
290
333
  acc.enums.length === 0 &&
291
- acc.compositeTypes.length === 0
334
+ acc.compositeTypes.length === 0 &&
335
+ acc.extensionBlocks.length === 0
292
336
  ) {
293
337
  continue;
294
338
  }
@@ -316,14 +360,59 @@ export function parsePslDocument(input: ParsePslDocumentInput): ParsePslDocument
316
360
  };
317
361
  }),
318
362
  }));
319
- namespaces.push({
320
- kind: 'namespace',
321
- name,
322
- models: normalizedModels,
323
- enums: acc.enums,
324
- compositeTypes: acc.compositeTypes,
325
- span: acc.span ?? documentSpan,
326
- });
363
+ namespaces.push(
364
+ makePslNamespace({
365
+ kind: 'namespace',
366
+ name,
367
+ entries: makePslNamespaceEntries(
368
+ normalizedModels,
369
+ acc.enums,
370
+ acc.compositeTypes,
371
+ acc.extensionBlocks,
372
+ ),
373
+ span: acc.span ?? documentSpan,
374
+ }),
375
+ );
376
+ }
377
+
378
+ // Validate extension blocks with the generic validator after the full
379
+ // AST is assembled. The validator needs all namespaces for ref resolution.
380
+ // It runs whenever pslBlockDescriptors are registered — codecLookup falls
381
+ // back to emptyCodecLookup when not provided, which rejects value-kind
382
+ // parameters with unknown codecs. Callers with value parameters should
383
+ // always supply a codecLookup.
384
+ if (Object.keys(pslBlockNamespace).length > 0) {
385
+ const codecLookup = input.codecLookup ?? emptyCodecLookup;
386
+ // Build a discriminator → descriptor reverse map from the descriptor
387
+ // namespace. The parser keyed by keyword; the validator resolves by
388
+ // node.kind (= discriminator) so we need the reverse direction.
389
+ const descriptorByDiscriminator = new Map<string, AuthoringPslBlockDescriptor>();
390
+ for (const value of Object.values(pslBlockNamespace)) {
391
+ if (isAuthoringPslBlockDescriptor(value)) {
392
+ descriptorByDiscriminator.set(value.discriminator, value);
393
+ }
394
+ }
395
+ for (const ns of namespaces) {
396
+ // Collect extension blocks from entries using the canonical helper, then
397
+ // filter to only those whose discriminator is registered in the namespace.
398
+ for (const block of namespacePslExtensionBlocks(ns)) {
399
+ const descriptor = descriptorByDiscriminator.get(block.kind);
400
+ if (descriptor === undefined) {
401
+ continue;
402
+ }
403
+ const blockDiagnostics = validateExtensionBlock(
404
+ block,
405
+ descriptor,
406
+ input.sourceId,
407
+ codecLookup,
408
+ {
409
+ ownerNamespace: ns,
410
+ allNamespaces: namespaces,
411
+ },
412
+ );
413
+ diagnostics.push(...blockDiagnostics);
414
+ }
415
+ }
327
416
  }
328
417
 
329
418
  const ast: PslDocumentAst = {
@@ -787,11 +876,45 @@ function parseField(context: ParserContext, line: string, lineIndex: number): Ps
787
876
 
788
877
  let typeName: string;
789
878
  let typeNamespaceId: string | undefined;
879
+ let typeContractSpaceId: string | undefined;
790
880
 
791
881
  if (typeConstructor) {
792
882
  typeName = typeConstructor.path.join('.');
793
883
  } else {
794
- const dotCount = (baseTypeSource.match(/\./g) ?? []).length;
884
+ // Detect the colon-prefix cross-contract-space form: `<space>:<rest>` where
885
+ // `<rest>` is the existing dot-qualified or bare form (`<ns>.<Name>` or `<Name>`).
886
+ // Multiple colons (e.g. `a:b:c`) are invalid.
887
+ const colonCount = (baseTypeSource.match(/:/g) ?? []).length;
888
+ if (colonCount > 1) {
889
+ pushDiagnostic(context, {
890
+ code: 'PSL_INVALID_MODEL_MEMBER',
891
+ message: `Invalid model member declaration "${line}"`,
892
+ span: createTrimmedLineSpan(context, lineIndex),
893
+ });
894
+ return undefined;
895
+ }
896
+
897
+ let typeRefSource: string;
898
+ if (colonCount === 1) {
899
+ // Colon-prefix form: `<space>:<rest>`
900
+ const colonIndex = baseTypeSource.indexOf(':');
901
+ const spaceCandidate = baseTypeSource.slice(0, colonIndex);
902
+ typeRefSource = baseTypeSource.slice(colonIndex + 1);
903
+ // Validate space identifier
904
+ if (!/^[A-Za-z_]\w*$/.test(spaceCandidate)) {
905
+ pushDiagnostic(context, {
906
+ code: 'PSL_INVALID_MODEL_MEMBER',
907
+ message: `Invalid model member declaration "${line}"`,
908
+ span: createTrimmedLineSpan(context, lineIndex),
909
+ });
910
+ return undefined;
911
+ }
912
+ typeContractSpaceId = spaceCandidate;
913
+ } else {
914
+ typeRefSource = baseTypeSource;
915
+ }
916
+
917
+ const dotCount = (typeRefSource.match(/\./g) ?? []).length;
795
918
  if (dotCount > 1) {
796
919
  pushDiagnostic(context, {
797
920
  code: 'PSL_INVALID_QUALIFIED_TYPE',
@@ -805,7 +928,7 @@ function parseField(context: ParserContext, line: string, lineIndex: number): Ps
805
928
  });
806
929
  return undefined;
807
930
  }
808
- const singleMatch = baseTypeSource.match(/^([A-Za-z_]\w*)(?:\.([A-Za-z_]\w*))?$/);
931
+ const singleMatch = typeRefSource.match(/^([A-Za-z_]\w*)(?:\.([A-Za-z_]\w*))?$/);
809
932
  if (!singleMatch) {
810
933
  pushDiagnostic(context, {
811
934
  code: 'PSL_INVALID_MODEL_MEMBER',
@@ -841,6 +964,7 @@ function parseField(context: ParserContext, line: string, lineIndex: number): Ps
841
964
  name: fieldName,
842
965
  typeName,
843
966
  ...ifDefined('typeNamespaceId', typeNamespaceId),
967
+ ...ifDefined('typeContractSpaceId', typeContractSpaceId),
844
968
  ...ifDefined('typeConstructor', typeConstructor),
845
969
  optional,
846
970
  list,
@@ -866,6 +990,7 @@ function parseField(context: ParserContext, line: string, lineIndex: number): Ps
866
990
  name: fieldName,
867
991
  typeName,
868
992
  ...ifDefined('typeNamespaceId', typeNamespaceId),
993
+ ...ifDefined('typeContractSpaceId', typeContractSpaceId),
869
994
  ...ifDefined('typeConstructor', typeConstructor),
870
995
  optional,
871
996
  list,
@@ -1453,3 +1578,136 @@ function pushDiagnostic(
1453
1578
  sourceId: context.sourceId,
1454
1579
  });
1455
1580
  }
1581
+
1582
+ function lookupExtensionBlockDescriptor(
1583
+ namespace: AuthoringPslBlockDescriptorNamespace,
1584
+ keyword: string,
1585
+ ): AuthoringPslBlockDescriptor | undefined {
1586
+ if (!Object.hasOwn(namespace, keyword)) {
1587
+ return undefined;
1588
+ }
1589
+ const value = namespace[keyword];
1590
+ return isAuthoringPslBlockDescriptor(value) ? value : undefined;
1591
+ }
1592
+
1593
+ /**
1594
+ * Reads an extension block body generically, driven by the descriptor's
1595
+ * `parameters` map. Each `key = <rhs>` line in the body is matched against
1596
+ * a declared parameter and the RHS is captured per the parameter's kind:
1597
+ *
1598
+ * - `ref` → the bareword identifier token (`PslExtensionBlockParamRef`)
1599
+ * - `value` → the raw RHS text verbatim (`PslExtensionBlockParamScalarValue`)
1600
+ * - `option` → the chosen bareword token (`PslExtensionBlockParamOption`)
1601
+ * - `list` → bracketed `[a, b, …]` list, each element captured per `of`
1602
+ * (`PslExtensionBlockParamList`)
1603
+ *
1604
+ * No validation runs here (unknown key, missing required, codec acceptance,
1605
+ * option-in-set, ref resolution). A body line that is not `key = value`
1606
+ * shaped emits a `PSL_INVALID_EXTENSION_BLOCK_MEMBER` parse diagnostic.
1607
+ */
1608
+ function parseExtensionBlock(
1609
+ context: ParserContext,
1610
+ descriptor: AuthoringPslBlockDescriptor,
1611
+ blockName: string,
1612
+ keywordLineIndex: number,
1613
+ bounds: BlockBounds,
1614
+ ): PslExtensionBlock {
1615
+ const parameters: Record<string, PslExtensionBlockParamValue> = {};
1616
+
1617
+ for (let lineIndex = bounds.startLine + 1; lineIndex < bounds.endLine; lineIndex += 1) {
1618
+ const raw = context.lines[lineIndex] ?? '';
1619
+ const line = stripInlineComment(raw).trim();
1620
+ if (line.length === 0) {
1621
+ continue;
1622
+ }
1623
+
1624
+ const assignMatch = line.match(/^([A-Za-z_]\w*)\s*=\s*(.+)$/);
1625
+ if (!assignMatch) {
1626
+ pushDiagnostic(context, {
1627
+ code: 'PSL_INVALID_EXTENSION_BLOCK_MEMBER',
1628
+ message: `Invalid extension block body line "${line}"; expected "key = value"`,
1629
+ span: createTrimmedLineSpan(context, lineIndex),
1630
+ });
1631
+ continue;
1632
+ }
1633
+
1634
+ const key = assignMatch[1] ?? '';
1635
+ const rhsRaw = (assignMatch[2] ?? '').trim();
1636
+ const paramDescriptor = descriptor.parameters[key];
1637
+
1638
+ if (paramDescriptor === undefined) {
1639
+ // Unknown parameter — captured as a raw value; the generic validator reports this.
1640
+ parameters[key] = {
1641
+ kind: 'value',
1642
+ raw: rhsRaw,
1643
+ span: createTrimmedLineSpan(context, lineIndex),
1644
+ };
1645
+ continue;
1646
+ }
1647
+
1648
+ const captured = captureParamRhs(context, lineIndex, rhsRaw, paramDescriptor);
1649
+ if (captured !== undefined) {
1650
+ parameters[key] = captured;
1651
+ }
1652
+ }
1653
+
1654
+ return {
1655
+ kind: descriptor.discriminator,
1656
+ name: blockName,
1657
+ parameters,
1658
+ span: createLineRangeSpan(context, keywordLineIndex, bounds.endLine),
1659
+ };
1660
+ }
1661
+
1662
+ /**
1663
+ * Captures a single parameter RHS string according to the declared parameter
1664
+ * kind. Returns `undefined` and pushes a diagnostic only if the syntax is
1665
+ * genuinely malformed (e.g. a `list` with no opening bracket). Value
1666
+ * acceptance (codec, option-in-set, ref resolution) is handled by the validator.
1667
+ */
1668
+ function captureParamRhs(
1669
+ context: ParserContext,
1670
+ lineIndex: number,
1671
+ rhs: string,
1672
+ param: AuthoringPslBlockDescriptor['parameters'][string],
1673
+ ): PslExtensionBlockParamValue | undefined {
1674
+ const span = createTrimmedLineSpan(context, lineIndex);
1675
+
1676
+ switch (param.kind) {
1677
+ case 'ref': {
1678
+ return { kind: 'ref', identifier: rhs, span };
1679
+ }
1680
+ case 'value': {
1681
+ return { kind: 'value', raw: rhs, span };
1682
+ }
1683
+ case 'option': {
1684
+ return { kind: 'option', token: rhs, span };
1685
+ }
1686
+ case 'list': {
1687
+ if (!rhs.startsWith('[') || !rhs.endsWith(']')) {
1688
+ pushDiagnostic(context, {
1689
+ code: 'PSL_INVALID_EXTENSION_BLOCK_MEMBER',
1690
+ message: `Expected a bracketed list "[…]" for list parameter, got "${rhs}"`,
1691
+ span,
1692
+ });
1693
+ return undefined;
1694
+ }
1695
+ const inner = rhs.slice(1, -1).trim();
1696
+ const items: PslExtensionBlockParamValue[] = [];
1697
+ if (inner.length > 0) {
1698
+ const segments = splitTopLevelSegments(inner, ',');
1699
+ for (const segment of segments) {
1700
+ const itemRhs = segment.value.trim();
1701
+ if (itemRhs.length === 0) {
1702
+ continue;
1703
+ }
1704
+ const item = captureParamRhs(context, lineIndex, itemRhs, param.of);
1705
+ if (item !== undefined) {
1706
+ items.push(item);
1707
+ }
1708
+ }
1709
+ }
1710
+ return { kind: 'list', items, span };
1711
+ }
1712
+ }
1713
+ }