@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,27 @@
1
+ import { Schema } from "effect";
2
+ //#region src/interpolate.d.ts
3
+ declare const DocumentVariableTypeSchema: Schema.Literals<readonly ["string", "number", "boolean", "date", "array", "object"]>;
4
+ type DocumentVariableType = typeof DocumentVariableTypeSchema.Type;
5
+ /** One entry of a template's declared variable schema. */
6
+ declare const DocumentVariableSchema: Schema.Struct<{
7
+ readonly name: Schema.String;
8
+ readonly type: Schema.Literals<readonly ["string", "number", "boolean", "date", "array", "object"]>;
9
+ readonly required: Schema.Boolean;
10
+ readonly default: Schema.optional<Schema.Unknown>;
11
+ readonly description: Schema.optional<Schema.String>;
12
+ }>;
13
+ interface DocumentVariable extends Schema.Schema.Type<typeof DocumentVariableSchema> {}
14
+ declare const variableReferencePattern: RegExp;
15
+ declare const getVariableValue: (variables: Record<string, unknown>, variableSchema: ReadonlyArray<DocumentVariable>, name: string) => unknown;
16
+ declare const stringifyVariable: (value: unknown) => string;
17
+ interface InterpolationResult {
18
+ readonly rendered: string;
19
+ /** Required variables that resolved to nothing (no value, no default). */
20
+ readonly missing: ReadonlySet<string>;
21
+ }
22
+ declare const interpolate: (text: string, variables: Record<string, unknown>, variableSchema: ReadonlyArray<DocumentVariable>) => InterpolationResult;
23
+ declare const variableReferences: (text: string) => Set<string>;
24
+ declare const addVariableReferences: (target: Set<string>, text: string) => void;
25
+ //#endregion
26
+ export { DocumentVariable, DocumentVariableSchema, DocumentVariableType, DocumentVariableTypeSchema, InterpolationResult, addVariableReferences, getVariableValue, interpolate, stringifyVariable, variableReferencePattern, variableReferences };
27
+ //# sourceMappingURL=interpolate.d.ts.map
@@ -0,0 +1,61 @@
1
+ import { Schema } from "effect";
2
+ //#region src/interpolate.ts
3
+ const limitedString = (maxLength) => Schema.String.check(Schema.isMaxLength(maxLength));
4
+ const DocumentVariableTypeSchema = Schema.Literals([
5
+ "string",
6
+ "number",
7
+ "boolean",
8
+ "date",
9
+ "array",
10
+ "object"
11
+ ]);
12
+ /** One entry of a template's declared variable schema. */
13
+ const DocumentVariableSchema = Schema.Struct({
14
+ name: limitedString(128),
15
+ type: DocumentVariableTypeSchema,
16
+ required: Schema.Boolean,
17
+ default: Schema.optional(Schema.Unknown),
18
+ description: Schema.optional(limitedString(512))
19
+ });
20
+ const variableReferencePattern = /\$\{\s*\/?([\w.]+)\s*\}|\{\{\s*([\w.]+)\s*\}\}/g;
21
+ const getVariableValue = (variables, variableSchema, name) => {
22
+ const schema = variableSchema.find((item) => item.name === name);
23
+ const value = variables[name];
24
+ if (value !== void 0 && value !== null) return value;
25
+ return schema?.default;
26
+ };
27
+ const stringifyVariable = (value) => {
28
+ if (value === void 0 || value === null) return "";
29
+ if (value instanceof Date) return value.toISOString();
30
+ if (Array.isArray(value)) return value.join(", ");
31
+ if (typeof value === "object") return JSON.stringify(value);
32
+ return String(value);
33
+ };
34
+ const interpolate = (text, variables, variableSchema) => {
35
+ const missing = /* @__PURE__ */ new Set();
36
+ const substitute = (name) => {
37
+ const value = getVariableValue(variables, variableSchema, name);
38
+ const schema = variableSchema.find((item) => item.name === name);
39
+ if ((value === void 0 || value === null) && schema?.required) missing.add(name);
40
+ return stringifyVariable(value);
41
+ };
42
+ return {
43
+ rendered: text.replace(variableReferencePattern, (_match, dollarName, braceName) => substitute(dollarName ?? braceName ?? "")),
44
+ missing
45
+ };
46
+ };
47
+ const variableReferences = (text) => {
48
+ const names = /* @__PURE__ */ new Set();
49
+ for (const match of text.matchAll(variableReferencePattern)) {
50
+ const name = match[1] ?? match[2];
51
+ if (name !== void 0 && name !== "") names.add(name);
52
+ }
53
+ return names;
54
+ };
55
+ const addVariableReferences = (target, text) => {
56
+ variableReferences(text).forEach((name) => target.add(name));
57
+ };
58
+ //#endregion
59
+ export { DocumentVariableSchema, DocumentVariableTypeSchema, addVariableReferences, getVariableValue, interpolate, stringifyVariable, variableReferencePattern, variableReferences };
60
+
61
+ //# sourceMappingURL=interpolate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolate.js","names":[],"sources":["../src/interpolate.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\n// Send-time `{{name}}` / `${name}` interpolation. Originally ported from\n// @samva/document (variables.ts); now a single left-to-right pass over both\n// syntaxes (see `variableReferencePattern`) so a substituted value is never\n// re-scanned — a variable whose value itself contains `{{x}}`/`${x}` is emitted\n// literally instead of being recursively interpolated. Channel-agnostic: it\n// runs over compiled email artifacts, sms bodies, and whatsapp component text\n// alike, always AFTER resolveArtifactConditionals has selected branches and\n// stripped markers.\n\nconst limitedString = (maxLength: number) => Schema.String.check(Schema.isMaxLength(maxLength));\n\nexport const DocumentVariableTypeSchema = Schema.Literals([\n \"string\",\n \"number\",\n \"boolean\",\n \"date\",\n \"array\",\n \"object\",\n]);\n\nexport type DocumentVariableType = typeof DocumentVariableTypeSchema.Type;\n\n/** One entry of a template's declared variable schema. */\nexport const DocumentVariableSchema = Schema.Struct({\n name: limitedString(128),\n type: DocumentVariableTypeSchema,\n required: Schema.Boolean,\n default: Schema.optional(Schema.Unknown),\n description: Schema.optional(limitedString(512)),\n});\n\nexport interface DocumentVariable extends Schema.Schema.Type<typeof DocumentVariableSchema> {}\n\nexport const variableReferencePattern = /\\$\\{\\s*\\/?([\\w.]+)\\s*\\}|\\{\\{\\s*([\\w.]+)\\s*\\}\\}/g;\n\nexport const getVariableValue = (\n variables: Record<string, unknown>,\n variableSchema: ReadonlyArray<DocumentVariable>,\n name: string,\n): unknown => {\n const schema = variableSchema.find((item) => item.name === name);\n const value = variables[name];\n if (value !== undefined && value !== null) return value;\n return schema?.default;\n};\n\nexport const stringifyVariable = (value: unknown): string => {\n if (value === undefined || value === null) return \"\";\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value)) return value.join(\", \");\n if (typeof value === \"object\") return JSON.stringify(value);\n return String(value);\n};\n\nexport interface InterpolationResult {\n readonly rendered: string;\n /** Required variables that resolved to nothing (no value, no default). */\n readonly missing: ReadonlySet<string>;\n}\n\nexport const interpolate = (\n text: string,\n variables: Record<string, unknown>,\n variableSchema: ReadonlyArray<DocumentVariable>,\n): InterpolationResult => {\n const missing = new Set<string>();\n const substitute = (name: string): string => {\n const value = getVariableValue(variables, variableSchema, name);\n const schema = variableSchema.find((item) => item.name === name);\n if ((value === undefined || value === null) && schema?.required) missing.add(name);\n return stringifyVariable(value);\n };\n // One pass over the combined pattern: group 1 is the `${…}` name, group 2 the\n // `{{…}}` name; exactly one is set per match. `replace` resets the shared\n // pattern's lastIndex, so sharing it with `variableReferences` is safe.\n const rendered = text.replace(\n variableReferencePattern,\n (_match, dollarName: string | undefined, braceName: string | undefined) =>\n substitute(dollarName ?? braceName ?? \"\"),\n );\n return { rendered, missing };\n};\n\nexport const variableReferences = (text: string): Set<string> => {\n const names = new Set<string>();\n for (const match of text.matchAll(variableReferencePattern)) {\n const name = match[1] ?? match[2];\n if (name !== undefined && name !== \"\") names.add(name);\n }\n return names;\n};\n\nexport const addVariableReferences = (target: Set<string>, text: string): void => {\n variableReferences(text).forEach((name) => target.add(name));\n};\n"],"mappings":";;AAWA,MAAM,iBAAiB,cAAsB,OAAO,OAAO,MAAM,OAAO,YAAY,SAAS,CAAC;AAE9F,MAAa,6BAA6B,OAAO,SAAS;CACxD;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAKD,MAAa,yBAAyB,OAAO,OAAO;CAClD,MAAM,cAAc,GAAG;CACvB,MAAM;CACN,UAAU,OAAO;CACjB,SAAS,OAAO,SAAS,OAAO,OAAO;CACvC,aAAa,OAAO,SAAS,cAAc,GAAG,CAAC;AACjD,CAAC;AAID,MAAa,2BAA2B;AAExC,MAAa,oBACX,WACA,gBACA,SACY;CACZ,MAAM,SAAS,eAAe,MAAM,SAAS,KAAK,SAAS,IAAI;CAC/D,MAAM,QAAQ,UAAU;CACxB,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO;CAClD,OAAO,QAAQ;AACjB;AAEA,MAAa,qBAAqB,UAA2B;CAC3D,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO;CAClD,IAAI,iBAAiB,MAAM,OAAO,MAAM,YAAY;CACpD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,KAAK,IAAI;CAChD,IAAI,OAAO,UAAU,UAAU,OAAO,KAAK,UAAU,KAAK;CAC1D,OAAO,OAAO,KAAK;AACrB;AAQA,MAAa,eACX,MACA,WACA,mBACwB;CACxB,MAAM,0BAAU,IAAI,IAAY;CAChC,MAAM,cAAc,SAAyB;EAC3C,MAAM,QAAQ,iBAAiB,WAAW,gBAAgB,IAAI;EAC9D,MAAM,SAAS,eAAe,MAAM,SAAS,KAAK,SAAS,IAAI;EAC/D,KAAK,UAAU,KAAA,KAAa,UAAU,SAAS,QAAQ,UAAU,QAAQ,IAAI,IAAI;EACjF,OAAO,kBAAkB,KAAK;CAChC;CASA,OAAO;EAAE,UALQ,KAAK,QACpB,2BACC,QAAQ,YAAgC,cACvC,WAAW,cAAc,aAAa,EAAE,CAE5B;EAAG;CAAQ;AAC7B;AAEA,MAAa,sBAAsB,SAA8B;CAC/D,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,SAAS,KAAK,SAAS,wBAAwB,GAAG;EAC3D,MAAM,OAAO,MAAM,MAAM,MAAM;EAC/B,IAAI,SAAS,KAAA,KAAa,SAAS,IAAI,MAAM,IAAI,IAAI;CACvD;CACA,OAAO;AACT;AAEA,MAAa,yBAAyB,QAAqB,SAAuB;CAChF,mBAAmB,IAAI,CAAC,CAAC,SAAS,SAAS,OAAO,IAAI,IAAI,CAAC;AAC7D"}
@@ -0,0 +1,10 @@
1
+ import { a as JsxResult, i as JSX, n as Fragment, t as Component } from "./jsx-runtime-fSxcvlx4.js";
2
+ //#region src/jsx-dev-runtime.d.ts
3
+ interface JsxDevProps {
4
+ readonly children?: unknown;
5
+ readonly [key: string]: unknown;
6
+ }
7
+ declare const jsxDEV: (type: Component<JsxDevProps> | typeof Fragment, props: JsxDevProps) => JsxResult;
8
+ //#endregion
9
+ export { Fragment, type JSX, jsxDEV };
10
+ //# sourceMappingURL=jsx-dev-runtime.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { Fragment, jsx } from "./jsx-runtime.js";
2
+ //#region src/jsx-dev-runtime.ts
3
+ const jsxDEV = (type, props) => jsx(type, props);
4
+ //#endregion
5
+ export { Fragment, jsxDEV };
6
+
7
+ //# sourceMappingURL=jsx-dev-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-dev-runtime.js","names":[],"sources":["../src/jsx-dev-runtime.ts"],"sourcesContent":["import type { Component } from \"./jsx-runtime\";\nimport { Fragment, jsx, type JsxResult } from \"./jsx-runtime\";\n\n// Development JSX runtime. The dev transform emits `jsxDEV(...)` with extra\n// debug arguments (key, isStaticChildren, source, self) that we don't need —\n// SML has no runtime reconciliation, only build-time construction. We forward to\n// the production `jsx`. Fragment + the JSX namespace re-export so tooling that\n// imports the dev runtime sees the same types.\n\nexport { Fragment } from \"./jsx-runtime\";\nexport type { JSX } from \"./jsx-runtime\";\n\ninterface JsxDevProps {\n readonly children?: unknown;\n readonly [key: string]: unknown;\n}\n\nexport const jsxDEV = (\n type: Component<JsxDevProps> | typeof Fragment,\n props: JsxDevProps,\n): JsxResult => jsx(type, props);\n"],"mappings":";;AAiBA,MAAa,UACX,MACA,UACc,IAAI,MAAM,KAAK"}
@@ -0,0 +1,57 @@
1
+ import { T as SmlNode, m as ElementNode, n as Attribute } from "./ast-DAhnAvQN.js";
2
+ import { s as VariableRef } from "./variables-3NGLpNTB.js";
3
+ //#region src/jsx-runtime.d.ts
4
+ /** A component is a plain function of its props that returns a draft node. */
5
+ type Component<P> = (props: P) => SmlNode;
6
+ /**
7
+ * What may appear in a component's `children`. Booleans/null/undefined are
8
+ * skipped (so `{cond && <X/>}` and `{cond ? <A/> : null}` are BUILD-TIME
9
+ * conditionals that evaluate away); strings/numbers become text; VariableRefs
10
+ * become `{{interpolation}}` nodes; arrays flatten; nested nodes nest. Anything
11
+ * else is a typed authoring error.
12
+ */
13
+ type SmlChild = SmlNode | FragmentDraft | VariableRef<unknown> | string | number | boolean | null | undefined | ReadonlyArray<SmlChild>;
14
+ type SmlChildren = SmlChild | ReadonlyArray<SmlChild>;
15
+ /** Fragment children pass through un-wrapped — a Fragment is not an element. */
16
+ interface FragmentDraft {
17
+ readonly kind: "fragment";
18
+ readonly children: ReadonlyArray<SmlNode>;
19
+ }
20
+ type JsxResult = SmlNode | FragmentDraft;
21
+ /**
22
+ * Flattens and normalizes a component's raw `children` prop into AST child
23
+ * nodes. Fragments splice their children in; booleans/null/undefined drop out.
24
+ */
25
+ declare const normalizeChildren: (children: unknown) => Array<SmlNode>;
26
+ /**
27
+ * Builds an element draft from a tag, attribute list (already in authored
28
+ * order), and normalized children. Kept here so the runtime owns the AST shape;
29
+ * ./components maps typed props onto attributes and calls this.
30
+ */
31
+ declare const elementDraft: (tag: ElementNode["tag"], attributes: ReadonlyArray<Attribute>, children: ReadonlyArray<SmlNode>) => ElementNode;
32
+ interface JsxProps {
33
+ readonly children?: unknown;
34
+ readonly [key: string]: unknown;
35
+ }
36
+ /**
37
+ * The automatic-runtime entry point. `type` is always one of our typed
38
+ * component functions (or `Fragment`); we invoke it with its props. The JSX
39
+ * transform routes single-child elements here and multi-child elements to
40
+ * `jsxs` — behaviour is identical for us (components normalize their own
41
+ * children), so `jsxs` re-exports this.
42
+ */
43
+ declare const jsx: (type: Component<JsxProps> | typeof Fragment, props: JsxProps) => JsxResult;
44
+ declare const jsxs: (type: Component<JsxProps> | typeof Fragment, props: JsxProps) => JsxResult;
45
+ /** Groups children without an element wrapper. Its children splice into the parent. */
46
+ declare const Fragment: unique symbol;
47
+ declare namespace JSX {
48
+ type Element = JsxResult;
49
+ type ElementType = Component<never> | typeof Fragment;
50
+ interface ElementChildrenAttribute {
51
+ readonly children: object;
52
+ }
53
+ type IntrinsicElements = Record<string, never>;
54
+ }
55
+ //#endregion
56
+ export { JsxResult as a, elementDraft as c, normalizeChildren as d, JSX as i, jsx as l, Fragment as n, SmlChild as o, FragmentDraft as r, SmlChildren as s, Component as t, jsxs as u };
57
+ //# sourceMappingURL=jsx-runtime-fSxcvlx4.d.ts.map
@@ -0,0 +1,2 @@
1
+ import { a as JsxResult, c as elementDraft, d as normalizeChildren, i as JSX, l as jsx, n as Fragment, o as SmlChild, r as FragmentDraft, s as SmlChildren, t as Component, u as jsxs } from "./jsx-runtime-fSxcvlx4.js";
2
+ export { Component, Fragment, FragmentDraft, JSX, JsxResult, SmlChild, SmlChildren, elementDraft, jsx, jsxs, normalizeChildren };
@@ -0,0 +1,102 @@
1
+ import { VARIABLE_REF, isVariableRef } from "./variables.js";
2
+ import { Predicate } from "effect";
3
+ //#region src/jsx-runtime.ts
4
+ const PLACEHOLDER_SPAN = {
5
+ start: 0,
6
+ end: 0
7
+ };
8
+ const isFragmentDraft = (value) => Predicate.hasProperty(value, "kind") && value.kind === "fragment";
9
+ const isSmlNode = (value) => {
10
+ if (!Predicate.hasProperty(value, "kind")) return false;
11
+ const kind = value.kind;
12
+ return kind === "element" || kind === "text" || kind === "interpolation";
13
+ };
14
+ const textNode = (value) => ({
15
+ kind: "text",
16
+ id: "",
17
+ value,
18
+ span: PLACEHOLDER_SPAN
19
+ });
20
+ const interpolationNode = (ref) => ({
21
+ kind: "interpolation",
22
+ id: "",
23
+ name: ref[VARIABLE_REF].path,
24
+ syntax: "curly",
25
+ span: PLACEHOLDER_SPAN
26
+ });
27
+ /**
28
+ * Flattens and normalizes a component's raw `children` prop into AST child
29
+ * nodes. Fragments splice their children in; booleans/null/undefined drop out.
30
+ */
31
+ const normalizeChildren = (children) => {
32
+ const out = [];
33
+ const push = (child) => {
34
+ if (child === null || child === void 0 || typeof child === "boolean") return;
35
+ if (typeof child === "string") {
36
+ out.push(textNode(child));
37
+ return;
38
+ }
39
+ if (typeof child === "number") {
40
+ out.push(textNode(String(child)));
41
+ return;
42
+ }
43
+ if (Array.isArray(child)) {
44
+ for (const item of child) push(item);
45
+ return;
46
+ }
47
+ if (isVariableRef(child)) {
48
+ out.push(interpolationNode(child));
49
+ return;
50
+ }
51
+ if (isFragmentDraft(child)) {
52
+ for (const node of child.children) out.push(node);
53
+ return;
54
+ }
55
+ if (isSmlNode(child)) {
56
+ out.push(child);
57
+ return;
58
+ }
59
+ throw new TypeError(`invalid SML child: ${describe(child)} — children may be text, numbers, variables, elements, arrays of these, or false/null/undefined (which are skipped)`);
60
+ };
61
+ push(children);
62
+ return out;
63
+ };
64
+ const describe = (value) => {
65
+ if (typeof value === "function") return "a function (did you pass a component instead of an element?)";
66
+ if (typeof value === "object") return `an object (${JSON.stringify(value)})`;
67
+ return `${typeof value} (${JSON.stringify(value)})`;
68
+ };
69
+ /**
70
+ * Builds an element draft from a tag, attribute list (already in authored
71
+ * order), and normalized children. Kept here so the runtime owns the AST shape;
72
+ * ./components maps typed props onto attributes and calls this.
73
+ */
74
+ const elementDraft = (tag, attributes, children) => ({
75
+ kind: "element",
76
+ id: "",
77
+ tag,
78
+ attributes,
79
+ children,
80
+ span: PLACEHOLDER_SPAN
81
+ });
82
+ /**
83
+ * The automatic-runtime entry point. `type` is always one of our typed
84
+ * component functions (or `Fragment`); we invoke it with its props. The JSX
85
+ * transform routes single-child elements here and multi-child elements to
86
+ * `jsxs` — behaviour is identical for us (components normalize their own
87
+ * children), so `jsxs` re-exports this.
88
+ */
89
+ const jsx = (type, props) => {
90
+ if (type === Fragment) return {
91
+ kind: "fragment",
92
+ children: normalizeChildren(props.children)
93
+ };
94
+ return type(props);
95
+ };
96
+ const jsxs = jsx;
97
+ /** Groups children without an element wrapper. Its children splice into the parent. */
98
+ const Fragment = Symbol.for("@samva/markup/Fragment");
99
+ //#endregion
100
+ export { Fragment, elementDraft, jsx, jsxs, normalizeChildren };
101
+
102
+ //# sourceMappingURL=jsx-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsx-runtime.js","names":[],"sources":["../src/jsx-runtime.ts"],"sourcesContent":["import { Predicate } from \"effect\";\n\nimport type { Attribute, ElementNode, InterpolationNode, SmlNode, TextNode } from \"./ast\";\nimport { isVariableRef, VARIABLE_REF, type VariableRef } from \"./variables\";\n\n// Automatic-runtime JSX factory for the TSX authoring layer (design brief §7b).\n//\n// This is EVALUATION, not parsing: `template.tsx` runs at the user's build time,\n// its JSX transforms to jsx()/jsxs() calls, and those construct M1 AST drafts.\n// We never statically analyse TSX — any TypeScript that runs is accepted and the\n// OUTPUT is validated against the closed grammar (print → parse in renderTemplate).\n//\n// Drafts are real `SmlNode` shapes with placeholder id/span: the canonical path\n// is print → parse, so parse assigns the authoritative ids and spans and runs the\n// full content-model validation. Nothing here re-implements the grammar; the\n// typed components in ./components restrict what callers can construct, and the\n// parser is the backstop for hand-assembled or dynamically-built trees.\n\nconst PLACEHOLDER_SPAN = { start: 0, end: 0 } as const;\n\n/** A component is a plain function of its props that returns a draft node. */\nexport type Component<P> = (props: P) => SmlNode;\n\n/**\n * What may appear in a component's `children`. Booleans/null/undefined are\n * skipped (so `{cond && <X/>}` and `{cond ? <A/> : null}` are BUILD-TIME\n * conditionals that evaluate away); strings/numbers become text; VariableRefs\n * become `{{interpolation}}` nodes; arrays flatten; nested nodes nest. Anything\n * else is a typed authoring error.\n */\nexport type SmlChild =\n | SmlNode\n | FragmentDraft\n | VariableRef<unknown>\n | string\n | number\n | boolean\n | null\n | undefined\n | ReadonlyArray<SmlChild>;\n\nexport type SmlChildren = SmlChild | ReadonlyArray<SmlChild>;\n\n/** Fragment children pass through un-wrapped — a Fragment is not an element. */\nexport interface FragmentDraft {\n readonly kind: \"fragment\";\n readonly children: ReadonlyArray<SmlNode>;\n}\n\nexport type JsxResult = SmlNode | FragmentDraft;\n\nconst isFragmentDraft = (value: unknown): value is FragmentDraft =>\n Predicate.hasProperty(value, \"kind\") && value.kind === \"fragment\";\n\nconst isSmlNode = (value: unknown): value is SmlNode => {\n if (!Predicate.hasProperty(value, \"kind\")) return false;\n const kind = value.kind;\n return kind === \"element\" || kind === \"text\" || kind === \"interpolation\";\n};\n\nconst textNode = (value: string): TextNode => ({\n kind: \"text\",\n id: \"\",\n value,\n span: PLACEHOLDER_SPAN,\n});\n\nconst interpolationNode = (ref: VariableRef<unknown>): InterpolationNode => ({\n kind: \"interpolation\",\n id: \"\",\n name: ref[VARIABLE_REF].path,\n // TSX authoring uses curly syntax exclusively; `${name}` is a source-only\n // alternate the parser still accepts, never emitted from code.\n syntax: \"curly\",\n span: PLACEHOLDER_SPAN,\n});\n\n/**\n * Flattens and normalizes a component's raw `children` prop into AST child\n * nodes. Fragments splice their children in; booleans/null/undefined drop out.\n */\nexport const normalizeChildren = (children: unknown): Array<SmlNode> => {\n const out: Array<SmlNode> = [];\n const push = (child: unknown): void => {\n if (child === null || child === undefined || typeof child === \"boolean\") return;\n if (typeof child === \"string\") {\n out.push(textNode(child));\n return;\n }\n if (typeof child === \"number\") {\n out.push(textNode(String(child)));\n return;\n }\n if (Array.isArray(child)) {\n for (const item of child) push(item);\n return;\n }\n if (isVariableRef(child)) {\n out.push(interpolationNode(child));\n return;\n }\n if (isFragmentDraft(child)) {\n for (const node of child.children) out.push(node);\n return;\n }\n if (isSmlNode(child)) {\n out.push(child);\n return;\n }\n // oxlint-disable-next-line samva/no-try-catch-or-throw, samva/no-error-constructor -- User-build authoring boundary: this runs in the user's TSX build (not Effect domain); an invalid child is a programmer error that must fail their build loudly, not become a typed Effect failure.\n throw new TypeError(\n `invalid SML child: ${describe(child)} — children may be text, numbers, variables, elements, arrays of these, or false/null/undefined (which are skipped)`,\n );\n };\n push(children);\n return out;\n};\n\nconst describe = (value: unknown): string => {\n if (typeof value === \"function\")\n return \"a function (did you pass a component instead of an element?)\";\n if (typeof value === \"object\") return `an object (${JSON.stringify(value)})`;\n return `${typeof value} (${JSON.stringify(value)})`;\n};\n\n// ── Element draft builder (used by ./components) ────────────────────────────────\n\n/**\n * Builds an element draft from a tag, attribute list (already in authored\n * order), and normalized children. Kept here so the runtime owns the AST shape;\n * ./components maps typed props onto attributes and calls this.\n */\nexport const elementDraft = (\n tag: ElementNode[\"tag\"],\n attributes: ReadonlyArray<Attribute>,\n children: ReadonlyArray<SmlNode>,\n): ElementNode => ({\n kind: \"element\",\n id: \"\",\n tag,\n attributes,\n children,\n span: PLACEHOLDER_SPAN,\n});\n\n// ── jsx / jsxs / Fragment (automatic runtime contract) ──────────────────────────\n\ninterface JsxProps {\n readonly children?: unknown;\n readonly [key: string]: unknown;\n}\n\n/**\n * The automatic-runtime entry point. `type` is always one of our typed\n * component functions (or `Fragment`); we invoke it with its props. The JSX\n * transform routes single-child elements here and multi-child elements to\n * `jsxs` — behaviour is identical for us (components normalize their own\n * children), so `jsxs` re-exports this.\n */\nexport const jsx = (type: Component<JsxProps> | typeof Fragment, props: JsxProps): JsxResult => {\n if (type === Fragment) {\n return { kind: \"fragment\", children: normalizeChildren(props.children) };\n }\n return (type as Component<JsxProps>)(props);\n};\n\nexport const jsxs = jsx;\n\n/** Groups children without an element wrapper. Its children splice into the parent. */\nexport const Fragment = Symbol.for(\"@samva/markup/Fragment\");\n\n// ── JSX namespace (automatic runtime contract) ──────────────────────────────────\n//\n// With `jsxImportSource: \"@samva/markup\"` the TSX transform resolves types from\n// this module's `JSX` namespace. Our vocabulary is all capitalized components\n// (functions from ./components), so there are no lowercase intrinsics — a `<div>`\n// simply has no component and fails to resolve, which is the closed grammar\n// enforced at the type level. Every element evaluates to an `SmlNode`.\n\nexport namespace JSX {\n export type Element = JsxResult;\n export type ElementType = Component<never> | typeof Fragment;\n export interface ElementChildrenAttribute {\n readonly children: object;\n }\n // No lowercase host elements: the grammar is closed and component-only, so\n // `<div>` has no matching intrinsic and fails to type-resolve.\n export type IntrinsicElements = Record<string, never>;\n}\n"],"mappings":";;;AAkBA,MAAM,mBAAmB;CAAE,OAAO;CAAG,KAAK;AAAE;AAiC5C,MAAM,mBAAmB,UACvB,UAAU,YAAY,OAAO,MAAM,KAAK,MAAM,SAAS;AAEzD,MAAM,aAAa,UAAqC;CACtD,IAAI,CAAC,UAAU,YAAY,OAAO,MAAM,GAAG,OAAO;CAClD,MAAM,OAAO,MAAM;CACnB,OAAO,SAAS,aAAa,SAAS,UAAU,SAAS;AAC3D;AAEA,MAAM,YAAY,WAA6B;CAC7C,MAAM;CACN,IAAI;CACJ;CACA,MAAM;AACR;AAEA,MAAM,qBAAqB,SAAkD;CAC3E,MAAM;CACN,IAAI;CACJ,MAAM,IAAI,aAAa,CAAC;CAGxB,QAAQ;CACR,MAAM;AACR;;;;;AAMA,MAAa,qBAAqB,aAAsC;CACtE,MAAM,MAAsB,CAAC;CAC7B,MAAM,QAAQ,UAAyB;EACrC,IAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,OAAO,UAAU,WAAW;EACzE,IAAI,OAAO,UAAU,UAAU;GAC7B,IAAI,KAAK,SAAS,KAAK,CAAC;GACxB;EACF;EACA,IAAI,OAAO,UAAU,UAAU;GAC7B,IAAI,KAAK,SAAS,OAAO,KAAK,CAAC,CAAC;GAChC;EACF;EACA,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,KAAK,MAAM,QAAQ,OAAO,KAAK,IAAI;GACnC;EACF;EACA,IAAI,cAAc,KAAK,GAAG;GACxB,IAAI,KAAK,kBAAkB,KAAK,CAAC;GACjC;EACF;EACA,IAAI,gBAAgB,KAAK,GAAG;GAC1B,KAAK,MAAM,QAAQ,MAAM,UAAU,IAAI,KAAK,IAAI;GAChD;EACF;EACA,IAAI,UAAU,KAAK,GAAG;GACpB,IAAI,KAAK,KAAK;GACd;EACF;EAEA,MAAM,IAAI,UACR,sBAAsB,SAAS,KAAK,EAAE,oHACxC;CACF;CACA,KAAK,QAAQ;CACb,OAAO;AACT;AAEA,MAAM,YAAY,UAA2B;CAC3C,IAAI,OAAO,UAAU,YACnB,OAAO;CACT,IAAI,OAAO,UAAU,UAAU,OAAO,cAAc,KAAK,UAAU,KAAK,EAAE;CAC1E,OAAO,GAAG,OAAO,MAAM,IAAI,KAAK,UAAU,KAAK,EAAE;AACnD;;;;;;AASA,MAAa,gBACX,KACA,YACA,cACiB;CACjB,MAAM;CACN,IAAI;CACJ;CACA;CACA;CACA,MAAM;AACR;;;;;;;;AAgBA,MAAa,OAAO,MAA6C,UAA+B;CAC9F,IAAI,SAAS,UACX,OAAO;EAAE,MAAM;EAAY,UAAU,kBAAkB,MAAM,QAAQ;CAAE;CAEzE,OAAQ,KAA6B,KAAK;AAC5C;AAEA,MAAa,OAAO;;AAGpB,MAAa,WAAW,OAAO,IAAI,wBAAwB"}
@@ -0,0 +1,60 @@
1
+ //#region src/lexer.d.ts
2
+ declare const TokenKind: {
3
+ readonly EndOfFile: 0;
4
+ /** `<` opening a start tag (text mode). */
5
+ readonly LessThan: 1;
6
+ /** `</` opening a close tag (text mode). */
7
+ readonly LessThanSlash: 2;
8
+ readonly GreaterThan: 3;
9
+ readonly SlashGreaterThan: 4;
10
+ readonly Identifier: 5;
11
+ readonly Equals: 6;
12
+ /** Quoted attribute value; getStringValue decodes entities. */
13
+ readonly String: 7;
14
+ readonly OpenBrace: 8;
15
+ readonly CloseBrace: 9;
16
+ /** Unsigned integer inside a brace attribute value. */
17
+ readonly Number: 10;
18
+ /** Raw text run; getTextValue decodes entities. */
19
+ readonly Text: 11;
20
+ /** `{{name}}` or `${name}`; name/syntax carried on the token. */
21
+ readonly Interpolation: 12;
22
+ /** Lexically invalid input; error carried on the token. */
23
+ readonly Unknown: 13;
24
+ };
25
+ type TokenKind = (typeof TokenKind)[keyof typeof TokenKind];
26
+ interface TokenError {
27
+ readonly code: string;
28
+ readonly message: string;
29
+ }
30
+ interface Token {
31
+ readonly kind: TokenKind;
32
+ readonly start: number;
33
+ readonly end: number;
34
+ /** Interpolation only. */
35
+ readonly name?: string | undefined;
36
+ /** Interpolation only. */
37
+ readonly syntax?: "curly" | "dollar" | undefined;
38
+ /** Unknown only. */
39
+ readonly error?: TokenError | undefined;
40
+ }
41
+ /** Decodes entities in text that already passed `scanEntity` validation. */
42
+ declare const decodeEntities: (raw: string) => string;
43
+ interface Lexer {
44
+ /** Current scan position (start of the next token). */
45
+ readonly position: () => number;
46
+ /** Scan one token in text mode (between tags). */
47
+ readonly scanTextToken: () => Token;
48
+ /** Scan one token in tag mode (inside `<...>`), skipping whitespace. */
49
+ readonly scanTagToken: () => Token;
50
+ /** Lazy raw source slice for a token. */
51
+ readonly getText: (token: Token) => string;
52
+ /** Entity-decoded value of a Text token. */
53
+ readonly getTextValue: (token: Token) => string;
54
+ /** Entity-decoded contents of a String token (without quotes). */
55
+ readonly getStringValue: (token: Token) => string;
56
+ }
57
+ declare const createLexer: (source: string) => Lexer;
58
+ //#endregion
59
+ export { Lexer, Token, TokenError, TokenKind, createLexer, decodeEntities };
60
+ //# sourceMappingURL=lexer.d.ts.map
package/dist/lexer.js ADDED
@@ -0,0 +1,269 @@
1
+ //#region src/lexer.ts
2
+ const CHAR_TAB = 9;
3
+ const CHAR_LINE_FEED = 10;
4
+ const CHAR_CARRIAGE_RETURN = 13;
5
+ const CHAR_SPACE = 32;
6
+ const CHAR_BANG = 33;
7
+ const CHAR_DOUBLE_QUOTE = 34;
8
+ const CHAR_HASH = 35;
9
+ const CHAR_DOLLAR = 36;
10
+ const CHAR_AMPERSAND = 38;
11
+ const CHAR_SINGLE_QUOTE = 39;
12
+ const CHAR_SLASH = 47;
13
+ const CHAR_SEMICOLON = 59;
14
+ const CHAR_LESS_THAN = 60;
15
+ const CHAR_EQUALS = 61;
16
+ const CHAR_GREATER_THAN = 62;
17
+ const CHAR_OPEN_BRACE = 123;
18
+ const CHAR_CLOSE_BRACE = 125;
19
+ const CHAR_DOT = 46;
20
+ const CHAR_UNDERSCORE = 95;
21
+ const CHAR_0 = 48;
22
+ const CHAR_9 = 57;
23
+ const CHAR_UPPER_A = 65;
24
+ const CHAR_UPPER_Z = 90;
25
+ const CHAR_LOWER_A = 97;
26
+ const CHAR_LOWER_X = 120;
27
+ const CHAR_LOWER_Z = 122;
28
+ const TokenKind = {
29
+ EndOfFile: 0,
30
+ /** `<` opening a start tag (text mode). */
31
+ LessThan: 1,
32
+ /** `</` opening a close tag (text mode). */
33
+ LessThanSlash: 2,
34
+ GreaterThan: 3,
35
+ SlashGreaterThan: 4,
36
+ Identifier: 5,
37
+ Equals: 6,
38
+ /** Quoted attribute value; getStringValue decodes entities. */
39
+ String: 7,
40
+ OpenBrace: 8,
41
+ CloseBrace: 9,
42
+ /** Unsigned integer inside a brace attribute value. */
43
+ Number: 10,
44
+ /** Raw text run; getTextValue decodes entities. */
45
+ Text: 11,
46
+ /** `{{name}}` or `${name}`; name/syntax carried on the token. */
47
+ Interpolation: 12,
48
+ /** Lexically invalid input; error carried on the token. */
49
+ Unknown: 13
50
+ };
51
+ const isWhitespace = (code) => code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_LINE_FEED || code === CHAR_CARRIAGE_RETURN;
52
+ const isDigit = (code) => code >= CHAR_0 && code <= CHAR_9;
53
+ const isLetter = (code) => code >= CHAR_UPPER_A && code <= CHAR_UPPER_Z || code >= CHAR_LOWER_A && code <= CHAR_LOWER_Z;
54
+ const isNameChar = (code) => isLetter(code) || isDigit(code) || code === CHAR_UNDERSCORE || code === CHAR_DOT;
55
+ const NAMED_ENTITIES = {
56
+ amp: "&",
57
+ lt: "<",
58
+ gt: ">",
59
+ quot: "\"",
60
+ apos: "'",
61
+ nbsp: "\xA0"
62
+ };
63
+ /**
64
+ * Validates the entity starting at `offset` (which points at `&`).
65
+ * Returns the end offset (past `;`) or -1 when malformed.
66
+ */
67
+ const scanEntity = (source, offset) => {
68
+ let index = offset + 1;
69
+ if (source.charCodeAt(index) === CHAR_HASH) {
70
+ index++;
71
+ const hex = source.charCodeAt(index) === CHAR_LOWER_X;
72
+ if (hex) index++;
73
+ const digitsStart = index;
74
+ while (index < source.length) {
75
+ const code = source.charCodeAt(index);
76
+ if (!(isDigit(code) || hex && (code >= 97 && code <= 102 || code >= 65 && code <= 70))) break;
77
+ index++;
78
+ }
79
+ if (index === digitsStart) return -1;
80
+ return source.charCodeAt(index) === CHAR_SEMICOLON ? index + 1 : -1;
81
+ }
82
+ const nameStart = index;
83
+ while (index < source.length && isLetter(source.charCodeAt(index))) index++;
84
+ if (!(source.slice(nameStart, index) in NAMED_ENTITIES)) return -1;
85
+ return source.charCodeAt(index) === CHAR_SEMICOLON ? index + 1 : -1;
86
+ };
87
+ /** Decodes entities in text that already passed `scanEntity` validation. */
88
+ const decodeEntities = (raw) => {
89
+ if (!raw.includes("&")) return raw;
90
+ let result = "";
91
+ let index = 0;
92
+ while (index < raw.length) {
93
+ const ampersand = raw.indexOf("&", index);
94
+ if (ampersand === -1) {
95
+ result += raw.slice(index);
96
+ break;
97
+ }
98
+ result += raw.slice(index, ampersand);
99
+ const semicolon = raw.indexOf(";", ampersand);
100
+ if (semicolon === -1) {
101
+ result += raw.slice(ampersand);
102
+ break;
103
+ }
104
+ const body = raw.slice(ampersand + 1, semicolon);
105
+ if (body.startsWith("#x") || body.startsWith("#X")) result += String.fromCodePoint(Number.parseInt(body.slice(2), 16));
106
+ else if (body.startsWith("#")) result += String.fromCodePoint(Number.parseInt(body.slice(1), 10));
107
+ else result += NAMED_ENTITIES[body];
108
+ index = semicolon + 1;
109
+ }
110
+ return result;
111
+ };
112
+ const createLexer = (source) => {
113
+ let position = 0;
114
+ const length = source.length;
115
+ const token = (kind, start, extra) => {
116
+ return {
117
+ kind,
118
+ start,
119
+ end: position,
120
+ ...extra
121
+ };
122
+ };
123
+ const unknown = (start, code, message) => {
124
+ if (position === start) position = Math.min(start + 1, length);
125
+ return token(TokenKind.Unknown, start, { error: {
126
+ code,
127
+ message
128
+ } });
129
+ };
130
+ const scanInterpolation = (start, syntax) => {
131
+ position = start + 2;
132
+ while (position < length && isWhitespace(source.charCodeAt(position))) position++;
133
+ if (syntax === "dollar" && source.charCodeAt(position) === CHAR_SLASH) position++;
134
+ const nameStart = position;
135
+ while (position < length && isNameChar(source.charCodeAt(position))) position++;
136
+ const name = source.slice(nameStart, position);
137
+ while (position < length && isWhitespace(source.charCodeAt(position))) position++;
138
+ const closed = syntax === "curly" ? source.charCodeAt(position) === CHAR_CLOSE_BRACE && source.charCodeAt(position + 1) === CHAR_CLOSE_BRACE : source.charCodeAt(position) === CHAR_CLOSE_BRACE;
139
+ if (name.length === 0 || !closed) {
140
+ position = start;
141
+ return unknown(start, "invalid-interpolation", `malformed interpolation — write ${syntax === "curly" ? "{{variableName}}" : "${variableName}"} using letters, digits, underscore, or dot`);
142
+ }
143
+ position += syntax === "curly" ? 2 : 1;
144
+ return token(TokenKind.Interpolation, start, {
145
+ name,
146
+ syntax
147
+ });
148
+ };
149
+ const scanTextToken = () => {
150
+ const start = position;
151
+ if (position >= length) return token(TokenKind.EndOfFile, start);
152
+ const first = source.charCodeAt(position);
153
+ if (first === CHAR_LESS_THAN) {
154
+ const next = source.charCodeAt(position + 1);
155
+ if (next === CHAR_SLASH) {
156
+ position += 2;
157
+ return token(TokenKind.LessThanSlash, start);
158
+ }
159
+ if (isLetter(next)) {
160
+ position += 1;
161
+ return token(TokenKind.LessThan, start);
162
+ }
163
+ if (next === CHAR_BANG) return unknown(start, "comment-not-supported", source.startsWith("<!--", position) ? "comments are not part of SML — remove the <!-- ... --> block" : "unexpected <! — SML has no doctype or declaration syntax; remove it");
164
+ return unknown(start, "stray-angle-bracket", "stray < in text — escape it as &lt;");
165
+ }
166
+ if (first === CHAR_OPEN_BRACE && source.charCodeAt(position + 1) === CHAR_OPEN_BRACE) return scanInterpolation(start, "curly");
167
+ if (first === CHAR_DOLLAR && source.charCodeAt(position + 1) === CHAR_OPEN_BRACE) return scanInterpolation(start, "dollar");
168
+ while (position < length) {
169
+ const code = source.charCodeAt(position);
170
+ if (code === CHAR_LESS_THAN) break;
171
+ if (code === CHAR_OPEN_BRACE && source.charCodeAt(position + 1) === CHAR_OPEN_BRACE) break;
172
+ if (code === CHAR_DOLLAR && source.charCodeAt(position + 1) === CHAR_OPEN_BRACE) break;
173
+ if (code === CHAR_AMPERSAND) {
174
+ const entityEnd = scanEntity(source, position);
175
+ if (entityEnd === -1) {
176
+ if (position > start) return token(TokenKind.Text, start);
177
+ return unknown(start, "bare-ampersand", "bare & in text — escape it as &amp; (or use a supported entity like &lt;)");
178
+ }
179
+ position = entityEnd;
180
+ continue;
181
+ }
182
+ position++;
183
+ }
184
+ return token(TokenKind.Text, start);
185
+ };
186
+ const scanTagToken = () => {
187
+ while (position < length && isWhitespace(source.charCodeAt(position))) position++;
188
+ const start = position;
189
+ if (position >= length) return token(TokenKind.EndOfFile, start);
190
+ const code = source.charCodeAt(position);
191
+ switch (code) {
192
+ case CHAR_GREATER_THAN:
193
+ position++;
194
+ return token(TokenKind.GreaterThan, start);
195
+ case CHAR_SLASH:
196
+ if (source.charCodeAt(position + 1) === CHAR_GREATER_THAN) {
197
+ position += 2;
198
+ return token(TokenKind.SlashGreaterThan, start);
199
+ }
200
+ return unknown(start, "unexpected-character", "unexpected / inside tag — write /> to self-close");
201
+ case CHAR_EQUALS:
202
+ position++;
203
+ return token(TokenKind.Equals, start);
204
+ case CHAR_OPEN_BRACE:
205
+ position++;
206
+ return token(TokenKind.OpenBrace, start);
207
+ case CHAR_CLOSE_BRACE:
208
+ position++;
209
+ return token(TokenKind.CloseBrace, start);
210
+ case CHAR_DOUBLE_QUOTE:
211
+ case CHAR_SINGLE_QUOTE:
212
+ position++;
213
+ while (position < length) {
214
+ const current = source.charCodeAt(position);
215
+ if (current === code) break;
216
+ if (current === CHAR_AMPERSAND) {
217
+ const entityEnd = scanEntity(source, position);
218
+ if (entityEnd === -1) return unknown(position, "bare-ampersand", "bare & in attribute value — escape it as &amp;");
219
+ position = entityEnd;
220
+ continue;
221
+ }
222
+ if (current === CHAR_LESS_THAN || current === CHAR_LINE_FEED) break;
223
+ position++;
224
+ }
225
+ if (source.charCodeAt(position) !== code) {
226
+ const quote = String.fromCharCode(code);
227
+ return {
228
+ kind: TokenKind.Unknown,
229
+ start,
230
+ end: position,
231
+ error: {
232
+ code: "unterminated-string",
233
+ message: `unterminated attribute value — add a closing ${quote}`
234
+ }
235
+ };
236
+ }
237
+ position++;
238
+ return token(TokenKind.String, start);
239
+ default:
240
+ if (isLetter(code)) {
241
+ position++;
242
+ while (position < length) {
243
+ const current = source.charCodeAt(position);
244
+ if (!isLetter(current) && !isDigit(current)) break;
245
+ position++;
246
+ }
247
+ return token(TokenKind.Identifier, start);
248
+ }
249
+ if (isDigit(code)) {
250
+ position++;
251
+ while (position < length && isDigit(source.charCodeAt(position))) position++;
252
+ return token(TokenKind.Number, start);
253
+ }
254
+ return unknown(start, "unexpected-character", `unexpected character ${JSON.stringify(String.fromCharCode(code))} inside tag`);
255
+ }
256
+ };
257
+ return {
258
+ position: () => position,
259
+ scanTextToken,
260
+ scanTagToken,
261
+ getText: (t) => source.slice(t.start, t.end),
262
+ getTextValue: (t) => decodeEntities(source.slice(t.start, t.end)),
263
+ getStringValue: (t) => decodeEntities(source.slice(t.start + 1, t.end - 1))
264
+ };
265
+ };
266
+ //#endregion
267
+ export { TokenKind, createLexer, decodeEntities };
268
+
269
+ //# sourceMappingURL=lexer.js.map