├── .gitignore ├── .prettierignore ├── README.md ├── package.json ├── packages ├── mints-legacy │ ├── .gitignore │ ├── README.md │ ├── benchmark │ │ ├── bench.ts │ │ ├── bootstrap.js │ │ └── cases │ │ │ ├── example0.ts │ │ │ ├── example1.ts │ │ │ ├── example2.ts │ │ │ ├── example3.ts │ │ │ ├── example4.ts │ │ │ ├── example5.ts │ │ │ └── example6.ts │ ├── package.json │ ├── rollup.config.analyze.js │ ├── rollup.config.js │ ├── run_test262.bash │ ├── src │ │ ├── constants.ts │ │ ├── ctx.ts │ │ ├── grammar.ts │ │ ├── index.ts │ │ ├── preprocess.ts │ │ └── tokenizer.ts │ ├── test │ │ ├── clone-test262.ts │ │ ├── preprocess.js │ │ └── run_bundled.ts │ ├── tsconfig.json │ ├── tsconfig.types.json │ └── vite.config.ts ├── mints │ ├── .gitignore │ ├── README.md │ ├── benchmark │ │ ├── bench.ts │ │ ├── bootstrap.js │ │ └── cases │ │ │ ├── _scratch.ts │ │ │ ├── example0.ts │ │ │ ├── example1.ts │ │ │ ├── example2.ts │ │ │ ├── example3.ts │ │ │ ├── example4.ts │ │ │ ├── example5.tsx │ │ │ ├── example6.tsx │ │ │ └── example7.ts │ ├── package.json │ ├── rollup.config.analyze.js │ ├── rollup.config.js │ ├── run_test262.bash │ ├── src │ │ ├── gen │ │ │ ├── __reserved.json │ │ │ ├── __strings.json │ │ │ └── snapshot_b64.ts │ │ ├── index.ts │ │ ├── node_main.ts │ │ ├── node_worker.ts │ │ ├── prebuild │ │ │ ├── constants.ts │ │ │ ├── ctx.ts │ │ │ ├── grammar.ts │ │ │ ├── index.ts │ │ │ └── prebuild.ts │ │ ├── processor.ts │ │ ├── rpc │ │ │ ├── node.ts │ │ │ └── shared.ts │ │ ├── runtime │ │ │ ├── funcs.ts │ │ │ ├── load_snapshot.ts │ │ │ ├── options.ts │ │ │ └── tokenizer.ts │ │ ├── tokenizer.ts │ │ └── types.ts │ ├── test.ts │ ├── test │ │ ├── clone-test262.ts │ │ └── preprocess.js │ ├── tsconfig.json │ └── tsconfig.types.json ├── pargen-tokenized │ ├── README.md │ ├── decoder │ │ ├── decode_b64.d.ts │ │ ├── decode_b64.ts │ │ ├── decode_cbor_subset.d.ts │ │ ├── decode_cbor_subset.ts │ │ ├── decoder.d.ts │ │ └── decoder.ts │ ├── encoder │ │ ├── cbor_subset.d.ts │ │ ├── cbor_subset.ts │ │ ├── encoder.d.ts │ │ └── encoder.ts │ ├── package.json │ ├── src │ │ ├── builder.d.ts │ │ ├── builder.ts │ │ ├── constants.d.ts │ │ ├── constants.ts │ │ ├── format.d.ts │ │ ├── format.ts │ │ ├── index.d.ts │ │ ├── index.ts │ │ ├── runtime.d.ts │ │ ├── runtime.ts │ │ ├── types.d.ts │ │ └── types.ts │ ├── tsconfig.json │ └── vite.config.ts ├── pargen │ ├── README.md │ ├── package.json │ ├── scratch.ts │ ├── src │ │ ├── builder.ts │ │ ├── compiler.ts │ │ ├── error_reporter.ts │ │ ├── index.ts │ │ ├── preparse.ts │ │ ├── serializer.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── vite.config.ts ├── playground │ ├── index.html │ ├── package.json │ ├── src │ │ ├── index.tsx │ │ ├── process-worker.ts │ │ └── tokenize-worker.ts │ ├── tsconfig.json │ ├── vite.config.ts │ └── yarn.lock └── sw-mints-proxy │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── public │ └── foo.tsx │ ├── src │ ├── main.ts │ ├── sw.ts │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.sw.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | types 7 | # Local Netlify folder 8 | .netlify -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | packages/mints/benchmark/cases/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mints: tiny typescript compiler 2 | 3 | 7.8kb 4 | 5 | - `packages/mints`: tiny typescript compiler 6 | - `packages/pargen`: parser generator 7 | - `packages/pargen-tokenized`: parser generator for tokens (mints use this) 8 | - `packages/playground`: mints web playground 9 | 10 | Demo https://mints-playground.netlify.app/ 11 | 12 | ## LICENSE 13 | 14 | MIT 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mints-workspace", 3 | "private": true, 4 | "repository": "git@github.com:mizchi/mints.git", 5 | "author": "mizchi ", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "yarn test:pargen && yarn test:mints", 9 | "test:pargen": "yarn workspace @mizchi/pargen-tokenized run test", 10 | "test:mints": "yarn workspace @mizchi/mints run test" 11 | }, 12 | "devDependencies": { 13 | "@mizchi/test": "^0.1.1", 14 | "@types/node": "^16.9.1", 15 | "@types/react": "^17.0.30", 16 | "@types/react-dom": "^17.0.9", 17 | "esbuild": "^0.13.3", 18 | "esbuild-register": "^3.0.0", 19 | "glob": "^7.2.0", 20 | "preact": "^10.5.15", 21 | "react": "^17.0.2", 22 | "react-dom": "^17.0.2", 23 | "test262-harness": "^9.0.0", 24 | "typescript": "^4.4.4", 25 | "vite": "^2.4.2" 26 | }, 27 | "workspaces": [ 28 | "packages/*" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/mints-legacy/.gitignore: -------------------------------------------------------------------------------- 1 | test262 -------------------------------------------------------------------------------- /packages/mints-legacy/README.md: -------------------------------------------------------------------------------- 1 | # mints: 5kb typescript compiler 2 | 3 | ## Goal 4 | 5 | - Just Strip Type Annotations by typescript 6 | - Imperfect Superset Syntax (expect prettier formatted code as input) 7 | - Not Fast 8 | - Extreme lightweight: 5.1kb (gzip) 9 | 10 | ## How to use 11 | 12 | ```ts 13 | import {transform} from "@mizchi/mints"; 14 | const out = transform(`const x: number = 1;`); 15 | console.log(out.result); 16 | ``` 17 | 18 | ## TODO 19 | 20 | ### TS 21 | 22 | - `type T = import("path").Foo`; 23 | - `infer T` 24 | 25 | ### Advanced Transform 26 | 27 | - [x] constructor's initialization(private/public/protected) 28 | - [ ] enum 29 | - [ ] JSX 30 | 31 | ## May not support 32 | 33 | - with (may not support) 34 | - namespace (may not support) 35 | - decorator (may not support) 36 | 37 | ## LICENSE 38 | 39 | MIT -------------------------------------------------------------------------------- /packages/mints-legacy/benchmark/bench.ts: -------------------------------------------------------------------------------- 1 | import { transform } from "../src/index"; 2 | import ts from "typescript"; 3 | import fs from "fs"; 4 | import path from "path"; 5 | import { printPerfResult } from "@mizchi/pargen/src"; 6 | // import { formatError } from "../src/_testHelpers"; 7 | // import prettier from "prettier"; 8 | 9 | const code0 = fs.readFileSync( 10 | path.join(__dirname, "cases/example0.ts"), 11 | "utf-8" 12 | ); 13 | 14 | const code1 = fs.readFileSync( 15 | path.join(__dirname, "cases/example1.ts"), 16 | "utf-8" 17 | ); 18 | // pargen 19 | const code2 = fs.readFileSync( 20 | path.join(__dirname, "cases/example2.ts"), 21 | "utf-8" 22 | ); 23 | 24 | // pargen 25 | const code3 = fs.readFileSync( 26 | path.join(__dirname, "cases/example3.ts"), 27 | "utf-8" 28 | ); 29 | 30 | // pargen 31 | const code4 = fs.readFileSync( 32 | path.join(__dirname, "cases/example4.ts"), 33 | "utf-8" 34 | ); 35 | 36 | // pargen 37 | const code5 = fs.readFileSync( 38 | path.join(__dirname, "cases/example5.ts"), 39 | "utf-8" 40 | ); 41 | 42 | function compileTsc(input: string) { 43 | return ts.transpileModule(input, { 44 | compilerOptions: { 45 | module: ts.ModuleKind.ESNext, 46 | target: ts.ScriptTarget.Latest, 47 | }, 48 | }).outputText; 49 | } 50 | 51 | function compileMints(input: string) { 52 | const out = transform(input); 53 | if (out.error) { 54 | // out.reportErrorDetail(); 55 | throw out; 56 | } 57 | return out.result as string; 58 | } 59 | 60 | export function main() { 61 | const compilers = [compileTsc, compileMints]; 62 | 63 | // const targets = [code1, code2, code3]; 64 | const targets = [ 65 | // x 66 | code0, 67 | code1, 68 | code2, 69 | code3, 70 | // code4, 71 | // code5, 72 | ]; 73 | 74 | for (const code of targets) { 75 | for (const compiler of compilers) { 76 | // console.log("[pre]", preprocessLight(code)); 77 | const N = 2; 78 | for (let i = 0; i < N; i++) { 79 | const now = Date.now(); 80 | const out = compiler(code); 81 | console.log(compiler.name, `[${i}]`, Date.now() - now); 82 | if (i === N - 1) { 83 | // console.log("[out]", out); 84 | } 85 | // console.log("raw:", out); 86 | // console.log("----"); 87 | // console.log(prettier.format(out, { parser: "typescript" })); 88 | } 89 | if (process.env.NODE_ENV === "perf" && compiler === compileMints) { 90 | printPerfResult(); 91 | } 92 | } 93 | } 94 | } 95 | main(); 96 | -------------------------------------------------------------------------------- /packages/mints-legacy/benchmark/bootstrap.js: -------------------------------------------------------------------------------- 1 | // import { transform } from "./src/prebuild/index"; 2 | console.time("load"); 3 | const { transform } = require("../dist/index.cjs"); 4 | console.timeEnd("load"); 5 | 6 | console.time("run1"); 7 | const out = transform("const x: number = 1;"); 8 | console.log(out.result); 9 | console.timeEnd("run1"); 10 | 11 | console.time("run2"); 12 | const out2 = transform("const x: number = 1;"); 13 | // console.log(out2); 14 | console.timeEnd("run2"); 15 | -------------------------------------------------------------------------------- /packages/mints-legacy/benchmark/cases/example0.ts: -------------------------------------------------------------------------------- 1 | // , b: number[], c: Array; 2 | const x: number = 1; 3 | 4 | let a,b,c; 5 | 6 | function square(x: number): number { 7 | return x ** 2; 8 | }; 9 | 10 | square(2); 11 | 12 | type IPoint = { 13 | x: number; 14 | y: number; 15 | }; 16 | 17 | if (1) { 18 | 1; 19 | } else { 20 | while(false) {} 21 | } 22 | 23 | class Point implements Object { 24 | public x: number; 25 | private y: number; 26 | constructor(x: number, y: number) { 27 | this.x = x; 28 | this.y = y; 29 | } 30 | public static async foo(arg: number): Promise { 31 | return arg; 32 | } 33 | } 34 | 35 | const p = new Point(1, 2); 36 | 37 | // console.log(p.x); 38 | 39 | // func(); 40 | 41 | export { x, x as y, p }; 42 | 43 | export const v = 1; 44 | 45 | export class Foo { 46 | x: number = 1; 47 | }; 48 | 49 | // console.log("aaa"); 50 | 51 | const el = document.querySelector("#app"); 52 | 53 | console.log("el", el); 54 | 55 | switch (1 as number) { 56 | case 1: 57 | case 2: { 58 | break 59 | } 60 | default: { 61 | console.log(!!1) 62 | } 63 | } 64 | 65 | import { 66 | OPERATORS, 67 | RESERVED_WORDS, 68 | REST_SPREAD, 69 | SPACE_REQUIRED_OPERATORS, 70 | _ as _w, 71 | __ as __w, 72 | } from "../../src/constants"; 73 | 74 | 75 | 76 | // declare const foo: any; 77 | 78 | import { RootCompiler, RootParser } from "@mizchi/pargen/src/types"; 79 | import { createContext } from "@mizchi/pargen/src"; 80 | import { preprocessLight } from "../../src/preprocess"; 81 | const { compile, builder } = createContext({ 82 | composeTokens: true, 83 | // pairs: ["{", "}"], 84 | }); 85 | 86 | const compileWithPreprocess: RootCompiler = (input, opts) => { 87 | const parser = compile(input, opts); 88 | const newParser: RootParser = (input, ctx) => { 89 | const pre = preprocessLight(input); 90 | const ret = parser(pre, ctx); 91 | return ret; 92 | }; 93 | return newParser; 94 | }; 95 | 96 | export { compileWithPreprocess as compile, builder }; 97 | 98 | interface X {} 99 | 100 | export function escapeWhistespace(input: string) { 101 | return input 102 | .replace(/[ ]{1,}/gmu, (text) => `@W${text.length}}`) 103 | .replace(/\n{1,}/gmu, (text) => `@N${text.length}}`); 104 | }; 105 | 106 | export function restoreEscaped(input: string, literals: Map) { 107 | return input.replace(/@(W|L|N)(\d+)\}/, (full, type, $2)=>{}); 108 | 109 | }; 110 | 111 | 112 | export function main() { 113 | // const compilers = [ 114 | // compileTsc, 115 | // compileMints, 116 | // ]; 117 | 118 | // for (const compiler of compilers) { 119 | // for (let i = 0; i < 3; i++) { 120 | // const now = Date.now(); 121 | // const out = compiler(code); 122 | // console.log(compiler.name, `[${i}]`, Date.now() - now); 123 | // // printPerfResult(); 124 | // console.log("raw:", out); 125 | // // console.log("----"); 126 | // // console.log(prettier.format(out, { parser: "typescript" })); 127 | // } 128 | // } 129 | }; 130 | main(); 131 | -------------------------------------------------------------------------------- /packages/mints-legacy/benchmark/cases/example1.ts: -------------------------------------------------------------------------------- 1 | import ts from "typescript"; 2 | import { ErrorType, ParseError } from "@mizchi/pargen/src/types"; 3 | import { preprocessLight } from "../../src/preprocess"; 4 | import prettier from "prettier"; 5 | 6 | function compileTsc(input: string) { 7 | return ts.transpile(input, { 8 | module: ts.ModuleKind.ESNext, 9 | target: ts.ScriptTarget.Latest, 10 | }); 11 | } 12 | 13 | const _format = (input: string, format: boolean, stripTypes: boolean) => { 14 | input = stripTypes ? compileTsc(input) : input; 15 | return format ? prettier.format(input, { parser: "typescript" }) : input; 16 | }; 17 | 18 | export const expectSame = ( 19 | parse: any, 20 | inputs: string[], 21 | { 22 | format = true, 23 | stripTypes = true, 24 | }: { format?: boolean; stripTypes?: boolean } = {} 25 | ) => { 26 | inputs.forEach((raw) => { 27 | const input = preprocessLight(raw); 28 | const result = parse(input); 29 | if (result.error) { 30 | formatError(input, result); 31 | throw new Error("Unexpected Result:" + input); 32 | } else { 33 | const resultf = format 34 | ? _format(result.result as string, format, stripTypes) 35 | : result.result; 36 | const expectedf = format ? _format(input, format, stripTypes) : input; 37 | if (resultf !== expectedf) { 38 | throw `Expect: ${input}\nOutput: ${JSON.stringify(result, null, 2)}`; 39 | } 40 | } 41 | }); 42 | }; 43 | 44 | export const expectError = (parse: any, inputs: string[]) => { 45 | inputs.forEach((input) => { 46 | const result = parse(preprocessLight(input)); 47 | if (!result.error) { 48 | throw new Error("Unexpected SameResult:" + result); 49 | } 50 | }); 51 | }; 52 | 53 | export function formatError(input: string, error: ParseError) { 54 | const deepError = findMaxPosError(error, error); 55 | console.log("max depth", deepError.pos); 56 | _formatError(input, deepError); 57 | } 58 | 59 | export function findMaxPosError( 60 | error: ParseError, 61 | currentError: ParseError 62 | ): ParseError { 63 | currentError = error.pos > currentError.pos ? error : currentError; 64 | 65 | if (error.code === ErrorType.Seq_Stop) { 66 | currentError = findMaxPosError(error.detail.child, currentError); 67 | } 68 | 69 | if (error.code === ErrorType.Or_UnmatchAll) { 70 | for (const e of error.detail.children) { 71 | currentError = findMaxPosError(e, currentError); 72 | } 73 | } 74 | return currentError; 75 | } 76 | 77 | function _formatError(input: string, error: ParseError, depth: number = 0) { 78 | if (depth === 0) { 79 | console.error("[parse:fail]", error.pos); 80 | } 81 | const prefix = " ".repeat(depth * 2); 82 | 83 | console.log( 84 | prefix, 85 | `${ErrorType?.[error.code]}[${error.pos}]`, 86 | `<$>`, 87 | input.substr(error.pos).split("\n")[0] + " ..." 88 | ); 89 | if (error.code === ErrorType.Token_Unmatch && error.detail) { 90 | console.log(prefix, ">", error.detail); 91 | } 92 | if (error.code === ErrorType.Not_IncorrectMatch) { 93 | console.log(prefix, "matche", error); 94 | } 95 | 96 | if (error.code === ErrorType.Seq_Stop) { 97 | _formatError(input, error.detail.child, depth + 1); 98 | } 99 | 100 | if (error.code === ErrorType.Or_UnmatchAll) { 101 | for (const e of error.detail.children) { 102 | _formatError(input, e, depth + 1); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /packages/mints-legacy/benchmark/cases/example2.ts: -------------------------------------------------------------------------------- 1 | import type { IPromisesAPI } from "memfs/lib/promises"; 2 | import { Volume } from "memfs"; 3 | import createFs from "memfs/lib/promises"; 4 | 5 | import path from "path"; 6 | import { analyzeModule } from "./analyzer"; 7 | import { parse } from "./parser"; 8 | import type { BundleOptions, AnalyzedChunk, ImportMap } from "./types"; 9 | import { ModulesMap } from "./types"; 10 | import { optimize } from "./optimizer"; 11 | import { render } from "./renderer"; 12 | import { filepathToFlatSymbol } from "./helpers"; 13 | 14 | const defaultImportMap = { 15 | imports: {}, 16 | }; 17 | 18 | export class Bundler { 19 | private modulesMap = new Map(); 20 | public fs: IPromisesAPI; 21 | 22 | constructor( 23 | // public files: { [k: string]: string }, 24 | // public importMap: ImportMap = defaultImportMap 25 | ) { 26 | this.fs = createMemoryFs(files); 27 | } 28 | 29 | public async bundle( 30 | entry: string 31 | // { exposeToGlobal = null, optimize: _optimize = true }: BundleOptions 32 | ) { 33 | await this.addModule(entry); 34 | const chunks = aggregateChunks(this.modulesMap, entry); 35 | const optimizedChunks = _optimize 36 | ? optimize(chunks, entry, this.importMap) 37 | : chunks; 38 | return render(entry, optimizedChunks, { 39 | exposeToGlobal, 40 | transformDynamicImport: false, 41 | }); 42 | } 43 | 44 | public async bundleChunks( 45 | entry: string 46 | // {} : {} = {} 47 | ) { 48 | if (builtChunks.find((c) => c.entry === entry)) { 49 | return; 50 | } 51 | await this.addModule(entry); 52 | const chunks = aggregateChunks(this.modulesMap, entry); 53 | const optimizedChunks = _optimize 54 | ? optimize(chunks, entry, this.importMap) 55 | : chunks; 56 | const built = render(entry, optimizedChunks, { 57 | exposeToGlobal: exposeToGlobal, 58 | transformDynamicImport: true, 59 | publicPath, 60 | }); 61 | 62 | if (root) { 63 | builtChunks.push({ 64 | type: "entry", 65 | entry, 66 | builtCode: built, 67 | }); 68 | } else { 69 | builtChunks.push({ 70 | type: "chunk", 71 | entry, 72 | chunkName: filepathToFlatSymbol(entry, publicPath), 73 | builtCode: built, 74 | }); 75 | } 76 | 77 | const dynamicImports = optimizedChunks.map((c) => c.dynamicImports).flat(); 78 | dynamicImports.forEach((i) => { 79 | this.bundleChunks( 80 | i.filepath, 81 | { 82 | exposeToGlobal: null, 83 | optimize: _optimize, 84 | root: false, 85 | publicPath, 86 | }, 87 | builtChunks 88 | ); 89 | }); 90 | console.log("bundle", dynamicImports); 91 | const workerSources = optimizedChunks.map((c) => c.workerSources).flat(); 92 | workerSources.forEach((i) => { 93 | this.bundleChunks( 94 | i.filepath, 95 | { 96 | exposeToGlobal: null, 97 | optimize: _optimize, 98 | root: false, 99 | publicPath, 100 | }, 101 | builtChunks 102 | ); 103 | }); 104 | 105 | return builtChunks; 106 | } 107 | 108 | public async updateModule(filepath: string, nextContent: string) { 109 | await this.fs.writeFile(filepath, nextContent); 110 | this.modulesMap.delete(filepath); 111 | this.addModule(filepath); 112 | } 113 | 114 | // // TODO: need this? 115 | async deleteRecursive(filepath: string) { 116 | const cached = this.modulesMap.get(filepath)!; 117 | if (cached) { 118 | for (const i of cached.imports) { 119 | this.deleteRecursive(i.filepath); 120 | this.modulesMap.delete(i.filepath); 121 | } 122 | } 123 | } 124 | 125 | private async addModule(filepath: string): Promise { 126 | // console.log("add module", filepath); 127 | if (this.modulesMap.has(filepath)) { 128 | return; 129 | } 130 | 131 | const basepath = path.dirname(filepath); 132 | const raw = await readFile(this.fs, filepath); 133 | const ast = parse(raw, filepath); 134 | 135 | const analyzed = analyzeModule(ast, basepath, this.importMap); 136 | this.modulesMap.set(filepath, { 137 | ...analyzed, 138 | raw, 139 | filepath, 140 | ast, 141 | }); 142 | 143 | // console.log("used", filepath, JSON.stringify(imports, null, 2)); 144 | for (const i of analyzed.imports) { 145 | await this.addModule(i.filepath); 146 | } 147 | for (const di of analyzed.dynamicImports) { 148 | await this.addModule(di.filepath); 149 | } 150 | for (const w of analyzed.workerSources) { 151 | await this.addModule(w.filepath); 152 | } 153 | } 154 | } 155 | 156 | export function aggregateChunks(modulesMap: ModulesMap, entryPath: string) { 157 | const entryMod = modulesMap.get(entryPath)!; 158 | const chunks: AnalyzedChunk[] = []; 159 | _aggregate(entryMod); 160 | return chunks; 161 | 162 | function _aggregate(mod: AnalyzedChunk) { 163 | if (chunks.find((x) => x.filepath === mod.filepath)) { 164 | return chunks; 165 | } 166 | 167 | for (const imp of mod.imports) { 168 | if (chunks.find((x) => x.filepath === imp.filepath)) { 169 | continue; 170 | } 171 | const child = modulesMap.get(imp.filepath)!; 172 | _aggregate(child); 173 | } 174 | 175 | for (const dimp of mod.dynamicImports) { 176 | if (chunks.find((x) => x.filepath === dimp.filepath)) { 177 | continue; 178 | } 179 | const child = modulesMap.get(dimp.filepath)!; 180 | _aggregate(child); 181 | } 182 | 183 | chunks.push(mod); 184 | return chunks; 185 | } 186 | } 187 | 188 | // helper 189 | export function createMemoryFs( 190 | // files: { [k: string]: string } 191 | ): IPromisesAPI { 192 | const vol = Volume.fromJSON(files, "/"); 193 | return createFs(vol) as IPromisesAPI; 194 | } 195 | 196 | export async function readFile(fs: IPromisesAPI, filepath: string) { 197 | const raw = (await fs.readFile(filepath, { 198 | encoding: "utf-8", 199 | })) as string; 200 | return raw; 201 | } 202 | -------------------------------------------------------------------------------- /packages/mints-legacy/benchmark/cases/example5.ts: -------------------------------------------------------------------------------- 1 | switch (1 as number) { 2 | case 1: 3 | try {} catch (error) {} 4 | case 2: 5 | } 6 | 7 | -------------------------------------------------------------------------------- /packages/mints-legacy/benchmark/cases/example6.ts: -------------------------------------------------------------------------------- 1 | console.log('assert.throws requires two arguments: the error constructor ' + 2 | 'and a function to run'); -------------------------------------------------------------------------------- /packages/mints-legacy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mizchi/mints-legacy", 3 | "version": "0.0.2", 4 | "license": "MIT", 5 | "types": "types/mints/src/index.d.ts", 6 | "main": "dist/index.cjs", 7 | "module": "dist/index.js", 8 | "scripts": { 9 | "prepublishOnly": "yarn test && yarn build && yarn build:types", 10 | "build": "rollup -c && ls -lh dist | cut -f 10,14 -d ' '", 11 | "build:types": "tsc -p tsconfig.types.json", 12 | "analyze": "rollup -c rollup.config.analyze.js", 13 | "bench": "node -r esbuild-register benchmark/bench.ts", 14 | "bench:0x": "0x -- node -r esbuild-register benchmark/bench.ts", 15 | "test": "NODE_ENV=test node -r esbuild-register src/" 16 | }, 17 | "devDependencies": { 18 | "0x": "^4.11.0", 19 | "@atomico/rollup-plugin-sizes": "^1.1.4", 20 | "@rollup/plugin-node-resolve": "^13.0.6", 21 | "@rollup/plugin-replace": "^3.0.0", 22 | "@sosukesuzuki/rollup-plugin-bundle-size": "^0.1.0", 23 | "@types/prettier": "^2.4.1", 24 | "prettier": "^2.4.1", 25 | "rollup-plugin-analyzer": "^4.0.0", 26 | "rollup-plugin-define": "^1.0.1", 27 | "rollup-plugin-esbuild": "^4.6.0", 28 | "rollup-plugin-terser": "^7.0.2", 29 | "rollup-plugin-ts": "^1.4.7", 30 | "test262-harness": "^9.0.0", 31 | "typescript": "^4.4.4", 32 | "vite": "^2.6.10" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/mints-legacy/rollup.config.analyze.js: -------------------------------------------------------------------------------- 1 | import analyzer from "rollup-plugin-analyzer"; 2 | import config from "./rollup.config"; 3 | 4 | export default { 5 | ...config, 6 | output: { 7 | file: "dist/index.js", 8 | format: "es", 9 | }, 10 | plugins: [...config.plugins, analyzer({ summaryOnly: true })], 11 | }; 12 | -------------------------------------------------------------------------------- /packages/mints-legacy/rollup.config.js: -------------------------------------------------------------------------------- 1 | import esbuild from "rollup-plugin-esbuild"; 2 | import { terser } from "rollup-plugin-terser"; 3 | import replace from "@rollup/plugin-replace"; 4 | import nodeResolve from "@rollup/plugin-node-resolve"; 5 | 6 | export default { 7 | input: ["src/index.ts"], 8 | output: [ 9 | { 10 | file: "dist/index.js", 11 | format: "es", 12 | }, 13 | { 14 | file: "dist/index.cjs", 15 | format: "cjs", 16 | }, 17 | ], 18 | plugins: [ 19 | nodeResolve(), 20 | replace({ 21 | preventAssignment: true, 22 | "require.main === module": JSON.stringify(false), 23 | "process.env.NODE_ENV": JSON.stringify("production"), 24 | }), 25 | esbuild({ 26 | target: "esnext", 27 | }), 28 | terser({ module: true }), 29 | ], 30 | }; 31 | -------------------------------------------------------------------------------- /packages/mints-legacy/run_test262.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | node -r esbuild-register test/clone-test262.ts 4 | npx test262-harness --preprocessor test/preprocess.js \ 5 | test262/test262-checkout/test/language/expressions/array/11.1.4-0.js 6 | 7 | # npx test262-harness --preprocessor test/preprocess.js \ 8 | # test262/test262-checkout/test/language/expressions/array/*.js, 9 | # test262/test262-checkout/test/language/expressions/array/11.1.4-0.js 10 | -------------------------------------------------------------------------------- /packages/mints-legacy/src/constants.ts: -------------------------------------------------------------------------------- 1 | // export const _ = "[\\s\\n]*"; 2 | export const _ = "([\\s\\n]+)?"; 3 | export const __ = `[\\s\\n]+`; 4 | 5 | export const K_QUESTION = "?"; 6 | export const K_BANG = "!"; 7 | export const K_CASE = "case"; 8 | export const K_TRY = "try"; 9 | 10 | export const K_CONSTRUCTOR = "constructor"; 11 | export const K_FROM = "from"; 12 | export const K_STATIC = "static"; 13 | export const K_CLASS = "class"; 14 | export const K_CONTINUE = "continue"; 15 | export const K_TRUE = "true"; 16 | export const K_FALSE = "false"; 17 | export const K_FOR = "for"; 18 | export const K_IF = "if"; 19 | export const K_ELSE = "else"; 20 | export const K_AS = "as"; 21 | export const K_DEFAULT = "default"; 22 | export const K_EXPORT = "export"; 23 | export const K_FUNCTION = "function"; 24 | export const K_PRIVATE = "private"; 25 | export const K_PUBLIC = "public"; 26 | export const K_PROTECTED = "protected"; 27 | export const K_ASYNC = "async"; 28 | export const K_NULL = "null"; 29 | export const K_NEW = "new"; 30 | export const K_EXTENDS = "extends"; 31 | export const K_VOID = "void"; 32 | export const K_ABSTRACT = "abstract"; 33 | export const K_IMPLEMENTS = "implements"; 34 | export const K_IMPORT = "import"; 35 | export const K_THIS = "this"; 36 | export const K_READONLY = "readonly"; 37 | export const K_AWAIT = "await"; 38 | export const K_TYPEOF = "typeof"; 39 | export const K_DELETE = "delete"; 40 | export const K_BREAK = "break"; 41 | export const K_DEBUGGER = "debugger"; 42 | export const K_THROW = "throw"; 43 | export const K_RETURN = "return"; 44 | export const K_YIELD = "yield"; 45 | export const K_DO = "do"; 46 | export const K_WHILE = "while"; 47 | export const K_FINALLY = "finally"; 48 | export const K_VAR = "var"; 49 | export const K_CONST = "const"; 50 | export const K_LET = "let"; 51 | export const K_INTERFACE = "interface"; 52 | export const K_TYPE = "type"; 53 | export const K_DECLARE = "declare"; 54 | export const K_GET = "get"; 55 | export const K_SET = "set"; 56 | export const K_SWITCH = "switch"; 57 | export const K_INSTANCEOF = "instanceof"; 58 | export const K_CATCH = "catch"; 59 | export const K_IN = "in"; 60 | export const K_ENUM = "enum"; 61 | 62 | export const K_PAREN_OPEN = "("; 63 | export const K_PAREN_CLOSE = ")"; 64 | export const K_BLACE_OPEN = "{"; 65 | export const K_BLACE_CLOSE = "}"; 66 | 67 | // "(?!.*@N).*?" 68 | // export const PAIRED_CHARS = ["(", ")", "{", "}", "[", "]", "<", ">"] as const; 69 | export const REST_SPREAD = "..."; 70 | export const SPACE_REQUIRED_OPERATORS = [K_INSTANCEOF, K_IN]; 71 | 72 | const SINGLE_OPERATORS = [ 73 | "+", 74 | "-", 75 | "|", 76 | "&", 77 | "*", 78 | "/", 79 | ">", 80 | "<", 81 | "^", 82 | "%", 83 | "=", 84 | ]; 85 | 86 | export const OPERATORS = [ 87 | // relation 88 | // "instanceof", 89 | // "in", 90 | 91 | // 3 chars 92 | ">>>", 93 | "===", 94 | "!==", 95 | 96 | // 2 chars 97 | "||", 98 | "&&", 99 | "**", 100 | ">=", 101 | "<=", 102 | "==", 103 | "!=", 104 | "<<", 105 | ">>", 106 | 107 | "+=", 108 | "-=", 109 | "*=", 110 | "|=", 111 | "/=", 112 | "??", 113 | 114 | // 1 chars 115 | ...SINGLE_OPERATORS, 116 | ] as const; 117 | 118 | const KEYWORDS = [ 119 | K_BREAK, 120 | K_DO, 121 | K_INSTANCEOF, 122 | K_TYPEOF, 123 | K_CASE, 124 | K_ELSE, 125 | K_NEW, 126 | K_VAR, 127 | K_CATCH, 128 | K_FINALLY, 129 | K_RETURN, 130 | K_VOID, 131 | K_CONTINUE, 132 | K_FOR, 133 | K_SWITCH, 134 | K_WHILE, 135 | K_DEBUGGER, 136 | K_FUNCTION, 137 | K_THIS, 138 | "with", 139 | K_DEFAULT, 140 | K_IF, 141 | K_THROW, 142 | K_DELETE, 143 | K_IN, 144 | K_TRY, 145 | // Future reserved words 146 | K_CLASS, 147 | K_ENUM, 148 | K_EXTENDS, 149 | "super", 150 | K_CONST, 151 | K_EXPORT, 152 | K_IMPORT, 153 | K_IMPLEMENTS, 154 | K_LET, 155 | K_PRIVATE, 156 | K_PUBLIC, 157 | K_YIELD, 158 | K_INTERFACE, 159 | K_PROTECTED, 160 | "package", 161 | K_STATIC, 162 | ] as const; 163 | 164 | export const LITERAL_KEYWORDS = [K_NULL, K_TRUE, K_FALSE] as const; 165 | 166 | export const RESERVED_WORDS = [...KEYWORDS, ...LITERAL_KEYWORDS] as const; 167 | -------------------------------------------------------------------------------- /packages/mints-legacy/src/ctx.ts: -------------------------------------------------------------------------------- 1 | import { Rule, RootCompilerOptions } from "@mizchi/pargen/src/types"; 2 | import { createContext } from "@mizchi/pargen/src"; 3 | import { detectPragma, preprocessLight } from "./preprocess"; 4 | 5 | const { compile } = createContext({}); 6 | 7 | // TODO: Fix singleton 8 | 9 | const defaultJsx = "React.createElement"; 10 | const defaultJsxFragment = "React.Fragment"; 11 | 12 | export let config = { 13 | jsx: defaultJsx, 14 | jsxFragment: defaultJsxFragment, 15 | }; 16 | 17 | const compileWithPreprocess = ( 18 | inputRule: Rule | number, 19 | opts: RootCompilerOptions = {} 20 | ) => { 21 | const parser = compile(inputRule, opts); 22 | const wrappedParser = ( 23 | input: string, 24 | opts?: { jsx: string; jsxFragment: string } 25 | ) => { 26 | const pragma = detectPragma(input); 27 | const overrideJsx = pragma.jsx ?? opts?.jsx; 28 | if (overrideJsx) config.jsx = overrideJsx; 29 | const overrideJsxFragment = pragma.jsxFragment ?? opts?.jsxFragment; 30 | if (overrideJsxFragment) config.jsxFragment = overrideJsxFragment; 31 | // console.log("override", config, pragma, input); 32 | const pre = preprocessLight(input); 33 | const parseResult = parser(pre, 0); 34 | 35 | // restore pragma 36 | config.jsx = defaultJsx; 37 | config.jsxFragment = defaultJsxFragment; 38 | return parseResult; 39 | }; 40 | return wrappedParser; 41 | }; 42 | 43 | export { compileWithPreprocess as compile }; 44 | -------------------------------------------------------------------------------- /packages/mints-legacy/src/index.ts: -------------------------------------------------------------------------------- 1 | import { program } from "./grammar"; 2 | import { compile } from "./ctx"; 3 | 4 | const parse = compile(program, { end: true }); 5 | 6 | export function transform(input: string) { 7 | return parse(input); 8 | } 9 | 10 | import { is, run, test } from "@mizchi/test"; 11 | const isMain = require.main === module; 12 | if (process.env.NODE_ENV === "test") { 13 | const now = Date.now(); 14 | 15 | test("jsx: no-pragma", () => { 16 | const code = `const el =
1
`; 17 | const result = transform(code); 18 | is(result, { 19 | result: `const el=React.createElement("div",{},"1")`, 20 | }); 21 | }); 22 | 23 | test("jsx pragma", () => { 24 | const code = `/* @jsx h */\nconst el =
`; 25 | const result = transform(code); 26 | // console.log("config", config); 27 | // console.log(result); 28 | is(result, { 29 | result: `const el=h("div",{})`, 30 | }); 31 | }); 32 | 33 | run({ stopOnFail: true, stub: true, isMain }).then(() => { 34 | console.log("[test:time]", Date.now() - now); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /packages/mints-legacy/src/preprocess.ts: -------------------------------------------------------------------------------- 1 | export function escapeWhistespace(input: string) { 2 | return input 3 | .replace(/[ ]{1,}/gmu, (text) => `@W${text.length}}`) 4 | .replace(/\n{1,}/gmu, (text) => `@N${text.length}}`); 5 | } 6 | 7 | const jsxRegex = /\/\*\s?@jsx\s+([a-zA-Z\.]+)\s*\*\//; 8 | const jsxFragmentRegex = /\/\*\s?@jsxFragment\s+([a-zA-Z\.]+)\s*\*\//; 9 | 10 | export function detectPragma(input: string): { 11 | jsx: string | undefined; 12 | jsxFragment: string | undefined; 13 | } { 14 | let jsx = undefined, 15 | jsxFragment = undefined; 16 | const jsxMatch = jsxRegex.exec(input); 17 | if (jsxMatch) { 18 | jsx = jsxMatch[1]; 19 | } 20 | 21 | const jsxFragmentMatch = jsxFragmentRegex.exec(input); 22 | 23 | if (jsxFragmentMatch) { 24 | jsxFragment = jsxFragmentMatch[1]; 25 | } 26 | return { jsx, jsxFragment }; 27 | } 28 | 29 | export function restoreEscaped(input: string, literals: Map) { 30 | return input.replace(/@(W|L|N)(\d+)\}/g, (full, type, $2) => { 31 | if (type === "L") { 32 | return literals.get(full) as string; 33 | } 34 | if (type === "W") { 35 | return " ".repeat(Number($2)); 36 | } 37 | if (type === "N") { 38 | return "\n".repeat(Number($2)); 39 | } 40 | throw new Error(`Unknown type ${type}`); 41 | }); 42 | } 43 | 44 | export function escapeLiteral(input: string) { 45 | const literals = new Map(); 46 | let cnt = 1; 47 | const escaped = input.replace( 48 | /"[^"]*"|'[^']*'|`[^`]*`|\/!\*[^\/]*!\*\//gmu, 49 | (text) => { 50 | const key = `@L${cnt++}}`; 51 | literals.set(key, text); 52 | return key; 53 | } 54 | ); 55 | return { escaped, literals }; 56 | } 57 | 58 | export function preprocessLight(input: string) { 59 | const { escaped, literals } = escapeLiteral(input); 60 | const out = escaped 61 | // delete inline comments /*...*/ 62 | .replace(/\/\*(.*?)\*\//gmu, "") 63 | // delete comment line 64 | .replace(/(.*?)(\/\/.*)/gu, "$1") 65 | // delete redundunt semicollon except for statement 66 | .replace(/(?<%&\|}{\(\)\+\-\*\/\/\n\r,;><])[\n\r\t]+\s*/gmu, "$1"); 71 | return restoreEscaped(out, literals); 72 | } 73 | 74 | export function preprocess(input: string) { 75 | const { escaped, literals } = escapeLiteral(input); 76 | const modified = escaped 77 | // delete inline comments 78 | .replace(/\/\*([.\n]*?)\*\//gmu, "") 79 | // delete line comments 80 | .replace(/(.*)(\/\/.*)/gu, "$1"); 81 | const escaped2 = escapeWhistespace(modified); 82 | return { out: escaped2, data: literals }; 83 | } 84 | 85 | export function postprocess(out: string, literals: Map) { 86 | return restoreEscaped(out, literals); 87 | } 88 | 89 | import { is, run, test } from "@mizchi/test"; 90 | const isMain = require.main === module; 91 | if (process.env.NODE_ENV === "test") { 92 | const now = Date.now(); 93 | test("escape literal", () => { 94 | is(escapeLiteral(`"x"`), { escaped: `@L1}` }); 95 | is(escapeLiteral(`"x" "y"`), { escaped: `@L1} @L2}` }); 96 | is(escapeLiteral(`"x" "y" 'zzz'`), { escaped: `@L1} @L2} @L3}` }); 97 | const e = escapeLiteral(`"x" "y" 'zzz'`); 98 | is(restoreEscaped(e.escaped, e.literals), `"x" "y" 'zzz'`); 99 | }); 100 | 101 | test("tokenize whitespace", () => { 102 | is(escapeWhistespace(` a`), `@W1}a`); 103 | is(escapeWhistespace(` a`), `@W2}a`); 104 | is(escapeWhistespace(` a `), `@W2}a@W1}`); 105 | is(restoreEscaped(escapeWhistespace(` a `), new Map()), ` a `); 106 | }); 107 | 108 | // class X{f() {1;\t\t\n \nconst y = 1;\t}} 109 | test("preprocessLight", () => { 110 | is(preprocessLight(` a \n`), ` a \n`); 111 | is(preprocessLight(` a \nb`), ` a \nb`); 112 | is( 113 | preprocessLight(`class X{ 114 | f() { 115 | 1; 116 | // x 117 | // y 118 | 119 | const y = 1; 120 | } 121 | } 122 | `), 123 | `class X{f() {1;const y = 1;}}` 124 | ); 125 | is(preprocessLight(``), ``); 126 | }); 127 | 128 | test("strip inline comment", () => { 129 | is(preprocess("/**/").out, ""); 130 | is(preprocess("/**/a/**/").out, "a"); 131 | is(preprocess("/*\n*/ax/**/").out, "ax"); 132 | }); 133 | 134 | test("line comment", () => { 135 | is(preprocess("a").out, "a"); 136 | is(preprocess("//a").out, ""); 137 | is(preprocess(" //a").out, "@W1}"); 138 | is(preprocess("//a\n//b").out, "@N1}"); 139 | is(preprocess("a//a\n//b").out, "a@N1}"); 140 | is(preprocess("a//a\n//b").out, "a@N1}"); 141 | is(preprocess("a\n//xxx\n").out, "a@N2}"); 142 | }); 143 | 144 | run({ stopOnFail: true, stub: true, isMain }).then(() => { 145 | console.log("[test:time]", Date.now() - now); 146 | }); 147 | } 148 | -------------------------------------------------------------------------------- /packages/mints-legacy/src/tokenizer.ts: -------------------------------------------------------------------------------- 1 | const DOUBLE_QUOTE = '"'; 2 | const SINGLE_QUOTE = "'"; 3 | const BACK_QUOTE = "`"; 4 | const SLASH = "/"; 5 | 6 | const STRING_PAIR = [SINGLE_QUOTE, DOUBLE_QUOTE, BACK_QUOTE] as const; 7 | 8 | const L_BRACE = "{"; 9 | const R_BRACE = "}"; 10 | 11 | const CONTROL_TOKENS = [ 12 | ";", 13 | ",", 14 | L_BRACE, 15 | R_BRACE, 16 | "(", 17 | ")", 18 | "+", 19 | "-", 20 | "/", 21 | "%", 22 | ">", 23 | "<", 24 | "'", 25 | '"', 26 | "`", 27 | "=", 28 | "!", 29 | "&", 30 | "|", 31 | "^", 32 | "~", 33 | "?", 34 | ":", 35 | ".", 36 | "*", 37 | "[", 38 | "]", 39 | "\n", 40 | "\r", 41 | "\t", 42 | " ", 43 | ]; 44 | const SKIP_TOKENS = ["\n", " ", "\t", "\r"]; 45 | 46 | function parseTokens(input: string): Generator { 47 | const chars = createCharSlice(input); 48 | return parseStream(chars) as Generator; 49 | } 50 | 51 | function* parseStream( 52 | // unicode slice or raw ascii string 53 | chars: string | string[], 54 | initialCursor: number = 0, 55 | root = true 56 | ): Generator { 57 | let _buf = ""; 58 | let wrapStringContext: "'" | '"' | "`" | null = null; 59 | let openBraceStack = 0; 60 | let isLineComment = false; 61 | let isInlineComment = false; 62 | let i: number; 63 | for (i = initialCursor; i < chars.length; i++) { 64 | const char = chars[i]; 65 | if (isLineComment) { 66 | if (char === "\n") { 67 | isLineComment = false; 68 | } 69 | continue; 70 | } 71 | // skip under inline comment 72 | if (isInlineComment) { 73 | const nextChar = chars[i + 1]; 74 | if (char === "*" && nextChar === "/") { 75 | isInlineComment = false; 76 | i += 1; // exit comment 77 | } 78 | continue; 79 | } 80 | // string 81 | if (wrapStringContext) { 82 | const prevChar = chars[i - 1]; 83 | if (char === wrapStringContext && prevChar !== "\\") { 84 | if (_buf.length > 0) { 85 | yield _buf; 86 | _buf = ""; 87 | } 88 | wrapStringContext = null; 89 | yield char; 90 | } else { 91 | // detect ${expr} in `` 92 | if ( 93 | wrapStringContext === "`" && 94 | char === "$" && 95 | chars[i + 1] === L_BRACE 96 | ) { 97 | if (_buf.length > 0) { 98 | yield _buf; 99 | _buf = ""; 100 | } 101 | yield "${"; 102 | i += 2; 103 | for (const tok of parseStream(chars, i, false)) { 104 | if (typeof tok === "string") { 105 | yield tok; 106 | } else { 107 | i = tok; 108 | } 109 | } 110 | yield "}"; 111 | i += 1; 112 | } else { 113 | _buf += char; 114 | } 115 | } 116 | continue; 117 | } 118 | if (CONTROL_TOKENS.includes(char)) { 119 | const nextChar = chars[i + 1]; 120 | if (char === SLASH) { 121 | if (nextChar === "*") { 122 | if (_buf.length > 0) { 123 | yield _buf; 124 | _buf = ""; 125 | } 126 | isInlineComment = true; 127 | continue; 128 | } 129 | if (nextChar === SLASH) { 130 | if (_buf.length > 0) { 131 | yield _buf; 132 | _buf = ""; 133 | } 134 | isLineComment = true; 135 | i += 1; 136 | continue; 137 | } 138 | } 139 | 140 | // Handle negative stack to go out parent 141 | if (char === L_BRACE) openBraceStack++; 142 | else if (char === R_BRACE) { 143 | openBraceStack--; 144 | if (!root && openBraceStack < 0) { 145 | // exit by negative stack 146 | i--; // back to prev char 147 | break; 148 | } 149 | if (openBraceStack === 0 && nextChar === "\n") { 150 | // push separator 151 | yield "\n"; 152 | } 153 | } 154 | // switch to string context 155 | if (STRING_PAIR.includes(char as any)) { 156 | wrapStringContext = char as any; 157 | } 158 | if (_buf.length > 0) { 159 | yield _buf; 160 | _buf = ""; 161 | } 162 | if (!SKIP_TOKENS.includes(char)) { 163 | yield char; 164 | if ( 165 | char === ";" && 166 | openBraceStack === 0 && 167 | wrapStringContext === null 168 | ) { 169 | yield "\n"; 170 | } 171 | } 172 | } else { 173 | _buf += char; 174 | } 175 | } 176 | 177 | if (_buf.length > 0) { 178 | yield _buf; 179 | _buf = ""; 180 | } 181 | 182 | if (!root) { 183 | yield i; 184 | } 185 | 186 | if (isInlineComment || wrapStringContext) { 187 | throw new Error(`unclosed ${i}`); 188 | } 189 | } 190 | 191 | function createCharSlice(input: string) { 192 | let chars: string | string[] = Array.from(input); 193 | return chars.length === input.length ? input : chars; 194 | } 195 | 196 | import { test, run } from "@mizchi/test"; 197 | const isMain = require.main === module; 198 | if (process.env.NODE_ENV === "test") { 199 | const assert = require("assert"); 200 | const eq = assert.deepStrictEqual; 201 | const expectParseResult = (input: string, expected: string[]) => { 202 | for (const token of parseStream(input)) { 203 | eq(expected.shift(), token); 204 | } 205 | }; 206 | test("parse tokens", () => { 207 | const input = "a;bb cccc d+e"; 208 | let expected = ["a", ";", "\n", "bb", "cccc", "d", "+", "e"]; 209 | expectParseResult(input, expected); 210 | }); 211 | test("parse string", () => { 212 | const input = "'x y '"; 213 | let expected = ["'", "x y ", "'"]; 214 | expectParseResult(input, expected); 215 | }); 216 | 217 | // TODO: Handle escape 218 | // test("parse escaped string", () => { 219 | // // prettier-ignore 220 | // const input = "'\'aaa\''"; 221 | // // prettier-ignore 222 | // let expected = ["'", "'aaa'", "'"]; 223 | // expectParseResult(input, expected); 224 | // }); 225 | 226 | test("parse template", () => { 227 | const input = "`xxx`"; 228 | let expected = ["`", "xxx", "`"]; 229 | expectParseResult(input, expected); 230 | }); 231 | test("parse template expression", () => { 232 | const input = "`a${b+ c}d`"; 233 | let expected = ["`", "a", "${", "b", "+", "c", "}", "d", "`"]; 234 | expectParseResult(input, expected); 235 | }); 236 | test("parse template expression 2", () => { 237 | const input = "`aaa ${bb + c } dd `"; 238 | let expected = ["`", "aaa ", "${", "bb", "+", "c", "}", " dd ", "`"]; 239 | expectParseResult(input, expected); 240 | }); 241 | 242 | test("parse template expression 3", () => { 243 | const input = "`${a} x x ${b}`"; 244 | let expected = ["`", "${", "a", "}", " x x ", "${", "b", "}", "`"]; 245 | expectParseResult(input, expected); 246 | }); 247 | 248 | test("parse template expression 4 nested", () => { 249 | const input = "`${`xxx`}`"; 250 | let expected = ["`", "${", "`", "xxx", "`", "}", "`"]; 251 | expectParseResult(input, expected); 252 | }); 253 | 254 | test("parse template expression 5 nested", () => { 255 | const input = "`${`xxx ${1}`}`"; 256 | let expected = ["`", "${", "`", "xxx ", "${", "1", "}", "`", "}", "`"]; 257 | expectParseResult(input, expected); 258 | }); 259 | 260 | test("line comment", () => { 261 | expectParseResult("//aaa", []); 262 | expectParseResult("a//aaa", ["a"]); 263 | expectParseResult("a//aaa \nb\n//", ["a", "b"]); 264 | // expectParseResult("a//aaa \nb", ["a", "b"]); 265 | }); 266 | 267 | test("inline comment2 ", () => { 268 | expectParseResult("/* */", []); 269 | expectParseResult("/**/", []); 270 | expectParseResult("a/**/", ["a"]); 271 | expectParseResult("a/* */", ["a"]); 272 | 273 | expectParseResult("a/* */a", ["a", "a"]); 274 | expectParseResult("a/* */a/**/a", ["a", "a", "a"]); 275 | }); 276 | 277 | test("inline comment 3", () => { 278 | const code = `{ /* Invalid session is passed. Ignore. */}x`; 279 | const result = [...parseStream(code)].map((token) => token); 280 | console.log(result); 281 | eq(result, ["{", "}", "x"]); 282 | }); 283 | test("Unicode", () => { 284 | expectParseResult("あ あ", ["あ", "あ"]); 285 | expectParseResult("あ/*え*/あ", ["あ", "あ"]); 286 | expectParseResult("𠮷/*𠮷*/𠮷", ["𠮷", "𠮷"]); 287 | }); 288 | 289 | run({ stopOnFail: true, stub: true, isMain }); 290 | } 291 | 292 | if (process.env.NODE_ENV === "perf" && isMain) { 293 | const fs = require("fs"); 294 | const path = require("path"); 295 | const code = fs.readFileSync( 296 | path.join(__dirname, "../benchmark/cases/example4.ts"), 297 | "utf8" 298 | ); 299 | for (let i = 0; i < 10; i++) { 300 | const start = process.hrtime.bigint(); 301 | let result = []; 302 | let tokenCount = 0; 303 | for (const token of parseTokens(code)) { 304 | if (token === "\n") { 305 | tokenCount += result.length; 306 | result = []; 307 | } else { 308 | result.push(token); 309 | } 310 | } 311 | console.log( 312 | "finish", 313 | `${i}`, 314 | tokenCount + "tokens", 315 | Number(process.hrtime.bigint() - start) / 1_000_000 + "ms", 316 | Math.floor( 317 | tokenCount / (Number(process.hrtime.bigint() - start) / 1_000_000) 318 | ) + "tokens/ms" 319 | ); 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /packages/mints-legacy/test/clone-test262.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { spawn } from "child_process"; 3 | 4 | const TEST262_DIR = "./test262/test262-checkout"; 5 | const TEST262_REPO_URL = "https://github.com/tc39/test262.git"; 6 | const TEST262_REVISION = "157b18d16b5d52501c4d75ac422d3a80bfad1c17"; 7 | 8 | function run(command: string): Promise { 9 | console.log(`> ${command}`); 10 | return new Promise((resolve, reject) => { 11 | const childProcess = spawn("/bin/bash", ["-c", command], { 12 | stdio: "inherit", 13 | }); 14 | childProcess.on("close", (code) => { 15 | if (code === 0) { 16 | resolve(); 17 | } else { 18 | reject(new Error(`Command failed: ${command}`)); 19 | } 20 | }); 21 | }); 22 | } 23 | 24 | /** 25 | * Run the test262 suite on some tests that we know are useful. 26 | */ 27 | async function main(): Promise { 28 | if (!fs.existsSync(TEST262_DIR)) { 29 | console.log(`Directory ${TEST262_DIR} not found, cloning a new one.`); 30 | await run(`git clone ${TEST262_REPO_URL} ${TEST262_DIR} --depth 1`); 31 | } 32 | 33 | // Force a specific revision so we don't get a breakage from changes to the main branch. 34 | const originalCwd = process.cwd(); 35 | try { 36 | process.chdir(TEST262_DIR); 37 | await run(`git reset --hard ${TEST262_REVISION}`); 38 | await run(`git clean -f`); 39 | } catch (e) { 40 | await run("git fetch"); 41 | await run(`git reset --hard ${TEST262_REVISION}`); 42 | await run(`git clean -f`); 43 | } finally { 44 | process.chdir(originalCwd); 45 | } 46 | } 47 | 48 | main().catch((e) => { 49 | console.error("Unhandled error:"); 50 | console.error(e); 51 | process.exitCode = 1; 52 | }); 53 | -------------------------------------------------------------------------------- /packages/mints-legacy/test/preprocess.js: -------------------------------------------------------------------------------- 1 | const mints = require("../dist/index.cjs"); 2 | 3 | /** 4 | * test262-harness preprocessor documented here: 5 | https://github.com/bterlson/test262-harness#preprocessor 6 | */ 7 | module.exports = function (test) { 8 | // console.log(test); 9 | // console.log("try to parse", test.file); 10 | 11 | // Mints doesn't attempt to throw SyntaxError on bad syntax, so skip those tests. 12 | if (test.attrs.negative) { 13 | return null; 14 | } 15 | 16 | // Mints assumes strict mode, so skip sloppy mode tests. 17 | if (test.scenario === "default") { 18 | return null; 19 | } 20 | 21 | // TCO tests seem to fail in V8 normally, so skip those. 22 | if (test.attrs.features?.includes("tail-call-optimization")) { 23 | return null; 24 | } 25 | 26 | try { 27 | // console.log("transformed", test.file, test.contents.slice(0, 10)); 28 | const transformed = mints.transform(test.contents); 29 | // console.log("transformed", transformed.result); 30 | console.log("input", test.contents, "\n====\n", transformed); 31 | 32 | if (transformed.error) { 33 | throw new Error("parse-error"); 34 | } 35 | test.contents = transformed.result; 36 | } catch (error) { 37 | // console.log("failed", test.file); 38 | test.result = { 39 | stderr: JSON.stringify(error, null, 2), 40 | stderr: "error", 41 | stdout: "", 42 | error, 43 | }; 44 | } 45 | 46 | return test; 47 | }; 48 | -------------------------------------------------------------------------------- /packages/mints-legacy/test/run_bundled.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { transform, reportError } from "../dist/index.js.js"; 3 | console.log(transform("export const x: number = 1;")); 4 | 5 | const errorCode = `const let:number`; 6 | 7 | console.log(reportError(errorCode, transform(errorCode))); 8 | -------------------------------------------------------------------------------- /packages/mints-legacy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "emitDeclarationOnly": false, 5 | "noEmit": true 6 | }, 7 | "exclude": ["benchmark/cases"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/mints-legacy/tsconfig.types.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | // "rootDir": "src", 5 | "declaration": true, 6 | "target": "ESNext", 7 | "module": "ESNext", 8 | "outDir": "types", 9 | "noUnusedLocals": false 10 | }, 11 | "include": ["src", "../pargen/src"], 12 | "exclude": ["benchmark/cases"] 13 | } 14 | -------------------------------------------------------------------------------- /packages/mints-legacy/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | export default defineConfig({ 3 | define: { 4 | "require.main === module": JSON.stringify(false), 5 | "process.env.NODE_ENV": JSON.stringify("production"), 6 | }, 7 | build: { 8 | target: "esnext", 9 | minify: "terser", 10 | // minify: true, 11 | // @ts-ignore 12 | lib: { 13 | entry: "src/index", 14 | formats: ["es", "cjs"], 15 | fileName: (format) => { 16 | if (format === "cjs") { 17 | return `index.cjs`; 18 | } 19 | if (format === "es") { 20 | return `index.js`; 21 | } 22 | return ""; 23 | }, 24 | }, 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /packages/mints/.gitignore: -------------------------------------------------------------------------------- 1 | test262 2 | src/runtime/snapshot_b64.ts 3 | -------------------------------------------------------------------------------- /packages/mints/README.md: -------------------------------------------------------------------------------- 1 | # mints 2 | 3 | minimum typescript compiler (7.6kb, gzip) 4 | 5 | Demo https://mints-playground.netlify.app/ 6 | 7 | ## Goal 8 | 9 | - Lightweight 10 | - Just drop type annotations like `:number` and transform `enum`, `constructor`'s `public/private/proctected` and `jsx`. 11 | - Fast first compile (use prebuild binary format) 12 | - Support parallel compile 13 | 14 | ## Syntax Limitations 15 | 16 | - All statements except those terminated by `}` require `;` (expect prettier format) 17 | - mints tokenizer is unstable for RegExp Literal `/.../` yet. 18 | - `/ ` can not parse as RexExp (expect binary divider). use `/[ ]...`. 19 | - `/>` can not parse as RegExp (expect jsx) 20 | - ` { 87 | if (i === out.pos) return "<[ " + t + " ]>"; 88 | return t; 89 | }) 90 | .join(" "); 91 | console.log("---\n", out, "\n"); 92 | throw new Error(`[Mints:ParseError]\n${errorText}`); 93 | } 94 | return out.code as string; 95 | } 96 | 97 | const transformer = createTransformer( 98 | path.join(__dirname, "../dist/node_worker.js"), 99 | 14 100 | ); 101 | process.on("exit", () => transformer.terminate()); 102 | 103 | async function mints_para(input: string) { 104 | const out = await transformer.transform(input); 105 | if (typeof out === "object") { 106 | throw out; 107 | } 108 | return out as string; 109 | } 110 | 111 | export async function main() { 112 | const compilers = [ 113 | tsc, 114 | // sucrase, 115 | esbuild, 116 | mints, 117 | mints_para, 118 | ]; 119 | 120 | // const targets = [code_scratch]; 121 | const targets = [code0, code1, code2, code3, code4, code5, code6, code7]; 122 | 123 | // check mints can parse all 124 | console.log("=== mints-check"); 125 | for (const target of targets) { 126 | const _out = mints(target); 127 | console.log("pass", JSON.stringify(target.slice(0, 10)) + "..."); 128 | } 129 | 130 | console.log("=== perf start"); 131 | for (const code of targets) { 132 | const caseName = 133 | "example:" + targets.indexOf(code) + ":" + code.length + "chars"; 134 | console.log("---------", caseName); 135 | for (const compiler of compilers) { 136 | const results: number[] = []; 137 | for (let i = 0; i < N; i++) { 138 | const now = Date.now(); 139 | // console.log("bench", code); 140 | const out = await compiler(code); 141 | results.push(Date.now() - now); 142 | } 143 | console.log( 144 | `[${compiler.name}]`, 145 | Math.floor(results.reduce((s, n) => s + n, 0) / results.length) + "ms" 146 | ); 147 | } 148 | } 149 | process.exit(0); 150 | } 151 | 152 | main().catch((e) => { 153 | console.error(e); 154 | process.exit(1); 155 | }); 156 | -------------------------------------------------------------------------------- /packages/mints/benchmark/bootstrap.js: -------------------------------------------------------------------------------- 1 | // import { transform } from "./src/prebuild/index"; 2 | console.time("load"); 3 | const { transform } = require("../dist/index.cjs"); 4 | console.timeEnd("load"); 5 | 6 | console.time("run1"); 7 | const out = transform("const x: number = 1;"); 8 | console.log(out); 9 | console.timeEnd("run1"); 10 | 11 | console.time("run2"); 12 | const out2 = transform("const x: number = 1;"); 13 | // console.log(out2); 14 | console.timeEnd("run2"); 15 | -------------------------------------------------------------------------------- /packages/mints/benchmark/cases/_scratch.ts: -------------------------------------------------------------------------------- 1 | // This file was procedurally generated from the following sources: 2 | // - src/dstr-binding/obj-ptrn-id-init-fn-name-cover.case 3 | // - src/dstr-binding/default/arrow-function-dflt.template 4 | /*--- 5 | description: SingleNameBinding assigns `name` to "anonymous" functions "through" cover grammar (arrow function expression (default parameter)) 6 | esid: sec-arrow-function-definitions-runtime-semantics-evaluation 7 | features: [destructuring-binding, default-parameters] 8 | flags: [generated] 9 | info: | 10 | ArrowFunction : ArrowParameters => ConciseBody 11 | 12 | [...] 13 | 4. Let closure be FunctionCreate(Arrow, parameters, ConciseBody, scope, strict). 14 | [...] 15 | 16 | 9.2.1 [[Call]] ( thisArgument, argumentsList) 17 | 18 | [...] 19 | 7. Let result be OrdinaryCallEvaluateBody(F, argumentsList). 20 | [...] 21 | 22 | 9.2.1.3 OrdinaryCallEvaluateBody ( F, argumentsList ) 23 | 24 | 1. Let status be FunctionDeclarationInstantiation(F, argumentsList). 25 | [...] 26 | 27 | 9.2.12 FunctionDeclarationInstantiation(func, argumentsList) 28 | 29 | [...] 30 | 23. Let iteratorRecord be Record {[[iterator]]: 31 | CreateListIterator(argumentsList), [[done]]: false}. 32 | 24. If hasDuplicates is true, then 33 | [...] 34 | 25. Else, 35 | b. Let formalStatus be IteratorBindingInitialization for formals with 36 | iteratorRecord and env as arguments. 37 | [...] 38 | 39 | 13.3.3.7 Runtime Semantics: KeyedBindingInitialization 40 | 41 | SingleNameBinding : BindingIdentifier Initializeropt 42 | 43 | [...] 44 | 6. If Initializer is present and v is undefined, then 45 | [...] 46 | d. If IsAnonymousFunctionDefinition(Initializer) is true, then 47 | i. Let hasNameProperty be HasOwnProperty(v, "name"). 48 | ii. ReturnIfAbrupt(hasNameProperty). 49 | iii. If hasNameProperty is false, perform SetFunctionName(v, 50 | bindingId). 51 | ---*/ 52 | 53 | // var callCount = 0; 54 | // var f; 55 | // f = ({ cover = (function () {}), xCover = (0, function() {}) } = {}) => { 56 | // // assert.sameValue(cover.name, 'cover'); 57 | // // assert.notSameValue(xCover.name, 'xCover'); 58 | // // callCount = callCount + 1; 59 | // }; 60 | 61 | // f(); 62 | // assert.sameValue(callCount, 1, 'arrow function invoked exactly once'); 63 | 64 | // const xxx = { 65 | // get x(): number { 66 | // return 1; 67 | // } 68 | // } 69 | 70 | // const xxx1 = { 71 | // get x() { 72 | // return 1; 73 | // } 74 | // } 75 | 76 | class X { 77 | async * f() { 78 | return 1; 79 | } 80 | } 81 | 82 | const x = { 83 | async * [Symbol.asyncIterator]() { 84 | for (const c of []) { 85 | yield c; 86 | } 87 | }, 88 | }; 89 | 90 | function * f() { 91 | yield 1; 92 | yield 2; 93 | } 94 | 95 | 96 | 97 | // const _createListener = (): Deno.Listener => { 98 | // const rid = _genRid(); 99 | // const connections: Deno.Conn[] = []; 100 | // return { 101 | // rid, 102 | // /** Waits for and resolves to the next connection to the `Listener`. */ 103 | // async accept(): Promise { 104 | // return null as any; 105 | // }, 106 | // /** Close closes the listener. Any pending accept promises will be rejected 107 | // * with errors. */ 108 | // close() { 109 | // _deleteResource(rid); 110 | // }, 111 | // addr: { 112 | // transport: "tcp", 113 | // hostname: "0.0.0.0", 114 | // port: 1400, 115 | // }, 116 | // async *[Symbol.asyncIterator]() { 117 | // for (const c of connections) { 118 | // yield c; 119 | // } 120 | // }, 121 | // }; 122 | // }; 123 | -------------------------------------------------------------------------------- /packages/mints/benchmark/cases/example0.ts: -------------------------------------------------------------------------------- 1 | // , b: number[], c: Array; 2 | const x: number = 1; 3 | 4 | let a, b, c; 5 | 6 | function square(x: number): number { 7 | return x ** 2; 8 | } 9 | 10 | square(2); 11 | 12 | type IPoint = { 13 | x: number; 14 | y: number; 15 | }; 16 | 17 | if (1) { 18 | 1; 19 | } else { 20 | while (false) {} 21 | } 22 | 23 | class Point implements Object { 24 | public x: number; 25 | private y: number; 26 | constructor(x: number, y: number) { 27 | this.x = x; 28 | this.y = y; 29 | } 30 | public static async foo(arg: number): Promise { 31 | return arg; 32 | } 33 | } 34 | 35 | const p = new Point(1, 2); 36 | 37 | console.log(p.x); 38 | 39 | func(); 40 | 41 | export { x, x as y, p }; 42 | 43 | export const v = 1; 44 | 45 | export class Foo { 46 | x: number = 1; 47 | } 48 | 49 | console.log("aaa"); 50 | 51 | const el = document.querySelector("#app"); 52 | 53 | console.log("el", el); 54 | 55 | switch (1 as number) { 56 | case 1: 57 | case 2: { 58 | break; 59 | } 60 | default: { 61 | console.log(!!1); 62 | } 63 | } 64 | 65 | import { 66 | OPERATORS, 67 | RESERVED_WORDS, 68 | REST_SPREAD, 69 | SPACE_REQUIRED_OPERATORS, 70 | _ as _w, 71 | __ as __w, 72 | } from "../../src/constants"; 73 | 74 | declare const foo: any; 75 | 76 | import { RootCompiler, RootParser } from "@mizchi/pargen/src/types"; 77 | import { createContext } from "@mizchi/pargen/src"; 78 | import { preprocessLight } from "../../src/preprocess"; 79 | const { compile } = createContext({ 80 | // composeTokens: true, 81 | // pairs: ["{", "}"], 82 | }); 83 | 84 | const compileWithPreprocess: RootCompiler = (input, opts) => { 85 | const parser = compile(input, opts); 86 | const newParser: RootParser = (input, ctx) => { 87 | const pre = preprocessLight(input); 88 | const ret = parser(pre, ctx); 89 | return ret; 90 | }; 91 | return newParser; 92 | }; 93 | 94 | export { compileWithPreprocess as compile }; 95 | 96 | interface X {} 97 | 98 | export function escapeWhistespace(input: string) { 99 | return input.replace(/[ ]{1,}/gmu, (text) => `@W${text.length}}`); 100 | } 101 | 102 | export function restoreEscaped(input: string, literals: Map) { 103 | return input.replace(/@(W|L|N)(\d+)\}/, (full, type, $2) => {}); 104 | } 105 | 106 | export function main() { 107 | const compilers = [compileTsc, compileMints]; 108 | for (const compiler of compilers) { 109 | for (let i = 0; i < 3; i++) { 110 | const now = Date.now(); 111 | const out = compiler(code); 112 | console.log(compiler.name, `[${i}]`, Date.now() - now); 113 | printPerfResult(); 114 | console.log("raw:", out); 115 | console.log("----"); 116 | console.log(prettier.format(out, { parser: "typescript" })); 117 | } 118 | } 119 | } 120 | main(); 121 | 122 | class _Point { 123 | private z: Num = 0; 124 | constructor(private x: Num, private y: Num) {} 125 | } 126 | 127 | 128 | class X { 129 | async * f() { 130 | return 1; 131 | } 132 | } 133 | 134 | const itr = { 135 | async * [Symbol.asyncIterator]() { 136 | for (const c of []) { 137 | yield c; 138 | } 139 | }, 140 | }; 141 | 142 | function * f() { 143 | yield 1; 144 | yield 2; 145 | } 146 | 147 | type Unpacked = T extends (infer U)[] 148 | ? U 149 | : T extends (...args: any[]) => infer U 150 | ? U 151 | : T extends Promise 152 | ? U 153 | : T; -------------------------------------------------------------------------------- /packages/mints/benchmark/cases/example1.ts: -------------------------------------------------------------------------------- 1 | import ts from "typescript"; 2 | import { ErrorType, ParseError } from "../../../pargen-tokenized/src/types"; 3 | import { preprocessLight } from "../../src/preprocess"; 4 | import prettier from "prettier"; 5 | 6 | function compileTsc(input: string) { 7 | return ts.transpile(input, { 8 | module: ts.ModuleKind.ESNext, 9 | target: ts.ScriptTarget.Latest, 10 | }); 11 | } 12 | 13 | const _format = (input: string, format: boolean, stripTypes: boolean) => { 14 | input = stripTypes ? compileTsc(input) : input; 15 | return format ? prettier.format(input, { parser: "typescript" }) : input; 16 | }; 17 | 18 | export const expectSame = ( 19 | parse: any, 20 | inputs: string[] 21 | // { 22 | // format = true, 23 | // stripTypes = true, 24 | // }: { format?: boolean; stripTypes?: boolean } = {} 25 | ) => { 26 | inputs.forEach((raw) => { 27 | const input = preprocessLight(raw); 28 | const result = parse(input); 29 | if (result.error) { 30 | formatError(input, result); 31 | throw new Error("Unexpected Result:" + input); 32 | } else { 33 | const resultf = format 34 | ? _format(result.result as string, format, stripTypes) 35 | : result.result; 36 | const expectedf = format ? _format(input, format, stripTypes) : input; 37 | if (resultf !== expectedf) { 38 | throw `Expect: ${input}\nOutput: ${JSON.stringify(result, null, 2)}`; 39 | } 40 | } 41 | }); 42 | }; 43 | 44 | export const expectError = (parse: any, inputs: string[]) => { 45 | inputs.forEach((input) => { 46 | const result = parse(preprocessLight(input)); 47 | if (!result.error) { 48 | throw new Error("Unexpected SameResult:" + result); 49 | } 50 | }); 51 | }; 52 | 53 | export function formatError(input: string, error: ParseError) { 54 | const deepError = findMaxPosError(error, error); 55 | console.log("max depth", deepError.pos); 56 | _formatError(input, deepError); 57 | } 58 | 59 | export function findMaxPosError( 60 | error: ParseError, 61 | currentError: ParseError 62 | ): ParseError { 63 | currentError = error.pos > currentError.pos ? error : currentError; 64 | 65 | if (error.code === ErrorType.Seq_Stop) { 66 | currentError = findMaxPosError(error.detail.child, currentError); 67 | } 68 | 69 | if (error.code === ErrorType.Or_UnmatchAll) { 70 | for (const e of error.detail.children) { 71 | currentError = findMaxPosError(e, currentError); 72 | } 73 | } 74 | return currentError; 75 | } 76 | 77 | function _formatError(input: string, error: ParseError, depth: number = 0) { 78 | if (depth === 0) { 79 | console.error("[parse:fail]", error.pos); 80 | } 81 | const prefix = " ".repeat(depth * 2); 82 | console.log( 83 | prefix, 84 | `${ErrorType?.[error.code]}[${error.pos}]`, 85 | `<$>`, 86 | input.substr(error.pos).split("\n")[0] + " ..." 87 | ); 88 | if (error.code === ErrorType.Token_Unmatch && error.detail) { 89 | console.log(prefix, ">", error.detail); 90 | } 91 | if (error.code === ErrorType.Not_IncorrectMatch) { 92 | console.log(prefix, "matche", error); 93 | } 94 | if (error.code === ErrorType.Seq_Stop) { 95 | _formatError(input, error.detail.child, depth + 1); 96 | } 97 | if (error.code === ErrorType.Or_UnmatchAll) { 98 | for (const e of error.detail.children) { 99 | _formatError(input, e, depth + 1); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /packages/mints/benchmark/cases/example2.ts: -------------------------------------------------------------------------------- 1 | import type { IPromisesAPI } from "memfs/lib/promises"; 2 | import { Volume } from "memfs"; 3 | import createFs from "memfs/lib/promises"; 4 | 5 | import path from "path"; 6 | import { analyzeModule } from "./analyzer"; 7 | import { parse } from "./parser"; 8 | import type { BundleOptions, AnalyzedChunk, ImportMap } from "./types"; 9 | import { ModulesMap } from "./types"; 10 | import { optimize } from "./optimizer"; 11 | import { render } from "./renderer"; 12 | import { filepathToFlatSymbol } from "./helpers"; 13 | 14 | const defaultImportMap = { 15 | imports: {}, 16 | }; 17 | 18 | export class Bundler { 19 | private modulesMap = new Map(); 20 | public fs: IPromisesAPI; 21 | public importMap: ImportMap = defaultImportMap; 22 | // { 23 | // this.fs = createMemoryFs(files); 24 | // } 25 | public async bundle( 26 | entry: string, 27 | { exposeToGlobal = null, optimize: _optimize = true }: BundleOptions 28 | ) { 29 | await this.addModule(entry); 30 | const chunks = aggregateChunks(this.modulesMap, entry); 31 | const optimizedChunks = _optimize 32 | ? optimize(chunks, entry, this.importMap) 33 | : chunks; 34 | return render(entry, optimizedChunks, { 35 | exposeToGlobal, 36 | transformDynamicImport: false, 37 | }); 38 | } 39 | public async bundleChunks( 40 | entry: string 41 | // {} : {} = {} 42 | ) { 43 | if (builtChunks.find((c) => c.entry === entry)) { 44 | return; 45 | } 46 | await this.addModule(entry); 47 | const chunks = aggregateChunks(this.modulesMap, entry); 48 | const optimizedChunks = _optimize 49 | ? optimize(chunks, entry, this.importMap) 50 | : chunks; 51 | const built = render(entry, optimizedChunks, { 52 | exposeToGlobal: exposeToGlobal, 53 | transformDynamicImport: true, 54 | publicPath, 55 | }); 56 | if (root) { 57 | builtChunks.push({ 58 | type: "entry", 59 | entry, 60 | builtCode: built, 61 | }); 62 | } else { 63 | builtChunks.push({ 64 | type: "chunk", 65 | entry, 66 | chunkName: filepathToFlatSymbol(entry, publicPath), 67 | builtCode: built, 68 | }); 69 | } 70 | const dynamicImports = optimizedChunks.map((c) => c.dynamicImports).flat(); 71 | dynamicImports.forEach((i) => { 72 | this.bundleChunks( 73 | i.filepath, 74 | { 75 | exposeToGlobal: null, 76 | optimize: _optimize, 77 | root: false, 78 | publicPath, 79 | }, 80 | builtChunks 81 | ); 82 | }); 83 | console.log("bundle", dynamicImports); 84 | const workerSources = optimizedChunks.map((c) => c.workerSources).flat(); 85 | workerSources.forEach((i) => { 86 | this.bundleChunks( 87 | i.filepath, 88 | { 89 | exposeToGlobal: null, 90 | optimize: _optimize, 91 | root: false, 92 | publicPath, 93 | }, 94 | builtChunks 95 | ); 96 | }); 97 | return builtChunks; 98 | } 99 | public async updateModule(filepath: string, nextContent: string) { 100 | await this.fs.writeFile(filepath, nextContent); 101 | this.modulesMap.delete(filepath); 102 | this.addModule(filepath); 103 | } 104 | 105 | // // // TODO: need this? 106 | async deleteRecursive(filepath: string) { 107 | const cached = this.modulesMap.get(filepath)!; 108 | if (cached) { 109 | for (const i of cached.imports) { 110 | this.deleteRecursive(i.filepath); 111 | this.modulesMap.delete(i.filepath); 112 | } 113 | } 114 | } 115 | private async addModule(filepath: string): Promise { 116 | console.log("add module", filepath); 117 | if (this.modulesMap.has(filepath)) { 118 | return; 119 | } 120 | const basepath = path.dirname(filepath); 121 | const raw = await readFile(this.fs, filepath); 122 | const ast = parse(raw, filepath); 123 | const analyzed = analyzeModule(ast, basepath, this.importMap); 124 | this.modulesMap.set(filepath, { 125 | ...analyzed, 126 | raw, 127 | filepath, 128 | ast, 129 | }); 130 | console.log("used", filepath, JSON.stringify(imports, null, 2)); 131 | for (const i of analyzed.imports) { 132 | await this.addModule(i.filepath); 133 | } 134 | for (const di of analyzed.dynamicImports) { 135 | await this.addModule(di.filepath); 136 | } 137 | for (const w of analyzed.workerSources) { 138 | await this.addModule(w.filepath); 139 | } 140 | } 141 | } 142 | 143 | export function aggregateChunks(modulesMap: ModulesMap, entryPath: string) { 144 | const entryMod = modulesMap.get(entryPath)!; 145 | const chunks: AnalyzedChunk[] = []; 146 | _aggregate(entryMod); 147 | return chunks; 148 | function _aggregate(mod: AnalyzedChunk) { 149 | if (chunks.find((x) => x.filepath === mod.filepath)) { 150 | return chunks; 151 | } 152 | for (const imp of mod.imports) { 153 | if (chunks.find((x) => x.filepath === imp.filepath)) { 154 | continue; 155 | } 156 | const child = modulesMap.get(imp.filepath)!; 157 | _aggregate(child); 158 | } 159 | for (const dimp of mod.dynamicImports) { 160 | if (chunks.find((x) => x.filepath === dimp.filepath)) { 161 | continue; 162 | } 163 | const child = modulesMap.get(dimp.filepath)!; 164 | _aggregate(child); 165 | } 166 | chunks.push(mod); 167 | return chunks; 168 | } 169 | } 170 | 171 | // helper 172 | export function createMemoryFs(): IPromisesAPI { 173 | // files: { [k: string]: string } 174 | const vol = Volume.fromJSON(files, "/"); 175 | return createFs(vol) as IPromisesAPI; 176 | } 177 | 178 | export async function readFile(fs: IPromisesAPI, filepath: string) { 179 | const raw = (await fs.readFile(filepath, { 180 | encoding: "utf-8", 181 | })) as string; 182 | return raw; 183 | } 184 | -------------------------------------------------------------------------------- /packages/mints/benchmark/cases/example5.tsx: -------------------------------------------------------------------------------- 1 | /* @jsx h */ 2 | import { h, render } from "preact"; 3 | import { useState, useEffect, useCallback, useRef } from "preact/hooks"; 4 | // import { wrap } from "comlink"; 5 | // import type { TokenizerApi } from "./tokenize-worker"; 6 | // @ts-ignore 7 | import { transformSync } from "@mizchi/mints/dist/index.js"; 8 | // @ts-ignore 9 | // import TokenizeWorker from "./tokenize-worker?worker"; 10 | 11 | // const api = wrap(new TokenizeWorker()); 12 | 13 | console.time("ui"); 14 | console.time("first-compile"); 15 | 16 | const initialCode = `hello`; 17 | 18 | let timeout: any = null; 19 | let firstCompileDone = false; 20 | 21 | function App() { 22 | const [code, setCode] = useState(initialCode); 23 | const [output, setOutput] = useState(""); 24 | const [buildTime, setBuildTime] = useState(0); 25 | const ref = useRef(null); 26 | useEffect(() => { 27 | try { 28 | if (timeout) clearTimeout(timeout); 29 | timeout = setTimeout( 30 | async () => { 31 | timeout = null; 32 | const now = Date.now(); 33 | // const out = await api.transform(code); 34 | const out = await transformSync(code); 35 | // @ts-ignore 36 | if (out.error) { 37 | setOutput(JSON.stringify(out, null, 2)); 38 | } else { 39 | setBuildTime(Date.now() - now); 40 | setOutput(out); 41 | if (!firstCompileDone) { 42 | firstCompileDone = true; 43 | console.timeEnd("first-compile"); 44 | console.log("first compile ends at", performance.now()); 45 | } 46 | } 47 | }, 48 | firstCompileDone ? 400 : 0 49 | ); 50 | } catch (err) { 51 | console.error(err); 52 | } 53 | }, [code, setCode, setOutput, setBuildTime]); 54 | const onClickRun = useCallback(() => { 55 | if (ref.current == null) return; 56 | const encoded = btoa(unescape(encodeURIComponent(output))); 57 | const blob = new Blob( 58 | [ 59 | ` 60 | 61 | 62 | 63 | 64 | 68 | 69 | `, 70 | ], 71 | { type: "text/html" } 72 | ); 73 | ref.current.src = URL.createObjectURL(blob); 74 | }, [output, ref]); 75 | return ( 76 |
77 |
85 |
86 |

Mints Playground

87 |
88 | by{" "} 89 | 90 | @mizchi 91 | 92 |
93 |
94 |
95 |