sommark 1.0.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/LICENSE +21 -0
- package/README.md +155 -0
- package/cli/cli.mjs +181 -0
- package/core/ids.js +2 -0
- package/core/lexer.js +293 -0
- package/core/names.js +13 -0
- package/core/parser.js +458 -0
- package/core/tokenTypes.js +20 -0
- package/core/transpiler.js +133 -0
- package/core/validator.js +127 -0
- package/formatter/mark.js +137 -0
- package/formatter/tag.js +70 -0
- package/grammar.ebnf +37 -0
- package/helpers/colorize.js +17 -0
- package/helpers/escapeHTML.js +8 -0
- package/helpers/peek.js +13 -0
- package/helpers/removeChar.js +19 -0
- package/index.js +104 -0
- package/lib/highlight.js +12 -0
- package/mappers/default_mode/smark.html.js +75 -0
- package/mappers/default_mode/smark.md.js +73 -0
- package/mappers/default_mode/smark.mdx.js +5 -0
- package/mappers/mapper.js +192 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Adam Elmi Eid
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
|
|
2
|
+
<img width="2000" height="491" alt="SomMark Cover" src="https://raw.githubusercontent.com/Adam-Elmi/SomMark/master/assets/smark_bg.png" />
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
SomMark is a lightweight, custom documentation markup language designed to be simple, readable, and easy to process.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<span>Website (Coming Soon)</span>
|
|
12
|
+
·
|
|
13
|
+
<span>Docs (Coming Soon)</span>
|
|
14
|
+
·
|
|
15
|
+
<span>Community (Coming Soon)</span>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
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" />
|
|
22
|
+
<img src="https://img.shields.io/badge/type-markup%20language-purple?style=flat-square" />
|
|
23
|
+
<img src="https://img.shields.io/badge/html-supported-orange?style=flat-square" />
|
|
24
|
+
<img src="https://img.shields.io/badge/markdown-supported-lightyellow?style=flat-square" />
|
|
25
|
+
<img src="https://img.shields.io/badge/mdx-supported-lightblue?style=flat-square" />
|
|
26
|
+
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
# SomMark v1
|
|
32
|
+
|
|
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
|
+
|
|
35
|
+
SomMark is built around **clear syntax**, **explicit structure**, and a **mapping-based output system**.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Core Syntax
|
|
40
|
+
|
|
41
|
+
SomMark has **three main syntax types**.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 1. Block
|
|
46
|
+
|
|
47
|
+
A **Block** is a container.
|
|
48
|
+
It holds arguments and child content.
|
|
49
|
+
|
|
50
|
+
```ini
|
|
51
|
+
[Block = arg1, arg2, arg3]
|
|
52
|
+
This is the body.
|
|
53
|
+
These texts are considered as children.
|
|
54
|
+
[end]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
* `Block` is the block name
|
|
58
|
+
* Arguments are optional
|
|
59
|
+
* Everything inside is treated as block content
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 2. Inline Statement
|
|
64
|
+
|
|
65
|
+
An **Inline Statement** is used inside text to apply formatting or behavior such as color, links, or styles.
|
|
66
|
+
|
|
67
|
+
```ini
|
|
68
|
+
[Block]
|
|
69
|
+
This is the (text)->(color:red).
|
|
70
|
+
These words are (important)->(bold).
|
|
71
|
+
[end]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Inline statements modify specific parts of text without breaking the flow.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 3. At Block
|
|
79
|
+
|
|
80
|
+
Sometimes inline statements are not enough.
|
|
81
|
+
**At Blocks** are used for complex structures like tables, lists, code blocks, and custom content.
|
|
82
|
+
|
|
83
|
+
```ini
|
|
84
|
+
[Block]
|
|
85
|
+
@_table_@: month, revenue, expenses
|
|
86
|
+
- January, 1200, 400
|
|
87
|
+
- February, 1400, 600
|
|
88
|
+
- March, 2000, 800
|
|
89
|
+
@_end_@
|
|
90
|
+
|
|
91
|
+
@_code_@: lua
|
|
92
|
+
function add(a, b)
|
|
93
|
+
return a + b
|
|
94
|
+
end
|
|
95
|
+
@_end_@
|
|
96
|
+
[end]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
At Blocks give you full control over how structured data is handled.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Modes
|
|
104
|
+
|
|
105
|
+
SomMark has **two modes**:
|
|
106
|
+
|
|
107
|
+
### Default Mode
|
|
108
|
+
|
|
109
|
+
* Comes with predefined identifiers such as `table`, `code`, `list`, and more
|
|
110
|
+
* Can be directly transpiled to HTML or Markdown
|
|
111
|
+
* Ideal for documentation
|
|
112
|
+
|
|
113
|
+
### Custom Mode
|
|
114
|
+
|
|
115
|
+
* You define your own identifiers
|
|
116
|
+
* Full control over behavior and output
|
|
117
|
+
* Useful for custom formats or tools
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Mapping Concept
|
|
122
|
+
|
|
123
|
+
SomMark uses a **mapping system**.
|
|
124
|
+
|
|
125
|
+
* You define how each block, inline statement, or at-block should be converted
|
|
126
|
+
* Output is not fixed
|
|
127
|
+
* The same SomMark file can generate different results (HTML, MD, MDX, etc.)
|
|
128
|
+
|
|
129
|
+
This makes SomMark highly extensible and future-proof.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Escape Character
|
|
134
|
+
|
|
135
|
+
To prevent SomMark from parsing syntax, use the escape character (`` ` ``):
|
|
136
|
+
|
|
137
|
+
```ini
|
|
138
|
+
[Block]
|
|
139
|
+
This is a text `[Hello]` not a block.
|
|
140
|
+
Also this `(world)` is not inline syntax.
|
|
141
|
+
[end]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Escaped content is treated as plain text.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Purpose
|
|
149
|
+
|
|
150
|
+
SomMark is designed for:
|
|
151
|
+
|
|
152
|
+
* Documentation
|
|
153
|
+
* Structured writing
|
|
154
|
+
* Tooling and transpilation
|
|
155
|
+
* Extensible content systems
|
package/cli/cli.mjs
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import projectJson from "../package.json" with { type: "json" };
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { cliError, formatMessage } from "../core/validator.js";
|
|
7
|
+
import lexer from "../core/lexer.js";
|
|
8
|
+
import parser from "../core/parser.js";
|
|
9
|
+
import transpiler from "../core/transpiler.js";
|
|
10
|
+
import html from "../mappers/default_mode/smark.html.js";
|
|
11
|
+
import md from "../mappers/default_mode/smark.md.js";
|
|
12
|
+
import mdx from "../mappers/default_mode/smark.mdx.js";
|
|
13
|
+
|
|
14
|
+
const options = ["-v", "--version", "-h", "--help", "--html", "--md", "--mdx"];
|
|
15
|
+
|
|
16
|
+
function getHelp(unknown_option = true) {
|
|
17
|
+
const msg = [
|
|
18
|
+
`${unknown_option && process.argv[2] ? `<$red:Unrecognized option$> <$blue: '${process.argv[2]}'$>` : "<$cyan:[Help]$>"}`,
|
|
19
|
+
"{N}<$yellow:Usage:$> <$blue:smark [options] [targetFile] [option] [outputFile] [outputDir]$>",
|
|
20
|
+
"{N}<$yellow:Available options are:$>",
|
|
21
|
+
"{N} <$green:-h or --help$> <$cyan: show help message$>",
|
|
22
|
+
"{N} <$green:-v or --version$> <$cyan: show version information$>",
|
|
23
|
+
"{N} <$green:--html$> <$cyan: transpile to html$>",
|
|
24
|
+
"{N} <$green:--md$> <$cyan: transpile to markdown$>",
|
|
25
|
+
"{N} <$green:--mdx> <$cyan: transpile to mdx>"
|
|
26
|
+
].join("");
|
|
27
|
+
const help_msg = formatMessage(msg);
|
|
28
|
+
if (!options.includes(process.argv[2]) && unknown_option) {
|
|
29
|
+
console.log(help_msg);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
} else if (process.argv[2] === "-h" || process.argv[2] === "--help") {
|
|
32
|
+
console.log(help_msg);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (process.argv[2] && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
|
|
38
|
+
getHelp(false);
|
|
39
|
+
} else {
|
|
40
|
+
getHelp();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (process.argv.length <= 2) {
|
|
44
|
+
console.log(
|
|
45
|
+
[
|
|
46
|
+
projectJson.version && typeof parseFloat(projectJson.version) === "number" ? projectJson.version : "",
|
|
47
|
+
"SomMark is a structural markup language for writing structured documents.",
|
|
48
|
+
"Copyright (C) Adam Elmi",
|
|
49
|
+
"Github: https://github.com/Adam-Elmi/SomMark"
|
|
50
|
+
]
|
|
51
|
+
.filter(value => value !== "")
|
|
52
|
+
.join("\n")
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (projectJson && projectJson.version !== undefined && (process.argv[2] === "-v" || process.argv[2] === "--version")) {
|
|
57
|
+
console.log(projectJson.version);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const isExist = async path => {
|
|
61
|
+
try {
|
|
62
|
+
if (path) {
|
|
63
|
+
await fs.access(path);
|
|
64
|
+
return true;
|
|
65
|
+
} else {
|
|
66
|
+
throw new Error("Path is not found");
|
|
67
|
+
}
|
|
68
|
+
} catch (_) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
async function createFile(folder, file, content) {
|
|
74
|
+
if (!(await isExist(folder))) {
|
|
75
|
+
await fs.mkdir(folder, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
await fs.writeFile(path.join(folder, file), content);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function readContent(path) {
|
|
81
|
+
const content = await fs.readFile(path);
|
|
82
|
+
return content;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const CONFIG_FILE_NAME = "smark.config.js";
|
|
86
|
+
const currentDir = process.cwd();
|
|
87
|
+
const configPath = path.join(currentDir, CONFIG_FILE_NAME);
|
|
88
|
+
|
|
89
|
+
let config = {
|
|
90
|
+
outputFile: "output",
|
|
91
|
+
outputDir: "",
|
|
92
|
+
mode: "default",
|
|
93
|
+
mappingFile: ""
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
async function loadConfig() {
|
|
97
|
+
if (isExist(configPath)) {
|
|
98
|
+
try {
|
|
99
|
+
const configURL = pathToFileURL(configPath).href;
|
|
100
|
+
const loadedModule = await import(configURL);
|
|
101
|
+
config = loadedModule.default;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error(`Error loading configuration file ${CONFIG_FILE_NAME}:`, error.message);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
console.log(`${CONFIG_FILE_NAME} not found. Using default configuration.`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!config.outputDir) {
|
|
110
|
+
config.outputDir = process.cwd();
|
|
111
|
+
}
|
|
112
|
+
return config;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function transpile({ src, format, mappingFile = "" }) {
|
|
116
|
+
if ((await loadConfig()).mode === "default") {
|
|
117
|
+
return transpiler({ ast: parser(lexer(src)), format, mapperFile: format === "html" ? html : format === "md" ? md : mdx });
|
|
118
|
+
} else if (mappingFile && isExist(mappingFile)) {
|
|
119
|
+
return transpiler({ ast: parser(lexer(src)), format, mappingFile });
|
|
120
|
+
} else {
|
|
121
|
+
cliError([`{line}<$red:File$> <$blue:'${mappingFile}'$> <$red: is not found$>{line}`]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function generateOutput(outputDir, outputFile, format) {
|
|
126
|
+
let source_code = await readContent(process.argv[3]);
|
|
127
|
+
source_code = source_code.toString();
|
|
128
|
+
const output = await transpile({ src: source_code, format, mappingFile: config.mappingFile });
|
|
129
|
+
await createFile(outputDir, `${outputFile}.${format}`, output);
|
|
130
|
+
}
|
|
131
|
+
async function generateFile() {
|
|
132
|
+
try {
|
|
133
|
+
const format_option = process.argv[2] ?? "";
|
|
134
|
+
const format = format_option.replaceAll("-", "") ?? "";
|
|
135
|
+
if (format && ["html", "md", "mdx"].includes(format)) {
|
|
136
|
+
if (Array.isArray(process.argv) && process.argv.length > 0) {
|
|
137
|
+
const targetFile = process.argv[3] ? path.parse(process.argv[3]) : "";
|
|
138
|
+
if (await isExist(process.argv[3])) {
|
|
139
|
+
const file = path.parse(process.argv[3]);
|
|
140
|
+
if (file.ext === ".smark") {
|
|
141
|
+
const config = await loadConfig();
|
|
142
|
+
const success_msg = (outputDir, outputFile) => {
|
|
143
|
+
return formatMessage(
|
|
144
|
+
[
|
|
145
|
+
`{line}[<$yellow: STATUS$> : <$green: SUCCESS$>]{line}<$blue: File$> <$yellow:'${outputFile}.${format}'$> <$blue: is successfully created`,
|
|
146
|
+
` in directory$> <$yellow: '${outputDir}'$>{line}`
|
|
147
|
+
].join("")
|
|
148
|
+
);
|
|
149
|
+
};
|
|
150
|
+
if (process.argv[4] === undefined) {
|
|
151
|
+
if (config.mode === "default") {
|
|
152
|
+
config.mappingFile = format === "html" ? html : format === "md" ? md : format === "mdx" ? mdx : null;
|
|
153
|
+
}
|
|
154
|
+
await generateOutput(config.outputDir, config.outputFile, format);
|
|
155
|
+
console.log(success_msg(config.outputDir, config.outputFile));
|
|
156
|
+
} else if (process.argv[4] === "-o") {
|
|
157
|
+
if (process.argv[5] !== undefined) {
|
|
158
|
+
config.outputFile = path.parse(process.argv[5]).name;
|
|
159
|
+
config.outputDir = process.argv[6] !== undefined ? process.argv[6] : config.outputDir;
|
|
160
|
+
generateOutput(config.outputDir, config.outputFile, format);
|
|
161
|
+
console.log(success_msg(config.outputDir, config.outputFile));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
cliError([
|
|
166
|
+
`{line}<$red:Unrecognized file extension$> <$blue: '${file.ext}'$> , <$red: only files with file extension$> <$green:'.smark'$> <$red: are accepted.$>{line}`
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
cliError([
|
|
171
|
+
`{line}<$magenta:File$> <$blue:'${process.argv[3] ? targetFile.name + targetFile.ext : "Unknown"}'$> <$magenta: is not found$>${(await isExist(targetFile.dir)) ? ` <$magenta: in directory$> <$yellow:${targetFile.dir}$>` : "."}`,
|
|
172
|
+
`${process.argv[3] ? "" : "{N}<$magenta:<Unknown>$> -> <$yellow:means you did not define the file path.$>"}{line}`
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
console.error(err);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
generateFile();
|
package/core/ids.js
ADDED
package/core/lexer.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import TOKEN_TYPES from "./tokenTypes.js";
|
|
2
|
+
import peek from "../helpers/peek.js";
|
|
3
|
+
import { block_value, block_id, inline_id, inline_value, at_id, at_value, end_keyword } from "./names.js";
|
|
4
|
+
import { lexerError } from "./validator.js";
|
|
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) {
|
|
11
|
+
if (char === "\n") {
|
|
12
|
+
break;
|
|
13
|
+
} else if (char === "[" && !scope_state) {
|
|
14
|
+
break;
|
|
15
|
+
} else if (char === "=" && !scope_state) {
|
|
16
|
+
break;
|
|
17
|
+
} else if (char === "]" && !scope_state) {
|
|
18
|
+
break;
|
|
19
|
+
} else if (char === "(" && !scope_state) {
|
|
20
|
+
break;
|
|
21
|
+
} else if (char === "-" && peek(input, char_index, 1) === ">" && !scope_state) {
|
|
22
|
+
break;
|
|
23
|
+
} else if (char === "@" && peek(input, char_index, 1) === "_" && !scope_state) {
|
|
24
|
+
break;
|
|
25
|
+
} else if (char === "_" && peek(input, char_index, 1) === "@" && !scope_state) {
|
|
26
|
+
break;
|
|
27
|
+
} else if (char === "#" && !scope_state) {
|
|
28
|
+
break;
|
|
29
|
+
} else if (char === "`" && !scope_state) {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
str += char;
|
|
33
|
+
} 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;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return str;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function lexer(src) {
|
|
51
|
+
if (src && typeof src === "string") {
|
|
52
|
+
const tokens = [];
|
|
53
|
+
let scope_state = false;
|
|
54
|
+
let line = 1;
|
|
55
|
+
let start;
|
|
56
|
+
let end;
|
|
57
|
+
let depth_stack = [];
|
|
58
|
+
let context = "",
|
|
59
|
+
temp_str = "",
|
|
60
|
+
previous_value = "";
|
|
61
|
+
|
|
62
|
+
function addToken(type, value) {
|
|
63
|
+
tokens.push({ type, value, line, start, end, depth: depth_stack.length });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < src.length; i++) {
|
|
67
|
+
let current_char = src[i];
|
|
68
|
+
// Token: Open Bracket
|
|
69
|
+
if (current_char === "[" && !scope_state) {
|
|
70
|
+
if (i === 0) {
|
|
71
|
+
// Update Column
|
|
72
|
+
start = 1;
|
|
73
|
+
end = start;
|
|
74
|
+
} else {
|
|
75
|
+
// Update Column
|
|
76
|
+
start = end !== undefined ? end : 1;
|
|
77
|
+
end = start;
|
|
78
|
+
}
|
|
79
|
+
// Push to depth if next token is not end_keyword
|
|
80
|
+
temp_str = concat(src, i + 1, ["]"], scope_state);
|
|
81
|
+
if (temp_str && temp_str.length > 0) {
|
|
82
|
+
if (temp_str.trim() !== end_keyword) {
|
|
83
|
+
depth_stack.push("Block");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
addToken(TOKEN_TYPES.OPEN_BRACKET, current_char);
|
|
87
|
+
previous_value = current_char;
|
|
88
|
+
}
|
|
89
|
+
// Token: Equal Sign
|
|
90
|
+
else if (current_char === "=" && !scope_state) {
|
|
91
|
+
// Update Column
|
|
92
|
+
start = end !== undefined ? end : 1;
|
|
93
|
+
end = start;
|
|
94
|
+
addToken(TOKEN_TYPES.EQUAL, current_char);
|
|
95
|
+
previous_value = current_char;
|
|
96
|
+
}
|
|
97
|
+
// Token: Close Bracket
|
|
98
|
+
else if (current_char === "]" && !scope_state) {
|
|
99
|
+
// Update Column
|
|
100
|
+
start = end !== undefined ? end : 1;
|
|
101
|
+
end = start;
|
|
102
|
+
addToken(TOKEN_TYPES.CLOSE_BRACKET, current_char);
|
|
103
|
+
if (previous_value === end_keyword) {
|
|
104
|
+
depth_stack.pop();
|
|
105
|
+
}
|
|
106
|
+
previous_value = current_char;
|
|
107
|
+
}
|
|
108
|
+
// Token: Open Parenthesis
|
|
109
|
+
else if (current_char === "(" && !scope_state) {
|
|
110
|
+
// Update Column
|
|
111
|
+
start = previous_value === "\n" ? end : end !== undefined ? end + 1 : 1;
|
|
112
|
+
end = start;
|
|
113
|
+
addToken(TOKEN_TYPES.OPEN_PAREN, current_char);
|
|
114
|
+
if (previous_value !== "->") {
|
|
115
|
+
previous_value = current_char;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Token: Thin Arrow
|
|
119
|
+
else if (current_char === "-" && peek(src, i, 1) === ">") {
|
|
120
|
+
temp_str = current_char + peek(src, i, 1);
|
|
121
|
+
i += temp_str.length - 1;
|
|
122
|
+
// Update Column
|
|
123
|
+
start = end !== undefined ? end : 1;
|
|
124
|
+
end = start + temp_str.length;
|
|
125
|
+
addToken(TOKEN_TYPES.THIN_ARROW, temp_str);
|
|
126
|
+
previous_value = temp_str;
|
|
127
|
+
}
|
|
128
|
+
// Token: Close Parenthesis
|
|
129
|
+
else if (current_char === ")" && !scope_state) {
|
|
130
|
+
// Update Column
|
|
131
|
+
start = end !== undefined ? end : 1;
|
|
132
|
+
end = start;
|
|
133
|
+
addToken(TOKEN_TYPES.CLOSE_PAREN, current_char);
|
|
134
|
+
previous_value = current_char;
|
|
135
|
+
}
|
|
136
|
+
// Token: Open At (@_)
|
|
137
|
+
else if (current_char === "@" && peek(src, i, 1) === "_") {
|
|
138
|
+
temp_str = current_char + peek(src, i, 1);
|
|
139
|
+
i += temp_str.length - 1;
|
|
140
|
+
// Update Column
|
|
141
|
+
start = end !== undefined ? end : 1;
|
|
142
|
+
end = start + temp_str.length;
|
|
143
|
+
scope_state = true;
|
|
144
|
+
addToken(TOKEN_TYPES.OPEN_AT, temp_str);
|
|
145
|
+
previous_value = temp_str;
|
|
146
|
+
}
|
|
147
|
+
// Token: Close At (_@)
|
|
148
|
+
else if (current_char === "_" && peek(src, i, 1) === "@") {
|
|
149
|
+
temp_str = current_char + peek(src, i, 1);
|
|
150
|
+
i += temp_str.length - 1;
|
|
151
|
+
// Update Column
|
|
152
|
+
start = end !== undefined ? end + 1 : 1;
|
|
153
|
+
end = start + temp_str.length;
|
|
154
|
+
addToken(TOKEN_TYPES.CLOSE_AT, temp_str);
|
|
155
|
+
previous_value = temp_str;
|
|
156
|
+
}
|
|
157
|
+
// Token: Colon
|
|
158
|
+
else if (current_char === ":" && previous_value === "_@") {
|
|
159
|
+
// Update Column
|
|
160
|
+
start = end !== undefined ? end : 1;
|
|
161
|
+
end = start;
|
|
162
|
+
addToken(TOKEN_TYPES.COLON, current_char);
|
|
163
|
+
previous_value = current_char;
|
|
164
|
+
}
|
|
165
|
+
// Token: Newline
|
|
166
|
+
else if (current_char === "\n") {
|
|
167
|
+
line++;
|
|
168
|
+
start = 0;
|
|
169
|
+
end = 0;
|
|
170
|
+
previous_value = current_char;
|
|
171
|
+
addToken(TOKEN_TYPES.NEWLINE, current_char);
|
|
172
|
+
}
|
|
173
|
+
// 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) {
|
|
177
|
+
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
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Token: Block Identifier OR Token: Block Value OR Token: End Keyword
|
|
186
|
+
else {
|
|
187
|
+
if (previous_value === "[" || (previous_value === "=" && !scope_state)) {
|
|
188
|
+
temp_str = concat(src, i, ["=", "]", "\n"], scope_state);
|
|
189
|
+
i += temp_str.length - 1;
|
|
190
|
+
// Update Column
|
|
191
|
+
start = end !== undefined ? end : 1;
|
|
192
|
+
end = start + temp_str.length;
|
|
193
|
+
if (temp_str.trim()) {
|
|
194
|
+
if (previous_value === "[") {
|
|
195
|
+
// Token: End Keyword
|
|
196
|
+
if (temp_str.trim() === end_keyword) {
|
|
197
|
+
addToken(TOKEN_TYPES.END_KEYWORD, temp_str);
|
|
198
|
+
previous_value = temp_str.trim();
|
|
199
|
+
scope_state = false;
|
|
200
|
+
}
|
|
201
|
+
// Token: Block Identifier
|
|
202
|
+
else {
|
|
203
|
+
addToken(TOKEN_TYPES.IDENTIFIER, temp_str);
|
|
204
|
+
previous_value = block_id;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Token: Block Value
|
|
208
|
+
else if (previous_value === "=") {
|
|
209
|
+
addToken(TOKEN_TYPES.VALUE, temp_str);
|
|
210
|
+
previous_value = block_value;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Token: Inline Value OR Token: Inline Identifier
|
|
215
|
+
else if (previous_value === "(" || (previous_value === "->" && !scope_state)) {
|
|
216
|
+
temp_str = concat(src, i, [")", "["], scope_state, true, ")");
|
|
217
|
+
i += temp_str.length - 1;
|
|
218
|
+
// Update Column
|
|
219
|
+
start = end !== undefined ? end : 1;
|
|
220
|
+
end = start + temp_str.length;
|
|
221
|
+
if (temp_str.trim()) {
|
|
222
|
+
if (previous_value === "(") {
|
|
223
|
+
// Token: Inline Value
|
|
224
|
+
addToken(TOKEN_TYPES.VALUE, temp_str);
|
|
225
|
+
previous_value = inline_value;
|
|
226
|
+
} else if (previous_value === "->") {
|
|
227
|
+
// Token: Inline Identifier
|
|
228
|
+
addToken(TOKEN_TYPES.IDENTIFIER, temp_str);
|
|
229
|
+
previous_value = inline_id;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Token: At Identifier OR Token: At Value OR Token: End Keyword
|
|
234
|
+
else if (previous_value === "@_" || previous_value === ":") {
|
|
235
|
+
temp_str = concat(src, i, ["_", "\n"], scope_state);
|
|
236
|
+
i += temp_str.length - 1;
|
|
237
|
+
if (temp_str.trim()) {
|
|
238
|
+
// Update Column
|
|
239
|
+
start = end !== undefined ? end + 1 : 1;
|
|
240
|
+
end = start + temp_str.length;
|
|
241
|
+
if (previous_value === "@_") {
|
|
242
|
+
// Token: End Keyword
|
|
243
|
+
if (temp_str.trim() === end_keyword) {
|
|
244
|
+
addToken(TOKEN_TYPES.END_KEYWORD, temp_str);
|
|
245
|
+
previous_value = temp_str.trim();
|
|
246
|
+
scope_state = false;
|
|
247
|
+
}
|
|
248
|
+
// Token: At Identifier
|
|
249
|
+
else {
|
|
250
|
+
addToken(TOKEN_TYPES.IDENTIFIER, temp_str);
|
|
251
|
+
previous_value = at_id;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Token: At Value
|
|
255
|
+
else if (previous_value === ":") {
|
|
256
|
+
addToken(TOKEN_TYPES.VALUE, temp_str);
|
|
257
|
+
previous_value = at_value;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Token: Comment
|
|
262
|
+
else if (current_char === "#") {
|
|
263
|
+
temp_str = concat(src, i, ["\n"], scope_state);
|
|
264
|
+
// Update Column
|
|
265
|
+
start = previous_value === "\n" ? end : end !== undefined ? end + 1 : 1;
|
|
266
|
+
end = start + temp_str.length;
|
|
267
|
+
if (temp_str.trim()) {
|
|
268
|
+
i += temp_str.length - 1;
|
|
269
|
+
addToken(TOKEN_TYPES.COMMENT, temp_str);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Token: Text
|
|
273
|
+
else {
|
|
274
|
+
context = concat(src, i, null, scope_state);
|
|
275
|
+
i += context.length - 1;
|
|
276
|
+
// Update Column
|
|
277
|
+
start = previous_value === "\n" ? end : end !== undefined ? end + 1 : 1;
|
|
278
|
+
end = start + context.length;
|
|
279
|
+
if (context.trim()) {
|
|
280
|
+
addToken(TOKEN_TYPES.TEXT, context);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
context = "";
|
|
285
|
+
temp_str = "";
|
|
286
|
+
}
|
|
287
|
+
return tokens;
|
|
288
|
+
} else {
|
|
289
|
+
lexerError(["{line}<$red:Invalid Source Code:$> <$yellow:Source code is not a string or is empty.$>{line}"]);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export default lexer;
|
package/core/names.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const BLOCK = "Block",
|
|
2
|
+
TEXT = "Text",
|
|
3
|
+
INLINE = "Inline",
|
|
4
|
+
ATBLOCK = "AtBlock",
|
|
5
|
+
NEWLINE = "Newline",
|
|
6
|
+
COMMENT = "Comment",
|
|
7
|
+
block_id = "Block Identifier",
|
|
8
|
+
block_value = "Block Value",
|
|
9
|
+
inline_id = "Inline Identifier",
|
|
10
|
+
inline_value = "Inline Value",
|
|
11
|
+
at_id = "At Identifier",
|
|
12
|
+
at_value = "At Value",
|
|
13
|
+
end_keyword = "end";
|