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 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/badge/version-v1.0.0-blue?style=flat-square" />
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 v1
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 concat(input, index, stop_at_char = [], scope_state, include_then_break = false, escapeChar = "") {
7
- let str = "";
8
- for (let char_index = index; char_index < input.length; char_index++) {
9
- let char = input[char_index];
10
- if (stop_at_char === null) {
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, char_index, 1) === ">" && !scope_state) {
26
+ } else if (char === "-" && peek(input, i, 1) === ">" && !scope_state) {
22
27
  break;
23
- } else if (char === "@" && peek(input, char_index, 1) === "_" && !scope_state) {
28
+ } else if (char === "@" && peek(input, i, 1) === "_") {
24
29
  break;
25
- } else if (char === "_" && peek(input, char_index, 1) === "@" && !scope_state) {
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 === "`" && !scope_state) {
34
+ } else if (char === "\\") {
30
35
  break;
31
36
  }
32
- str += char;
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
- if (char === escapeChar && peek(input, char_index, 1) === "`") {
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
- return str;
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 = concat(src, i + 1, ["]"], scope_state);
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 === "`" && !scope_state) {
175
- temp_str = current_char + concat(src, i + 1, ["`"], scope_state, true);
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
- if (previous_value === "(") {
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 OR Token: Block Value OR Token: End Keyword
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 = concat(src, i, ["=", "]", "\n"], scope_state);
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 OR Token: Inline Identifier
259
+ // Token: Inline Value
260
+ // Token: Inline Identifier
215
261
  else if (previous_value === "(" || (previous_value === "->" && !scope_state)) {
216
- temp_str = concat(src, i, [")", "["], scope_state, true, ")");
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 OR Token: At Value OR Token: End Keyword
279
+ // Token: At Identifier
280
+ // Token: At Value
281
+ // Token: End Keyword
234
282
  else if (previous_value === "@_" || previous_value === ":") {
235
- temp_str = concat(src, i, ["_", "\n"], scope_state);
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 = concat(src, i, ["\n"], scope_state);
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 = concat(src, i, null, scope_state);
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
- // Update Data
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
- // Update Data
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
- tokens_stack.length = 0;
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 === "@_" && peek(tokens, i, 1).type !== TOKEN_TYPES.END_KEYWORD) {
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;
@@ -14,7 +14,8 @@ const TOKEN_TYPES = {
14
14
  CLOSE_AT: "CLOSE_AT",
15
15
  COLON: "COLON",
16
16
  COMMENT: "COMMENT",
17
- NEWLINE: "NEWLINE"
17
+ NEWLINE: "NEWLINE",
18
+ ESCAPE: "ESCAPE"
18
19
  };
19
20
 
20
21
  export default TOKEN_TYPES;
@@ -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. &gt;
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&quot;Hello, <span class="hljs-subst">{name}</span>!&quot;</span>
18
+ <span class="hljs-built_in">print</span>(greet(<span class="hljs-string">&quot;World&quot;</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">&quot;World&quot;</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">&quot;Hello, <span class="hljs-subst">#{name}</span>!&quot;</span>
37
+ <span class="hljs-keyword">end</span>
38
+ puts greet(<span class="hljs-string">&quot;World&quot;</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">&quot; &quot;</span>.join([msg] * n)
50
+ <span class="hljs-built_in">print</span>(repeat(<span class="hljs-string">&quot;Echo&quot;</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>) =&gt; <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">&quot; &quot;</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">&quot;Echo&quot;</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}}`); // React/MDX props might need different handling, but usually raw JS expression?
39
+ this.#attr.push(`${key2}={${value}}`); // [NEEDS IMPROVEMENT]
40
40
  break;
41
41
  }
42
42
  }
@@ -2,7 +2,6 @@ import Mapper from "../mapper.js";
2
2
  const html = new Mapper();
3
3
  const { tag, code, list } = html;
4
4
 
5
- html.selectedTheme = "paraiso-dark";
6
5
  html.env = "browser";
7
6
 
8
7
  // Block
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sommark",
3
- "version": "1.1.1",
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": "./cli/cli.mjs"
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
+ }