-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlexer.js
75 lines (66 loc) · 2.59 KB
/
lexer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
const error = require("./util/error");
const keywords = [
"let",
"true",
"false",
"if",
"else",
"then",
"import",
"from",
"export"
];
/* prettier-ignore */
const operators = [
"=>",
"|>", ">>", "<|", "<<",
"::", "@",
"<=", ">=", "!=", "==", "||", "&&",
"<", ">", "=", "+", "-", "/", "%", "*", "**"
];
const punctuation = "()[]{};?:._";
const isId = str => str && /^[a-zA-Z-]*$/.test(str);
const isKw = str => str && keywords.includes(str);
const isNum = str => str && /^\d+(.\d+)?$/.test(str);
const isNumPart = str => str && /^[\d.]$/.test(str);
const isOp = str => str && operators.includes(str);
const isPunc = str => str && punctuation.includes(str);
const isString = str => str && /^".*"$/.test(str);
const isComment = str => str && str.startsWith("//");
const reducer = ([tokens, stack, line, col], char, index, input) => {
const consumeStack = (type, value = stack) => {
const start = { line, col: col - stack.length + 1 };
const end = { line, col: col };
const loc = { start, end };
return [tokens.concat({ type, value, loc }), char, line, col + 1];
};
const next = () => [tokens, stack + char, line, col + 1];
const nextLine = () => [tokens, char, line + 1, 1];
const skip = () => [tokens, char, line, col + 1];
if (isComment(stack)) return stack.endsWith("\n") ? nextLine() : next();
if (stack === "/" && char === "/") return next();
if (isOp(stack) || isOp(stack + char))
return isOp(stack + char) ? next() : consumeStack("op");
if (isPunc(stack)) return consumeStack("punc");
if (isKw(stack) && !isId(char)) return consumeStack("kw");
if (isId(stack) && !isId(char)) return consumeStack("id");
if (isNum(stack) && !isNumPart(char)) return consumeStack("number");
if (isString(stack) && !isString(char))
return consumeStack("string", stack.replace(/"/g, ""));
// NOTE: If the stack holds any value when reaching the end of the line, the first character of the stack is invalid
if (stack && char === "\n" && stack !== "\n") {
const character = stack[0] === "\n" ? "newline" : `character '${stack[0]}'`;
const startLoc = { line, col: col - stack.length + 1 };
const loc = { start: startLoc, end: startLoc };
error(`Parse error: Unexpected ${character}`, input.join(""), loc);
}
if (stack === "\n") return nextLine();
if (stack === " ") return skip();
return next();
};
module.exports = source => {
// NOTE: Ensure file ends with \n to avoid handling end of file as a separate case
const str = source.endsWith("\n") ? source : `${source}\n`;
const [tokens] = str.split("").reduce(reducer, [[], "", 1, 0]);
return tokens;
};