]> git.kianting.info Git - clo/blob - src/parser.ts
867b8f789596c8e9290db1a371837511d09c7baa
[clo] / src / parser.ts
1 /**
2 * parser.ts - parser and js generator of clo.
3 */
4 import { text } from 'pdfkit';
5 import * as p from 'typescript-parsec';
6 import { Token } from 'typescript-parsec';
7
8 /**
9 *
10 * # REPRESENTATION
11 */
12
13 /**
14 * convert a `tkTree` AST to S-expr string
15 * @param t the `tkTree`
16 * @returns S-expr String
17 *
18 export function tkTreeToSExp(t: tkTree): string{
19 var str = "";
20
21 if (Array.isArray(t)){
22 let strArray = t.map((x)=>tkTreeToSExp(x));
23 str = "(" + strArray.join("◎") + ")";
24 }else{
25 if (t=== undefined){
26 str = "%undefined"
27 }else{
28 str = t;
29 }
30 }
31
32 return str;
33 }*/
34
35 export type tkTree = string | tkTree[];
36
37 export enum TokenKind {
38 Seperator, // ---
39 Semicolon, // ;
40 Number,
41 Op,
42 ExprMark, // @
43 ExcapeAt, // \@
44 Paren,
45 SpaceNL, // \s\t\n\r
46 Id,
47 Str,
48 Comment, // /* ooo */
49 }
50
51 /**
52 * Parsing
53 */
54 export const lexer = p.buildLexer([
55 [true, /^\d+(\.\d+)?/g, TokenKind.Number],
56 [true, /^[\\][\\]/g, TokenKind.Op],
57 [true, /^\\\@/g, TokenKind.ExcapeAt],
58 [true, /^\/\*([^/]|\/[^*])*\*\//g, TokenKind.Comment],
59 [true, /^\;/g, TokenKind.Semicolon],
60 [true, /^[-][-][-]/g, TokenKind.Seperator],
61 [true, /^[\+\-\*\/\&\|\!\^\<\>\~\=\?]+/g, TokenKind.Op],
62 [true, /^\@/g, TokenKind.ExprMark],
63 [true, /^[()\[\]{}]/g, TokenKind.Paren],
64 [true, /^[\`]([^\`]|[\\].)*[\`]/g, TokenKind.Str],
65 [true, /^[\"]([^\"]|[\\].)*[\"]/g, TokenKind.Str],
66 [true, /^[\']([^\']|[\\].)*[\']/g, TokenKind.Str],
67 [true, /^[()\[\]{}]/g, TokenKind.Paren],
68 [true, /^[^\/\\\@\s\n\t\r;]+/g, TokenKind.Id],
69 [true, /^(\s|\n|\r|\t)+/g, TokenKind.SpaceNL],
70
71 ]);
72
73 /**
74 *
75 * # TEST
76 */
77
78
79
80 export const PROG = p.rule<TokenKind, tkTree>();
81 export const SEGMENT = p.rule<TokenKind, tkTree>();
82 export const IMPORT = p.rule<TokenKind, tkTree>();
83 export const IMPORTS = p.rule<TokenKind, tkTree>();
84 export const SEMICOLON = p.rule<TokenKind, tkTree>();
85 export const NOT_AT_TEXT = p.rule<TokenKind, tkTree>();
86 export const CONTENT = p.rule<TokenKind, tkTree>();
87
88
89 export function applySegment(input: [Token<TokenKind>, Token<TokenKind>[],
90 Token<TokenKind>]): tkTree[]{
91 let unpackedInnerExprs = input[1].map((x)=>{return x.text});
92 return ["%exprs", unpackedInnerExprs];
93 }
94
95 export function applySemiColon(value: Token<TokenKind.Semicolon>): tkTree{
96 return value.text;
97 }
98
99 export function applyParts(first: tkTree,
100 second: [Token<TokenKind>, Token<TokenKind>, tkTree]):tkTree {
101 return ["%clo", first , second[2]];
102 }
103
104 export function applyPartsWithoutImport(
105 parsed: [Token<TokenKind>, Token<TokenKind>, tkTree]):tkTree {
106 return ["%clo", "" , parsed[2]];
107 }
108
109
110 export function applyComment(value: Token<TokenKind.Comment>): tkTree[]{
111 return [value.text];
112 }
113
114
115 export function applyImport(input: [Token<TokenKind>,Token<TokenKind>[], tkTree]) : tkTree{
116 let importTail = input[1].map(x=>x.text);
117 return ["import"].concat(importTail);
118 };
119
120
121 /*
122 function applyImportComment(input: [Token<TokenKind>,Token<TokenKind>[],
123 tkTree, Token<TokenKind.Comment>]) : tkTree{
124 let importTail = input[1].map(x=>x.text);
125 let comment = [input[3].text];
126 return ["import"].concat(importTail).concat(comment);
127 };*/
128
129 export function applyImports(input : [tkTree, tkTree[]]): tkTree{
130 let resultBody = [input[0]].concat(input[1]);
131 let resultWrapper = ["%import", resultBody];
132 return resultWrapper;
133 };
134
135
136
137
138 export function applyNotAtText(value : Token<TokenKind>): tkTree{
139 if (value.text == "\\\@"){
140 return '@';
141 }
142 else{return value.text;}
143 };
144
145 export function applyText (input : tkTree): tkTree[]{
146
147 return ["%text", input];
148 };
149
150
151
152 export function applyContent(input : tkTree[]): tkTree[]{
153 return ["%content", input];
154 };
155
156 export function applySpaceNL(value : Token<TokenKind.SpaceNL>): tkTree{
157 return value.text;
158 }
159
160 /**
161 * IMPORTEE: Number, Op, Paren, Id, Str, Comment,
162 */
163 export let IMPORTEE = p.alt(p.tok(TokenKind.Number),
164 p.tok(TokenKind.Op),
165 p.tok(TokenKind.Paren),
166 p.tok(TokenKind.Id),
167 p.tok(TokenKind.Str),
168 p.tok(TokenKind.SpaceNL),
169 p.tok(TokenKind.Comment));
170
171 export let NOT_AT = p.alt(p.tok(TokenKind.Seperator),
172 p.tok(TokenKind.Semicolon),
173 p.tok(TokenKind.Number),
174 p.tok(TokenKind.ExcapeAt),
175 p.tok(TokenKind.Op),
176 p.tok(TokenKind.Paren),
177 p.tok(TokenKind.SpaceNL),
178 p.tok(TokenKind.Id),
179 p.tok(TokenKind.Str),
180 p.tok(TokenKind.Comment),
181 );
182
183 /**
184 * PROG : IMPORTS '---' NEWLINE CONTENT | '---' NEWLINE CONTNENT
185 */
186 PROG.setPattern(
187 p.alt(
188 p.lrec_sc(IMPORTS, p.seq(p.str('---'), p.str("\n"), CONTENT), applyParts),
189 p.apply(p.seq(p.str('---'), p.str("\n"), CONTENT), applyPartsWithoutImport))
190
191 )
192
193 /**
194 * NOT_AT_TEXT : NOT_AT
195 */
196 NOT_AT_TEXT.setPattern(
197 p.apply(NOT_AT, applyNotAtText)
198 );
199
200 IMPORTS.setPattern(
201 p.apply( p.seq(IMPORT, p.rep(IMPORT)), applyImports)
202 );
203
204 /**
205 * IMPORT :
206 * 'import' IMPORTEE* SEMICOLON |
207 * COMMENT |
208 */
209 IMPORT.setPattern(
210 p.alt(
211 p.apply(p.seq(p.str('import'), p.rep_sc(IMPORTEE), SEMICOLON),
212 applyImport),
213 p.apply(p.tok(TokenKind.Comment), applyComment),
214 p.apply(p.tok(TokenKind.SpaceNL), applySpaceNL)
215
216 )
217 );
218
219 /**
220 * SEMICOLON : ';';
221 */
222 SEMICOLON.setPattern(
223 p.apply(p.tok(TokenKind.Semicolon), applySemiColon)
224 );
225
226
227
228 /**
229 * SEGMENT : '@' NOT_AT* '@' |
230 * (NOT_AT_TEXT | EXCAPE_AT)*
231 */
232 SEGMENT.setPattern(
233 p.alt(
234 p.apply(p.rep_sc(NOT_AT_TEXT), applyText),
235 p.apply(p.seq(p.str('@'), p.rep(NOT_AT), p.str('@')), applySegment),
236 )
237 );
238
239 /**
240 * CONTENT : SEGMENT*
241 */
242 CONTENT.setPattern(
243 p.apply(p.rep(SEGMENT), applyContent)
244 );
245
246
247
248 /**
249 * the head part of the output JS code : before import
250 */
251 export let outputHead = `
252 /* clo, a typesetting engine, generated JS file*/
253 /* CLO: beginning of head*/
254
255 let cloLib = require("./src/libclo/index.js");
256 let clo = new cloLib.Clo();
257
258 /* CLO: end of head*/\n`
259
260 /**
261 * the middle part of the output JS code : between import part and content part
262 */
263 export let outputMiddle =`
264 /* CLO: beginning of middle part*/
265 clo.mainStream = /* CLO: end of middle part*/
266 `
267
268 /**
269 * the end part of the output JS code : after content part
270 */
271 export let outputEnd =`
272 /* CLO: beginning of end part*/
273 clo.generatePdf();
274 /*CLO : end of end part*/
275 `
276
277 export function splitText(input : tkTree): tkTree{
278 var ret;
279 if (!Array.isArray(input)){
280 ret = input.split(/(\s+)/);
281 }else{
282 ret = input.map((x)=>splitText(x));
283 }
284 return ret;
285 }
286
287 /**
288 * Convert `tree` (ASTTree; `tkTree`) to JS Code.
289 */
290 export function treeToJS(tree : tkTree): string{
291
292 let head = tree[0];
293 if (head == "%clo"){
294 let totalResult = outputHead + treeToJS(tree[1]) +
295 outputMiddle + treeToJS(tree[2]) + outputEnd;
296 return totalResult;
297 }
298 if (head == "%import"){
299 let imports = tree[1];
300 if (Array.isArray(imports)){
301 let importsText = imports.map(
302 (x)=>{
303 if (Array.isArray(x)){
304 return x.join('') + ';';
305 }
306 else{
307 return x;
308 }
309 });
310 let importTextCombined = importsText.join('');
311 return importTextCombined;
312 }
313 else{
314 return imports;
315 }
316 }
317 if (head == "%content"){
318 let tail = tree[1];
319 if (Array.isArray(tail)){
320 if (tail.length == 1){
321 return tail.map((x)=>treeToJS(x)).join("').concat('")+ ";";
322 }
323 let tailStrings = tail.map((x)=>treeToJS(x));
324 return "(" + tailStrings.join(').concat(') + ");";
325 }else{
326 return tail;
327 }
328 }
329 if (head == "%text"){
330 var textContents = splitText(tree[1]);
331 if (Array.isArray(textContents)){
332 textContents = textContents.flat().filter((x)=>{return x !== ""});
333 let decoratedArray = textContents
334 .flatMap(x=>String(x))
335 .map(x=>x.replace("\'", "\\\'"))
336 .map(x=>x.replace("\`","\\\`"));
337
338 return "[`" + decoratedArray.join("\`, \`") + "`]";
339 }else{
340 let decorated = textContents.replace("\`","\\\`").replace("\'", "\\\'");
341
342 return "[`" + decorated + "`]";
343 }
344 }
345
346 if (head == "%exprs"){
347 let content = tree[1];
348 if (Array.isArray(content)){
349 let flattenContent = content.flat();
350 return flattenContent.join('');
351 }
352 else{
353 return content;
354 }
355
356 }
357 else{
358 if (Array.isArray(tree)){
359 return tree.join('');
360 }else{
361 return tree;
362 }
363 }
364 }
365
366
367 /**
368 * `inputText` to `tkTree` (ASTTree)
369 */
370 export function inputTextToTree(inputText : string){
371 // force convert Windows newline to Linux newline
372 inputText = inputText.replace("\r\n", "\n");
373
374 return p.expectSingleResult(
375 p.expectEOF(PROG.parse(lexer.parse(inputText))));
376 }