From b748ee4fc00904d52798d256c4d91eaae4311a55 Mon Sep 17 00:00:00 2001 From: Ethan Davidson <31261035+EthanThatOneKid@users.noreply.github.com> Date: Wed, 22 Dec 2021 17:13:53 -0800 Subject: [PATCH] progress --- lib/repl/repl.ts | 5 - lib/transpile/mod.ts | 1 + .../text_builder/text_builder.test.ts | 92 ++++++++++++++++++- lib/transpile/text_builder/text_builder.ts | 44 +++++++-- lib/transpile/transpile.test.ts | 37 +++++--- lib/transpile/transpile.ts | 73 +++++++-------- lib/transpile/utils.ts | 4 +- 7 files changed, 185 insertions(+), 71 deletions(-) delete mode 100644 lib/repl/repl.ts diff --git a/lib/repl/repl.ts b/lib/repl/repl.ts deleted file mode 100644 index f80f942..0000000 --- a/lib/repl/repl.ts +++ /dev/null @@ -1,5 +0,0 @@ -// TODO(@ethanthatonekid): create a repl around the Fart transpiler -export class FartRepl { - constructor() { - } -} diff --git a/lib/transpile/mod.ts b/lib/transpile/mod.ts index e69de29..57534ec 100644 --- a/lib/transpile/mod.ts +++ b/lib/transpile/mod.ts @@ -0,0 +1 @@ +export { transpile } from "./transpile.ts"; diff --git a/lib/transpile/text_builder/text_builder.test.ts b/lib/transpile/text_builder/text_builder.test.ts index 848bb8d..b4ce8d6 100644 --- a/lib/transpile/text_builder/text_builder.test.ts +++ b/lib/transpile/text_builder/text_builder.test.ts @@ -1,9 +1,99 @@ import { assertEquals } from "../../../deps/std/testing.ts"; import { TextBuilder } from "./text_builder.ts"; -import { Cartridge } from "../cartridge/mod.ts"; +import { Cartridge, CartridgeEvent } from "../cartridge/mod.ts"; +import { T } from "../tokenize/mod.ts"; Deno.test("text builder exports an empty string when nothing is appended", () => { const cartridge = new Cartridge(); const builder = new TextBuilder(cartridge); assertEquals(builder.export(), ""); }); + +Deno.test("text builder appends file_start event", async () => { + const cartridge = new Cartridge(); + cartridge.on(CartridgeEvent.FileStart, () => "ABC"); + const builder = new TextBuilder(cartridge); + await builder.append(CartridgeEvent.FileStart); + assertEquals(builder.export(), "ABC"); +}); + +Deno.test("text builder appends inline_comment event", async () => { + const cartridge = new Cartridge(); + cartridge.on(CartridgeEvent.InlineComment, () => "ABC"); + const builder = new TextBuilder(cartridge); + await builder.append( + CartridgeEvent.InlineComment, + [T.comment("; Example", 1, 1)], + [], + ); + assertEquals(builder.export(), "ABC"); +}); + +Deno.test("text builder appends multiline_comment event", async () => { + const cartridge = new Cartridge(); + cartridge.on(CartridgeEvent.MultilineComment, () => "ABC"); + const builder = new TextBuilder(cartridge); + await builder.append( + CartridgeEvent.MultilineComment, + [T.multiline_comment( + `/* + This is a multiline comment! +*/`, + 1, + 1, + )], + [], + ); + assertEquals(builder.export(), "ABC"); +}); + +Deno.test("text builder appends load event", async () => { + const cartridge = new Cartridge(); + cartridge.on(CartridgeEvent.Load, () => "ABC"); + const builder = new TextBuilder(cartridge); + await builder.append( + CartridgeEvent.Load, + /* tokens=*/ [], + /* comments=*/ [], + /* always undefined=*/ undefined, + /* src=*/ "", + /* dep1=*/ "", + ); + assertEquals(builder.export(), "ABC"); +}); + +Deno.test("text builder appends struct_open event", async () => { + const cartridge = new Cartridge(); + cartridge.on(CartridgeEvent.StructOpen, () => "ABC"); + const builder = new TextBuilder(cartridge); + await builder.append(CartridgeEvent.StructOpen, [], []); + assertEquals(builder.export(), "ABC"); +}); + +Deno.test("text builder appends set_property event", async () => { + const cartridge = new Cartridge(); + cartridge.on(CartridgeEvent.SetProperty, () => "ABC"); + const builder = new TextBuilder(cartridge); + await builder.append(CartridgeEvent.SetProperty, [], []); + assertEquals(builder.export(), "ABC"); +}); + +Deno.test("text builder appends struct_close event", async () => { + const cartridge = new Cartridge(); + cartridge.on(CartridgeEvent.StructClose, () => "ABC"); + const builder = new TextBuilder(cartridge); + await builder.append( + CartridgeEvent.StructClose, + [T.denest(1, 1)], + [], + ); + assertEquals(builder.export(), "ABC"); +}); + +Deno.test("text builder appends file_end event", async () => { + const cartridge = new Cartridge(); + cartridge.on(CartridgeEvent.FileEnd, () => "ABC"); + const builder = new TextBuilder(cartridge); + await builder.append(CartridgeEvent.FileEnd, [], []); + assertEquals(builder.export(), "ABC"); +}); diff --git a/lib/transpile/text_builder/text_builder.ts b/lib/transpile/text_builder/text_builder.ts index b1e30d1..1553344 100644 --- a/lib/transpile/text_builder/text_builder.ts +++ b/lib/transpile/text_builder/text_builder.ts @@ -5,7 +5,6 @@ import { CartridgeEvent, PropertyDefinition, } from "../cartridge/mod.ts"; -import { Lexicon } from "../tokenize/mod.ts"; import type { Token } from "../tokenize/mod.ts"; import { makeFileEndEventContext, @@ -17,22 +16,34 @@ import { makeStructCloseEventContext, makeStructOpenEventContext, } from "./utils.ts"; -import { assertKind } from "../utils.ts"; +// import { assertKind } from "../utils.ts"; export class TextBuilder { private blocks: CodeBlock[]; private currentBlock: CodeBlock; private indentLevel: number; + constructor(private cartridge: Cartridge) { this.blocks = []; this.currentBlock = new CodeBlock(); this.indentLevel = 0; } + /** + * _stash_ away the current code block into the list of + * code blocks ready to be exported. + */ + private stash() { + if (this.currentBlock.code.length > 0) { + this.blocks.push(this.currentBlock); + this.currentBlock = new CodeBlock(); + } + } + public async append( event: CartridgeEvent.FileStart, - tokens: Token[], - comments: Token[], + tokens?: Token[], + comments?: Token[], ): Promise; public async append( event: CartridgeEvent.InlineComment, @@ -56,6 +67,7 @@ export class TextBuilder { event: CartridgeEvent.StructOpen, tokens: Token[], comments: Token[], + value?: PropertyDefinition, ): Promise; public async append( event: CartridgeEvent.SetProperty, @@ -74,12 +86,13 @@ export class TextBuilder { ): Promise; public async append( event: CartridgeEvent, - tokens: Token[], + tokens: Token[] = [], comments: Token[] = [], value?: PropertyDefinition, ...rest: string[] ): Promise { let code: string | void | null; + switch (event) { case CartridgeEvent.FileStart: { code = await this.cartridge.dispatch( @@ -88,6 +101,7 @@ export class TextBuilder { ); break; } + case CartridgeEvent.InlineComment: { code = await this.cartridge.dispatch( CartridgeEvent.InlineComment, @@ -95,6 +109,7 @@ export class TextBuilder { ); break; } + case CartridgeEvent.MultilineComment: { code = await this.cartridge.dispatch( CartridgeEvent.MultilineComment, @@ -102,6 +117,7 @@ export class TextBuilder { ); break; } + case CartridgeEvent.Load: { const [source, ...dependencies] = rest; code = await this.cartridge.dispatch( @@ -116,14 +132,21 @@ export class TextBuilder { ); break; } + case CartridgeEvent.StructOpen: { - const { value: name } = assertKind(tokens[1], Lexicon.Identifier); code = await this.cartridge.dispatch( CartridgeEvent.StructOpen, - makeStructOpenEventContext(this.currentBlock, tokens, comments, name), + makeStructOpenEventContext( + this.currentBlock, + tokens, + comments, + value?.value, + ), ); + this.indentLevel++; break; } + case CartridgeEvent.SetProperty: { const [name] = rest; code = await this.cartridge.dispatch( @@ -138,13 +161,16 @@ export class TextBuilder { ); break; } + case CartridgeEvent.StructClose: { + if (--this.indentLevel === 0) this.stash(); code = await this.cartridge.dispatch( - CartridgeEvent.StructOpen, + CartridgeEvent.StructClose, makeStructCloseEventContext(this.currentBlock, tokens), ); break; } + case CartridgeEvent.FileEnd: { code = await this.cartridge.dispatch( CartridgeEvent.FileEnd, @@ -153,12 +179,14 @@ export class TextBuilder { break; } } + if (typeof code === "string") { this.currentBlock.append(code); } } export(indent: IndentOption = Indent.Space2): string { + this.stash(); return CodeBlock.join(indent, ...this.blocks); } } diff --git a/lib/transpile/transpile.test.ts b/lib/transpile/transpile.test.ts index dc18202..0069d09 100644 --- a/lib/transpile/transpile.test.ts +++ b/lib/transpile/transpile.test.ts @@ -1,19 +1,28 @@ import { assertEquals } from "../../deps/std/testing.ts"; -import { transpile } from "./transpile.ts"; +import { TranspilationContext, transpile } from "./transpile.ts"; import { Cartridge, CartridgeEvent } from "./cartridge/mod.ts"; import type { CartridgeEventContext } from "./cartridge/mod.ts"; -// import type { FartOptions } from "./transpile.ts"; +import { TextBuilder } from "./text_builder/mod.ts"; +import { tokenize } from "./tokenize/mod.ts"; -Deno.test("empty string results in empty string", async () => { - const fakeCart = new Cartridge(); - fakeCart.on( - CartridgeEvent.StructOpen, - (event: CartridgeEventContext) => { - assertEquals(event.data.name, "Example"); - console.log({ event }); - return ""; - }, - ); - const result = await transpile(`type Example {`, fakeCart); - assertEquals(result, ""); +Deno.test("create transpilation context without crashing", () => { + const iterator = tokenize(""); + const cartridge = new Cartridge(); + const builder = new TextBuilder(cartridge); + const ctx = new TranspilationContext(iterator, builder); + assertEquals(ctx.started, false); }); + +// Deno.test("transpiles struct_open event", async () => { +// const fakeCart = new Cartridge(); +// fakeCart.on( +// CartridgeEvent.StructOpen, +// (event: CartridgeEventContext) => { +// assertEquals(event.data.name, "Example"); +// assertEquals(event.data.comments, []); +// return "ABC"; +// }, +// ); +// const result = await transpile(`type Example {`, fakeCart); +// assertEquals(result, "ABC"); +// }); diff --git a/lib/transpile/transpile.ts b/lib/transpile/transpile.ts index 11c7bc0..09ecd90 100644 --- a/lib/transpile/transpile.ts +++ b/lib/transpile/transpile.ts @@ -18,30 +18,22 @@ export interface FartOptions { preserveComments: boolean; } -class TranspilationContext { +export class TranspilationContext { + public started = false; + public done = false; + public prevTokens: Token[] = []; + constructor( - public tokenizer?: FartTokenGenerator, - public builder?: TextBuilder, - public currentToken: IteratorResult | null = null, - public prevTokens: Token[] = [], + public tokenizer: FartTokenGenerator, + public builder: TextBuilder, ) {} - public nextToken(): Token | null { - if (this.tokenizer !== undefined) { - if ( - this.currentToken !== null && - this.prevTokens !== undefined && - this.currentToken !== undefined - ) { - this.prevTokens.push(this.currentToken.value); - } - const curr = this.tokenizer.next(); - if (curr.value !== undefined) { - this.currentToken = curr; - return curr.value; - } - } - return null; + public nextToken(): Token | undefined { + if (this.done) return undefined; + this.started = true; + const curr = this.tokenizer.next(); + if (curr.done) this.done = true; + return curr.value; } /** @@ -65,10 +57,11 @@ class TranspilationContext { * - indentation: number; * - preserveComments: boolean; */ -export function transpile( +export async function transpile( code: string, options: Cartridge | FartOptions, ): Promise { + // return Promise.resolve(""); // const srcLang = (options as FartOptions).sourceLanguage ?? Lang.Fart; // const targetLang = (options as FartOptions).sourceLanguage ?? Lang.TypeScript; // const indentation: number | undefined = (options as FartOptions).indentation; @@ -76,16 +69,17 @@ export function transpile( const cartridge = options instanceof Cartridge ? options : options.codeCartridge; + const builder = new TextBuilder(cartridge); const ctx = new TranspilationContext(tokenize(code), builder); - do { - switch (ctx.nextToken()?.kind) { + for ( + const token = ctx.nextToken(); + !ctx.done; + ) { + switch (token?.kind) { case Lexicon.Load: { - const loader = assertKind( - ctx.currentToken?.value as Token, - Lexicon.Load, - ); + const loader = assertKind(token, Lexicon.Load); const source = assertKind(ctx.nextToken(), Lexicon.TextLiteral); const opener = assertKind(ctx.nextToken(), Lexicon.StructOpener); console.log({ loader, source, opener }); @@ -99,23 +93,20 @@ export function transpile( } case Lexicon.TypeDefiner: { - const definer = assertKind( - ctx.currentToken?.value as Token, - Lexicon.TypeDefiner, - ); + const definer = assertKind(token, Lexicon.TypeDefiner); const ident = assertKind(ctx.nextToken(), Lexicon.Identifier); const opener = assertKind(ctx.nextToken(), Lexicon.StructOpener); - console.log({ definer, ident, opener }); - // await ctx.builder?.append( - // CartridgeEvent.StructOpen, - // [definer, ident, opener], - // [], - // ); - // await ctx.nextStruct(); + await ctx.builder.append( + CartridgeEvent.StructOpen, + [definer, ident, opener], + /* comments=*/ [], + { value: ident.value }, + ); + await ctx.nextStruct(); break; } } - } while (!ctx.currentToken?.done); + } - return Promise.resolve(ctx.builder?.export() ?? ""); + return ctx.builder.export(); } diff --git a/lib/transpile/utils.ts b/lib/transpile/utils.ts index 1175f39..dc83242 100644 --- a/lib/transpile/utils.ts +++ b/lib/transpile/utils.ts @@ -3,8 +3,8 @@ import { Lexicon, Token } from "./tokenize/mod.ts"; /** * @todo write tests in utils.test.ts */ -export function assertKind(token: Token | null, lexeme: Lexicon): Token { - if (token === null || token.kind !== lexeme) { +export function assertKind(token: Token | undefined, lexeme: Lexicon): Token { + if (token === undefined || token.kind !== lexeme) { throw new Error( `Expected token kind ${Lexicon[lexeme]}, got ${token}`, );