sommark 4.0.2 → 4.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.
- package/README.md +274 -73
- package/cli/cli.mjs +2 -2
- package/cli/commands/build.js +3 -1
- package/cli/commands/help.js +4 -1
- package/cli/commands/init.js +25 -6
- package/cli/commands/show.js +20 -10
- package/cli/constants.js +2 -1
- package/cli/helpers/transpile.js +5 -2
- package/constants/html_props.js +1 -0
- package/core/evaluator.js +785 -0
- package/core/formats.js +15 -7
- package/core/helpers/config-loader.js +28 -15
- package/core/helpers/lib.js +75 -0
- package/core/helpers/preprocessor.js +185 -0
- package/core/helpers/runtimeOutput.js +28 -0
- package/core/labels.js +9 -2
- package/core/lexer.js +228 -61
- package/core/modules.js +331 -55
- package/core/parser.js +275 -55
- package/core/tokenTypes.js +11 -0
- package/core/transpiler.js +341 -59
- package/core/validator.js +85 -13
- package/formatter/tag.js +31 -7
- package/grammar.ebnf +21 -10
- package/helpers/safeDataParser.js +3 -3
- package/helpers/spinner.js +91 -0
- package/helpers/utils.js +46 -0
- package/index.js +125 -38
- package/mappers/languages/html.js +50 -9
- package/mappers/languages/json.js +81 -38
- package/mappers/languages/jsonc.js +82 -0
- package/mappers/languages/markdown.js +88 -48
- package/mappers/languages/mdx.js +50 -15
- package/mappers/languages/text.js +67 -0
- package/mappers/languages/xml.js +6 -6
- package/mappers/mapper.js +36 -4
- package/mappers/shared/index.js +12 -13
- package/package.json +6 -1
- package/core/formatter.js +0 -215
package/core/formatter.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { IMPORT, USE_MODULE, TEXT, INLINE, BLOCK, ATBLOCK, COMMENT } from "./labels.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Turns an AST back into a clean SomMark source string.
|
|
5
|
-
* This is useful for "pretty-printing" or saving changes back to a file.
|
|
6
|
-
*
|
|
7
|
-
* @param {Object[]|Object} ast - The AST or single node to turn into text.
|
|
8
|
-
* @param {Object} [options] - Optional settings for formatting.
|
|
9
|
-
* @returns {string} - The final SomMark source code.
|
|
10
|
-
*/
|
|
11
|
-
export function formatAST(ast, options = {}) {
|
|
12
|
-
const indentStr = options.indentString || "\t";
|
|
13
|
-
|
|
14
|
-
// -- Escaping Helpers ----------------------------------------------------- //
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Escapes special characters in argument values so they don't break the syntax.
|
|
18
|
-
*
|
|
19
|
-
* @param {any} val - The value to escape.
|
|
20
|
-
* @param {string} type - The type of tag (e.g., Block or Inline).
|
|
21
|
-
* @returns {string} - The safely escaped text.
|
|
22
|
-
*/
|
|
23
|
-
const escapeArg = (val, type) => {
|
|
24
|
-
let escaped = String(val).replace(/\\/g, "\\\\").replace(/,/g, "\\,");
|
|
25
|
-
if (type === BLOCK || type === ATBLOCK) escaped = escaped.replace(/:/g, "\\:");
|
|
26
|
-
if (type === ATBLOCK) escaped = escaped.replace(/;/g, "\\;");
|
|
27
|
-
if (type === BLOCK && escaped.startsWith("=")) escaped = escaped.replace(/^=/, "\\=");
|
|
28
|
-
return escaped;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Escapes characters in the left side of an inline statement (the text in parentheses).
|
|
33
|
-
*
|
|
34
|
-
* @param {any} val - The text inside parentheses.
|
|
35
|
-
* @returns {string} - The safely escaped text.
|
|
36
|
-
*/
|
|
37
|
-
const escapeInlineValue = (val) => String(val).replace(/\\/g, "\\\\").replace(/\)/g, "\\)");
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Escapes special characters in plain text so they aren't mistaken for SomMark tags.
|
|
41
|
-
*
|
|
42
|
-
* @param {string} str - The raw text to escape.
|
|
43
|
-
* @returns {string} - The safe text.
|
|
44
|
-
*/
|
|
45
|
-
const escapeText = (str) => {
|
|
46
|
-
return String(str)
|
|
47
|
-
.replace(/\\/g, "\\\\")
|
|
48
|
-
.replace(/\[/g, "\\[")
|
|
49
|
-
.replace(/\]/g, "\\]")
|
|
50
|
-
.replace(/\(/g, "\\(")
|
|
51
|
-
.replace(/\)/g, "\\)")
|
|
52
|
-
.replace(/->/g, "\\->")
|
|
53
|
-
.replace(/@_/g, "\\@_")
|
|
54
|
-
.replace(/_@/g, "\\_@")
|
|
55
|
-
.replace(/#/g, "\\#");
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Checks if a value needs to be wrapped in double quotes (like if it has spaces or commas).
|
|
60
|
-
*
|
|
61
|
-
* @param {any} val - The value to check.
|
|
62
|
-
* @returns {boolean} - True if quotes are needed.
|
|
63
|
-
*/
|
|
64
|
-
const shouldQuote = (val) => {
|
|
65
|
-
if (typeof val !== "string") return false;
|
|
66
|
-
return /[ \t\n\r,:[\]()@#]/.test(val);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// -- Formatting Logic ----------------------------------------------------- //
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Formats the arguments of a node into a SomMark string (e.g., "= key: value, 123").
|
|
73
|
-
*
|
|
74
|
-
* @param {Object} args - The list of arguments to format.
|
|
75
|
-
* @param {string} type - The type of tag.
|
|
76
|
-
* @returns {string} - The final formatted argument string.
|
|
77
|
-
*/
|
|
78
|
-
const formatArgs = (args, type) => {
|
|
79
|
-
if (!args || Object.keys(args).length === 0) return "";
|
|
80
|
-
let usedKeys = new Set();
|
|
81
|
-
let formattedArgs = [];
|
|
82
|
-
|
|
83
|
-
const keys = Object.keys(args);
|
|
84
|
-
const positionalCount = keys.filter(k => !isNaN(parseInt(k))).length;
|
|
85
|
-
|
|
86
|
-
for (let i = 0; i < positionalCount; i++) {
|
|
87
|
-
let val = args[i];
|
|
88
|
-
let matchedKey = null;
|
|
89
|
-
|
|
90
|
-
// Find if this value has a named alias
|
|
91
|
-
if (type !== INLINE) {
|
|
92
|
-
for (const key of keys) {
|
|
93
|
-
if (isNaN(parseInt(key)) && args[key] === val && !usedKeys.has(key)) {
|
|
94
|
-
matchedKey = key;
|
|
95
|
-
usedKeys.add(key);
|
|
96
|
-
break;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
let escapedVal = escapeArg(val, type);
|
|
102
|
-
if (shouldQuote(val)) {
|
|
103
|
-
const quotedVal = String(val).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
104
|
-
escapedVal = `"${quotedVal}"`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (matchedKey) formattedArgs.push(`${matchedKey}: ${escapedVal}`);
|
|
108
|
-
else formattedArgs.push(escapedVal);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const res = formattedArgs.join(", ");
|
|
112
|
-
if (!res) return "";
|
|
113
|
-
|
|
114
|
-
if (type === BLOCK || type === IMPORT || type === USE_MODULE) return " = " + res;
|
|
115
|
-
if (type === ATBLOCK) return ": " + res + ";";
|
|
116
|
-
if (type === INLINE) return ": " + res;
|
|
117
|
-
return res;
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Formats a list of nodes (like the body of a block) into indented SomMark source.
|
|
122
|
-
* It also handles the correct spacing between text and inline tags.
|
|
123
|
-
*
|
|
124
|
-
* @param {Object[]} body - The list of content nodes.
|
|
125
|
-
* @param {number} depth - How far to indent the text.
|
|
126
|
-
* @returns {string} - The final formatted text content.
|
|
127
|
-
*/
|
|
128
|
-
const formatBody = (body, depth) => {
|
|
129
|
-
if (!body || !Array.isArray(body)) return "";
|
|
130
|
-
const innerIndentStr = depth >= 0 ? indentStr.repeat(depth) : "";
|
|
131
|
-
let result = "";
|
|
132
|
-
let currentText = "";
|
|
133
|
-
|
|
134
|
-
const flushText = () => {
|
|
135
|
-
if (!currentText) return;
|
|
136
|
-
const cleanText = currentText
|
|
137
|
-
.replace(/[ \t]+/g, " ")
|
|
138
|
-
.replace(/\n([ \t]*\n)+/g, "\n\n")
|
|
139
|
-
.trim();
|
|
140
|
-
|
|
141
|
-
if (cleanText) {
|
|
142
|
-
const indentedText = cleanText.split("\n").map(line => {
|
|
143
|
-
return line.trim() ? innerIndentStr + line.trim() : "";
|
|
144
|
-
}).join("\n");
|
|
145
|
-
result += `${indentedText}\n`;
|
|
146
|
-
}
|
|
147
|
-
currentText = "";
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
for (let i = 0; i < body.length; i++) {
|
|
151
|
-
const child = body[i];
|
|
152
|
-
if (child.type === TEXT) {
|
|
153
|
-
let textStr = escapeText(child.text);
|
|
154
|
-
if (i > 0 && body[i - 1].type === INLINE) {
|
|
155
|
-
if (textStr.length > 0 && !/^\s/.test(textStr) && !/^[.,!?;:\])}>"']/.test(textStr)) {
|
|
156
|
-
textStr = " " + textStr;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
currentText += textStr;
|
|
160
|
-
} else if (child.type === INLINE) {
|
|
161
|
-
const argsStr = formatArgs(child.args, INLINE);
|
|
162
|
-
const inlineVal = child.value ? String(child.value).trim() : "";
|
|
163
|
-
const inlineStr = `(${escapeInlineValue(inlineVal)})->(${child.id}${argsStr})`;
|
|
164
|
-
if (i > 0) {
|
|
165
|
-
const prev = body[i - 1];
|
|
166
|
-
if (prev.type === INLINE) { if (!/[ \t\n\r]$/.test(currentText)) currentText += " "; }
|
|
167
|
-
else if (prev.type === TEXT) { if (currentText.length > 0 && !/[ \t\n\r]$/.test(currentText) && !/[({\[<"']$/.test(currentText)) currentText += " "; }
|
|
168
|
-
}
|
|
169
|
-
currentText += inlineStr;
|
|
170
|
-
} else {
|
|
171
|
-
flushText();
|
|
172
|
-
if (child.type === BLOCK) {
|
|
173
|
-
const argsStr = formatArgs(child.args, BLOCK);
|
|
174
|
-
// Check if it's a self-closing block (Rules support)
|
|
175
|
-
const isSelfClosing = child.rules?.is_self_closing;
|
|
176
|
-
if (isSelfClosing && (!child.body || child.body.length === 0)) {
|
|
177
|
-
result += `${innerIndentStr}[${child.id}${argsStr}][end]\n`;
|
|
178
|
-
} else {
|
|
179
|
-
result += `${innerIndentStr}[${child.id}${argsStr}]\n`;
|
|
180
|
-
result += formatBody(child.body, depth + 1);
|
|
181
|
-
result += `${innerIndentStr}[end]\n`;
|
|
182
|
-
}
|
|
183
|
-
} else if (child.type === ATBLOCK) {
|
|
184
|
-
const argsStr = formatArgs(child.args, ATBLOCK);
|
|
185
|
-
const atHeader = argsStr ? `@_${child.id}_@${argsStr}` : `@_${child.id}_@;`;
|
|
186
|
-
result += `${innerIndentStr}${atHeader}\n`;
|
|
187
|
-
if (child.content) {
|
|
188
|
-
const lines = child.content.replace(/\r\n/g, "\n").split("\n");
|
|
189
|
-
while (lines.length && !lines[0].trim()) lines.shift();
|
|
190
|
-
while (lines.length && !lines[lines.length - 1].trim()) lines.pop();
|
|
191
|
-
let minIndent = Infinity;
|
|
192
|
-
for (const line of lines) { if (line.trim()) { const leading = line.match(/^[ \t]*/)[0].length; if (leading < minIndent) minIndent = leading; } }
|
|
193
|
-
if (minIndent === Infinity) minIndent = 0;
|
|
194
|
-
const indentedContent = lines.map(line => line.trim() ? innerIndentStr + indentStr + line.substring(minIndent) : "").join("\n");
|
|
195
|
-
result += indentedContent + "\n";
|
|
196
|
-
}
|
|
197
|
-
result += `${innerIndentStr}@_end_@\n`;
|
|
198
|
-
} else if (child.type === COMMENT) {
|
|
199
|
-
result += `${innerIndentStr}# ${child.text.replace(/^#+\s*/, "").trim()}\n`;
|
|
200
|
-
} else if (child.type === IMPORT) {
|
|
201
|
-
const argsStr = formatArgs(child.args, IMPORT);
|
|
202
|
-
result += `${innerIndentStr}[import${argsStr}][end]\n`;
|
|
203
|
-
} else if (child.type === USE_MODULE) {
|
|
204
|
-
const argsStr = formatArgs(child.args, USE_MODULE);
|
|
205
|
-
result += `${innerIndentStr}[$use-module${argsStr}][end]\n`;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
flushText();
|
|
210
|
-
return result;
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const rootNodes = Array.isArray(ast) ? ast : [ast];
|
|
214
|
-
return formatBody(rootNodes, 0).trim() + "\n";
|
|
215
|
-
}
|