sommark 2.1.2 → 2.2.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.
package/core/parser.js CHANGED
@@ -102,7 +102,6 @@ const updateData = (tokens, i) => {
102
102
  }
103
103
  };
104
104
 
105
-
106
105
  const errorMessage = (tokens, i, expectedValue, behindValue, frontText) => {
107
106
  const tokensUntilError = tokens.slice(0, i);
108
107
  const contextText = tokensUntilError.map(t => t.value).join("");
@@ -123,7 +122,6 @@ const errorMessage = (tokens, i, expectedValue, behindValue, frontText) => {
123
122
  // ========================================================================== //
124
123
  function parseKey(tokens, i) {
125
124
  let key = current_token(tokens, i).value.trim();
126
- validateName(key);
127
125
  // ========================================================================== //
128
126
  // consume Key //
129
127
  // ========================================================================== //
@@ -261,7 +259,12 @@ function parseBlock(tokens, i) {
261
259
  let [key, keyIndex] = parseKey(tokens, i);
262
260
  k = key;
263
261
  i = keyIndex;
262
+ const prev = current_token(tokens, i);
264
263
  i = parseColon(tokens, i, block_id);
264
+ if (current_token(tokens, i).type !== TOKEN_TYPES.VALUE && current_token(tokens, i).type !== TOKEN_TYPES.ESCAPE) {
265
+ parserError(errorMessage(tokens, i, block_value, ":"));
266
+ }
267
+ validateName(k);
265
268
  continue;
266
269
  } else if (
267
270
  current_token(tokens, i) &&
@@ -403,6 +406,7 @@ function parseInline(tokens, i) {
403
406
  parserError(errorMessage(tokens, i, inline_value, "("));
404
407
  }
405
408
  inlineNode.value = current_token(tokens, i).value;
409
+ inlineNode.depth = current_token(tokens, i).depth;
406
410
  // ========================================================================== //
407
411
  // consume Inline Value //
408
412
  // ========================================================================== //
@@ -467,11 +471,12 @@ function parseInline(tokens, i) {
467
471
  parserError(errorMessage(tokens, i, inline_value, ":"));
468
472
  }
469
473
  let v = "";
470
-
471
474
  const pushArg = () => {
472
475
  if (v !== "") {
473
476
  inlineNode.args.push(v);
474
- inlineNode.args[v] = v;
477
+ if (!Number.isInteger(Number(v))) {
478
+ inlineNode.args[v] = v;
479
+ }
475
480
  v = "";
476
481
  }
477
482
  };
@@ -519,11 +524,11 @@ function parseInline(tokens, i) {
519
524
  if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.COMMA) {
520
525
  parserError(errorMessage(tokens, i, ",", "", "Found extra"));
521
526
  }
522
- if (!current_token(tokens, i) || (
523
- current_token(tokens, i) &&
524
- current_token(tokens, i).type !== TOKEN_TYPES.VALUE &&
525
- current_token(tokens, i).type !== TOKEN_TYPES.ESCAPE
526
- )
527
+ if (
528
+ !current_token(tokens, i) ||
529
+ (current_token(tokens, i) &&
530
+ current_token(tokens, i).type !== TOKEN_TYPES.VALUE &&
531
+ current_token(tokens, i).type !== TOKEN_TYPES.ESCAPE)
527
532
  ) {
528
533
  parserError(errorMessage(tokens, i, inline_value, ","));
529
534
  }
@@ -649,6 +654,10 @@ function parseAtBlock(tokens, i) {
649
654
  k = key;
650
655
  i = keyIndex;
651
656
  i = parseColon(tokens, i, at_id);
657
+ if (current_token(tokens, i).type !== TOKEN_TYPES.VALUE && current_token(tokens, i).type !== TOKEN_TYPES.ESCAPE) {
658
+ parserError(errorMessage(tokens, i, at_value, ":"));
659
+ }
660
+ validateName(k);
652
661
  continue;
653
662
  } else if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.ESCAPE) {
654
663
  let [escape_character, escapeIndex] = parseEscape(tokens, i);
@@ -1,7 +1,9 @@
1
1
  import { BLOCK, TEXT, INLINE, ATBLOCK, COMMENT } from "./labels.js";
2
2
  import escapeHTML from "../helpers/escapeHTML.js";
3
3
  import { transpilerError } from "./errors.js";
4
- import { textFormat, htmlFormat, markdownFormat, mdxFormat } from "./formats.js";
4
+ import { textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat } from "./formats.js";
5
+
6
+ const expose_for_fmts = [jsonFormat];
5
7
 
6
8
  // ========================================================================== //
7
9
  // Extracting target identifier //
@@ -26,7 +28,7 @@ function matchedValue(outputs, targetId) {
26
28
  return result;
27
29
  }
28
30
 
29
- function validateRules(target, args, content) {
31
+ function validateRules(target, args, content, type = null) {
30
32
  if (!target || !target.options || !target.options.rules) {
31
33
  return;
32
34
  }
@@ -96,6 +98,15 @@ function validateRules(target, args, content) {
96
98
  ]);
97
99
  }
98
100
  }
101
+ // Validate element type
102
+ if (id && rules.type && type) {
103
+ if (rules.type !== type) {
104
+ transpilerError([
105
+ "{line}<$red:Validation Error:$> ",
106
+ `<$yellow:Identifier$> <$blue:'${Array.isArray(id) ? id.join(" | ") : id}'$> <$yellow:is expected to be type$> <$green:'${rules.type}'$>{N}<$cyan:Received type: $> <$magenta:'${type}'$>{line}`
107
+ ]);
108
+ }
109
+ }
99
110
  }
100
111
  // ========================================================================== //
101
112
  // +++++++++++++++++++++++++++++ //
@@ -104,14 +115,16 @@ async function generateOutput(ast, i, format, mapper_file) {
104
115
  const node = Array.isArray(ast) ? ast[i] : ast;
105
116
  let result = "";
106
117
  let context = "";
107
- let target = mapper_file ? matchedValue(mapper_file.outputs, node.id) : "";
118
+ let target = mapper_file ? matchedValue(mapper_file.outputs, node.id) : null;
119
+ const block_formats = [htmlFormat, mdxFormat, jsonFormat];
108
120
  if (target) {
109
- validateRules(target, node.args, "");
110
- result +=
111
- format === htmlFormat || format === mdxFormat
112
- ? `${node.depth > 1 ? " ".repeat(node.depth) : ""}${target.render({ args: node.args, content: "\n<%smark>" })}${node.depth > 1 ? " ".repeat(node.depth) : ""}`
113
- : target.render({ args: node.args, content: "" });
114
- for (const body_node of node.body) {
121
+ validateRules(target, node.args, "", node.type);
122
+ result += block_formats.includes(format)
123
+ ? `${target.render({ args: node.args, content: "<%smark>", ast: expose_for_fmts.includes(format) ? ast[i] : null })}`
124
+ : target.render({ args: node.args, content: "" });
125
+ // Body nodes
126
+ for (let j = 0; j < node.body.length; j++) {
127
+ const body_node = node.body[j];
115
128
  switch (body_node.type) {
116
129
  // ========================================================================== //
117
130
  // Text //
@@ -119,8 +132,7 @@ async function generateOutput(ast, i, format, mapper_file) {
119
132
  case TEXT:
120
133
  validateRules(target, body_node.args, body_node.text);
121
134
  const shouldEscape = target && target.options && target.options.escape === false ? false : true;
122
- context +=
123
- (format === htmlFormat || format === mdxFormat) && shouldEscape ? escapeHTML(body_node.text) : body_node.text;
135
+ context += [htmlFormat, mdxFormat].includes(format) && shouldEscape ? escapeHTML(body_node.text) : body_node.text;
124
136
  break;
125
137
  // ========================================================================== //
126
138
  // Inline //
@@ -128,13 +140,11 @@ async function generateOutput(ast, i, format, mapper_file) {
128
140
  case INLINE:
129
141
  target = matchedValue(mapper_file.outputs, body_node.id);
130
142
  if (target) {
131
- validateRules(target, body_node.args, body_node.value);
132
- context +=
133
- (format === htmlFormat || format === mdxFormat ? "\n" : "") +
134
- target.render({
135
- args: body_node.args.length > 0 ? body_node.args : "",
136
- content: format === htmlFormat || format === mdxFormat ? escapeHTML(body_node.value) : body_node.value
137
- });
143
+ validateRules(target, body_node.args, body_node.value, body_node.type);
144
+ context += target.render({
145
+ args: body_node.args.length > 0 ? body_node.args : [],
146
+ content: format === htmlFormat || format === mdxFormat ? escapeHTML(body_node.value) : body_node.value
147
+ });
138
148
  }
139
149
  break;
140
150
  // ========================================================================== //
@@ -143,7 +153,7 @@ async function generateOutput(ast, i, format, mapper_file) {
143
153
  case ATBLOCK:
144
154
  target = matchedValue(mapper_file.outputs, body_node.id);
145
155
  if (target) {
146
- validateRules(target, body_node.args, body_node.content);
156
+ validateRules(target, body_node.args, body_node.content, body_node.type);
147
157
  // Escape logic: fallback to options.escape, default true
148
158
  const shouldEscape = target.options?.escape ?? true;
149
159
  if (shouldEscape) {
@@ -171,7 +181,8 @@ async function generateOutput(ast, i, format, mapper_file) {
171
181
  break;
172
182
  }
173
183
  }
174
- if (format === htmlFormat || format === mdxFormat) {
184
+
185
+ if (format === htmlFormat || format === mdxFormat || format === jsonFormat) {
175
186
  result = result.replace("<%smark>", context);
176
187
  } else {
177
188
  result += context;
@@ -245,6 +256,9 @@ async function transpiler({ ast, format, mapperFile, includeDocument = true }) {
245
256
  const document = `<!DOCTYPE html>\n<html>\n${finalHeader}\n<body>\n${output}\n</body>\n</html>\n`;
246
257
  return document;
247
258
  }
259
+ if (format === jsonFormat) {
260
+ output = JSON.parse(JSON.stringify(output));
261
+ }
248
262
  return output;
249
263
  }
250
264
 
package/formatter/mark.js CHANGED
@@ -18,7 +18,7 @@ class MarkdownBuilder {
18
18
  } else if (level < min) {
19
19
  level = min;
20
20
  }
21
- return `${"#".repeat(level)} ${text}\n`;
21
+ return `\n${"#".repeat(level)} ${text}\n`;
22
22
  }
23
23
  return text;
24
24
  }
@@ -29,7 +29,7 @@ class MarkdownBuilder {
29
29
  if (!text && !url) {
30
30
  return "";
31
31
  }
32
- return ` ${type === "image" ? "!" : ""}[${text}](${url + (title ? " " : "")}${title}) `;
32
+ return ` ${type === "image" ? "!" : ""}[${text}](${url + (title ? " " : "")}${title ? JSON.stringify(title) : ""}) `;
33
33
  }
34
34
  // ========================================================================== //
35
35
  // Bold //
@@ -91,10 +91,7 @@ class MarkdownBuilder {
91
91
  // Horizontal rule //
92
92
  // ========================================================================== //
93
93
  horizontal(format = "*") {
94
- if (!format) {
95
- return "\n***\n";
96
- }
97
- return format === "*" ? "\n***\n" : format === "_" ? "___" : format === "*" ? "***" : "";
94
+ return `\n${format.repeat(3)}\n`;
98
95
  }
99
96
  // ========================================================================== //
100
97
  // Escape //
package/grammar.ebnf CHANGED
@@ -12,7 +12,6 @@ EscapeChar = "\";
12
12
  (* 1. Identifiers can be only letters and numbers. *)
13
13
  (* 2. Semi-colon is only recognized in Atblock arguments. *)
14
14
  (* 3. Colon is a separator in Block and Atblock arguments; must be escaped if part of value. *)
15
- (* 4. Bodies and Values cannot be empty. *)
16
15
 
17
16
  (* Comments *)
18
17
  Comment = "#", { ? any character except "\n" ? }, "\n";
package/index.js CHANGED
@@ -5,10 +5,11 @@ import Mapper from "./mappers/mapper.js";
5
5
  import HTML from "./mappers/languages/html.js";
6
6
  import MARKDOWN from "./mappers/languages/markdown.js";
7
7
  import MDX from "./mappers/languages/mdx.js";
8
+ import Json from "./mappers/languages/json.js";
8
9
  import TagBuilder from "./formatter/tag.js";
9
10
  import MarkdownBuilder from "./formatter/mark.js";
10
11
  import { runtimeError } from "./core/errors.js";
11
- import FORMATS, { textFormat, htmlFormat, markdownFormat, mdxFormat } from "./core/formats.js";
12
+ import FORMATS, { textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat } from "./core/formats.js";
12
13
  import TOKEN_TYPES from "./core/tokenTypes.js";
13
14
  import * as labels from "./core/labels.js";
14
15
  class SomMark {
@@ -19,7 +20,7 @@ class SomMark {
19
20
 
20
21
  this.Mapper = Mapper;
21
22
  this.includeDocument = includeDocument;
22
- const accepted_formats = [textFormat, htmlFormat, markdownFormat, mdxFormat];
23
+ const accepted_formats = [textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat];
23
24
  if (!this.format) {
24
25
  runtimeError(["{line}<$red:Undefined Format$>: <$yellow:Format argument is not defined.$>{line}"]);
25
26
  }
@@ -29,7 +30,7 @@ class SomMark {
29
30
  `{N}<$yellow:Accepted formats are:$> [<$cyan: ${accepted_formats.join(", ")}$>]{line}`
30
31
  ]);
31
32
  }
32
- const mapperFiles = { [htmlFormat]: HTML, [markdownFormat]: MARKDOWN, [mdxFormat]: MDX };
33
+ const mapperFiles = { [htmlFormat]: HTML, [markdownFormat]: MARKDOWN, [mdxFormat]: MDX, [jsonFormat]: Json};
33
34
  if (!this.mapperFile && this.format) {
34
35
  this.mapperFile = mapperFiles[this.format];
35
36
  }
@@ -95,7 +96,8 @@ async function transpile(options = {}) {
95
96
  export {
96
97
  HTML,
97
98
  MARKDOWN,
98
- MDX,
99
+ MDX,
100
+ Json,
99
101
  Mapper,
100
102
  TagBuilder,
101
103
  MarkdownBuilder,
@@ -1,15 +1,56 @@
1
1
  import Mapper from "../mapper.js";
2
2
  const HTML = new Mapper();
3
- const { tag, code, list } = HTML;
3
+ const { tag, code, list, safeArg } = HTML;
4
+
5
+ HTML.register(
6
+ ["Html", "html"],
7
+ ({ args }) => {
8
+ HTML.pageProps.pageTitle = safeArg(args, undefined, "title", null, null, HTML.pageProps.pageTitle);
9
+ HTML.pageProps.charset = safeArg(args, undefined, "charset", null, null, HTML.pageProps.charset);
10
+ HTML.pageProps.tabIcon.src = safeArg(args, undefined, "iconSrc", null, null, HTML.pageProps.tabIcon.src);
11
+ HTML.pageProps.tabIcon.type = safeArg(args, undefined, "iconType", null, null, HTML.pageProps.tabIcon.type);
12
+ HTML.pageProps.httpEquiv["X-UA-Compatible"] = safeArg(
13
+ args,
14
+ undefined,
15
+ "httpEquiv",
16
+ null,
17
+ null,
18
+ HTML.pageProps.httpEquiv["X-UA-Compatible"]
19
+ );
20
+ HTML.pageProps.viewport = safeArg(args, undefined, "viewport", null, null, HTML.pageProps.viewport);
21
+ return "";
22
+ },
23
+ {
24
+ rules: {
25
+ type: "Block"
26
+ }
27
+ }
28
+ );
4
29
 
5
30
  // Block
6
- HTML.register("Block", ({ content }) => {
7
- return content;
8
- });
31
+ HTML.register(
32
+ ["Block", "block"],
33
+ ({ content }) => {
34
+ return content;
35
+ },
36
+ {
37
+ rules: {
38
+ type: "Block"
39
+ }
40
+ }
41
+ );
9
42
  // Section
10
- HTML.register("Section", ({ content }) => {
11
- return tag("section").body(content);
12
- });
43
+ HTML.register(
44
+ ["Section", "section"],
45
+ ({ content }) => {
46
+ return tag("section").body(content);
47
+ },
48
+ {
49
+ rules: {
50
+ type: "Block"
51
+ }
52
+ }
53
+ );
13
54
  // Headings
14
55
  ["h1", "h2", "h3", "h4", "h5", "h6"].forEach(heading => {
15
56
  HTML.register(heading, ({ content }) => {
@@ -17,44 +58,44 @@ HTML.register("Section", ({ content }) => {
17
58
  });
18
59
  });
19
60
  // Bold
20
- HTML.register(["bold", "b"], ({ content }) => {
61
+ HTML.register(["bold", "Bold", "b"], ({ content }) => {
21
62
  return tag("strong").body(content);
22
63
  });
23
64
  // Italic
24
65
  HTML.register(["italic", "i"], ({ content }) => {
25
66
  return tag("i").body(content);
26
67
  });
27
- // Italic
68
+ // Emphasis
28
69
  HTML.register(["emphasis", "e"], ({ content }) => {
29
70
  return tag("span").attributes({ style: "font-weight:bold; font-style: italic;" }).body(content);
30
71
  });
31
72
  // Colored Text
32
- HTML.register("color", ({ args, content }) => {
73
+ HTML.register(["color", "Color"], ({ args, content }) => {
74
+ const color = safeArg(args, 0, undefined, null, null, "none");
33
75
  return tag("span")
34
- .attributes({ style: `color:${args[0]}` })
76
+ .attributes({ style: `color:${color}` })
35
77
  .body(content);
36
78
  });
37
79
  // Link
38
- HTML.register("link", ({ args, content }) => {
39
- return tag("a")
40
- .attributes({ href: args[0].trim(), title: args[1] ? args[1].trim() : "" })
41
- .body(content);
80
+ HTML.register(["link", "Link"], ({ args, content }) => {
81
+ const url = safeArg(args, 0, "url", null, null, "");
82
+ const title = safeArg(args, 1, "title", null, null, "");
83
+ return tag("a").attributes({ href: url.trim(), title: title.trim(), target: "_blank" }).body(content);
42
84
  });
43
85
  // Image
44
86
  HTML.register(
45
87
  ["image", "Image"],
46
88
  ({ args }) => {
47
- const src = args && args["src"] ? args["src"] : "";
48
- const alt = args && args["alt"] ? args["alt"] : "";
49
- const width = args && args["width"] ? args["width"] : "",
50
- height = args && args["height"] ? args["height"] : "";
89
+ const src = safeArg(args, undefined, "src", null, null, "");
90
+ const alt = safeArg(args, undefined, "alt", null, null, "");
91
+ const width = safeArg(args, undefined, "width", null, null, "");
92
+ const height = safeArg(args, undefined, "height", null, null, "");
51
93
  return tag("img").attributes({ src, alt, width, height }).selfClose();
52
94
  },
53
95
  {
54
96
  rules: {
55
- is_Self_closing: true,
56
97
  args: {
57
- required: ["src"]
98
+ required: ["src"]
58
99
  }
59
100
  }
60
101
  }
@@ -65,7 +106,7 @@ HTML.register(
65
106
  ({ args, content }) => {
66
107
  return code(args, content);
67
108
  },
68
- { escape: false }
109
+ { escape: false, rules: { type: "AtBlock" } }
69
110
  );
70
111
  // List
71
112
  HTML.register(
@@ -81,7 +122,12 @@ HTML.register(
81
122
  ({ content, args }) => {
82
123
  return HTML.htmlTable(content.split(/\n/), args);
83
124
  },
84
- { escape: false }
125
+ {
126
+ escape: false,
127
+ rules: {
128
+ type: "AtBlock"
129
+ }
130
+ }
85
131
  );
86
132
  // Horizontal Rule
87
133
  HTML.register(
@@ -98,9 +144,8 @@ HTML.register(
98
144
  // Todo
99
145
  HTML.register("todo", ({ args, content }) => {
100
146
  const checked = HTML.todo(content);
101
- return tag("div").body(
102
- tag("input").attributes({ type: "checkbox", disabled: true, checked }).selfClose() + (args[0] ? args[0] : "")
103
- );
147
+ const task = safeArg(args, 0, undefined, null, null, "");
148
+ return tag("div").body(tag("input").attributes({ type: "checkbox", disabled: true, checked }).selfClose() + task);
104
149
  });
105
150
 
106
151
  export default HTML;
@@ -0,0 +1,172 @@
1
+ import Mapper from "../mapper.js";
2
+ import { TEXT } from "../../core/labels.js";
3
+ import { transpilerError } from "../../core/errors.js";
4
+
5
+ const Json = new Mapper();
6
+
7
+ // ========================================================================== //
8
+ // Helpers //
9
+ // ========================================================================== //
10
+
11
+ function escapeString(str) {
12
+ return JSON.stringify(str);
13
+ }
14
+
15
+ function processNode(node, parentType = null) {
16
+ if (!node) return "";
17
+
18
+ if (node.id === "Object" || node.id === "Array" || node.id === "Json") {
19
+ return renderBlock(node, parentType);
20
+ } else if (["string", "number", "bool", "null", "array", "none"].includes(node.id)) {
21
+ return renderInline(node, parentType);
22
+ } else if (node.type === TEXT) {
23
+ return "";
24
+ }
25
+ return "";
26
+ }
27
+
28
+ function renderBlock(node, parentType) {
29
+ let output = "";
30
+ let key = "";
31
+ let isRoot = node.id === "Json";
32
+ let type = node.id === "Array" || (isRoot && node.args.includes("array")) ? "array" : "object";
33
+
34
+ // ========================================================================== //
35
+ // Key //
36
+ // ========================================================================== //
37
+ if (!isRoot) {
38
+ if (parentType === "object") {
39
+ key = node.args && node.args[0] ? escapeString(node.args[0]) : null;
40
+ if (!key) {
41
+ key = '"unknown_key"';
42
+ }
43
+ }
44
+ }
45
+
46
+ // ========================================================================== //
47
+ // Children //
48
+ // ========================================================================== //
49
+ let children = [];
50
+ if (node.body && node.body.length > 0) {
51
+ for (const child of node.body) {
52
+ const childOutput = processNode(child, type);
53
+ if (childOutput) {
54
+ children.push(childOutput);
55
+ }
56
+ }
57
+ }
58
+
59
+ let content = children.join(",");
60
+ let wrapper = type === "array" ? `[${content}]` : `{${content}}`;
61
+
62
+ if (key) {
63
+ return `${key}:${wrapper}`;
64
+ } else {
65
+ if (parentType === "object") {
66
+ if (!node.args || !node.args[0]) {
67
+ transpilerError([`{line}<$red:JSON Error:$> <$yellow:Blocks inside an Object must have a key argument.$>{line}`]);
68
+ }
69
+ }
70
+ return wrapper;
71
+ }
72
+ }
73
+
74
+ function renderInline(node, parentType) {
75
+ let key = null;
76
+ let value = "";
77
+
78
+ // ========================================================================== //
79
+ // Value //
80
+ // ========================================================================== //
81
+ if (node.id === "string") {
82
+ if (!node.args || node.args.length === 0) {
83
+ transpilerError([`{line}<$red:JSON Error:$> <$yellow:String inline must have a value.$>{line}`]);
84
+ }
85
+ value = escapeString(node.args[0] || "");
86
+ } else if (node.id === "number") {
87
+ if (!node.args || node.args.length === 0 || isNaN(Number(node.args[0]))) {
88
+ transpilerError([`{line}<$red:JSON Error:$> <$yellow:Invalid or missing number value for inline.$>{line}`]);
89
+ }
90
+ value = node.args[0];
91
+ } else if (node.id === "bool") {
92
+ if (!node.args || (node.args[0] !== "true" && node.args[0] !== "false")) {
93
+ transpilerError([`{line}<$red:JSON Error:$> <$yellow:Bool inline must be 'true' or 'false'.$>{line}`]);
94
+ }
95
+ value = node.args[0] === "true" ? "true" : "false";
96
+ } else if (node.id === "null") {
97
+ value = "null";
98
+ } else if (node.id === "array") {
99
+ // ========================================================================== //
100
+ // Inline array //
101
+ // ========================================================================== //
102
+ // (data)->(array: 1, 2, 3)
103
+ // args = ["1", " 2", " 3"]
104
+ const items = node.args.map(arg => {
105
+ const trimmed = arg.trim();
106
+ if (trimmed === "null") return "null";
107
+ if (trimmed === "true" || trimmed === "false") return trimmed;
108
+ if (!isNaN(parseFloat(trimmed)) && isFinite(trimmed)) return trimmed;
109
+ return escapeString(trimmed);
110
+ });
111
+ value = `[${items.join(",")}]`;
112
+ } else if (node.id === "none") {
113
+ // Special case: (-)->(none: val)
114
+ if (parentType === "object") {
115
+ transpilerError([
116
+ `{line}<$red:JSON Error:$> <$yellow:'none' inline is not allowed directly inside an Object. It must be inside an Array.$>{line}`
117
+ ]);
118
+ return "";
119
+ }
120
+
121
+ // (-)->(none: 1, 2, null) -> [1, 2, null] -> args.length > 1
122
+ // (-)->(none: true) -> true -> args.length == 1
123
+
124
+ if (node.args.length > 1) {
125
+ const items = node.args.map(arg => {
126
+ const trimmed = arg.trim();
127
+ if (trimmed === "null") return "null";
128
+ if (trimmed === "true" || trimmed === "false") return trimmed;
129
+ if (!isNaN(parseFloat(trimmed)) && isFinite(trimmed)) return trimmed;
130
+ return escapeString(trimmed);
131
+ });
132
+ value = `[${items.join(",")}]`;
133
+ } else {
134
+ const arg = node.args[0] || "";
135
+ const trimmed = arg.trim();
136
+ if (trimmed === "null") value = "null";
137
+ else if (trimmed === "true" || trimmed === "false") value = trimmed;
138
+ else if (!isNaN(parseFloat(trimmed)) && isFinite(trimmed)) value = trimmed;
139
+ else value = escapeString(trimmed);
140
+ }
141
+ }
142
+
143
+ if (parentType === "object") {
144
+ if (node.id === "none") return "";
145
+
146
+ if (!node.value) {
147
+ transpilerError([
148
+ `{line}<$red:JSON Error:$> <$yellow:Inline elements inside an Object must have an identifier (key).$>{line}`
149
+ ]);
150
+ }
151
+
152
+ key = escapeString(node.value);
153
+ return `${key}:${value}`;
154
+ } else {
155
+ return value;
156
+ }
157
+ }
158
+
159
+ // ========================================================================== //
160
+ // Main Registration //
161
+ // ========================================================================== //
162
+
163
+ const noop = () => "";
164
+ Json.register(["Object", "Array"], noop);
165
+ Json.register(["string", "number", "bool", "null", "array", "none"], noop);
166
+
167
+ Json.register("Json", ({ args, content, ast }) => {
168
+ if (!ast) return "";
169
+ return processNode(ast, null);
170
+ });
171
+
172
+ export default Json;