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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.2.0 (2026-02-23)
4
+ ## Features
5
+ - Added JSON support
6
+ - Added type rules to validate element type
7
+ - Improved documentation
8
+ - Improved mapper files
9
+ - Removed **highlight.js** dependency
10
+ - Added new CLI feature: automatic configuration file creation
11
+
12
+
3
13
  ## v2.1.2 (2026-02-09)
4
14
  ### Fixes
5
15
  - Fixed cli print functionality
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  <img width="2000" height="491" alt="SomMark Cover" src="https://raw.githubusercontent.com/Adam-Elmi/SomMark/master/assets/smark_bg.png" />
2
2
 
3
3
  <p align="center">
4
- SomMark is a structural markup language designed for technical documentation. It focuses on explicit structure and flexibility.
4
+ SomMark is a declarative, extensible markup language for structured content that can be converted to HTML, Markdown, MDX, JSON, and more.
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -11,6 +11,7 @@
11
11
  <img src="https://img.shields.io/badge/html-supported-orange?style=flat-square" />
12
12
  <img src="https://img.shields.io/badge/markdown-supported-lightyellow?style=flat-square" />
13
13
  <img src="https://img.shields.io/badge/mdx-supported-lightblue?style=flat-square" />
14
+ <img src="https://img.shields.io/badge/json-supported-yellow?style=flat-square" />
14
15
  </p>
15
16
 
16
17
  # SomMark v2
@@ -18,118 +19,8 @@
18
19
  > [!WARNING]
19
20
  > Old version(v1) is no longer supported.
20
21
 
21
- SomMark provides a way to write structured content that can be converted into other formats like HTML or Markdown. It is different from standard Markdown because it uses explicit syntax for blocks and elements, which makes it easier to process and customize.
22
+ **SomMark** lets you write structured content that can be converted to HTML, Markdown, JSON, or other formats. Unlike standard Markdown, it uses explicit syntax for blocks and elements, making content easier to process, customize, and transform.
22
23
 
