-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.ts
173 lines (152 loc) · 4.78 KB
/
main.ts
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
import { HandleNonTerminal } from "./handlers.ts";
import { outputElement, ruleT, rulesT, stackOutput } from "./types.ts";
// Lexer, chaque ligne est séparée par un \n, si la ligne est vide, on ne la prend pas en compte
export function lexing(input: string): string[] {
return input.split("\n").flatMap((l) => {
const words = l.trim().split(" ");
if (words.length === 0 || (words.length === 1 && words[0] === "")) {
return [];
}
words.push("\n");
return words;
});
}
// Représentation de la grammaire (récupérée depuis un fichier JSON)
interface Grammar {
tokens: {
[index: string]: {
pattern: string;
};
};
rules: {
[index: string]: (string | null)[][];
};
}
// chargement de la grammaire
export async function loadGrammar(file = "./grammar.json"): Promise<Grammar> {
const grammarFile = await Deno.readTextFile(file);
return JSON.parse(grammarFile);
}
// Application d'une règle de la grammaire sur un ensemble de tokens
function applyRule(
input: string[],
rule: ruleT,
cursor: number,
grammar: Grammar
): { cursor: number; output: stackOutput } {
const elements: outputElement[] = [];
for (const matcher of rule) {
// si l'élément de la règle est null, on passe
if (matcher === null) {
continue;
}
// terminal, on vérifie que le mot correspond, on avance le curseur et ajoute le token à la sortie
if (Object.keys(grammar.tokens).includes(matcher)) {
const word = input[cursor];
if (word === undefined) {
return { cursor: -1, output: null };
}
cursor++;
const regexp = new RegExp(grammar.tokens[matcher].pattern);
console.log(cursor, regexp, word, regexp.test(word));
if (!regexp.test(word)) {
return { cursor: -1, output: null };
}
elements.push({ type: matcher, value: word });
}
// non terminal, on appelle la règle associée
else if (Object.keys(grammar.rules).includes(matcher)) {
console.log("début " + matcher);
// exécution de la règle associée
const returned = parseStep(
input,
grammar.rules[matcher],
cursor,
grammar
);
console.log("fin " + matcher);
// si la règle n'a pas fonctionné
if (returned.cursor === -1) {
return { cursor: -1, output: null };
}
cursor = returned.cursor;
// on ajoute la sortie de la règle à la sortie
if (returned.output !== null) {
// on applique le handler sur la sortie de la règle`
const ret = HandleNonTerminal(returned.output, matcher);
if (ret.error) {
console.error(ret.error);
return { cursor: -1, output: null };
}
if (ret.elements.length === 0) {
elements.push(...returned.output);
} else {
elements.push(...ret.elements);
}
}
} else {
console.error("invalid token type");
return { cursor: -1, output: null };
}
}
return { cursor, output: elements };
}
// on essaie d'appliquer toutes les règles
function parseStep(
input: string[],
rules: rulesT,
cursor: number,
grammar: Grammar
): { cursor: number; output: stackOutput } {
let best: { cursor: number; output: stackOutput } = {
cursor: -1,
output: null,
};
// On exécute toutes les règles et on retient celle qui a consommé le plus de tokens
for (const rule of rules) {
const inputCopy = input.slice();
const result = applyRule(inputCopy, rule, cursor, grammar);
// si la règle a tout consommé, on retourne le résultat
if (result.cursor >= best.cursor) {
best = result;
}
}
// si aucune règle n'a fonctionné
return best;
}
// On fait le parsing sur les tokens (après lexing)
export function parseTokens(
intput: string[],
grammar: Grammar
): { valid: boolean; output: stackOutput } {
const result = parseStep(intput, [["S"]], 0, grammar);
console.log(result, intput.length);
const valid = result.cursor >= intput.length;
return { valid, output: result.output };
}
// Lexing + parsing
export function fullParse(
input: string,
grammar: Grammar
): { valid: boolean; output: stackOutput } {
return parseTokens(lexing(input), grammar);
}
// si ce fichier est appelé directement, on lance le parsing
if (import.meta.main) {
if (Deno.args.length < 2) {
console.error(
"usage: deno run --allow-read=. main.ts <grammar file> <input file>"
);
Deno.exit(1);
}
const grammarFilePath = Deno.args[0];
const inputFilePath = Deno.args[1];
const grammar = await loadGrammar(grammarFilePath);
const input = await Deno.readTextFile(inputFilePath);
const result = fullParse(input, grammar);
if (!result.valid) {
console.error("invalid input file");
Deno.exit(1);
}
console.log(JSON.stringify(result.output, null, 1));
}