clo/src/parser.ts

359 lines
No EOL
8.5 KiB
TypeScript

/**
* parser.ts - parser and js generator of clo.
*/
import * as p from 'typescript-parsec';
import { Token } from 'typescript-parsec';
/**
*
* # REPRESENTATION
*/
/**
* convert a `tkTree` AST to S-expr string
* @param t the `tkTree`
* @returns S-expr String
*
export function tkTreeToSExp(t: tkTree): string{
var str = "";
if (Array.isArray(t)){
let strArray = t.map((x)=>tkTreeToSExp(x));
str = "(" + strArray.join("◎") + ")";
}else{
if (t=== undefined){
str = "%undefined"
}else{
str = t;
}
}
return str;
}*/
export type tkTree = string | tkTree[];
export enum TokenKind {
Seperator, // ---
Semicolon, // ;
Number,
Op,
ExprMark, // @
ExcapeAt, // \@
Paren,
SpaceNL, // \s\t\n\r
Id,
Str,
Comment, // /* ooo */
}
/**
* Parsing
*/
export const lexer = p.buildLexer([
[true, /^\d+(\.\d+)?/g, TokenKind.Number],
[true, /^[\\][\\]/g, TokenKind.Op],
[true, /^\\\@/g, TokenKind.ExcapeAt],
[true, /^\/\*([^/]|\/[^*])*\*\//g, TokenKind.Comment],
[true, /^\;/g, TokenKind.Semicolon],
[true, /^[-][-][-]/g, TokenKind.Seperator],
[true, /^[\+\-\*\/\&\|\!\^\<\>\~\=\?]+/g, TokenKind.Op],
[true, /^\@/g, TokenKind.ExprMark],
[true, /^[()\[\]{}]/g, TokenKind.Paren],
[true, /^[\"]([^\"]|[\\].)*[\"]/g, TokenKind.Str],
[true, /^[\']([^\']|[\\].)*[\']/g, TokenKind.Str],
[true, /^[()\[\]{}]/g, TokenKind.Paren],
[true, /^[^\/\\\@\s\n\t\r;]+/g, TokenKind.Id],
[true, /^(\s|\n|\r|\t)+/g, TokenKind.SpaceNL],
]);
/**
*
* # TEST
*/
export const PROG = p.rule<TokenKind, tkTree>();
export const SEGMENT = p.rule<TokenKind, tkTree>();
export const IMPORT = p.rule<TokenKind, tkTree>();
export const IMPORTS = p.rule<TokenKind, tkTree>();
export const SEMICOLON = p.rule<TokenKind, tkTree>();
export const NOT_AT_TEXT = p.rule<TokenKind, tkTree>();
export const CONTENT = p.rule<TokenKind, tkTree>();
export function applySegment(input: [Token<TokenKind>, Token<TokenKind>[],
Token<TokenKind>]): tkTree[]{
let unpackedInnerExprs = input[1].map((x)=>{return x.text});
return ["%exprs", unpackedInnerExprs];
}
export function applySemiColon(value: Token<TokenKind.Semicolon>): tkTree{
return value.text;
}
export function applyParts(first: tkTree,
second: [Token<TokenKind>, Token<TokenKind>, tkTree]):tkTree {
return ["%clo", first , second[2]];
}
export function applyPartsWithoutImport(
parsed: [Token<TokenKind>, Token<TokenKind>, tkTree]):tkTree {
return ["%clo", "" , parsed[2]];
}
export function applyComment(value: Token<TokenKind.Comment>): tkTree[]{
return [value.text];
}
export function applyImport(input: [Token<TokenKind>,Token<TokenKind>[], tkTree]) : tkTree{
let importTail = input[1].map(x=>x.text);
return ["import"].concat(importTail);
};
/*
function applyImportComment(input: [Token<TokenKind>,Token<TokenKind>[],
tkTree, Token<TokenKind.Comment>]) : tkTree{
let importTail = input[1].map(x=>x.text);
let comment = [input[3].text];
return ["import"].concat(importTail).concat(comment);
};*/
export function applyImports(input : [tkTree, tkTree[]]): tkTree{
let resultBody = [input[0]].concat(input[1]);
let resultWrapper = ["%import", resultBody];
return resultWrapper;
};
export function applyNotAtText(value : Token<TokenKind>): tkTree{
if (value.text == "\\\@"){
return '@';
}
else{return value.text;}
};
export function applyText (input : tkTree): tkTree[]{
return ["%text", input];
};
export function applyContent(input : tkTree[]): tkTree[]{
return ["%content", input];
};
export function applySpaceNL(value : Token<TokenKind.SpaceNL>): tkTree{
return value.text;
}
/**
* IMPORTEE: Number, Op, Paren, Id, Str, Comment,
*/
export let IMPORTEE = p.alt(p.tok(TokenKind.Number),
p.tok(TokenKind.Op),
p.tok(TokenKind.Paren),
p.tok(TokenKind.Id),
p.tok(TokenKind.Str),
p.tok(TokenKind.SpaceNL),
p.tok(TokenKind.Comment));
export let NOT_AT = p.alt(p.tok(TokenKind.Seperator),
p.tok(TokenKind.Semicolon),
p.tok(TokenKind.Number),
p.tok(TokenKind.ExcapeAt),
p.tok(TokenKind.Op),
p.tok(TokenKind.Paren),
p.tok(TokenKind.SpaceNL),
p.tok(TokenKind.Id),
p.tok(TokenKind.Str),
p.tok(TokenKind.Comment),
);
/**
* PROG : IMPORTS '---' NEWLINE CONTENT | '---' NEWLINE CONTNENT
*/
PROG.setPattern(
p.alt(
p.lrec_sc(IMPORTS, p.seq(p.str('---'), p.str("\n"), CONTENT), applyParts),
p.apply(p.seq(p.str('---'), p.str("\n"), CONTENT), applyPartsWithoutImport))
)
/**
* NOT_AT_TEXT : NOT_AT
*/
NOT_AT_TEXT.setPattern(
p.apply(NOT_AT, applyNotAtText)
);
IMPORTS.setPattern(
p.apply( p.seq(IMPORT, p.rep(IMPORT)), applyImports)
);
/**
* IMPORT :
* 'import' IMPORTEE* SEMICOLON |
* COMMENT |
*/
IMPORT.setPattern(
p.alt(
p.apply(p.seq(p.str('import'), p.rep_sc(IMPORTEE), SEMICOLON),
applyImport),
p.apply(p.tok(TokenKind.Comment), applyComment),
p.apply(p.tok(TokenKind.SpaceNL), applySpaceNL)
)
);
/**
* SEMICOLON : ';';
*/
SEMICOLON.setPattern(
p.apply(p.tok(TokenKind.Semicolon), applySemiColon)
);
/**
* SEGMENT : '@' NOT_AT* '@' |
* (NOT_AT_TEXT | EXCAPE_AT)*
*/
SEGMENT.setPattern(
p.alt(
p.apply(p.rep_sc(NOT_AT_TEXT), applyText),
p.apply(p.seq(p.str('@'), p.rep(NOT_AT), p.str('@')), applySegment),
)
);
/**
* CONTENT : SEGMENT*
*/
CONTENT.setPattern(
p.apply(p.rep(SEGMENT), applyContent)
);
/**
* the head part of the output JS code : before import
*/
export let outputHead = `
/* clo, a typesetting engine, generated JS file*/
/* CLO: beginning of head*/
let cloLib = require("./src/libclo/index.js");
let clo = new cloLib.Clo();
/* CLO: end of head*/\n`
/**
* the middle part of the output JS code : between import part and content part
*/
export let outputMiddle =`
/* CLO: beginning of middle part*/
clo.mainStream = /* CLO: end of middle part*/
`
/**
* the end part of the output JS code : after content part
*/
export let outputEnd =`
/* CLO: beginning of end part*/
clo.generatePdf();
/*CLO : end of end part*/
`
/**
* Convert `tree` (ASTTree; `tkTree`) to JS Code.
*/
export function treeToJS(tree : tkTree): string{
let head = tree[0];
if (head == "%clo"){
let totalResult = outputHead + treeToJS(tree[1]) +
outputMiddle + treeToJS(tree[2]) + outputEnd;
return totalResult;
}
if (head == "%import"){
let imports = tree[1];
if (Array.isArray(imports)){
let importsText = imports.map(
(x)=>{
if (Array.isArray(x)){
return x.join('') + ';';
}
else{
return x;
}
});
let importTextCombined = importsText.join('');
return importTextCombined;
}
else{
return imports;
}
}
if (head == "%content"){
let tail = tree[1];
if (Array.isArray(tail)){
if (tail.length == 1){
return tail.map((x)=>treeToJS(x)).join("').concat('")+ ";";
}
let tailStrings = tail.map((x)=>treeToJS(x));
return "(" + tailStrings.join(').concat(') + ");";
}else{
return tail;
}
}
if (head == "%text"){
let textContents = tree[1];
if (Array.isArray(textContents)){
let decoratedArray = textContents
.flatMap(x=>String(x))
.map(x=>x.replace("\`","\\\`"));
return "[`" + decoratedArray.join("\`, \`") + "`]";
}else{
let decorated = textContents.replace("\`","\\\`");
return "[`" + decorated + "`]";
}
}
if (head == "%exprs"){
let content = tree[1];
if (Array.isArray(content)){
let flattenContent = content.flat();
return flattenContent.join('');
}
else{
return content;
}
}
else{
if (Array.isArray(tree)){
return tree.join('');
}else{
return tree;
}
}
}
/**
* `inputText` to `tkTree` (ASTTree)
*/
export function inputTextToTree(inputText : string){
// force convert Windows newline to Linux newline
inputText = inputText.replace("\r\n", "\n");
return p.expectSingleResult(
p.expectEOF(PROG.parse(lexer.parse(inputText))));
}