23
- # Features
24
-
25
- * **Explicit Syntax**: Every element has a clear start and end, which reduces parsing errors.
26
- * **Customizable Mappers**: You can convert SomMark content into any format (HTML, Markdown, JSON, etc.) by using Mappers.
27
- * **Validation**: You can define rules to check your content, such as limiting the number of arguments or requiring specific keys.
28
- * **MDX Support**: SomMark can map to existing React components ("Only Ready Components").
29
- > [!NOTE]
30
- > SomMark does not parse raw JSX. It only maps to ready components.
31
-
32
- # Syntax
33
-
34
- SomMark uses three main types of elements: Blocks, Inline Statements, and At-Blocks.
35
-
36
- ## 1. Block
37
-
38
- A Block is a container that holds content. It can contain text or other nested blocks.
39
-
40
- **With Arguments**
41
- You can pass data to a block using arguments. Arguments can have keys.
42
- ```ini
43
- [Alert = urgent, title: Warning]
44
- System maintenance in 10 minutes.
45
- This block uses a flag (urgent) and a key-value pair (title).
46
- [end]
47
- ```
48
- > [!NOTE]
49
- > The colon `:` separates keys and values.
50
- > Keys are optional.
51
-
52
- **Without Arguments**
53
- ```ini
54
- [Note]
55
- This is a simple block.
56
- [end]
57
- ```
58
-
59
- ## 2. Inline Statement
60
-
61
- Inline Statements are used to format specific parts of text.
62
-
63
- **With Arguments**
64
- ```ini
65
- This text is (bold)->(bold) and this is (red)->(color: red).
66
- ```
67
- > [!NOTE]
68
- > Inline arguments are values only. Keys are not supported.
69
-
70
- **Without Arguments**
71
- ```ini
72
- (Click here)->(Button)
73
- ```
74
-
75
- ## 3. At-Block
76
-
77
- At-Blocks are used for specific content like code snippets or tables. The content inside an At-Block is treated as plain text and cannot contain other SomMark elements.
78
-
79
- **With Arguments**
80
- ```ini
81
- @_Code_@: javascript;
82
- console.log("This is raw code.");
83
- @_end_@
84
- ```
85
- > [!NOTE]
86
- > You must use a semi-colon `;` to end the argument list.
87
-
88
- **Without Arguments**
89
- ```ini
90
- @_Raw_@
91
- Raw content here.
92
- @_end_@
93
- ```
94
-
95
- ## General Rules
96
- * SomMark Top-Level Rules:
97
- - Only Blocks and comments are allowed at the top level.
98
- - Inline Statements, Atblocks, and text cannot appear at the top level. They must be inside a Block, or it is invalid.
99
- **Invalid Top-Level:**
100
- ```ini
101
- Hello world ❌ (Text cannot be at top level)
102
-
103
- Welcome to (SomMark)->(Bold) ❌ (Inline statement cannot be at top level)
104
-
105
- @_Code_@: js;
106
- function add(a, b) {
107
- return a + b;
108
- }
109
- @_end_@ ❌ (Atblock cannot be at top level)
110
- ```
111
-
112
- **Valid Top-Level:**
113
- ```ini
114
- [Block]
115
- Hello world
116
- Welcome to (SomMark)->(Bold) # Inline statement inside block is valid
117
-
118
- @_Code_@: js;
119
- function add(a, b) {
120
- return a + b; # Treated as plain text
121
- }
122
- @_end_@
123
-
124
- [end]
125
- ```
126
-
127
-
128
- * **Identifiers**: Names can only contain letters and numbers.
129
- * **Escape Character**: Use the backslash `\` to escape special characters (like colons or commas) inside arguments.
130
- * **Colons**: inside Block and At-Block arguments, the colon (`:`) separates names from values.
131
- * **Semi-Colons**: The semi-colon (`;`) is only used in At-Blocks to assert the end of the argument list.
132
- * **Whitespace**: SomMark ignores extra spaces and newlines, so you can format your code however you like.
133
24
 
134
25
  # Installation
135
26
 
@@ -184,190 +75,3 @@ Detailed guides and API references are available in the `docs/` directory:
184
75
  * **[CLI Reference](docs/cli.md)**: Command line options and configurations.
185
76
  * **[API Quick Reference](docs/api)**: Fast lookup for all classes and functions.
186
77
  * **[Configuration Reference](docs/config.md)**: Guide for creating custom configurations.
187
-
188
-
189
-
190
- # Configuration (Only for CLI)
191
-
192
- You can create a `smark.config.js` file to configure the CLI.
193
-
194
- ```javascript
195
- /* smark.config.js */
196
- import myMapper from "./my-mapper.js";
197
-
198
- export default {
199
- outputFile: "output", // Default output filename
200
- outputDir: "./dist", // Where to save files
201
- mappingFile: myMapper // Use a custom mapper by default
202
- };
203
- ```
204
-
205
- # Creating Custom Mappers
206
-
207
- Mappers tell SomMark how to convert your content. You can define rules, options, and how arguments are handled.
208
-
209
- ## Basic Structure
210
-
211
- ```javascript
212
- import { Mapper } from "sommark";
213
- const myMapper = new Mapper();
214
- const { tag } = myMapper;
215
-
216
- // Define a Block
217
- myMapper.register("Alert", ({ args, content }) => {
218
- // Access arguments by index or name
219
- const type = args[0] || args.type || "info";
220
-
221
- // Use TagBuilder to create HTML elements safely
222
- return myMapper.tag("div")
223
- .attributes({ class: `alert ${type}` })
224
- .body(content);
225
- });
226
-
227
- export default myMapper;
228
- ```
229
- > [!WARNING]
230
- > The `.body()` and `.selfClose()` methods return the final HTML string. You must treat them as the end of the builder chain. If you forget to call them, you will return a builder object instead of a string.
231
-
232
- You also skip tag builder and use raw HTML.
233
-
234
- ```javascript
235
- import { Mapper } from "sommark";
236
- const myMapper = new Mapper();
237
-
238
- myMapper.register("Alert", ({ args, content }) => {
239
- // Access arguments by index or name
240
- const type = args[0] || args.type || "info";
241
-
242
- // Use raw HTML
243
- return `<div class="alert ${type}">${content}</div>`;
244
- });
245
- ```
246
-
247
- ## Mapping Multiple Identifiers
248
-
249
- You can use an array of strings to map multiple names to the same output function.
250
-
251
- ```javascript
252
- import { Mapper } from "sommark";
253
- const myMapper = new Mapper();
254
- const { tag } = myMapper;
255
-
256
- // Both [code] and [Code] will use this mapper
257
- myMapper.register(["code", "Code"], ({ content }) => {
258
- return tag("pre").body(tag("code").body(content));
259
- });
260
- ```
261
-
262
- ## Reusing Existing Mappers
263
-
264
- You can borrow rules from default mappers to avoid rewriting them.
265
-
266
- ```javascript
267
- import { Mapper, HTML } from "sommark";
268
- const myMapper = new Mapper();
269
-
270
- // Reuse the "Code" block from the default HTML mapper
271
- const codeOutput = HTML.get("Code");
272
- if (codeOutput) {
273
- myMapper.register(codeOutput.id, codeOutput.render, codeOutput.options);
274
- }
275
-
276
- // Add your own custom blocks...
277
- myMapper.register("MyBlock", ({ content }) => {
278
- return content;
279
- });
280
-
281
- export default myMapper;
282
- ```
283
-
284
- ## Using Rules (Validation)
285
-
286
- You can force strict rules on your content. If a rule is broken, SomMark will stop and show an error.
287
-
288
- ### Argument Validation (`args`)
289
-
290
- Validates the arguments passed to the tag.
291
-
292
- ```javascript
293
- myMapper.register("User", ({ args }) => {
294
- return tag("div").body(`User: ${args[0]}`);
295
- }, {
296
- rules: {
297
- args: {
298
- min: 1, // Must have at least 1 argument
299
- max: 3, // Cannot have more than 3 arguments
300
- required: ["id"], // The "id" named key is required
301
- includes: ["id", "role", "age"] // Only these keys are allowed
302
- }
303
- }
304
- });
305
- ```
306
-
307
- - **`min`**: Minimum number of arguments required.
308
- - **`max`**: Maximum number of arguments allowed.
309
- - **`required`**: Array of keys that MUST be present in the arguments.
310
- - **`includes`**: Whitelist of allowed argument keys. Any key not in this list will trigger an error.
311
-
312
- ### Content Validation (`content`)
313
-
314
- Validates the inner content (body) of the block.
315
-
316
- ```javascript
317
- myMapper.register("Summary", ({ content }) => {
318
- return tag("p").body(content);
319
- }, {
320
- rules: {
321
- content: {
322
- maxLength: 100 // Content must be 100 characters or less
323
- }
324
- }
325
- });
326
- ```
327
-
328
- - **`maxLength`**: Maximum length of the content string.
329
-
330
- ### Self-Closing Tags
331
-
332
- Ensures a tag is used without content or children.
333
-
334
- ```javascript
335
- myMapper.register("Separator", () => {
336
- return tag("hr").selfClose();
337
- }, {
338
- rules: {
339
- is_Self_closing: true
340
- }
341
- });
342
- ```
343
-
344
- - **`is_Self_closing`**: If `true`, SomMark will throw an error if the tag contains any content.
345
-
346
- *Example input that passes:* `[Image = src: image.png, alt: Image][end]`
347
-
348
- ## Using Options
349
-
350
- Options change how SomMark processes the content inside the block.
351
-
352
- ```javascript
353
- import { Mapper } from "sommark";
354
- const myMapper = new Mapper();
355
- const { tag } = myMapper;
356
-
357
- myMapper.register("Code", ({ content }) => {
358
- return tag("pre").body(content);
359
- }, {
360
- escape: false,
361
- rules: {
362
- args: {
363
- min: 1,
364
- required: ["id"]
365
- }
366
- }
367
- }); // options
368
- ```
369
- ---
370
-
371
- # License
372
-
373
- MIT
package/cli/cli.mjs CHANGED
@@ -3,6 +3,8 @@ import { getHelp } from "./commands/help.js";
3
3
  import { printVersion, printHeader } from "./commands/version.js";
4
4
  import { runBuild } from "./commands/build.js";
5
5
  import { printOutput } from "./commands/print.js";
6
+ import { runInit } from "./commands/init.js";
7
+ import { runShow } from "./commands/show.js";
6
8
  import { extensions } from "./constants.js";
7
9
 
8
10
  // ========================================================================== //
@@ -33,7 +35,19 @@ async function main() {
33
35
  return;
34
36
  }
35
37
 
36
- // 4. Print to Console ( -p or --print )
38
+ // 4. Init
39
+ if (command === "init") {
40
+ await runInit();
41
+ return;
42
+ }
43
+
44
+ // 5. Show
45
+ if (command === "show") {
46
+ await runShow(args[1]);
47
+ return;
48
+ }
49
+
50
+ // 6. Print to Console ( -p or --print )
37
51
 
38
52
  const format = command ? command.replace(/^--/, "") : "";
39
53
 
@@ -13,6 +13,8 @@ export function getHelp(unknown_option = true) {
13
13
  "{N}{N}<$yellow:Global Options:$>",
14
14
  "{N} <$green:-h, --help$> <$cyan: Show help message$>",
15
15
  "{N} <$green:-v, --version$> <$cyan: Show version information$>",
16
+ "{N} <$green:init$> <$cyan: Initialize global SomMark configuration directory$>",
17
+ "{N} <$green:show config$> <$cyan: Display the absolute paths to the active SomMark configuration files$>",
16
18
 
17
19
  "{N}{N}<$yellow:Transpilation Options:$>",
18
20
  "{N}<$yellow:Usage:$> <$blue:sommark [option] [targetFile] [option] [outputFile] [outputDir]$>",
@@ -0,0 +1,64 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import { cliError, formatMessage } from "../../core/errors.js";
5
+
6
+ // ========================================================================== //
7
+ // Init Command //
8
+ // ========================================================================== //
9
+
10
+ export function getConfigDir() {
11
+ const homeDir = os.homedir();
12
+ if (process.platform === "win32") {
13
+ return path.join(process.env.APPDATA || path.join(homeDir, "AppData", "Roaming"), "sommark");
14
+ } else if (process.platform === "darwin") {
15
+ return path.join(homeDir, "Library", "Application Support", "sommark");
16
+ } else {
17
+ return path.join(process.env.XDG_CONFIG_HOME || path.join(homeDir, ".config"), "sommark");
18
+ }
19
+ }
20
+
21
+ export async function runInit() {
22
+ try {
23
+ const configDir = getConfigDir();
24
+ const pluginsDir = path.join(configDir, "plugins");
25
+ const configFilePath = path.join(configDir, "smark.config.js");
26
+
27
+ // ======================================================
28
+ // Create directories
29
+ // ======================================================
30
+
31
+ await fs.mkdir(pluginsDir, { recursive: true });
32
+
33
+ // ======================================================
34
+ // Default configuration content
35
+ // ======================================================
36
+
37
+ const defaultConfigContent = `export default {
38
+ outputDir: "",
39
+ outputFile: "output",
40
+ mappingFile: "",
41
+ };
42
+ `;
43
+
44
+ // ======================================================
45
+ // Check if config file already exists
46
+ // ======================================================
47
+
48
+ try {
49
+ await fs.access(configFilePath);
50
+ console.log(formatMessage(`<$yellow:Configuration already exists at:$> <$cyan:${configFilePath}$>`));
51
+ } catch {
52
+ // ======================================================
53
+ // Create default config file if it doesn't exist
54
+ // ======================================================
55
+
56
+ await fs.writeFile(configFilePath, defaultConfigContent, "utf-8");
57
+ console.log(formatMessage(`<$green:Initialized SomMark configuration at:$> <$cyan:${configFilePath}$>`));
58
+ }
59
+ } catch (error) {
60
+ cliError([
61
+ `{line}<$red:Failed to initialize SomMark configuration:$> <$magenta:${error.message}$>{line}`
62
+ ]);
63
+ }
64
+ }
@@ -0,0 +1,46 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { cliError, formatMessage } from "../../core/errors.js";
4
+ import { getConfigDir } from "./init.js";
5
+
6
+ // ========================================================================== //
7
+ // Show Command //
8
+ // ========================================================================== //
9
+
10
+ export async function runShow(target) {
11
+ if (target === "config") {
12
+ try {
13
+ const configDir = getConfigDir();
14
+ const configFilePath = path.join(configDir, "smark.config.js");
15
+ const pluginsDir = path.join(configDir, "plugins");
16
+ //========================================================================== //
17
+ // Helper to check existence and format message //
18
+ //========================================================================== //
19
+ const checkPath = async (itemPath, isDir = false) => {
20
+ try {
21
+ await fs.access(itemPath);
22
+ return `<$green:${itemPath}$> <$cyan:(Exists)$>`;
23
+ } catch {
24
+ return `<$red:${itemPath}$> <$yellow:(Not Found)$>`;
25
+ }
26
+ };
27
+
28
+ const configStatus = await checkPath(configFilePath);
29
+ const pluginsStatus = await checkPath(pluginsDir, true);
30
+
31
+ console.log(formatMessage(`{N}<$yellow:SomMark Configuration Files:$>{N}`));
32
+ console.log(formatMessage(` <$magenta:Config File:$> ${configStatus}`));
33
+ console.log(formatMessage(` <$magenta:Plugins Dir:$> ${pluginsStatus}{N}`));
34
+
35
+ } catch (error) {
36
+ cliError([
37
+ `{line}<$red:Failed to retrieve configuration paths:$> <$magenta:${error.message}$>{line}`
38
+ ]);
39
+ }
40
+ } else {
41
+ cliError([
42
+ `{line}<$red:Invalid target for 'show' command:$> <$blue:'${target || ""}'$> `,
43
+ `<$yellow:Usage:$> <$cyan:sommark show config$>{line}`
44
+ ]);
45
+ }
46
+ }
@@ -14,7 +14,7 @@ export function printHeader() {
14
14
  console.log(
15
15
  [
16
16
  `SomMark-${projectJson.version}`,
17
- "SomMark is a structural markup language for writing structured documents.",
17
+ "SomMark is a structural markup language for writing structured content.",
18
18
  "Copyright (C) Adam Elmi",
19
19
  "Github: https://github.com/Adam-Elmi/SomMark"
20
20
  ]
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { pathToFileURL } from "node:url";
3
3
  import { isExist } from "./file.js";
4
+ import { getConfigDir } from "../commands/init.js";
4
5
 
5
6
  // ========================================================================== //
6
7
  // Configuration Loader //
@@ -8,7 +9,7 @@ import { isExist } from "./file.js";
8
9
 
9
10
  const CONFIG_FILE_NAME = "smark.config.js";
10
11
  const currentDir = process.cwd();
11
- const configPath = path.join(currentDir, CONFIG_FILE_NAME);
12
+ const localConfigPath = path.join(currentDir, CONFIG_FILE_NAME);
12
13
 
13
14
  // ========================================================================== //
14
15
  // Default Configuration //
@@ -16,7 +17,6 @@ const configPath = path.join(currentDir, CONFIG_FILE_NAME);
16
17
  let config = {
17
18
  outputFile: "output",
18
19
  outputDir: "",
19
- mode: "default",
20
20
  mappingFile: ""
21
21
  };
22
22
 
@@ -24,13 +24,22 @@ let config = {
24
24
  // Load Configuration //
25
25
  // ========================================================================== //
26
26
  export async function loadConfig() {
27
- if (await isExist(configPath)) {
27
+ const userConfigPath = path.join(getConfigDir(), CONFIG_FILE_NAME);
28
+ let targetConfigPath = null;
29
+
30
+ if (await isExist(userConfigPath)) {
31
+ targetConfigPath = userConfigPath;
32
+ } else if (await isExist(localConfigPath)) {
33
+ targetConfigPath = localConfigPath;
34
+ }
35
+
36
+ if (targetConfigPath) {
28
37
  try {
29
- const configURL = pathToFileURL(configPath).href;
38
+ const configURL = pathToFileURL(targetConfigPath).href;
30
39
  const loadedModule = await import(configURL);
31
40
  config = loadedModule.default || loadedModule;
32
41
  } catch (error) {
33
- console.error(`Error loading configuration file ${CONFIG_FILE_NAME}:`, error.message);
42
+ console.error(`Error loading configuration file ${targetConfigPath}:`, error.message);
34
43
  }
35
44
  } else {
36
45
  // console.log(`${CONFIG_FILE_NAME} not found. Using default configuration.`);
package/core/formats.js CHANGED
@@ -2,8 +2,9 @@ const formats = {
2
2
  textFormat: "text",
3
3
  htmlFormat: "html",
4
4
  markdownFormat: "markdown",
5
- mdxFormat: "mdx"
5
+ mdxFormat: "mdx",
6
+ jsonFormat: "json"
6
7
  };
7
8
 
8
- export const {textFormat, htmlFormat, markdownFormat, mdxFormat} = formats;
9
+ export const {textFormat, htmlFormat, markdownFormat, mdxFormat, jsonFormat} = formats;
9
10
  export default formats;
package/core/lexer.js CHANGED
@@ -67,7 +67,9 @@ function concatText(input, index, scope_state, extraConditions = []) {
67
67
  text += char;
68
68
  }
69
69
  if (!text) {
70
- sommarkError([`{line}<$red:Concatenation failed:$> string value is <$yellow:'${text}'$>{line}`]);
70
+ sommarkError([
71
+ `{line}From: <$magenta:concatText function:$>{N}<$red:Concatenation failed:$> string value is <$yellow:'${text}'$>{line}`
72
+ ]);
71
73
  }
72
74
  return text;
73
75
  } else {
@@ -117,7 +119,9 @@ function concatEscape(input, index) {
117
119
  sommarkError(["{line}<$red:Next character is not found:$> <$yellow:There is no character after escape character!$>{line}"]);
118
120
  }
119
121
  if (!str) {
120
- sommarkError([`{line}<$red:Concatenation failed:$> string value is <$yellow:'${str}'$>{line}`]);
122
+ sommarkError([
123
+ `{line}From: <$magenta:concatEscape function:$>{N}<$red:Concatenation failed:$> string value is <$yellow:'${str}'$>{line}`
124
+ ]);
121
125
  }
122
126
  if (WHITESPACE_SET.has(str[1])) {
123
127
  const matchedCharacter = Array.from(WHITESPACE_SET).find(ch => ch === str[1]);
@@ -158,7 +162,9 @@ function concatChar(input, index, stop_at_char) {
158
162
  ]);
159
163
  }
160
164
  if (!str) {
161
- sommarkError([`{line}<$red:Concatenation failed:$> string value is <$yellow:'${str}'$>{line}`]);
165
+ sommarkError([
166
+ `{line}From: <$magenta:concatChar function:$>{N}<$red:Concatenation failed:$> string value is <$yellow:'${str}'$>{line}`
167
+ ]);
162
168
  }
163
169
  return str;
164
170
  } else {
@@ -185,10 +191,10 @@ function lexer(src) {
185
191
  tokens.push({ type, value, line, start, end, depth: depth_stack.length });
186
192
  }
187
193
 
188
- const updateMetadata = (text) => {
194
+ const updateMetadata = text => {
189
195
  const newlines = updateNewLine(text) || 0;
190
196
  if (newlines > 0) {
191
- const lines = text.split('\n');
197
+ const lines = text.split("\n");
192
198
  const lastLineLength = lines[lines.length - 1].length;
193
199
  start = end + 1;
194
200
  end = lastLineLength;
@@ -431,7 +437,13 @@ function lexer(src) {
431
437
  // ========================================================================== //
432
438
  // Token: Block Value //
433
439
  // ========================================================================== //
434
- else if ((previous_value === "=" || previous_value === BLOCKCOMMA || previous_value === BLOCKCOLON || previous_value === block_value) && !scope_state) {
440
+ else if (
441
+ (previous_value === "=" ||
442
+ previous_value === BLOCKCOMMA ||
443
+ previous_value === BLOCKCOLON ||
444
+ previous_value === block_value) &&
445
+ !scope_state
446
+ ) {
435
447
  temp_str = concatChar(src, i, ["]", "\\", ",", ":"]);
436
448
  i += temp_str.length - 1;
437
449
  const nextToken = peek(src, i, 1);
@@ -484,12 +496,7 @@ function lexer(src) {
484
496
  previous_value === inline_value) &&
485
497
  !scope_state
486
498
  ) {
487
- temp_str = concatChar(src, i, [
488
- ")",
489
- "\\",
490
- ",",
491
- previous_value === INLINECOLON ? ":" : null
492
- ]);
499
+ temp_str = concatChar(src, i, [")", "\\", ",", previous_value === INLINECOLON ? ":" : null]);
493
500
  i += temp_str.length - 1;
494
501
  // Update Metadata
495
502
  updateMetadata(temp_str);
@@ -588,7 +595,7 @@ function lexer(src) {
588
595
  return tokens;
589
596
  } else {
590
597
  lexerError([
591
- `{line}<$red:Invalid SomMark syntax:$> <$yellow:Expected source input to be a string, got$> <$blue: '${typeof src}'$>{line}`
598
+ `{line}<$red:Invalid SomMark syntax:$> ${src === "" ? "<$yellow: Got empty string '' $>" : `<$yellow:Expected source input to be a string, got$> <$blue: '${typeof src}'$>`}{line}`
592
599
  ]);
593
600
  }
594
601
  }