]>
git.kianting.info Git - clo/blob - src/parser.ts
2 * parser.ts - parser and js generator of clo.
4 import { text
} from
'pdfkit';
5 import * as p from
'typescript-parsec';
6 import { Token
} from
'typescript-parsec';
14 * convert a `tkTree` AST to S-expr string
15 * @param t the `tkTree`
16 * @returns S-expr String
18 export function tkTreeToSExp(t: tkTree): string{
21 if (Array.isArray(t)){
22 let strArray = t.map((x)=>tkTreeToSExp(x));
23 str = "(" + strArray.join("◎") + ")";
35 export type tkTree
= string | tkTree
[];
37 export enum TokenKind
{
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],
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>();
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];
95 export function applySemiColon(value: Token<TokenKind.Semicolon>): tkTree{
99 export function applyParts(first: tkTree,
100 second: [Token<TokenKind>, Token<TokenKind>, tkTree]):tkTree {
101 return ["%clo", first , second[2]];
104 export function applyPartsWithoutImport(
105 parsed: [Token<TokenKind>, Token<TokenKind>, tkTree]):tkTree {
106 return ["%clo", "" , parsed[2]];
110 export function applyComment(value: Token<TokenKind.Comment>): tkTree[]{
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);
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);
129 export function applyImports(input : [tkTree, tkTree[]]): tkTree{
130 let resultBody = [input[0]].concat(input[1]);
131 let resultWrapper = ["%import", resultBody];
132 return resultWrapper;
138 export function applyNotAtText(value : Token<TokenKind>): tkTree{
139 if (value.text == "\\\@"){
142 else{return value.text;}
145 export function applyText (input : tkTree): tkTree[]{
147 return ["%text", input];
152 export function applyContent(input : tkTree[]): tkTree[]{
153 return ["%content", input];
156 export function applySpaceNL(value : Token<TokenKind.SpaceNL>): tkTree{
161 * IMPORTEE: Number, Op, Paren, Id, Str, Comment,
163 export let IMPORTEE = p.alt(p.tok(TokenKind.Number),
165 p.tok(TokenKind.Paren),
167 p.tok(TokenKind.Str),
168 p.tok(TokenKind.SpaceNL),
169 p.tok(TokenKind.Comment));
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),
176 p.tok(TokenKind.Paren),
177 p.tok(TokenKind.SpaceNL),
179 p.tok(TokenKind.Str),
180 p.tok(TokenKind.Comment),
184 * PROG : IMPORTS '---' NEWLINE CONTENT | '---' NEWLINE CONTNENT
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))
194 * NOT_AT_TEXT : NOT_AT
196 NOT_AT_TEXT.setPattern(
197 p.apply(NOT_AT, applyNotAtText)
201 p.apply( p.seq(IMPORT, p.rep(IMPORT)), applyImports)
206 * 'import' IMPORTEE* SEMICOLON |
211 p.apply(p.seq(p.str('import'), p.rep_sc(IMPORTEE), SEMICOLON),
213 p.apply(p.tok(TokenKind.Comment), applyComment),
214 p.apply(p.tok(TokenKind.SpaceNL), applySpaceNL)
222 SEMICOLON.setPattern(
223 p.apply(p.tok(TokenKind.Semicolon), applySemiColon)
229 * SEGMENT : '@' NOT_AT* '@' |
230 * (NOT_AT_TEXT | EXCAPE_AT)*
234 p.apply(p.rep_sc(NOT_AT_TEXT), applyText),
235 p.apply(p.seq(p.str('@'), p.rep(NOT_AT), p.str('@')), applySegment),
243 p.apply(p.rep(SEGMENT), applyContent)
249 * the head part of the output JS code : before import
251 export let outputHead = `
252 /* clo, a typesetting engine, generated JS file*/
253 /* CLO: beginning of head*/
255 let cloLib
= require("./src/libclo/index.js");
256 let clo
= new cloLib
.Clo();
258 /* CLO: end of head*/\n`
261 * the middle part of the output JS code : between import part and content part
263 export let outputMiddle =`
264 /* CLO: beginning of middle part*/
265 clo
.mainStream
= /* CLO: end of middle part*/
269 * the end part of the output JS code : after content part
271 export let outputEnd =`
272 /* CLO: beginning of end part*/
274 /*CLO : end of end part*/
277 export function splitText(input : tkTree): tkTree{
279 if (!Array.isArray(input)){
280 ret = input.split(/(\s+)/);
282 ret = input.map((x)=>splitText(x));
288 * Convert `tree
` (ASTTree; `tkTree
`) to JS Code.
290 export function treeToJS(tree : tkTree): string{
294 let totalResult = outputHead + treeToJS(tree[1]) +
295 outputMiddle + treeToJS(tree[2]) + outputEnd;
298 if (head == "%import"){
299 let imports = tree[1];
300 if (Array.isArray(imports)){
301 let importsText = imports.map(
303 if (Array.isArray(x)){
304 return x.join('') + ';';
310 let importTextCombined = importsText.join('');
311 return importTextCombined;
317 if (head == "%content"){
319 if (Array.isArray(tail)){
320 if (tail.length == 1){
321 return tail.map((x)=>treeToJS(x)).join("').concat('")+ ";";
323 let tailStrings = tail.map((x)=>treeToJS(x));
324 return "(" + tailStrings.join(').concat(') + ");";
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("\`","\\\
`"));
338 return "[`" + decoratedArray.join("\
`, \`") + "`]";
340 let decorated = textContents.replace("\`","\\\
`").replace("\'", "\\\'");
342 return "[`" + decorated + "`]";
346 if (head == "%exprs"){
347 let content = tree[1];
348 if (Array.isArray(content)){
349 let flattenContent = content.flat();
350 return flattenContent.join('');
358 if (Array.isArray(tree)){
359 return tree.join('');
368 * `inputText
` to `tkTree
` (ASTTree)
370 export function inputTextToTree(inputText : string){
371 // force convert Windows newline to Linux newline
372 inputText = inputText.replace("\r\n", "\n");
374 return p.expectSingleResult(
375 p.expectEOF(PROG.parse(lexer.parse(inputText))));