sommark 1.1.1 → 2.0.0-beta.1
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/CHANGELOG.md +74 -0
- package/README.md +2 -2
- package/core/lexer.js +86 -38
- package/core/parser.js +90 -45
- package/core/tokenTypes.js +2 -1
- package/custom_output.html +83 -0
- package/formatter/tag.js +1 -1
- package/mappers/default_mode/smark.html.js +0 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -35,3 +35,77 @@
|
|
|
35
35
|
### Bug Fixes
|
|
36
36
|
|
|
37
37
|
- **CLI**: Fixed a bug where passing a Mapper object in `smark.config.js` (Custom Mode) caused a crash. The CLI now correctly handles both file path strings and imported Mapper objects.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## 1.2.0 (2026-01-14)
|
|
41
|
+
|
|
42
|
+
### Bug Fixes
|
|
43
|
+
|
|
44
|
+
* Fixed an issue where consecutive standalone blocks were not fully rendered when not separated by a blank line.
|
|
45
|
+
|
|
46
|
+
```ini
|
|
47
|
+
[Block]
|
|
48
|
+
This is a test.
|
|
49
|
+
[end]
|
|
50
|
+
[Block]
|
|
51
|
+
This is another test.
|
|
52
|
+
[end]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
* Added support for inline block content while keeping the original multiline syntax fully compatible.
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
[Block]Hello World[end]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
### Code Improvements
|
|
64
|
+
|
|
65
|
+
* Removed unnecessary code
|
|
66
|
+
* Improved internal implementation
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## v2.0.0-beta.1 (2026-01-17)
|
|
71
|
+
|
|
72
|
+
### Breaking Changes
|
|
73
|
+
|
|
74
|
+
* Replaced backtick-based escape syntax (`escape content`) with a single-character escape using backslash (`\`).
|
|
75
|
+
* Escape handling is now active inside **at-blocks**, inline values, and block bodies.
|
|
76
|
+
* Documents using the previous escape syntax must be updated.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### Parser & Lexer Improvements
|
|
81
|
+
|
|
82
|
+
* Refactored character concatenation logic by replacing the single `concat` function with specialized handlers:
|
|
83
|
+
|
|
84
|
+
* `concatText`
|
|
85
|
+
* `concatEscape`
|
|
86
|
+
* `concatChar`
|
|
87
|
+
|
|
88
|
+
This separation eliminated multiple hidden parsing bugs and significantly improved maintainability.
|
|
89
|
+
|
|
90
|
+
* Fixed an issue where **at-block endings were incorrectly treated as text** when preceded by leading spaces, which could corrupt the entire document.
|
|
91
|
+
|
|
92
|
+
* Fixed errors caused by starting a new block on the same line where a previous block ended (e.g. `[end][Block]`).
|
|
93
|
+
|
|
94
|
+
* Improved correctness when handling consecutive blocks without implicit line separation.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
### Internal Improvements
|
|
99
|
+
|
|
100
|
+
* Simplified escape handling for safer and more predictable parsing
|
|
101
|
+
* Improved compatibility between escapes, inline values, and at-block bodies
|
|
102
|
+
* Reduced parser complexity and improved stability
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### Notes
|
|
107
|
+
|
|
108
|
+
This release introduces **intentional breaking changes** to core grammar behavior.
|
|
109
|
+
It is published as a **beta** to allow validation before the stable **v2.0.0** release.
|
|
110
|
+
|
|
111
|
+
---
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
20
|
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" />
|
|
21
|
-
<img src="https://img.shields.io/
|
|
21
|
+
<img src="https://img.shields.io/npm/v/sommark?style=flat-square" />
|
|
22
22
|
<img src="https://img.shields.io/badge/type-markup%20language-purple?style=flat-square" />
|
|
23
23
|
<img src="https://img.shields.io/badge/html-supported-orange?style=flat-square" />
|
|
24
24
|
<img src="https://img.shields.io/badge/markdown-supported-lightyellow?style=flat-square" />
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
-
# SomMark
|
|
31
|
+
# SomMark v2-beta
|
|
32
32
|
|
|
33
33
|
SomMark is a simple and extensible markup language designed for writing documentation and structured content. It is easy to read, easy to parse, and easy to convert into formats like HTML, Markdown, and MDX.
|
|
34
34
|
|
package/core/lexer.js
CHANGED
|
@@ -3,11 +3,16 @@ import peek from "../helpers/peek.js";
|
|
|
3
3
|
import { block_value, block_id, inline_id, inline_value, at_id, at_value, end_keyword } from "./names.js";
|
|
4
4
|
import { lexerError } from "./validator.js";
|
|
5
5
|
|
|
6
|
-
function
|
|
7
|
-
let
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
function concatText(input, index, scope_state) {
|
|
7
|
+
let text = "";
|
|
8
|
+
if (
|
|
9
|
+
(Array.isArray(input) || typeof input === "string") &&
|
|
10
|
+
input.length > 0 &&
|
|
11
|
+
typeof index === "number" &&
|
|
12
|
+
typeof scope_state === "boolean"
|
|
13
|
+
) {
|
|
14
|
+
for (let i = index; i < input.length; i++) {
|
|
15
|
+
const char = input[i];
|
|
11
16
|
if (char === "\n") {
|
|
12
17
|
break;
|
|
13
18
|
} else if (char === "[" && !scope_state) {
|
|
@@ -18,33 +23,75 @@ function concat(input, index, stop_at_char = [], scope_state, include_then_break
|
|
|
18
23
|
break;
|
|
19
24
|
} else if (char === "(" && !scope_state) {
|
|
20
25
|
break;
|
|
21
|
-
} else if (char === "-" && peek(input,
|
|
26
|
+
} else if (char === "-" && peek(input, i, 1) === ">" && !scope_state) {
|
|
22
27
|
break;
|
|
23
|
-
} else if (char === "@" && peek(input,
|
|
28
|
+
} else if (char === "@" && peek(input, i, 1) === "_") {
|
|
24
29
|
break;
|
|
25
|
-
} else if (char === "_" && peek(input,
|
|
30
|
+
} else if (char === "_" && peek(input, i, 1) === "@") {
|
|
26
31
|
break;
|
|
27
32
|
} else if (char === "#" && !scope_state) {
|
|
28
33
|
break;
|
|
29
|
-
} else if (char === "
|
|
34
|
+
} else if (char === "\\") {
|
|
30
35
|
break;
|
|
31
36
|
}
|
|
32
|
-
|
|
37
|
+
text += char;
|
|
38
|
+
}
|
|
39
|
+
return text;
|
|
40
|
+
} else {
|
|
41
|
+
lexerError([
|
|
42
|
+
"{line}<$red:Invalid Arguments:$> <$yellow:Assign arguments to their correct types, ",
|
|
43
|
+
"'input' must be an array and have to be not empty, 'index' must be a number value, and 'scope_state' ",
|
|
44
|
+
"must be a boolean.$>{line}."
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function concatEscape(input, index) {
|
|
50
|
+
if ((Array.isArray(input) || typeof input === "string") && input.length > 0 && typeof index === "number") {
|
|
51
|
+
let str = "";
|
|
52
|
+
if (input[index] === "\\" && peek(input, index, 1)) {
|
|
53
|
+
str += "\\" + peek(input, index, 1);
|
|
33
54
|
} else {
|
|
34
|
-
|
|
35
|
-
str += char + peek(input, char_index, 1);
|
|
36
|
-
break;
|
|
37
|
-
} else if (char === escapeChar && peek(input, char_index, 1) !== "`") {
|
|
38
|
-
break;
|
|
39
|
-
} else if (char !== escapeChar && Array.isArray(stop_at_char) && stop_at_char.includes(char)) {
|
|
40
|
-
include_then_break ? (str += char) : null;
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
str += char;
|
|
55
|
+
lexerError(["{line}<$red:Next character is not found:$> <$yellow:There is no character after escape character!$>{line}"]);
|
|
44
56
|
}
|
|
57
|
+
return str;
|
|
58
|
+
} else {
|
|
59
|
+
lexerError([
|
|
60
|
+
"{line}<$red:Invalid Arguments:$> <$yellow:Assign arguments to their correct types, ",
|
|
61
|
+
"'input' must be an array and have to be not empty, and 'index' must be a number value.$>{line}"
|
|
62
|
+
]);
|
|
45
63
|
}
|
|
64
|
+
}
|
|
46
65
|
|
|
47
|
-
|
|
66
|
+
function concatChar(input, index, stop_at_char, scope_state) {
|
|
67
|
+
if (
|
|
68
|
+
(Array.isArray(input) || typeof input === "string") &&
|
|
69
|
+
input.length > 0 &&
|
|
70
|
+
typeof index === "number" &&
|
|
71
|
+
typeof scope_state === "boolean"
|
|
72
|
+
) {
|
|
73
|
+
let str = "";
|
|
74
|
+
if (Array.isArray(stop_at_char) && stop_at_char.length > 0) {
|
|
75
|
+
for (let i = index; i < input.length; i++) {
|
|
76
|
+
const char = input[i];
|
|
77
|
+
if (stop_at_char.includes(char)) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
str += char;
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
lexerError([
|
|
84
|
+
"{line}<$red:Invalid Type:$> <$yellow:Argument 'stop_at_char' must be an array and have to be not empty array$>{line}"
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
return str;
|
|
88
|
+
} else {
|
|
89
|
+
lexerError([
|
|
90
|
+
"{line}<$red:Invalid Arguments:$> <$yellow:Assign arguments to their correct types, ",
|
|
91
|
+
"'input' must be an array and have to be not empty, 'index' must be a number value, and 'scope_state' ",
|
|
92
|
+
"must be a boolean.$>{line}"
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
48
95
|
}
|
|
49
96
|
|
|
50
97
|
function lexer(src) {
|
|
@@ -77,7 +124,7 @@ function lexer(src) {
|
|
|
77
124
|
end = start;
|
|
78
125
|
}
|
|
79
126
|
// Push to depth if next token is not end_keyword
|
|
80
|
-
temp_str =
|
|
127
|
+
temp_str = concatChar(src, i + 1, ["]"], scope_state);
|
|
81
128
|
if (temp_str && temp_str.length > 0) {
|
|
82
129
|
if (temp_str.trim() !== end_keyword) {
|
|
83
130
|
depth_stack.push("Block");
|
|
@@ -171,21 +218,19 @@ function lexer(src) {
|
|
|
171
218
|
addToken(TOKEN_TYPES.NEWLINE, current_char);
|
|
172
219
|
}
|
|
173
220
|
// Escape character
|
|
174
|
-
else if (current_char === "
|
|
175
|
-
temp_str =
|
|
176
|
-
if (temp_str && temp_str.length > 0) {
|
|
221
|
+
else if (current_char === "\\") {
|
|
222
|
+
temp_str = concatEscape(src, i, scope_state);
|
|
223
|
+
if (temp_str.trim() && temp_str.length > 0) {
|
|
177
224
|
i += temp_str.length - 1;
|
|
178
|
-
|
|
179
|
-
addToken(TOKEN_TYPES.VALUE, temp_str);
|
|
180
|
-
} else {
|
|
181
|
-
addToken(TOKEN_TYPES.TEXT, temp_str);
|
|
182
|
-
}
|
|
225
|
+
addToken(TOKEN_TYPES.ESCAPE, temp_str);
|
|
183
226
|
}
|
|
184
227
|
}
|
|
185
|
-
// Token: Block Identifier
|
|
228
|
+
// Token: Block Identifier
|
|
229
|
+
// Token: Block Value
|
|
230
|
+
// Token: End Keyword
|
|
186
231
|
else {
|
|
187
232
|
if (previous_value === "[" || (previous_value === "=" && !scope_state)) {
|
|
188
|
-
temp_str =
|
|
233
|
+
temp_str = concatChar(src, i, ["=", "]", "\n"], scope_state);
|
|
189
234
|
i += temp_str.length - 1;
|
|
190
235
|
// Update Column
|
|
191
236
|
start = end !== undefined ? end : 1;
|
|
@@ -211,9 +256,10 @@ function lexer(src) {
|
|
|
211
256
|
}
|
|
212
257
|
}
|
|
213
258
|
}
|
|
214
|
-
// Token: Inline Value
|
|
259
|
+
// Token: Inline Value
|
|
260
|
+
// Token: Inline Identifier
|
|
215
261
|
else if (previous_value === "(" || (previous_value === "->" && !scope_state)) {
|
|
216
|
-
temp_str =
|
|
262
|
+
temp_str = concatChar(src, i, [")", "[", "\\"], scope_state);
|
|
217
263
|
i += temp_str.length - 1;
|
|
218
264
|
// Update Column
|
|
219
265
|
start = end !== undefined ? end : 1;
|
|
@@ -230,9 +276,11 @@ function lexer(src) {
|
|
|
230
276
|
}
|
|
231
277
|
}
|
|
232
278
|
}
|
|
233
|
-
// Token: At Identifier
|
|
279
|
+
// Token: At Identifier
|
|
280
|
+
// Token: At Value
|
|
281
|
+
// Token: End Keyword
|
|
234
282
|
else if (previous_value === "@_" || previous_value === ":") {
|
|
235
|
-
temp_str =
|
|
283
|
+
temp_str = concatChar(src, i, ["_", "\n"], scope_state);
|
|
236
284
|
i += temp_str.length - 1;
|
|
237
285
|
if (temp_str.trim()) {
|
|
238
286
|
// Update Column
|
|
@@ -260,7 +308,7 @@ function lexer(src) {
|
|
|
260
308
|
}
|
|
261
309
|
// Token: Comment
|
|
262
310
|
else if (current_char === "#") {
|
|
263
|
-
temp_str =
|
|
311
|
+
temp_str = concatChar(src, i, ["\n"], scope_state);
|
|
264
312
|
// Update Column
|
|
265
313
|
start = previous_value === "\n" ? end : end !== undefined ? end + 1 : 1;
|
|
266
314
|
end = start + temp_str.length;
|
|
@@ -271,7 +319,7 @@ function lexer(src) {
|
|
|
271
319
|
}
|
|
272
320
|
// Token: Text
|
|
273
321
|
else {
|
|
274
|
-
context =
|
|
322
|
+
context = concatText(src, i, scope_state);
|
|
275
323
|
i += context.length - 1;
|
|
276
324
|
// Update Column
|
|
277
325
|
start = previous_value === "\n" ? end : end !== undefined ? end + 1 : 1;
|
package/core/parser.js
CHANGED
|
@@ -111,9 +111,10 @@ function parseBlock(tokens, i) {
|
|
|
111
111
|
block_stack.push(1);
|
|
112
112
|
end_stack.pop();
|
|
113
113
|
const blockNode = makeBlockNode();
|
|
114
|
+
// consume '['
|
|
115
|
+
i++;
|
|
114
116
|
// Update Data
|
|
115
117
|
updateData(tokens, i);
|
|
116
|
-
i++;
|
|
117
118
|
if (current_token(tokens, i).type === TOKEN_TYPES.IDENTIFIER) {
|
|
118
119
|
const id = current_token(tokens, i).value.trim();
|
|
119
120
|
validateId(id);
|
|
@@ -122,13 +123,15 @@ function parseBlock(tokens, i) {
|
|
|
122
123
|
} else {
|
|
123
124
|
parserError(errorMessage(tokens, i, block_id, "["));
|
|
124
125
|
}
|
|
126
|
+
// consume Block identifier
|
|
127
|
+
i++;
|
|
125
128
|
// Update Data
|
|
126
129
|
updateData(tokens, i);
|
|
127
|
-
i++;
|
|
128
130
|
if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.EQUAL) {
|
|
131
|
+
// consume '='
|
|
132
|
+
i++;
|
|
129
133
|
// Update Data
|
|
130
134
|
updateData(tokens, i);
|
|
131
|
-
i++;
|
|
132
135
|
if (current_token(tokens, i).type === TOKEN_TYPES.VALUE) {
|
|
133
136
|
current_token(tokens, i)
|
|
134
137
|
.value.split(",")
|
|
@@ -138,9 +141,10 @@ function parseBlock(tokens, i) {
|
|
|
138
141
|
} else {
|
|
139
142
|
parserError(errorMessage(tokens, i, block_value, "="));
|
|
140
143
|
}
|
|
144
|
+
// consume Block value
|
|
145
|
+
i++;
|
|
141
146
|
// Update Data
|
|
142
147
|
updateData(tokens, i);
|
|
143
|
-
i++;
|
|
144
148
|
}
|
|
145
149
|
if (current_token(tokens, i) && current_token(tokens, i).type !== TOKEN_TYPES.CLOSE_BRACKET) {
|
|
146
150
|
if (peek(tokens, i, -1) && peek(tokens, i, -1).type === TOKEN_TYPES.VALUE) {
|
|
@@ -149,20 +153,22 @@ function parseBlock(tokens, i) {
|
|
|
149
153
|
parserError(errorMessage(tokens, i, "]", block_id));
|
|
150
154
|
}
|
|
151
155
|
}
|
|
152
|
-
//
|
|
153
|
-
updateData(tokens, i);
|
|
156
|
+
// consume ']'
|
|
154
157
|
i++;
|
|
155
|
-
if (!current_token(tokens, i) || current_token(tokens, i).value !== "\n") {
|
|
156
|
-
parserError(errorMessage(tokens, i, "\\n", "]"));
|
|
157
|
-
}
|
|
158
158
|
// Update Data
|
|
159
159
|
updateData(tokens, i);
|
|
160
|
-
i
|
|
160
|
+
if (current_token(tokens, i) && current_token(tokens, i).type == TOKEN_TYPES.NEWLINE) {
|
|
161
|
+
// consume '\n'
|
|
162
|
+
i++;
|
|
163
|
+
// Update Data
|
|
164
|
+
updateData(tokens, i);
|
|
165
|
+
}
|
|
161
166
|
tokens_stack.length = 0;
|
|
162
167
|
while (i < tokens.length) {
|
|
163
168
|
if (current_token(tokens, i).value === "[" && peek(tokens, i, 1).value !== "end") {
|
|
164
169
|
const [childNode, nextIndex] = parseBlock(tokens, i);
|
|
165
170
|
blockNode.body.push(childNode);
|
|
171
|
+
// consume child node
|
|
166
172
|
i = nextIndex;
|
|
167
173
|
// Update Data
|
|
168
174
|
updateData(tokens, i);
|
|
@@ -170,20 +176,17 @@ function parseBlock(tokens, i) {
|
|
|
170
176
|
current_token(tokens, i).type === TOKEN_TYPES.OPEN_BRACKET &&
|
|
171
177
|
peek(tokens, i, 1).type === TOKEN_TYPES.END_KEYWORD
|
|
172
178
|
) {
|
|
179
|
+
// consume end keyword
|
|
180
|
+
i++;
|
|
173
181
|
// Update Data
|
|
174
182
|
updateData(tokens, i);
|
|
175
|
-
i++;
|
|
176
183
|
if (current_token(tokens, i).type === TOKEN_TYPES.END_KEYWORD) {
|
|
177
|
-
|
|
178
|
-
updateData(tokens, i);
|
|
179
|
-
if (peek(tokens, i, 1) && peek(tokens, i, 1).type === TOKEN_TYPES.CLOSE_BRACKET) {
|
|
180
|
-
// Update Data
|
|
181
|
-
updateData(tokens, i + 1);
|
|
182
|
-
} else {
|
|
184
|
+
if (peek(tokens, i, 1) && peek(tokens, i, 1).type !== TOKEN_TYPES.CLOSE_BRACKET) {
|
|
183
185
|
parserError(errorMessage(tokens, i, "]", end_keyword));
|
|
184
186
|
}
|
|
185
187
|
}
|
|
186
188
|
block_stack.pop();
|
|
189
|
+
// consume end keyword and ']'
|
|
187
190
|
i += 2;
|
|
188
191
|
// Update Data
|
|
189
192
|
updateData(tokens, i);
|
|
@@ -195,49 +198,69 @@ function parseBlock(tokens, i) {
|
|
|
195
198
|
continue;
|
|
196
199
|
}
|
|
197
200
|
blockNode.body.push(childNode);
|
|
198
|
-
if (blockNode.body[0].value === "\n") {
|
|
199
|
-
blockNode.body.splice(0, 1);
|
|
200
|
-
}
|
|
201
201
|
i = nextIndex;
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
-
i++;
|
|
205
204
|
return [blockNode, i];
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
// Parse Inline Statements
|
|
209
208
|
function parseInline(tokens, i) {
|
|
210
209
|
const inlineNode = makeInlineNode();
|
|
210
|
+
// consume '('
|
|
211
|
+
i++;
|
|
211
212
|
// Update Data
|
|
212
213
|
updateData(tokens, i);
|
|
213
|
-
i++;
|
|
214
214
|
if (current_token(tokens, i).type === TOKEN_TYPES.VALUE) {
|
|
215
215
|
inlineNode.value = current_token(tokens, i).value;
|
|
216
216
|
inlineNode.depth = current_token(tokens, i).depth;
|
|
217
217
|
} else {
|
|
218
218
|
parserError(errorMessage(tokens, i, inline_value, "("));
|
|
219
219
|
}
|
|
220
|
+
// consume Inline Value
|
|
221
|
+
i++;
|
|
222
|
+
// Edge case
|
|
223
|
+
let escape_characters = "";
|
|
224
|
+
if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.ESCAPE) {
|
|
225
|
+
while (i < tokens.length) {
|
|
226
|
+
if (current_token(tokens, i).type === TOKEN_TYPES.ESCAPE || current_token(tokens, i).type === TOKEN_TYPES.TEXT) {
|
|
227
|
+
switch (current_token(tokens, i).type) {
|
|
228
|
+
case TOKEN_TYPES.ESCAPE:
|
|
229
|
+
inlineNode.value += current_token(tokens, i).value.slice(1);
|
|
230
|
+
break;
|
|
231
|
+
case TOKEN_TYPES.TEXT:
|
|
232
|
+
inlineNode.value += current_token(tokens, i).value;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
i++;
|
|
236
|
+
} else {
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
220
241
|
// Update Data
|
|
221
242
|
updateData(tokens, i);
|
|
222
|
-
i++;
|
|
223
243
|
if (!current_token(tokens, i) || current_token(tokens, i).type !== TOKEN_TYPES.CLOSE_PAREN) {
|
|
224
244
|
parserError(errorMessage(tokens, i, ")", inline_value));
|
|
225
245
|
}
|
|
246
|
+
// consume ')'
|
|
247
|
+
i++;
|
|
226
248
|
// Update Data
|
|
227
249
|
updateData(tokens, i);
|
|
228
|
-
i++;
|
|
229
250
|
if (!current_token(tokens, i) || current_token(tokens, i).type !== TOKEN_TYPES.THIN_ARROW) {
|
|
230
251
|
parserError(errorMessage(tokens, i, "->", ")"));
|
|
231
252
|
}
|
|
253
|
+
// consume '->'
|
|
254
|
+
i++;
|
|
232
255
|
// Update Data
|
|
233
256
|
updateData(tokens, i);
|
|
234
|
-
i++;
|
|
235
257
|
if (!current_token(tokens, i) || current_token(tokens, i).type !== TOKEN_TYPES.OPEN_PAREN) {
|
|
236
258
|
parserError(errorMessage(tokens, i, "(", "->"));
|
|
237
259
|
}
|
|
260
|
+
// consume '('
|
|
261
|
+
i++;
|
|
238
262
|
// Update Data
|
|
239
263
|
updateData(tokens, i);
|
|
240
|
-
i++;
|
|
241
264
|
if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.IDENTIFIER) {
|
|
242
265
|
for (const id of PREDEFINED_IDS) {
|
|
243
266
|
if (current_token(tokens, i).value.includes(`${id}:`)) {
|
|
@@ -260,16 +283,18 @@ function parseInline(tokens, i) {
|
|
|
260
283
|
} else {
|
|
261
284
|
parserError(errorMessage(tokens, i, inline_id, "("));
|
|
262
285
|
}
|
|
286
|
+
// consume Inline Identifier
|
|
287
|
+
i++;
|
|
263
288
|
// Update Data
|
|
264
289
|
updateData(tokens, i);
|
|
265
|
-
i++;
|
|
266
290
|
if (!current_token(tokens, i) || current_token(tokens, i).type !== TOKEN_TYPES.CLOSE_PAREN) {
|
|
267
291
|
parserError(errorMessage(tokens, i, ")", inline_id));
|
|
268
292
|
}
|
|
293
|
+
// consume ')'
|
|
294
|
+
i++;
|
|
269
295
|
// Update Data
|
|
270
296
|
updateData(tokens, i);
|
|
271
297
|
tokens_stack.length = 0;
|
|
272
|
-
i++;
|
|
273
298
|
return [inlineNode, i];
|
|
274
299
|
}
|
|
275
300
|
|
|
@@ -280,18 +305,23 @@ function parseText(tokens, i) {
|
|
|
280
305
|
textNode.text = current_token(tokens, i).value;
|
|
281
306
|
textNode.depth = current_token(tokens, i).depth;
|
|
282
307
|
}
|
|
308
|
+
// consume TEXT
|
|
309
|
+
i++;
|
|
283
310
|
// Update Data
|
|
284
311
|
updateData(tokens, i);
|
|
285
|
-
i++;
|
|
286
312
|
return [textNode, i];
|
|
287
313
|
}
|
|
288
314
|
|
|
289
315
|
// Parse At_Block
|
|
290
316
|
function parseAtBlock(tokens, i) {
|
|
291
317
|
const atBlockNode = makeAtBlockNode();
|
|
318
|
+
// consume '@_'
|
|
319
|
+
i++;
|
|
292
320
|
// Update Data
|
|
293
321
|
updateData(tokens, i);
|
|
294
|
-
i
|
|
322
|
+
if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.END_KEYWORD) {
|
|
323
|
+
parserError(errorMessage(tokens, i, at_id, "@_"));
|
|
324
|
+
}
|
|
295
325
|
if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.IDENTIFIER) {
|
|
296
326
|
const id = current_token(tokens, i).value.trim();
|
|
297
327
|
validateId(id);
|
|
@@ -300,28 +330,32 @@ function parseAtBlock(tokens, i) {
|
|
|
300
330
|
} else {
|
|
301
331
|
parserError(errorMessage(tokens, i, at_id, "@_"));
|
|
302
332
|
}
|
|
333
|
+
// consume Atblock Identifier
|
|
334
|
+
i++;
|
|
303
335
|
// Update Data
|
|
304
336
|
updateData(tokens, i);
|
|
305
|
-
i++;
|
|
306
337
|
if (!current_token(tokens, i) || current_token(tokens, i).type !== TOKEN_TYPES.CLOSE_AT) {
|
|
307
338
|
parserError(errorMessage(tokens, i, "_@", at_id));
|
|
308
339
|
}
|
|
340
|
+
// consume '_@'
|
|
341
|
+
i++;
|
|
309
342
|
// Update Data
|
|
310
343
|
updateData(tokens, i);
|
|
311
|
-
i++;
|
|
312
344
|
if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.COLON) {
|
|
345
|
+
// consume ':'
|
|
346
|
+
i++;
|
|
313
347
|
// Update Data
|
|
314
348
|
updateData(tokens, i);
|
|
315
|
-
i++;
|
|
316
349
|
if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.VALUE) {
|
|
317
350
|
current_token(tokens, i)
|
|
318
351
|
.value.split(",")
|
|
319
352
|
.forEach(value => {
|
|
320
353
|
atBlockNode.args.push(value.trim());
|
|
321
354
|
});
|
|
355
|
+
// consume Atblock Value
|
|
356
|
+
i++;
|
|
322
357
|
// Update Data
|
|
323
358
|
updateData(tokens, i);
|
|
324
|
-
i++;
|
|
325
359
|
} else {
|
|
326
360
|
parserError(errorMessage(tokens, i, at_value, ":"));
|
|
327
361
|
}
|
|
@@ -329,55 +363,65 @@ function parseAtBlock(tokens, i) {
|
|
|
329
363
|
if (current_token(tokens, i) && current_token(tokens, i).type !== TOKEN_TYPES.NEWLINE) {
|
|
330
364
|
parserError(errorMessage(tokens, i, "\\n", "_@"));
|
|
331
365
|
}
|
|
366
|
+
// consume '\n'
|
|
367
|
+
i++;
|
|
332
368
|
// Update Data
|
|
333
369
|
updateData(tokens, i);
|
|
334
|
-
i++;
|
|
335
370
|
if (current_token(tokens, i) && current_token(tokens, i).type !== TOKEN_TYPES.TEXT) {
|
|
336
371
|
parserError(errorMessage(tokens, i, "Text", "\\n"));
|
|
337
372
|
}
|
|
338
373
|
while (i < tokens.length) {
|
|
339
374
|
if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.TEXT) {
|
|
340
375
|
atBlockNode.content.push(current_token(tokens, i).value);
|
|
376
|
+
// consume TEXT
|
|
377
|
+
i++;
|
|
341
378
|
// Update Data
|
|
342
379
|
updateData(tokens, i);
|
|
343
|
-
i++;
|
|
344
380
|
} else if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.NEWLINE) {
|
|
381
|
+
// consume '\n'
|
|
382
|
+
i++;
|
|
345
383
|
// Update Data
|
|
346
384
|
updateData(tokens, i);
|
|
347
|
-
i++;
|
|
348
385
|
continue;
|
|
349
386
|
} else {
|
|
350
387
|
break;
|
|
351
388
|
}
|
|
352
389
|
}
|
|
353
390
|
if (current_token(tokens, i) && current_token(tokens, i).type === TOKEN_TYPES.NEWLINE) {
|
|
391
|
+
// consume '\n'
|
|
392
|
+
i++;
|
|
354
393
|
// Update Data
|
|
355
394
|
updateData(tokens, i);
|
|
356
|
-
i++;
|
|
357
395
|
}
|
|
358
396
|
if (!current_token(tokens, i) || current_token(tokens, i).type !== TOKEN_TYPES.OPEN_AT) {
|
|
359
397
|
parserError(errorMessage(tokens, i, "@_", "\\n"));
|
|
360
398
|
}
|
|
399
|
+
// consume '@_'
|
|
400
|
+
i++;
|
|
361
401
|
// Update Data
|
|
362
402
|
updateData(tokens, i);
|
|
363
|
-
i++;
|
|
364
403
|
if (!current_token(tokens, i) || current_token(tokens, i).type !== TOKEN_TYPES.END_KEYWORD) {
|
|
365
404
|
parserError(errorMessage(tokens, i, end_keyword, "@_"));
|
|
366
405
|
}
|
|
406
|
+
// console end keyword
|
|
407
|
+
i++;
|
|
367
408
|
// Update Data
|
|
368
409
|
updateData(tokens, i);
|
|
369
|
-
i++;
|
|
370
410
|
if (!current_token(tokens, i) || current_token(tokens, i).type !== TOKEN_TYPES.CLOSE_AT) {
|
|
371
411
|
parserError(errorMessage(tokens, i, "_@", end_keyword));
|
|
372
412
|
}
|
|
413
|
+
// consume '_@'
|
|
414
|
+
i++;
|
|
373
415
|
// Update Data
|
|
374
416
|
updateData(tokens, i);
|
|
375
|
-
i++;
|
|
376
417
|
if (!current_token(tokens, i) || current_token(tokens, i).value !== "\n") {
|
|
377
418
|
parserError(errorMessage(tokens, i, "\\n", "_@"));
|
|
378
419
|
}
|
|
379
|
-
|
|
420
|
+
// consume '\n'
|
|
380
421
|
i++;
|
|
422
|
+
// Update Data
|
|
423
|
+
updateData(tokens, i);
|
|
424
|
+
tokens_stack.length = 0;
|
|
381
425
|
return [atBlockNode, i];
|
|
382
426
|
}
|
|
383
427
|
|
|
@@ -388,9 +432,10 @@ function parseCommentNode(tokens, i) {
|
|
|
388
432
|
commentNode.text = current_token(tokens, i).value;
|
|
389
433
|
commentNode.depth = current_token(tokens, i).depth;
|
|
390
434
|
}
|
|
435
|
+
// consume Comment
|
|
436
|
+
i++;
|
|
391
437
|
// Update Data
|
|
392
438
|
updateData(tokens, i);
|
|
393
|
-
i++;
|
|
394
439
|
return [commentNode, i];
|
|
395
440
|
}
|
|
396
441
|
|
|
@@ -415,7 +460,7 @@ function parseNode(tokens, i) {
|
|
|
415
460
|
return parseText(tokens, i);
|
|
416
461
|
}
|
|
417
462
|
// At_Block
|
|
418
|
-
else if (current_token(tokens, i).value === "@_"
|
|
463
|
+
else if (current_token(tokens, i).value === "@_") {
|
|
419
464
|
return parseAtBlock(tokens, i);
|
|
420
465
|
}
|
|
421
466
|
// Newline
|
|
@@ -462,4 +507,4 @@ function parser(tokens) {
|
|
|
462
507
|
return ast;
|
|
463
508
|
}
|
|
464
509
|
|
|
465
|
-
export default parser;
|
|
510
|
+
export default parser;
|
package/core/tokenTypes.js
CHANGED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<meta charset="UTF-8" />
|
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
6
|
+
<title>SomMark Page</title>
|
|
7
|
+
|
|
8
|
+
<link rel="stylesheet" href="node_modules/highlight.js/styles/atom-one-dark.min.css">
|
|
9
|
+
<body>
|
|
10
|
+
|
|
11
|
+
Title: Example Block — Python
|
|
12
|
+
This is a block of text in a .smark file. >
|
|
13
|
+
|
|
14
|
+
<h2>Heading</h2>
|
|
15
|
+
This block shows a simple Python example.
|
|
16
|
+
<pre><code class="hljs language-python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">greet</span>(<span class="hljs-params">name</span>):
|
|
17
|
+
<span class="hljs-keyword">return</span> <span class="hljs-string">f"Hello, <span class="hljs-subst">{name}</span>!"</span>
|
|
18
|
+
<span class="hljs-built_in">print</span>(greet(<span class="hljs-string">"World"</span>))</code></pre>
|
|
19
|
+
|
|
20
|
+
Title: Example Block — JavaScript
|
|
21
|
+
This is another block of text in a .smark file.
|
|
22
|
+
|
|
23
|
+
<h2>Heading</h2>
|
|
24
|
+
This block shows a simple JavaScript example.
|
|
25
|
+
<pre><code class="hljs language-javascript"><span class="hljs-keyword">function</span> <span class="hljs-title function_">greet</span>(<span class="hljs-params">name</span>) {
|
|
26
|
+
<span class="hljs-keyword">return</span> <span class="hljs-string">`Hello, <span class="hljs-subst">${name}</span>!`</span>;
|
|
27
|
+
}
|
|
28
|
+
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-title function_">greet</span>(<span class="hljs-string">"World"</span>));</code></pre>
|
|
29
|
+
|
|
30
|
+
Title: Example Block — Ruby
|
|
31
|
+
Nested block content and a Ruby example.
|
|
32
|
+
|
|
33
|
+
<h2>Heading</h2>
|
|
34
|
+
This block shows a simple Ruby example.
|
|
35
|
+
<pre><code class="hljs language-ruby"><span class="hljs-keyword">def</span> <span class="hljs-title function_">greet</span>(<span class="hljs-params">name</span>)
|
|
36
|
+
<span class="hljs-string">"Hello, <span class="hljs-subst">#{name}</span>!"</span>
|
|
37
|
+
<span class="hljs-keyword">end</span>
|
|
38
|
+
puts greet(<span class="hljs-string">"World"</span>)</code></pre>
|
|
39
|
+
|
|
40
|
+
Title: Parent Block with Nested Children
|
|
41
|
+
This parent block contains nested child blocks to demonstrate nesting.
|
|
42
|
+
|
|
43
|
+
<h1>Heading</h1>
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
Title: Child Block — Python Utility
|
|
47
|
+
Child block 1: a small utility function.
|
|
48
|
+
<pre><code class="hljs language-python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">repeat</span>(<span class="hljs-params">msg, n=<span class="hljs-number">3</span></span>):
|
|
49
|
+
<span class="hljs-keyword">return</span> <span class="hljs-string">" "</span>.join([msg] * n)
|
|
50
|
+
<span class="hljs-built_in">print</span>(repeat(<span class="hljs-string">"Echo"</span>))</code></pre>
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
Title: Child Block — JavaScript Utility
|
|
55
|
+
Child block 2: a small utility function.
|
|
56
|
+
<pre><code class="hljs language-javascript"><span class="hljs-keyword">const</span> <span class="hljs-title function_">repeat</span> = (<span class="hljs-params">msg, n = <span class="hljs-number">3</span></span>) => <span class="hljs-title class_">Array</span>(n).<span class="hljs-title function_">fill</span>(msg).<span class="hljs-title function_">join</span>(<span class="hljs-string">" "</span>);
|
|
57
|
+
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(<span class="hljs-title function_">repeat</span>(<span class="hljs-string">"Echo"</span>));</code></pre>
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
Title: Child Block — Nested Grandchild Example
|
|
62
|
+
This child contains a deeper nested block.
|
|
63
|
+
<pre><code class="hljs language-markdown"><span class="hljs-bullet">-</span> parent: Child Block
|
|
64
|
+
<span class="hljs-bullet">-</span> note: contains a grandchild block below</code></pre>
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
Title: Grandchild Block — Inline Note
|
|
69
|
+
Deeper nested block grandchild.
|
|
70
|
+
This illustrates multi-level nesting.
|
|
71
|
+
<pre><code class="hljs language-text">This is a grandchild block inside a child block.</code></pre>
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
Title: Final Organized Block
|
|
77
|
+
Summary and notes about the examples above.
|
|
78
|
+
|
|
79
|
+
<h3>Heading</h3>
|
|
80
|
+
Use these blocks as templates for adding more nested content.
|
|
81
|
+
|
|
82
|
+
</body>
|
|
83
|
+
</html>
|
package/formatter/tag.js
CHANGED
|
@@ -36,7 +36,7 @@ class TagBuilder {
|
|
|
36
36
|
this.#attr.push(`${key2}="${escapeHTML(value)}"`);
|
|
37
37
|
break;
|
|
38
38
|
case "other":
|
|
39
|
-
this.#attr.push(`${key2}={${value}}`); //
|
|
39
|
+
this.#attr.push(`${key2}={${value}}`); // [NEEDS IMPROVEMENT]
|
|
40
40
|
break;
|
|
41
41
|
}
|
|
42
42
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sommark",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
4
|
"description": "SomMark is a structural markup language for writing structured documents and converting them into HTML or Markdown or MDX(only ready components).",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"test:watch": "vitest watch"
|
|
17
17
|
},
|
|
18
18
|
"bin": {
|
|
19
|
-
"smark": "
|
|
19
|
+
"smark": "cli/cli.mjs"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
@@ -48,4 +48,4 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"vitest": "^4.0.16"
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|