UNPKG

derw

Version:

An Elm-inspired language that transpiles to TypeScript

641 lines (588 loc) 19.1 kB
import { isTestFile } from "./Utils"; import * as List from "./stdlib/List"; import { BlockKinds, Block, TypedBlock, Module } from "./types"; export { blockKind }; export { createUnparsedBlock }; export { intoBlocks }; export { typeBlocks }; export { exportTests }; type Validator = { test: (arg0: string) => boolean; blockKind: BlockKinds; } function Validator(args: { test: (arg0: string) => boolean, blockKind: BlockKinds }): Validator { return { ...args, }; } type Ok<value> = { kind: "Ok"; value: value; }; function Ok<value>(args: { value: value }): Ok<value> { return { kind: "Ok", ...args, }; } type Err<error> = { kind: "Err"; error: error; }; function Err<error>(args: { error: error }): Err<error> { return { kind: "Err", ...args, }; } type Result<error, value> = Ok<value> | Err<error>; type ImportBlock = { kind: "ImportBlock"; lineStart: number; lines: string[]; }; function ImportBlock(args: { lineStart: number, lines: string[] }): ImportBlock { return { kind: "ImportBlock", ...args, }; } type ExportBlock = { kind: "ExportBlock"; lineStart: number; lines: string[]; }; function ExportBlock(args: { lineStart: number, lines: string[] }): ExportBlock { return { kind: "ExportBlock", ...args, }; } type UnionTypeBlock = { kind: "UnionTypeBlock"; lineStart: number; lines: string[]; }; function UnionTypeBlock(args: { lineStart: number, lines: string[] }): UnionTypeBlock { return { kind: "UnionTypeBlock", ...args, }; } type UnionUntaggedTypeBlock = { kind: "UnionUntaggedTypeBlock"; lineStart: number; lines: string[]; }; function UnionUntaggedTypeBlock(args: { lineStart: number, lines: string[] }): UnionUntaggedTypeBlock { return { kind: "UnionUntaggedTypeBlock", ...args, }; } type TypeAliasBlock = { kind: "TypeAliasBlock"; lineStart: number; lines: string[]; }; function TypeAliasBlock(args: { lineStart: number, lines: string[] }): TypeAliasBlock { return { kind: "TypeAliasBlock", ...args, }; } type TypeclassBlock = { kind: "TypeclassBlock"; lineStart: number; lines: string[]; }; function TypeclassBlock(args: { lineStart: number, lines: string[] }): TypeclassBlock { return { kind: "TypeclassBlock", ...args, }; } type ImplBlock = { kind: "ImplBlock"; lineStart: number; lines: string[]; }; function ImplBlock(args: { lineStart: number, lines: string[] }): ImplBlock { return { kind: "ImplBlock", ...args, }; } type FunctionBlock = { kind: "FunctionBlock"; lineStart: number; lines: string[]; }; function FunctionBlock(args: { lineStart: number, lines: string[] }): FunctionBlock { return { kind: "FunctionBlock", ...args, }; } type ConstBlock = { kind: "ConstBlock"; lineStart: number; lines: string[]; }; function ConstBlock(args: { lineStart: number, lines: string[] }): ConstBlock { return { kind: "ConstBlock", ...args, }; } type CommentBlock = { kind: "CommentBlock"; lineStart: number; lines: string[]; }; function CommentBlock(args: { lineStart: number, lines: string[] }): CommentBlock { return { kind: "CommentBlock", ...args, }; } type MultilineCommentBlock = { kind: "MultilineCommentBlock"; lineStart: number; lines: string[]; }; function MultilineCommentBlock(args: { lineStart: number, lines: string[] }): MultilineCommentBlock { return { kind: "MultilineCommentBlock", ...args, }; } type UnknownBlock = { kind: "UnknownBlock"; lineStart: number; lines: string[]; }; function UnknownBlock(args: { lineStart: number, lines: string[] }): UnknownBlock { return { kind: "UnknownBlock", ...args, }; } type UnparsedBlock = ImportBlock | ExportBlock | UnionTypeBlock | UnionUntaggedTypeBlock | TypeAliasBlock | TypeclassBlock | ImplBlock | FunctionBlock | ConstBlock | CommentBlock | MultilineCommentBlock | UnknownBlock; function hasTypeLine(block: string): boolean { const _res1328002030 = block.split(":"); switch (_res1328002030.length) { case _res1328002030.length: { if (_res1328002030.length === 1) { const [ x ] = _res1328002030; return false; } } case _res1328002030.length: { if (_res1328002030.length >= 1) { const [ x, ...xs ] = _res1328002030; const trimmed: string = x.trim(); const split: string[] = trimmed.split(" "); const length: number = split.length; return length === 1; } } default: { return false; } } } function isAFunction(block: string): boolean { const _res1780524276 = block.split("\n"); switch (_res1780524276.length) { case _res1780524276.length: { if (_res1780524276.length >= 1) { const [ firstLine, ...xs ] = _res1780524276; return (function(y: any) { return y.length > 1; })(firstLine.split("->")); } } default: { return false; } } } const validators: Validator[] = [ { test: function(x: any) { return x.startsWith("--"); }, blockKind: "Comment" }, { test: function(x: any) { return x.startsWith("{-"); }, blockKind: "MultilineComment" }, { test: function(x: any) { return x.startsWith("type alias"); }, blockKind: "TypeAlias" }, { test: function(x: any) { return x.startsWith("typeclass "); }, blockKind: "Typeclass" }, { test: function(x: any) { return x.startsWith("impl "); }, blockKind: "Impl" }, { test: function(x: any) { return x.startsWith("type ") && x.includes(`"`); }, blockKind: "UnionUntaggedType" }, { test: function(x: any) { return x.startsWith("type "); }, blockKind: "UnionType" }, { test: function(x: any) { return x.startsWith(" ") || x.startsWith("}"); }, blockKind: "Indent" }, { test: function(x: any) { return x.startsWith("import"); }, blockKind: "Import" }, { test: function(x: any) { return x.startsWith("exposing"); }, blockKind: "Export" }, { test: function(x: any) { return hasTypeLine(x) && isAFunction(x); }, blockKind: "Function" }, { test: function(x: any) { return hasTypeLine(x); }, blockKind: "Const" }, { test: function(x: any) { return (function(y: any) { return y.length > 1; })(x.split("=")); }, blockKind: "Definition" } ]; function blockKindStep(block: string, validators: Validator[]): Result<string, BlockKinds> { switch (validators.length) { case validators.length: { if (validators.length >= 1) { const [ validator, ...ys ] = validators; if (validator.test(block)) { return Ok({ value: validator.blockKind }); } else { return blockKindStep(block, ys); }; } } default: { return Err({ error: "Unknown block type" }); } } } function blockKind(block: string): Result<string, BlockKinds> { return blockKindStep(block, validators); } function createUnparsedBlock(blockKind: BlockKinds, lineStart: number, lines: string[]): UnparsedBlock { switch (blockKind) { case "Import": { return ImportBlock({ lineStart, lines }); } case "Export": { return ExportBlock({ lineStart, lines }); } case "Const": { return ConstBlock({ lineStart, lines }); } case "Function": { return FunctionBlock({ lineStart, lines }); } case "UnionType": { return UnionTypeBlock({ lineStart, lines }); } case "UnionUntaggedType": { return UnionUntaggedTypeBlock({ lineStart, lines }); } case "TypeAlias": { return TypeAliasBlock({ lineStart, lines }); } case "Typeclass": { return TypeclassBlock({ lineStart, lines }); } case "Impl": { return ImplBlock({ lineStart, lines }); } case "Indent": { return UnknownBlock({ lineStart, lines }); } case "Definition": { return UnknownBlock({ lineStart, lines }); } case "Comment": { return CommentBlock({ lineStart, lines }); } case "MultilineComment": { return MultilineCommentBlock({ lineStart, lines }); } case "Unknown": { return UnknownBlock({ lineStart, lines }); } } } type IntoBlockInfo = { currentBlock: string[]; previousLine: string; currentBlockKind: Result<string, BlockKinds>; lineStart: number; } function IntoBlockInfo(args: { currentBlock: string[], previousLine: string, currentBlockKind: Result<string, BlockKinds>, lineStart: number }): IntoBlockInfo { return { ...args, }; } function stepMultilineComment(lineNumber: number, info: IntoBlockInfo, line: string, lines: string[]): UnparsedBlock[] { if (line === "-}") { const infoWithCurrentLine: IntoBlockInfo = { ...info, previousLine: line, currentBlock: List.append(info.currentBlock, [ line ]) }; const block: UnparsedBlock = createUnparsedBlock("MultilineComment", infoWithCurrentLine.lineStart, infoWithCurrentLine.currentBlock); const nextInfo: IntoBlockInfo = { currentBlock: [ ], previousLine: line, lineStart: lineNumber, currentBlockKind: Err({ error: "Nothing" }) }; return [ block, ...intoBlocksStep(lineNumber + 1, nextInfo, lines) ]; } else { const infoWithCurrentLine: IntoBlockInfo = { ...info, previousLine: line, currentBlock: List.append(info.currentBlock, [ line ]) }; return intoBlocksStep(lineNumber + 1, infoWithCurrentLine, lines); } } function indentOrDefinitionStep(lineNumber: number, info: IntoBlockInfo, line: string, lines: string[]): UnparsedBlock[] { const nextLines: string[] = lineNumber > 0 && info.previousLine.trim() === "" ? [ info.previousLine, line ] : [ line ]; const nextInfo: IntoBlockInfo = { ...info, currentBlock: List.append(info.currentBlock, nextLines), previousLine: line }; return intoBlocksStep(lineNumber + 1, nextInfo, lines); } function intoBlocksStep(lineNumber: number, info: IntoBlockInfo, lines: string[]): UnparsedBlock[] { switch (lines.length) { case lines.length: { if (lines.length >= 1) { const [ line, ...xs ] = lines; if (line.trim().length === 0) { const nextInfo: IntoBlockInfo = { ...info, previousLine: line }; return intoBlocksStep(lineNumber + 1, nextInfo, xs); } else { switch (info.currentBlock.length) { case 0: { const nextInfo: IntoBlockInfo = { previousLine: line, currentBlock: [ line ], lineStart: lineNumber, currentBlockKind: blockKind(line) }; return intoBlocksStep(lineNumber + 1, nextInfo, xs); } default: { const isInMultilineComment: boolean = info.currentBlockKind.kind === "Ok" && info.currentBlockKind.value === "MultilineComment"; if (isInMultilineComment) { return stepMultilineComment(lineNumber, info, line, xs); } else { const currentLineBlockKind: Result<string, BlockKinds> = blockKind(line); const isIndent: boolean = currentLineBlockKind.kind === "Ok" && currentLineBlockKind.value === "Indent"; const isDefinition: boolean = currentLineBlockKind.kind === "Ok" && currentLineBlockKind.value === "Definition"; if (isIndent || isDefinition) { return indentOrDefinitionStep(lineNumber, info, line, xs); } else { switch (info.currentBlockKind.kind) { case "Ok": { const { value } = info.currentBlockKind; const hasSpeech: boolean = (function(y: any) { return y.length > 0; })(info.currentBlock.filter(function(line: any) { return line.indexOf(`"`) > -1; })); const kind: BlockKinds = (function (): any { switch (value) { case "UnionType": { if (hasSpeech) { return "UnionUntaggedType"; } else { return value; }; } default: { return value; } } })(); const block: UnparsedBlock = createUnparsedBlock(kind, info.lineStart, info.currentBlock); const nextInfo: IntoBlockInfo = { previousLine: line, currentBlock: [ line ], lineStart: lineNumber, currentBlockKind: currentLineBlockKind }; return [ block, ...intoBlocksStep(lineNumber + 1, nextInfo, xs) ]; } case "Err": { const nextInfo: IntoBlockInfo = { ...info, previousLine: line, currentBlock: List.append(info.currentBlock, [ line ]) }; return intoBlocksStep(lineNumber + 1, nextInfo, xs); } }; }; }; } }; }; } } default: { if (info.currentBlock.length > 0) { switch (info.currentBlockKind.kind) { case "Ok": { const { value } = info.currentBlockKind; const hasSpeech: boolean = (function(y: any) { return y.length > 0; })(info.currentBlock.filter(function(line: any) { return line.indexOf(`"`) > -1; })); const kind: BlockKinds = (function (): any { switch (value) { case "UnionType": { if (hasSpeech) { return "UnionUntaggedType"; } else { return value; }; } default: { return value; } } })(); return [ createUnparsedBlock(kind, info.lineStart, info.currentBlock) ]; } case "Err": { return [ createUnparsedBlock("Unknown", info.lineStart, info.currentBlock) ]; } }; } else { return [ ]; }; } } } function intoBlocks(body: string): UnparsedBlock[] { const lines: string[] = body.split("\n"); return intoBlocksStep(0, { previousLine: "", currentBlock: [ ], currentBlockKind: Err({ error: "Nothing" }), lineStart: 0 }, lines); } function typeBlocks(blocks: Block[]): TypedBlock[] { return List.filter(function(block: any) { return block.kind === "UnionType" || block.kind === "TypeAlias" || block.kind === "UnionUntaggedType"; }, blocks); } type Export = { kind: "Export"; names: string[]; }; function Export(args: { names: string[] }): Export { return { kind: "Export", ...args, }; } type MyExport = Export; function exportTests(module: Module): MyExport { const isTest: boolean = isTestFile(module.name); const namesToExpose: string[] = isTest ? List.filter(function(name: any) { return name.startsWith("test") || name.startsWith("snapshot"); }, List.map(function(block: any) { return block.name; }, List.filter(function(block: any) { return block.kind === "Function" || block.kind === "Const"; }, module.body))) : [ ]; const exports: Export[] = List.filter(function(block: any) { return block.kind === "Export"; }, module.body); const exportNames: string[] = List.foldl(function(export_: any, allNames: any) { return List.append(export_.names, allNames); }, [ ], exports); const exposeWithoutDuplicates: string[] = List.filter(function(name: any) { return !exportNames.includes(name); }, namesToExpose); return Export({ names: exposeWithoutDuplicates }); }