@samva/markup 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 (124) hide show
  1. package/AGENTS.md +8 -0
  2. package/LICENSE +21 -0
  3. package/README.md +41 -0
  4. package/dist/ast-DAhnAvQN.d.ts +120 -0
  5. package/dist/ast.d.ts +2 -0
  6. package/dist/ast.js +159 -0
  7. package/dist/ast.js.map +1 -0
  8. package/dist/candidate.d.ts +29 -0
  9. package/dist/candidate.js +88 -0
  10. package/dist/candidate.js.map +1 -0
  11. package/dist/checks-CW0nFj5n.js +58922 -0
  12. package/dist/checks-CW0nFj5n.js.map +1 -0
  13. package/dist/checks.d.ts +26 -0
  14. package/dist/checks.js +2 -0
  15. package/dist/classes.d.ts +27 -0
  16. package/dist/classes.js +184 -0
  17. package/dist/classes.js.map +1 -0
  18. package/dist/compile-sms.d.ts +28 -0
  19. package/dist/compile-sms.js +44 -0
  20. package/dist/compile-sms.js.map +1 -0
  21. package/dist/compile-whatsapp.d.ts +56 -0
  22. package/dist/compile-whatsapp.js +186 -0
  23. package/dist/compile-whatsapp.js.map +1 -0
  24. package/dist/compile.d.ts +93 -0
  25. package/dist/compile.js +938 -0
  26. package/dist/compile.js.map +1 -0
  27. package/dist/components.d.ts +289 -0
  28. package/dist/components.js +106 -0
  29. package/dist/components.js.map +1 -0
  30. package/dist/conditionals.d.ts +10 -0
  31. package/dist/conditionals.js +42 -0
  32. package/dist/conditionals.js.map +1 -0
  33. package/dist/diagnostics-oveE-laV.d.ts +79 -0
  34. package/dist/diagnostics.d.ts +2 -0
  35. package/dist/diagnostics.js +62 -0
  36. package/dist/diagnostics.js.map +1 -0
  37. package/dist/emit.d.ts +50 -0
  38. package/dist/emit.js +151 -0
  39. package/dist/emit.js.map +1 -0
  40. package/dist/footer.d.ts +66 -0
  41. package/dist/footer.js +90 -0
  42. package/dist/footer.js.map +1 -0
  43. package/dist/interpolate.d.ts +27 -0
  44. package/dist/interpolate.js +61 -0
  45. package/dist/interpolate.js.map +1 -0
  46. package/dist/jsx-dev-runtime.d.ts +10 -0
  47. package/dist/jsx-dev-runtime.js +7 -0
  48. package/dist/jsx-dev-runtime.js.map +1 -0
  49. package/dist/jsx-runtime-fSxcvlx4.d.ts +57 -0
  50. package/dist/jsx-runtime.d.ts +2 -0
  51. package/dist/jsx-runtime.js +102 -0
  52. package/dist/jsx-runtime.js.map +1 -0
  53. package/dist/lexer.d.ts +60 -0
  54. package/dist/lexer.js +269 -0
  55. package/dist/lexer.js.map +1 -0
  56. package/dist/model.d.ts +39 -0
  57. package/dist/model.js +90 -0
  58. package/dist/model.js.map +1 -0
  59. package/dist/ops.d.ts +208 -0
  60. package/dist/ops.js +546 -0
  61. package/dist/ops.js.map +1 -0
  62. package/dist/parser-yzjUOGhc.d.ts +387 -0
  63. package/dist/parser.d.ts +2 -0
  64. package/dist/parser.js +821 -0
  65. package/dist/parser.js.map +1 -0
  66. package/dist/predicate-DTtVtLK7.d.ts +49 -0
  67. package/dist/predicate.d.ts +2 -0
  68. package/dist/predicate.js +417 -0
  69. package/dist/predicate.js.map +1 -0
  70. package/dist/presets/category-mapping.d.ts +13 -0
  71. package/dist/presets/category-mapping.js +49 -0
  72. package/dist/presets/category-mapping.js.map +1 -0
  73. package/dist/presets/public-gallery.d.ts +490 -0
  74. package/dist/presets/public-gallery.js +563 -0
  75. package/dist/presets/public-gallery.js.map +1 -0
  76. package/dist/presets/public-handoff.d.ts +68 -0
  77. package/dist/presets/public-handoff.js +105 -0
  78. package/dist/presets/public-handoff.js.map +1 -0
  79. package/dist/presets/public-preview-artifacts.d.ts +41 -0
  80. package/dist/presets/public-preview-artifacts.js +109 -0
  81. package/dist/presets/public-preview-artifacts.js.map +1 -0
  82. package/dist/presets/public-preview.d.ts +1168 -0
  83. package/dist/presets/public-preview.js +1158 -0
  84. package/dist/presets/public-preview.js.map +1 -0
  85. package/dist/presets/sources.d.ts +7 -0
  86. package/dist/presets/sources.js +54 -0
  87. package/dist/presets/sources.js.map +1 -0
  88. package/dist/presets.d.ts +350 -0
  89. package/dist/presets.js +1251 -0
  90. package/dist/presets.js.map +1 -0
  91. package/dist/printer.d.ts +6 -0
  92. package/dist/printer.js +122 -0
  93. package/dist/printer.js.map +1 -0
  94. package/dist/registry.d.ts +42 -0
  95. package/dist/registry.js +646 -0
  96. package/dist/registry.js.map +1 -0
  97. package/dist/render-template.d.ts +42 -0
  98. package/dist/render-template.js +74 -0
  99. package/dist/render-template.js.map +1 -0
  100. package/dist/sms-segments-y4FpPilJ.d.ts +13 -0
  101. package/dist/sms-segments.d.ts +2 -0
  102. package/dist/sms-segments.js +46 -0
  103. package/dist/sms-segments.js.map +1 -0
  104. package/dist/theme-6ApjjiBo.d.ts +116 -0
  105. package/dist/theme-css.d.ts +24 -0
  106. package/dist/theme-css.js +509 -0
  107. package/dist/theme-css.js.map +1 -0
  108. package/dist/theme.d.ts +2 -0
  109. package/dist/theme.js +95 -0
  110. package/dist/theme.js.map +1 -0
  111. package/dist/tokens.d.ts +6 -0
  112. package/dist/tokens.js +417 -0
  113. package/dist/tokens.js.map +1 -0
  114. package/dist/variables-3NGLpNTB.d.ts +77 -0
  115. package/dist/variables.d.ts +2 -0
  116. package/dist/variables.js +141 -0
  117. package/dist/variables.js.map +1 -0
  118. package/dist/version-BITr58kk.js +96 -0
  119. package/dist/version-BITr58kk.js.map +1 -0
  120. package/generated/sml-agent-contract.json +917 -0
  121. package/generated/sml-email-agent-contract.json +790 -0
  122. package/generated/sml-email-language-reference.md +160 -0
  123. package/generated/sml-language-reference.md +220 -0
  124. package/package.json +203 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","names":[],"sources":["../src/parser.ts"],"sourcesContent":["import { Result } from \"effect\";\n\nimport type {\n Attribute,\n AttributeValue,\n Channel,\n ElementNode,\n SmlDocument,\n SmlNode,\n SmlTag,\n} from \"./ast\";\nimport { CHANNEL_BY_ROOT_TAG, isRootTag, ROOT_TAGS, SML_TAGS } from \"./ast\";\nimport type { Diagnostic, Span } from \"./diagnostics\";\nimport { SmlParseError } from \"./diagnostics\";\nimport type { Token } from \"./lexer\";\nimport { createLexer, TokenKind } from \"./lexer\";\nimport { parsePredicate, printPredicate } from \"./predicate\";\n\n// Recursive-descent SML parser. JSX-strict: every tag closed, attributes\n// quoted (or `{number}`), known vocabulary only — no HTML5 repair semantics.\n// Fail-fast: the first diagnostic aborts the parse; every diagnostic carries a\n// span and names the fix so the agent write → validate → retry loop converges.\n//\n// The root tag declares the channel, and the channel selects the tag table:\n// email documents use EMAIL_TAG_SPECS, sms SMS_TAG_SPECS, whatsapp\n// WHATSAPP_TAG_SPECS. Tag names may repeat across channels with different\n// specs (<Button> is an email link-button and a WhatsApp template button).\n//\n// Whitespace has two regimes. Email inline content collapses runs to single\n// spaces (HTML-style, canonicalized at parse). Sms/WhatsApp text slots are\n// VERBATIM (pre-like): newlines and space runs are content and survive\n// parse ⇄ print byte-exactly.\n\nexport type ContentModel =\n | \"email\" // envelope metadata? then blocks\n | \"block\" // block children only\n | \"columns\" // Column children only\n | \"list\" // ListItem children only\n | \"image\" // Source children only\n | \"if\" // blocks, then at most one <Else> as the last child\n | \"inline\" // text, {{vars}}, strong/em/a\n | \"inline-nolink\" // text, {{vars}}, strong/em\n | \"preview\" // text and {{vars}} only\n | \"whatsapp\" // Header? Body Footer? Buttons? — in that order, each at most once\n | \"buttons\" // Button children only\n | \"verbatim\" // verbatim text, {{vars}}, <If> (sms body / whatsapp text slots)\n | \"verbatim-if\" // verbatim content, then at most one <Else> as the last child\n | \"void\"; // self-closing, no children\n\nexport interface AttributeSpec {\n readonly type: \"string\" | \"number\";\n readonly required?: boolean | undefined;\n /** Inclusive numeric bounds for number attributes. */\n readonly min?: number | undefined;\n readonly max?: number | undefined;\n /** Closed value set for string attributes; anything else is a parse error. */\n readonly values?: ReadonlyArray<string> | undefined;\n}\n\nexport interface TagSpec {\n readonly attributes: Readonly<Record<string, AttributeSpec>>;\n readonly content: ContentModel;\n}\n\n// Attribute value sets belong to the public SML contract.\nconst SMS_CATEGORIES = [\"transactional\", \"promotional\", \"service_implicit\"] as const;\nconst WHATSAPP_CATEGORIES = [\"marketing\", \"utility\", \"authentication\", \"service\"] as const;\nconst WHATSAPP_HEADER_TYPES = [\"text\", \"image\", \"video\", \"document\"] as const;\nconst WHATSAPP_BUTTON_TYPES = [\"quick-reply\", \"url\", \"phone\", \"copy-code\"] as const;\n\nconst BACKGROUND_POSITIONS = [\"center\", \"top\", \"bottom\"] as const;\nconst BACKGROUND_SIZES = [\"cover\", \"contain\", \"auto\"] as const;\n\n/**\n * Background-image attributes on the email block containers (Section/Container).\n * A URL is an ATTRIBUTE, not a class token: a URL inside the closed class\n * grammar is hostile (escaping, cache eviction, no caniemail slug), so it rides\n * the attribute channel like `src`/`href`. The compiler emits email-safe\n * background CSS from these (escaped `url(...)` + an Outlook `background` cell\n * attribute); Outlook for Windows renders no CSS background image, so\n * `checks` warns via `css-background-image`. Position/size are ignored by the\n * compiler when no `backgroundImage` is set.\n */\nconst BACKGROUND_ATTRIBUTES = {\n backgroundImage: { type: \"string\" },\n backgroundPosition: { type: \"string\", values: BACKGROUND_POSITIONS },\n backgroundSize: { type: \"string\", values: BACKGROUND_SIZES },\n} as const satisfies Readonly<Record<string, AttributeSpec>>;\n\n/**\n * The email language surface as data: per-tag attributes\n * (type/required/bounds) and content model. Public API — typed TSX components\n * and the reference-docs generator derive from this table, so it cannot drift\n * from the parser.\n */\nexport const EMAIL_TAG_SPECS = {\n Email: {\n attributes: {\n lang: { type: \"string\" },\n dir: { type: \"string\" },\n width: { type: \"number\", min: 1, max: 10_000 },\n class: { type: \"string\" },\n },\n content: \"email\",\n },\n Subject: { attributes: {}, content: \"preview\" },\n Preview: { attributes: {}, content: \"preview\" },\n Container: {\n attributes: { class: { type: \"string\" }, ...BACKGROUND_ATTRIBUTES },\n content: \"block\",\n },\n Section: {\n attributes: { class: { type: \"string\" }, ...BACKGROUND_ATTRIBUTES },\n content: \"block\",\n },\n Row: { attributes: { class: { type: \"string\" } }, content: \"columns\" },\n Column: {\n attributes: {\n class: { type: \"string\" },\n width: { type: \"string\" },\n valign: { type: \"string\", values: [\"top\", \"middle\", \"bottom\"] },\n },\n content: \"block\",\n },\n Heading: {\n attributes: { level: { type: \"number\", min: 1, max: 6 }, class: { type: \"string\" } },\n content: \"inline\",\n },\n Text: { attributes: { class: { type: \"string\" } }, content: \"inline\" },\n Button: {\n attributes: { href: { type: \"string\", required: true }, class: { type: \"string\" } },\n content: \"inline-nolink\",\n },\n Image: {\n attributes: {\n src: { type: \"string\", required: true },\n alt: { type: \"string\" },\n width: { type: \"number\", min: 0, max: 10_000 },\n height: { type: \"number\", min: 0, max: 10_000 },\n class: { type: \"string\" },\n },\n content: \"image\",\n },\n Source: {\n attributes: {\n scheme: { type: \"string\", required: true, values: [\"dark\"] },\n src: { type: \"string\", required: true },\n },\n content: \"void\",\n },\n Divider: { attributes: { class: { type: \"string\" } }, content: \"void\" },\n Spacer: {\n attributes: { height: { type: \"number\", required: true, min: 1, max: 2_000 } },\n content: \"void\",\n },\n List: {\n attributes: {\n type: { type: \"string\", values: [\"unordered\", \"ordered\"] },\n class: { type: \"string\" },\n },\n content: \"list\",\n },\n ListItem: { attributes: { class: { type: \"string\" } }, content: \"inline\" },\n If: {\n attributes: { test: { type: \"string\", required: true } },\n content: \"if\",\n },\n Else: { attributes: {}, content: \"block\" },\n a: {\n attributes: { href: { type: \"string\", required: true }, class: { type: \"string\" } },\n content: \"inline-nolink\",\n },\n strong: { attributes: {}, content: \"inline-nolink\" },\n em: { attributes: {}, content: \"inline-nolink\" },\n} as const satisfies Readonly<Partial<Record<SmlTag, TagSpec>>>;\n\n/**\n * The sms language surface: a raw verbatim text body with `{{vars}}` and\n * send-time conditionals — no layout tags, no classes, no theming.\n */\nexport const SMS_TAG_SPECS = {\n Sms: {\n attributes: { category: { type: \"string\", values: SMS_CATEGORIES } },\n content: \"verbatim\",\n },\n If: { attributes: { test: { type: \"string\", required: true } }, content: \"verbatim-if\" },\n Else: { attributes: {}, content: \"verbatim\" },\n} as const satisfies Readonly<Partial<Record<SmlTag, TagSpec>>>;\n\n/**\n * The whatsapp language surface: Meta's template anatomy IS the closed\n * grammar — Header? Body Footer? Buttons?, verbatim text slots, no classes.\n * Root attributes carry authoring intent (name defaults from the template\n * slug, language from the variant locale — defaulting happens at the API\n * layer; the language just carries the attributes).\n */\nexport const WHATSAPP_TAG_SPECS = {\n WhatsApp: {\n attributes: {\n name: { type: \"string\" },\n language: { type: \"string\" },\n category: { type: \"string\", required: true, values: WHATSAPP_CATEGORIES },\n },\n content: \"whatsapp\",\n },\n Header: {\n attributes: { type: { type: \"string\", values: WHATSAPP_HEADER_TYPES } },\n content: \"verbatim\",\n },\n Body: { attributes: {}, content: \"verbatim\" },\n Footer: { attributes: {}, content: \"verbatim\" },\n Buttons: { attributes: {}, content: \"buttons\" },\n Button: {\n attributes: {\n type: { type: \"string\", required: true, values: WHATSAPP_BUTTON_TYPES },\n url: { type: \"string\" },\n phone: { type: \"string\" },\n },\n content: \"verbatim\",\n },\n If: { attributes: { test: { type: \"string\", required: true } }, content: \"verbatim-if\" },\n Else: { attributes: {}, content: \"verbatim\" },\n} as const satisfies Readonly<Partial<Record<SmlTag, TagSpec>>>;\n\n/** Per-channel tag tables — the root tag picks one for the whole document. */\nexport const CHANNEL_TAG_SPECS: Readonly<\n Record<Channel, Readonly<Partial<Record<SmlTag, TagSpec>>>>\n> = {\n email: EMAIL_TAG_SPECS,\n sms: SMS_TAG_SPECS,\n whatsapp: WHATSAPP_TAG_SPECS,\n};\n\n/**\n * Editor-only attributes accepted on every email block-level tag on top of the\n * tag's own spec. The compiler ignores both, so neither ever reaches HTML:\n * `label` renames a node in the layout navigator; `hidden=\"true\"` drops it from\n * the compiled artifact while it stays in the source. Email-only — sms/whatsapp\n * compile has no strip pass, so their tags keep their own specs verbatim.\n */\nexport const UNIVERSAL_ATTRIBUTES = {\n label: { type: \"string\" },\n hidden: { type: \"string\", values: [\"true\"] },\n} as const satisfies Readonly<Record<string, AttributeSpec>>;\n\n/** Email tags a user manipulates as layers — the ones the universal attributes attach to. */\nexport const UNIVERSAL_ATTRIBUTE_TAGS: ReadonlySet<SmlTag> = new Set([\n \"Container\",\n \"Section\",\n \"Row\",\n \"Column\",\n \"Heading\",\n \"Text\",\n \"Button\",\n \"Image\",\n \"Divider\",\n \"Spacer\",\n \"List\",\n \"ListItem\",\n \"If\",\n \"Else\",\n]);\n\nconst universalAttributeSpec = (name: string): AttributeSpec | undefined =>\n Object.hasOwn(UNIVERSAL_ATTRIBUTES, name)\n ? UNIVERSAL_ATTRIBUTES[name as keyof typeof UNIVERSAL_ATTRIBUTES]\n : undefined;\n\nconst BLOCK_CHILDREN: ReadonlyArray<SmlTag> = [\n \"Container\",\n \"Section\",\n \"Row\",\n \"Heading\",\n \"Text\",\n \"Button\",\n \"Image\",\n \"Divider\",\n \"Spacer\",\n \"List\",\n \"If\",\n];\n\nconst EMAIL_ENVELOPE_CHILDREN: ReadonlyArray<SmlTag> = [\"Subject\", \"Preview\"];\n\n/** Tags a content model admits as direct children (public alongside the spec tables). */\nexport const allowedChildren = (model: ContentModel): ReadonlyArray<SmlTag> => {\n switch (model) {\n case \"email\":\n return [...EMAIL_ENVELOPE_CHILDREN, ...BLOCK_CHILDREN];\n case \"block\":\n return BLOCK_CHILDREN;\n case \"columns\":\n return [\"Column\"];\n case \"list\":\n return [\"ListItem\"];\n case \"image\":\n return [\"Source\"];\n case \"if\":\n return [...BLOCK_CHILDREN, \"Else\"];\n case \"inline\":\n return [\"strong\", \"em\", \"a\"];\n case \"inline-nolink\":\n return [\"strong\", \"em\"];\n case \"whatsapp\":\n return [\"Header\", \"Body\", \"Footer\", \"Buttons\"];\n case \"buttons\":\n return [\"Button\"];\n case \"verbatim\":\n return [\"If\"];\n case \"verbatim-if\":\n return [\"If\", \"Else\"];\n case \"preview\":\n case \"void\":\n return [];\n }\n};\n\nconst isInlineModel = (model: ContentModel): boolean =>\n model === \"inline\" || model === \"inline-nolink\" || model === \"preview\";\n\nconst isVerbatimModel = (model: ContentModel): boolean =>\n model === \"verbatim\" || model === \"verbatim-if\";\n\n/** Models whose direct children may be text / interpolations. */\nconst admitsText = (model: ContentModel): boolean => isInlineModel(model) || isVerbatimModel(model);\n\nconst listTags = (tags: ReadonlyArray<SmlTag>): string => tags.map((tag) => `<${tag}>`).join(\", \");\n\n/** Wrapper tag suggested when a loose child isn't allowed directly under its model. */\nconst WRAPPER_TAG_BY_MODEL: Partial<Record<ContentModel, string>> = {\n list: \"ListItem\",\n whatsapp: \"Body\",\n buttons: \"Button\",\n};\n\ninterface MutableElement {\n kind: \"element\";\n id: string;\n tag: SmlTag;\n attributes: Array<Attribute>;\n children: Array<SmlNode>;\n span: { start: number; end: number };\n}\n\nexport const parseSml = (source: string): Result.Result<SmlDocument, SmlParseError> => {\n const lexer = createLexer(source);\n let diagnostic: Diagnostic | undefined;\n // Set when the root tag is read; every spec lookup goes through the\n // channel's own table from then on.\n let channel: Channel | null = null;\n let specs: Readonly<Partial<Record<SmlTag, TagSpec>>> = EMAIL_TAG_SPECS;\n\n const fail = (code: string, message: string, span: Span): null => {\n diagnostic ??= { code, message, span };\n return null;\n };\n\n const failToken = (token: Token): null => {\n const error = token.error ?? {\n code: \"unexpected-token\",\n message: `unexpected ${JSON.stringify(lexer.getText(token))}`,\n };\n return fail(error.code, error.message, { start: token.start, end: token.end });\n };\n\n const parseAttributeValue = (\n tag: SmlTag,\n name: string,\n spec: AttributeSpec,\n nameToken: Token,\n ): { value: AttributeValue; valueSpan: Span } | null => {\n const nameSpan: Span = { start: nameToken.start, end: nameToken.end };\n const equalsToken = lexer.scanTagToken();\n if (equalsToken.kind !== TokenKind.Equals) {\n return fail(\n \"missing-attribute-value\",\n `attribute \"${name}\" needs a value — write ${name}=${spec.type === \"number\" ? `{${spec.min ?? 1}}` : '\"...\"'}`,\n nameSpan,\n );\n }\n const valueToken = lexer.scanTagToken();\n if (valueToken.kind === TokenKind.Unknown) return failToken(valueToken);\n\n if (valueToken.kind === TokenKind.Identifier || valueToken.kind === TokenKind.Number) {\n const raw = lexer.getText(valueToken);\n const suggestion = spec.type === \"number\" ? `${name}={${raw}}` : `${name}=\"${raw}\"`;\n return fail(\"unquoted-attribute\", `attribute values must be quoted — write ${suggestion}`, {\n start: valueToken.start,\n end: valueToken.end,\n });\n }\n\n if (valueToken.kind === TokenKind.OpenBrace) {\n const numberToken = lexer.scanTagToken();\n if (numberToken.kind !== TokenKind.Number) {\n return fail(\n \"invalid-attribute-value\",\n `expected a number inside braces — write ${name}={16}`,\n { start: numberToken.start, end: numberToken.end },\n );\n }\n const closeToken = lexer.scanTagToken();\n if (closeToken.kind !== TokenKind.CloseBrace) {\n return fail(\n \"invalid-attribute-value\",\n `missing } after ${name}={${lexer.getText(numberToken)}`,\n {\n start: closeToken.start,\n end: closeToken.end,\n },\n );\n }\n if (spec.type !== \"number\") {\n return fail(\n \"invalid-attribute-value\",\n `\"${name}\" on <${tag}> is a string attribute — write ${name}=\"...\"`,\n { start: valueToken.start, end: closeToken.end },\n );\n }\n const value = Number.parseInt(lexer.getText(numberToken), 10);\n return {\n value: { kind: \"number\", value },\n valueSpan: { start: numberToken.start, end: numberToken.end },\n };\n }\n\n if (valueToken.kind !== TokenKind.String) {\n return fail(\n \"missing-attribute-value\",\n `attribute \"${name}\" needs a value — write ${name}=${spec.type === \"number\" ? \"{16}\" : '\"...\"'}`,\n { start: valueToken.start, end: valueToken.end },\n );\n }\n\n const raw = lexer.getStringValue(valueToken);\n const valueSpan: Span = { start: valueToken.start + 1, end: valueToken.end - 1 };\n if (spec.type === \"number\") {\n if (!/^\\d+$/.test(raw)) {\n return fail(\n \"invalid-attribute-value\",\n `\"${name}\" on <${tag}> must be a number — write ${name}={16}`,\n valueSpan,\n );\n }\n return { value: { kind: \"number\", value: Number.parseInt(raw, 10) }, valueSpan };\n }\n if (spec.values !== undefined && !spec.values.includes(raw)) {\n return fail(\n \"invalid-attribute-value\",\n `\"${name}\" on <${tag}> must be one of ${spec.values.map((value) => `\"${value}\"`).join(\", \")}`,\n valueSpan,\n );\n }\n return { value: { kind: \"string\", value: raw }, valueSpan };\n };\n\n const validateNumberBounds = (\n tag: SmlTag,\n name: string,\n spec: AttributeSpec,\n attribute: Attribute,\n ): boolean => {\n if (attribute.value.kind !== \"number\") return true;\n const { value } = attribute.value;\n if (spec.min !== undefined && value < spec.min) {\n fail(\n \"invalid-attribute-value\",\n `\"${name}\" on <${tag}> must be at least ${spec.min}`,\n attribute.valueSpan,\n );\n return false;\n }\n if (spec.max !== undefined && value > spec.max) {\n fail(\n \"invalid-attribute-value\",\n `\"${name}\" on <${tag}> must be at most ${spec.max}`,\n attribute.valueSpan,\n );\n return false;\n }\n return true;\n };\n\n /** Called with the `<` already consumed; `lessThanToken` is that token. */\n const parseElement = (lessThanToken: Token): MutableElement | null => {\n const tagToken = lexer.scanTagToken();\n if (tagToken.kind !== TokenKind.Identifier) {\n if (tagToken.kind === TokenKind.Unknown) return failToken(tagToken);\n return fail(\"unknown-tag\", \"expected a tag name after <\", {\n start: tagToken.start,\n end: tagToken.end,\n });\n }\n const tagName = lexer.getText(tagToken);\n if (channel === null) {\n // The root tag declares the channel and selects the tag table.\n if (!isRootTag(tagName)) {\n return fail(\n \"invalid-root\",\n `the document root must be ${ROOT_TAGS.map((root) => `<${root}>`).join(\", \")} — the root tag declares the channel`,\n { start: tagToken.start, end: tagToken.end },\n );\n }\n channel = CHANNEL_BY_ROOT_TAG[tagName];\n specs = CHANNEL_TAG_SPECS[channel];\n }\n const tag = SML_TAGS.find((candidate) => candidate === tagName);\n // The channel tables are `as const` (so the typed TSX components can derive\n // precise prop types from them); the parser reads them through the wider\n // TagSpec view.\n const spec: TagSpec | undefined = tag === undefined ? undefined : specs[tag];\n if (tag === undefined || spec === undefined) {\n const channelTags = Object.keys(specs) as ReadonlyArray<SmlTag>;\n const suggestion = channelTags.find(\n (candidate) => candidate.toLowerCase() === tagName.toLowerCase(),\n );\n const hint =\n suggestion !== undefined\n ? `did you mean <${suggestion}>?`\n : tag !== undefined\n ? `<${tag}> is not part of ${channel} documents; ${channel} tags are ${listTags(channelTags)}`\n : `${channel} tags are ${listTags(channelTags)}`;\n return fail(\"unknown-tag\", `unknown tag <${tagName}> — ${hint}`, {\n start: tagToken.start,\n end: tagToken.end,\n });\n }\n\n const element: MutableElement = {\n kind: \"element\",\n id: \"\",\n tag,\n attributes: [],\n children: [],\n span: { start: lessThanToken.start, end: lessThanToken.end },\n };\n\n // ── Attributes + open-tag close ──────────────────────────────────────────\n let selfClosing = false;\n for (;;) {\n const token = lexer.scanTagToken();\n if (token.kind === TokenKind.Identifier) {\n const name = lexer.getText(token);\n // Email block-level tags also accept the universal editor attributes\n // (label/hidden) on top of their own spec.\n const universal = channel === \"email\" && UNIVERSAL_ATTRIBUTE_TAGS.has(tag);\n const attributeSpec =\n spec.attributes[name] ?? (universal ? universalAttributeSpec(name) : undefined);\n if (attributeSpec === undefined) {\n const known = [\n ...Object.keys(spec.attributes),\n ...(universal ? Object.keys(UNIVERSAL_ATTRIBUTES) : []),\n ];\n const hint =\n known.length === 0\n ? `<${tag}> takes no attributes`\n : `<${tag}> accepts ${known.map((k) => `\"${k}\"`).join(\", \")}`;\n return fail(\"unknown-attribute\", `unknown attribute \"${name}\" on <${tag}> — ${hint}`, {\n start: token.start,\n end: token.end,\n });\n }\n if (element.attributes.some((attribute) => attribute.name === name)) {\n return fail(\n \"duplicate-attribute\",\n `duplicate attribute \"${name}\" on <${tag}> — remove one`,\n { start: token.start, end: token.end },\n );\n }\n const parsed = parseAttributeValue(tag, name, attributeSpec, token);\n if (parsed === null) return null;\n const attribute: Attribute = {\n name,\n value: parsed.value,\n span: { start: token.start, end: parsed.valueSpan.end + 1 },\n valueSpan: parsed.valueSpan,\n };\n if (!validateNumberBounds(tag, name, attributeSpec, attribute)) return null;\n element.attributes.push(attribute);\n continue;\n }\n if (token.kind === TokenKind.SlashGreaterThan) {\n selfClosing = true;\n element.span.end = token.end;\n break;\n }\n if (token.kind === TokenKind.GreaterThan) {\n element.span.end = token.end;\n break;\n }\n if (token.kind === TokenKind.EndOfFile) {\n return fail(\"unclosed-tag\", `unclosed <${tag}> — add </${tag}>`, {\n start: lessThanToken.start,\n end: tagToken.end,\n });\n }\n return failToken(token);\n }\n\n for (const [name, attributeSpec] of Object.entries(spec.attributes)) {\n if (attributeSpec.required === true && !element.attributes.some((a) => a.name === name)) {\n const example =\n attributeSpec.type === \"number\"\n ? `${name}={16}`\n : tag === \"If\"\n ? `test=\"plan == 'pro'\"`\n : `${name}=\"...\"`;\n return fail(\n \"missing-attribute\",\n `<${tag}> requires the \"${name}\" attribute — add ${example}`,\n { start: lessThanToken.start, end: element.span.end },\n );\n }\n }\n\n if (tag === \"If\") {\n const testIndex = element.attributes.findIndex((attribute) => attribute.name === \"test\");\n const testAttribute = element.attributes[testIndex]!;\n if (testAttribute.value.kind === \"string\") {\n const parsed = parsePredicate(testAttribute.value.value, testAttribute.valueSpan.start);\n if (Result.isFailure(parsed)) {\n const first = parsed.failure[0]!;\n return fail(first.code, first.message, first.span);\n }\n // Canonicalize the predicate at parse time (like inline whitespace):\n // the stored value is already what the printer emits, so parse ∘ print\n // stays a fixpoint and the compiler embeds it in markers verbatim.\n element.attributes[testIndex] = {\n ...testAttribute,\n value: { kind: \"string\", value: printPredicate(parsed.success) },\n };\n }\n }\n\n if (spec.content === \"void\" && !selfClosing) {\n return fail(\"void-needs-self-close\", `<${tag}> is self-closing — write <${tag} ... />`, {\n start: lessThanToken.start,\n end: element.span.end,\n });\n }\n if (selfClosing) {\n if (spec.content === \"whatsapp\") {\n return fail(\n \"whatsapp-missing-body\",\n \"<WhatsApp> requires a <Body> component — add <Body>...</Body>\",\n element.span,\n );\n }\n return element;\n }\n\n // ── Children per content model ───────────────────────────────────────────\n // Note: <a> appears only in the \"inline\" model (Text/Heading), so links can\n // never nest inside links or buttons — their model is \"inline-nolink\".\n const model = spec.content;\n for (;;) {\n const token = lexer.scanTextToken();\n if (token.kind === TokenKind.EndOfFile) {\n return fail(\"unclosed-tag\", `unclosed <${tag}> — add </${tag}>`, {\n start: lessThanToken.start,\n end: tagToken.end,\n });\n }\n if (token.kind === TokenKind.Unknown) return failToken(token);\n const looseChildMessage = (label: string) => {\n if (model === \"image\") {\n return `${label} is not allowed directly inside <${tag}> — <${tag}> may contain only ${listTags(allowedChildren(model))}`;\n }\n const wrapper = WRAPPER_TAG_BY_MODEL[model] ?? \"Text\";\n return `${label} is not allowed directly inside <${tag}> — wrap it in <${wrapper}>`;\n };\n // In a verbatim <If>, everything after <Else> would be dead content\n // outside both branches — text included (whitespace is content here).\n const afterElse =\n model === \"verbatim-if\" &&\n element.children.some((existing) => existing.kind === \"element\" && existing.tag === \"Else\");\n if (token.kind === TokenKind.Text) {\n const value = lexer.getTextValue(token);\n if (admitsText(model)) {\n if (afterElse) {\n return fail(\n \"else-not-last\",\n \"content after <Else> — move it inside <Else> or above it\",\n { start: token.start, end: token.end },\n );\n }\n element.children.push({\n kind: \"text\",\n id: \"\",\n value,\n span: { start: token.start, end: token.end },\n });\n } else if (value.trim().length > 0) {\n return fail(\"invalid-child\", looseChildMessage(\"text\"), {\n start: token.start,\n end: token.end,\n });\n }\n continue;\n }\n if (token.kind === TokenKind.Interpolation) {\n if (!admitsText(model)) {\n return fail(\"invalid-child\", looseChildMessage(`{{${token.name}}}`), {\n start: token.start,\n end: token.end,\n });\n }\n if (afterElse) {\n return fail(\"else-not-last\", \"content after <Else> — move it inside <Else> or above it\", {\n start: token.start,\n end: token.end,\n });\n }\n element.children.push({\n kind: \"interpolation\",\n id: \"\",\n name: token.name!,\n syntax: token.syntax!,\n span: { start: token.start, end: token.end },\n });\n continue;\n }\n if (token.kind === TokenKind.LessThanSlash) {\n const closeName = lexer.scanTagToken();\n if (closeName.kind !== TokenKind.Identifier || lexer.getText(closeName) !== tag) {\n const found =\n closeName.kind === TokenKind.Identifier ? `</${lexer.getText(closeName)}>` : \"</>\";\n return fail(\n \"mismatched-close-tag\",\n `expected </${tag}> but found ${found} — close <${tag}> first`,\n { start: token.start, end: closeName.end },\n );\n }\n const closeEnd = lexer.scanTagToken();\n if (closeEnd.kind !== TokenKind.GreaterThan) {\n return fail(\"mismatched-close-tag\", `expected > to finish </${tag}>`, {\n start: closeEnd.start,\n end: closeEnd.end,\n });\n }\n element.span.end = closeEnd.end;\n break;\n }\n // token.kind === TokenKind.LessThan — a child element.\n const allowed = allowedChildren(model);\n const child = parseElement(token);\n if (child === null) return null;\n if (!allowed.includes(child.tag)) {\n const message =\n child.tag === \"Else\"\n ? `<Else> is only valid as the last child of <If> — wrap this branch in <If>`\n : isInlineModel(model)\n ? child.tag === \"a\"\n ? `<a> is not allowed inside <${tag}> — links cannot nest inside links or buttons`\n : `<${child.tag}> is not allowed inside <${tag}> — only text, {{variables}}${allowed.length > 0 ? `, and ${listTags(allowed)}` : \"\"} may appear here`\n : `<${child.tag}> is not allowed inside <${tag}> — <${tag}> may contain only ${listTags(allowed)}`;\n return fail(\"invalid-child\", message, child.span);\n }\n if (model === \"email\" && EMAIL_ENVELOPE_CHILDREN.includes(child.tag)) {\n const existingSame = element.children.some(\n (existing) => existing.kind === \"element\" && existing.tag === child.tag,\n );\n if (existingSame) {\n return fail(\n `${child.tag.toLowerCase()}-duplicate`,\n `<Email> allows at most one <${child.tag}> — merge them`,\n child.span,\n );\n }\n const hasBodyBlock = element.children.some(\n (existing) =>\n existing.kind === \"element\" && !EMAIL_ENVELOPE_CHILDREN.includes(existing.tag),\n );\n if (hasBodyBlock) {\n return fail(\n `${child.tag.toLowerCase()}-position`,\n `<${child.tag}> must appear before the body blocks in <Email> — move it above the other blocks`,\n child.span,\n );\n }\n }\n if (\n (model === \"if\" || model === \"verbatim-if\") &&\n element.children.some((existing) => existing.kind === \"element\" && existing.tag === \"Else\")\n ) {\n return fail(\n child.tag === \"Else\" ? \"duplicate-else\" : \"else-not-last\",\n child.tag === \"Else\"\n ? \"only one <Else> is allowed inside <If> — merge the branches\"\n : `<Else> must be the last child of <If> — move <${child.tag}> above it`,\n child.span,\n );\n }\n if (model === \"whatsapp\") {\n // Meta's template anatomy is ordered and single-occurrence:\n // Header? Body Footer? Buttons?.\n const rank: Readonly<Partial<Record<SmlTag, number>>> = {\n Header: 0,\n Body: 1,\n Footer: 2,\n Buttons: 3,\n };\n const childRank = rank[child.tag]!;\n for (const existing of element.children) {\n if (existing.kind !== \"element\") continue;\n if (existing.tag === child.tag) {\n return fail(\n \"whatsapp-duplicate-component\",\n `<WhatsApp> allows at most one <${child.tag}> — merge them`,\n child.span,\n );\n }\n if (rank[existing.tag]! > childRank) {\n return fail(\n \"whatsapp-component-order\",\n `<${child.tag}> must come before <${existing.tag}> — WhatsApp components are Header?, Body, Footer?, Buttons? in that order`,\n child.span,\n );\n }\n }\n }\n if (model === \"image\") {\n const scheme = child.attributes.find((attribute) => attribute.name === \"scheme\");\n if (\n scheme?.value.kind === \"string\" &&\n element.children.some(\n (existing) =>\n existing.kind === \"element\" &&\n existing.tag === \"Source\" &&\n existing.attributes.some(\n (attribute) =>\n attribute.name === \"scheme\" &&\n attribute.value.kind === \"string\" &&\n attribute.value.value === scheme.value.value,\n ),\n )\n ) {\n return fail(\n \"duplicate-source\",\n `<Image> allows one <Source scheme=\"${scheme.value.value}\">; remove the duplicate`,\n child.span,\n );\n }\n }\n element.children.push(child);\n }\n\n if (model === \"whatsapp\") {\n const hasBody = element.children.some(\n (child) => child.kind === \"element\" && child.tag === \"Body\",\n );\n if (!hasBody) {\n return fail(\n \"whatsapp-missing-body\",\n \"<WhatsApp> requires a <Body> component — add <Body>...</Body>\",\n element.span,\n );\n }\n }\n\n if (isInlineModel(model)) normalizeInlineChildren(element);\n return element;\n };\n\n // ── Document ───────────────────────────────────────────────────────────────\n const skipBlankText = (): Token => {\n for (;;) {\n const token = lexer.scanTextToken();\n if (token.kind === TokenKind.Text && lexer.getTextValue(token).trim().length === 0) continue;\n return token;\n }\n };\n\n const first = skipBlankText();\n if (first.kind === TokenKind.Unknown) {\n failToken(first);\n } else if (first.kind !== TokenKind.LessThan) {\n fail(\n \"invalid-root\",\n `the document must start with a channel root element: ${ROOT_TAGS.map((root) => `<${root}>`).join(\", \")}`,\n { start: first.start, end: first.end },\n );\n }\n\n let root: MutableElement | null = null;\n if (diagnostic === undefined) {\n root = parseElement(first);\n }\n if (root !== null) {\n const trailing = skipBlankText();\n if (trailing.kind !== TokenKind.EndOfFile) {\n fail(\n \"multiple-roots\",\n `content after </${root.tag}> — a document has exactly one root element`,\n { start: trailing.start, end: trailing.end },\n );\n root = null;\n }\n }\n\n if (root === null || diagnostic !== undefined) {\n return Result.fail(\n new SmlParseError({\n diagnostics: [\n diagnostic ?? {\n code: \"parse-failed\",\n message: \"parse failed\",\n span: { start: 0, end: 0 },\n },\n ],\n }),\n );\n }\n\n assignNodeIds(root);\n return Result.succeed({ kind: \"document\", root: root as ElementNode });\n};\n\n/**\n * Canonical inline whitespace: collapse runs to a single space, trim at the\n * element boundary, drop text nodes that end up empty. Makes parse ∘ print a\n * fixpoint — the printer emits exactly these values.\n */\nconst normalizeInlineChildren = (element: MutableElement): void => {\n for (const child of element.children) {\n if (child.kind === \"text\") {\n (child as { value: string }).value = child.value.replace(/\\s+/g, \" \");\n }\n }\n const firstChild = element.children[0];\n if (firstChild?.kind === \"text\") {\n (firstChild as { value: string }).value = firstChild.value.replace(/^ /, \"\");\n }\n const lastChild = element.children[element.children.length - 1];\n if (lastChild?.kind === \"text\") {\n (lastChild as { value: string }).value = lastChild.value.replace(/ $/, \"\");\n }\n element.children = element.children.filter(\n (child) => child.kind !== \"text\" || child.value.length > 0,\n );\n};\n\n/** Stable ids in document order — identical source ⇒ identical ids. */\nconst assignNodeIds = (root: MutableElement): void => {\n let next = 0;\n const visit = (node: SmlNode): void => {\n (node as { id: string }).id = `n${next}`;\n next++;\n if (node.kind === \"element\") for (const child of node.children) visit(child);\n };\n visit(root as ElementNode);\n};\n"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { s as Span, t as Diagnostic } from "./diagnostics-oveE-laV.js";
2
+ import { Result } from "effect";
3
+ //#region src/predicate.d.ts
4
+ type ComparisonOp = "==" | "!=" | ">" | ">=" | "<" | "<=";
5
+ type PredicateLiteral = {
6
+ readonly kind: "string";
7
+ readonly value: string;
8
+ } | {
9
+ readonly kind: "number";
10
+ readonly value: number;
11
+ };
12
+ type PredicateAst = {
13
+ readonly kind: "or";
14
+ readonly left: PredicateAst;
15
+ readonly right: PredicateAst;
16
+ readonly span: Span;
17
+ } | {
18
+ readonly kind: "and";
19
+ readonly left: PredicateAst;
20
+ readonly right: PredicateAst;
21
+ readonly span: Span;
22
+ } | {
23
+ readonly kind: "not";
24
+ readonly operand: PredicateAst;
25
+ readonly span: Span;
26
+ } | {
27
+ readonly kind: "comparison";
28
+ readonly name: string;
29
+ readonly op: ComparisonOp;
30
+ readonly literal: PredicateLiteral;
31
+ readonly span: Span;
32
+ } | {
33
+ readonly kind: "name";
34
+ readonly name: string;
35
+ readonly span: Span;
36
+ };
37
+ /**
38
+ * Parses a predicate. `baseOffset` is the absolute source offset of the
39
+ * predicate's first character (an <If> test attribute passes valueSpan.start),
40
+ * so diagnostic and node spans point into the containing document.
41
+ */
42
+ declare const parsePredicate: (source: string, baseOffset?: number) => Result.Result<PredicateAst, ReadonlyArray<Diagnostic>>;
43
+ declare const evaluatePredicate: (ast: PredicateAst, variables: Record<string, unknown>) => boolean;
44
+ /** Canonical form: single space around operators/keywords, minimal parens. */
45
+ declare const printPredicate: (ast: PredicateAst) => string;
46
+ declare const collectPredicateVariables: (ast: PredicateAst) => Set<string>;
47
+ //#endregion
48
+ export { evaluatePredicate as a, collectPredicateVariables as i, PredicateAst as n, parsePredicate as o, PredicateLiteral as r, printPredicate as s, ComparisonOp as t };
49
+ //# sourceMappingURL=predicate-DTtVtLK7.d.ts.map
@@ -0,0 +1,2 @@
1
+ import { a as evaluatePredicate, i as collectPredicateVariables, n as PredicateAst, o as parsePredicate, r as PredicateLiteral, s as printPredicate, t as ComparisonOp } from "./predicate-DTtVtLK7.js";
2
+ export { ComparisonOp, PredicateAst, PredicateLiteral, collectPredicateVariables, evaluatePredicate, parsePredicate, printPredicate };
@@ -0,0 +1,417 @@
1
+ import { Result } from "effect";
2
+ //#region src/predicate.ts
3
+ const CHAR_TAB = 9;
4
+ const CHAR_LINE_FEED = 10;
5
+ const CHAR_CARRIAGE_RETURN = 13;
6
+ const CHAR_SPACE = 32;
7
+ const CHAR_BANG = 33;
8
+ const CHAR_SINGLE_QUOTE = 39;
9
+ const CHAR_OPEN_PAREN = 40;
10
+ const CHAR_CLOSE_PAREN = 41;
11
+ const CHAR_MINUS = 45;
12
+ const CHAR_DOT = 46;
13
+ const CHAR_LESS_THAN = 60;
14
+ const CHAR_EQUALS = 61;
15
+ const CHAR_GREATER_THAN = 62;
16
+ const CHAR_UNDERSCORE = 95;
17
+ const CHAR_0 = 48;
18
+ const CHAR_9 = 57;
19
+ const CHAR_UPPER_A = 65;
20
+ const CHAR_UPPER_Z = 90;
21
+ const CHAR_LOWER_A = 97;
22
+ const CHAR_LOWER_Z = 122;
23
+ const isWhitespace = (code) => code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_LINE_FEED || code === CHAR_CARRIAGE_RETURN;
24
+ const isDigit = (code) => code >= CHAR_0 && code <= CHAR_9;
25
+ const isLetter = (code) => code >= CHAR_UPPER_A && code <= CHAR_UPPER_Z || code >= CHAR_LOWER_A && code <= CHAR_LOWER_Z;
26
+ const isNameStart = (code) => isLetter(code) || code === CHAR_UNDERSCORE;
27
+ const isNameChar = (code) => isNameStart(code) || isDigit(code);
28
+ const KEYWORDS = [
29
+ "and",
30
+ "or",
31
+ "not"
32
+ ];
33
+ const isKeyword = (text) => KEYWORDS.includes(text);
34
+ const tokenize = (source, baseOffset) => {
35
+ const tokens = [];
36
+ let index = 0;
37
+ const length = source.length;
38
+ const failAt = (code, message, start, end) => Result.fail({ diagnostic: {
39
+ code,
40
+ message,
41
+ span: {
42
+ start: baseOffset + start,
43
+ end: baseOffset + end
44
+ }
45
+ } });
46
+ while (index < length) {
47
+ const code = source.charCodeAt(index);
48
+ if (isWhitespace(code)) {
49
+ index++;
50
+ continue;
51
+ }
52
+ const start = index;
53
+ if (code === CHAR_OPEN_PAREN) {
54
+ index++;
55
+ tokens.push({
56
+ kind: "lparen",
57
+ text: "(",
58
+ span: {
59
+ start: baseOffset + start,
60
+ end: baseOffset + index
61
+ }
62
+ });
63
+ continue;
64
+ }
65
+ if (code === CHAR_CLOSE_PAREN) {
66
+ index++;
67
+ tokens.push({
68
+ kind: "rparen",
69
+ text: ")",
70
+ span: {
71
+ start: baseOffset + start,
72
+ end: baseOffset + index
73
+ }
74
+ });
75
+ continue;
76
+ }
77
+ if (code === CHAR_EQUALS || code === CHAR_BANG) {
78
+ if (source.charCodeAt(index + 1) === CHAR_EQUALS) {
79
+ index += 2;
80
+ tokens.push({
81
+ kind: "op",
82
+ text: code === CHAR_EQUALS ? "==" : "!=",
83
+ span: {
84
+ start: baseOffset + start,
85
+ end: baseOffset + index
86
+ }
87
+ });
88
+ continue;
89
+ }
90
+ return failAt("predicate-invalid-operator", `unexpected ${String.fromCharCode(code)} — ${code === CHAR_EQUALS ? "write == to compare" : "write != to compare"}`, start, start + 1);
91
+ }
92
+ if (code === CHAR_GREATER_THAN || code === CHAR_LESS_THAN) {
93
+ index++;
94
+ if (source.charCodeAt(index) === CHAR_EQUALS) index++;
95
+ tokens.push({
96
+ kind: "op",
97
+ text: source.slice(start, index),
98
+ span: {
99
+ start: baseOffset + start,
100
+ end: baseOffset + index
101
+ }
102
+ });
103
+ continue;
104
+ }
105
+ if (code === CHAR_SINGLE_QUOTE) {
106
+ index++;
107
+ const contentStart = index;
108
+ while (index < length && source.charCodeAt(index) !== CHAR_SINGLE_QUOTE) index++;
109
+ if (index >= length) return failAt("predicate-unterminated-string", "unterminated string — add a closing '", start, length);
110
+ const value = source.slice(contentStart, index);
111
+ index++;
112
+ if (value.includes("--")) return failAt("predicate-string-double-dash", "string literals cannot contain -- (predicates are embedded in HTML comment markers)", start, index);
113
+ tokens.push({
114
+ kind: "string",
115
+ text: source.slice(start, index),
116
+ string: value,
117
+ span: {
118
+ start: baseOffset + start,
119
+ end: baseOffset + index
120
+ }
121
+ });
122
+ continue;
123
+ }
124
+ if (isDigit(code) || code === CHAR_MINUS && isDigit(source.charCodeAt(index + 1))) {
125
+ index++;
126
+ while (index < length && isDigit(source.charCodeAt(index))) index++;
127
+ if (source.charCodeAt(index) === CHAR_DOT && isDigit(source.charCodeAt(index + 1))) {
128
+ index++;
129
+ while (index < length && isDigit(source.charCodeAt(index))) index++;
130
+ }
131
+ tokens.push({
132
+ kind: "number",
133
+ text: source.slice(start, index),
134
+ number: Number.parseFloat(source.slice(start, index)),
135
+ span: {
136
+ start: baseOffset + start,
137
+ end: baseOffset + index
138
+ }
139
+ });
140
+ continue;
141
+ }
142
+ if (isNameStart(code)) {
143
+ index++;
144
+ while (index < length && isNameChar(source.charCodeAt(index))) index++;
145
+ if (source.charCodeAt(index) === CHAR_DOT) return failAt("predicate-path-not-supported", `unexpected . after "${source.slice(start, index)}" — predicates use flat variable names, not paths`, start, index + 1);
146
+ tokens.push({
147
+ kind: "name",
148
+ text: source.slice(start, index),
149
+ span: {
150
+ start: baseOffset + start,
151
+ end: baseOffset + index
152
+ }
153
+ });
154
+ continue;
155
+ }
156
+ if (code === CHAR_DOT) return failAt("predicate-path-not-supported", "unexpected . — predicates use flat variable names, not paths", start, start + 1);
157
+ return failAt("predicate-unexpected-character", `unexpected character ${JSON.stringify(String.fromCharCode(code))} in predicate`, start, start + 1);
158
+ }
159
+ tokens.push({
160
+ kind: "eof",
161
+ text: "",
162
+ span: {
163
+ start: baseOffset + length,
164
+ end: baseOffset + length
165
+ }
166
+ });
167
+ return Result.succeed(tokens);
168
+ };
169
+ const COMPARISON_OPS = [
170
+ "==",
171
+ "!=",
172
+ ">",
173
+ ">=",
174
+ "<",
175
+ "<="
176
+ ];
177
+ /**
178
+ * Parses a predicate. `baseOffset` is the absolute source offset of the
179
+ * predicate's first character (an <If> test attribute passes valueSpan.start),
180
+ * so diagnostic and node spans point into the containing document.
181
+ */
182
+ const parsePredicate = (source, baseOffset = 0) => {
183
+ const lexed = tokenize(source, baseOffset);
184
+ if (Result.isFailure(lexed)) return Result.fail([lexed.failure.diagnostic]);
185
+ const tokens = lexed.success;
186
+ let index = 0;
187
+ let diagnostic;
188
+ const fail = (code, message, span) => {
189
+ diagnostic ??= {
190
+ code,
191
+ message,
192
+ span
193
+ };
194
+ return null;
195
+ };
196
+ const peek = () => tokens[index];
197
+ const advance = () => tokens[index++];
198
+ const isKeywordToken = (token, keyword) => token.kind === "name" && token.text === keyword;
199
+ const parsePrimary = () => {
200
+ const token = advance();
201
+ if (token.kind === "lparen") {
202
+ const inner = parseOr();
203
+ if (inner === null) return null;
204
+ const close = advance();
205
+ if (close.kind !== "rparen") return fail("predicate-missing-paren", "missing ) — close the group", close.span);
206
+ return inner;
207
+ }
208
+ if (token.kind === "name") {
209
+ if (isKeyword(token.text)) return fail("predicate-unexpected-token", `unexpected "${token.text}" — expected a variable name, comparison, or ( group )`, token.span);
210
+ const opToken = peek();
211
+ if (opToken.kind !== "op") return {
212
+ kind: "name",
213
+ name: token.text,
214
+ span: token.span
215
+ };
216
+ advance();
217
+ const op = COMPARISON_OPS.find((candidate) => candidate === opToken.text);
218
+ if (op === void 0) return fail("predicate-invalid-operator", `unknown operator ${opToken.text} — use ==, !=, >, >=, <, or <=`, opToken.span);
219
+ const literalToken = advance();
220
+ if (literalToken.kind === "string") return {
221
+ kind: "comparison",
222
+ name: token.text,
223
+ op,
224
+ literal: {
225
+ kind: "string",
226
+ value: literalToken.string
227
+ },
228
+ span: {
229
+ start: token.span.start,
230
+ end: literalToken.span.end
231
+ }
232
+ };
233
+ if (literalToken.kind === "number") return {
234
+ kind: "comparison",
235
+ name: token.text,
236
+ op,
237
+ literal: {
238
+ kind: "number",
239
+ value: literalToken.number
240
+ },
241
+ span: {
242
+ start: token.span.start,
243
+ end: literalToken.span.end
244
+ }
245
+ };
246
+ return fail("predicate-expected-literal", `expected a literal after ${op} — write ${token.text} ${op} 'value' or ${token.text} ${op} 5`, literalToken.span);
247
+ }
248
+ if (token.kind === "eof") return fail("predicate-unexpected-end", "unexpected end of predicate — expected a variable name, comparison, or ( group )", token.span);
249
+ return fail("predicate-unexpected-token", `unexpected ${token.text} — comparisons put the variable first: name == 'value'`, token.span);
250
+ };
251
+ const parseUnary = () => {
252
+ const token = peek();
253
+ if (isKeywordToken(token, "not")) {
254
+ advance();
255
+ const operand = parsePrimary();
256
+ if (operand === null) return null;
257
+ return {
258
+ kind: "not",
259
+ operand,
260
+ span: {
261
+ start: token.span.start,
262
+ end: operand.span.end
263
+ }
264
+ };
265
+ }
266
+ return parsePrimary();
267
+ };
268
+ const parseAnd = () => {
269
+ let left = parseUnary();
270
+ while (left !== null && isKeywordToken(peek(), "and")) {
271
+ advance();
272
+ const right = parseUnary();
273
+ if (right === null) return null;
274
+ left = {
275
+ kind: "and",
276
+ left,
277
+ right,
278
+ span: {
279
+ start: left.span.start,
280
+ end: right.span.end
281
+ }
282
+ };
283
+ }
284
+ return left;
285
+ };
286
+ const parseOr = () => {
287
+ let left = parseAnd();
288
+ while (left !== null && isKeywordToken(peek(), "or")) {
289
+ advance();
290
+ const right = parseAnd();
291
+ if (right === null) return null;
292
+ left = {
293
+ kind: "or",
294
+ left,
295
+ right,
296
+ span: {
297
+ start: left.span.start,
298
+ end: right.span.end
299
+ }
300
+ };
301
+ }
302
+ return left;
303
+ };
304
+ if (source.trim().length === 0) return Result.fail([{
305
+ code: "predicate-empty",
306
+ message: "empty predicate — write e.g. plan == 'pro'",
307
+ span: {
308
+ start: baseOffset,
309
+ end: baseOffset + source.length
310
+ }
311
+ }]);
312
+ const ast = parseOr();
313
+ if (ast !== null && diagnostic === void 0) {
314
+ const trailing = peek();
315
+ if (trailing.kind !== "eof") fail("predicate-trailing-input", `unexpected ${JSON.stringify(trailing.text)} after the predicate — join clauses with "and" or "or"`, trailing.span);
316
+ }
317
+ if (ast === null || diagnostic !== void 0) return Result.fail([diagnostic ?? {
318
+ code: "predicate-parse-failed",
319
+ message: "predicate parse failed",
320
+ span: {
321
+ start: baseOffset,
322
+ end: baseOffset + source.length
323
+ }
324
+ }]);
325
+ return Result.succeed(ast);
326
+ };
327
+ const toNumber = (value) => {
328
+ if (typeof value === "number") return value;
329
+ if (typeof value === "string" && value.trim() !== "") return Number(value);
330
+ return NaN;
331
+ };
332
+ const compareNumbers = (op, left, right) => {
333
+ switch (op) {
334
+ case "==": return left === right;
335
+ case "!=": return left !== right;
336
+ case ">": return left > right;
337
+ case ">=": return left >= right;
338
+ case "<": return left < right;
339
+ case "<=": return left <= right;
340
+ }
341
+ };
342
+ const evaluateComparison = (node, variables) => {
343
+ const value = variables[node.name];
344
+ if (value === void 0 || value === null) return false;
345
+ if (node.literal.kind === "number") {
346
+ const left = toNumber(value);
347
+ if (Number.isNaN(left)) return false;
348
+ return compareNumbers(node.op, left, node.literal.value);
349
+ }
350
+ if (node.op === "==" || node.op === "!=") {
351
+ if (typeof value !== "string") return false;
352
+ return node.op === "==" ? value === node.literal.value : value !== node.literal.value;
353
+ }
354
+ const left = toNumber(value);
355
+ const right = toNumber(node.literal.value);
356
+ if (Number.isNaN(left) || Number.isNaN(right)) return false;
357
+ return compareNumbers(node.op, left, right);
358
+ };
359
+ const evaluatePredicate = (ast, variables) => {
360
+ switch (ast.kind) {
361
+ case "or": return evaluatePredicate(ast.left, variables) || evaluatePredicate(ast.right, variables);
362
+ case "and": return evaluatePredicate(ast.left, variables) && evaluatePredicate(ast.right, variables);
363
+ case "not": return !evaluatePredicate(ast.operand, variables);
364
+ case "comparison": return evaluateComparison(ast, variables);
365
+ case "name": {
366
+ const value = variables[ast.name];
367
+ return value !== void 0 && value !== null && value !== "";
368
+ }
369
+ }
370
+ };
371
+ const precedence = (node) => {
372
+ switch (node.kind) {
373
+ case "or": return 1;
374
+ case "and": return 2;
375
+ case "not": return 3;
376
+ case "comparison":
377
+ case "name": return 4;
378
+ }
379
+ };
380
+ const printLiteral = (literal) => literal.kind === "string" ? `'${literal.value}'` : String(literal.value);
381
+ const printNode = (node, minPrecedence) => {
382
+ const wrap = (text) => precedence(node) < minPrecedence ? `(${text})` : text;
383
+ switch (node.kind) {
384
+ case "or": return wrap(`${printNode(node.left, 1)} or ${printNode(node.right, 1)}`);
385
+ case "and": return wrap(`${printNode(node.left, 2)} and ${printNode(node.right, 2)}`);
386
+ case "not": return wrap(`not ${printNode(node.operand, 4)}`);
387
+ case "comparison": return wrap(`${node.name} ${node.op} ${printLiteral(node.literal)}`);
388
+ case "name": return wrap(node.name);
389
+ }
390
+ };
391
+ /** Canonical form: single space around operators/keywords, minimal parens. */
392
+ const printPredicate = (ast) => printNode(ast, 0);
393
+ const collectPredicateVariables = (ast) => {
394
+ const names = /* @__PURE__ */ new Set();
395
+ const visit = (node) => {
396
+ switch (node.kind) {
397
+ case "or":
398
+ case "and":
399
+ visit(node.left);
400
+ visit(node.right);
401
+ return;
402
+ case "not":
403
+ visit(node.operand);
404
+ return;
405
+ case "comparison":
406
+ case "name":
407
+ names.add(node.name);
408
+ return;
409
+ }
410
+ };
411
+ visit(ast);
412
+ return names;
413
+ };
414
+ //#endregion
415
+ export { collectPredicateVariables, evaluatePredicate, parsePredicate, printPredicate };
416
+
417
+ //# sourceMappingURL=predicate.js.map