Skip to content

Commit

Permalink
Codegen (#3)
Browse files Browse the repository at this point in the history
* Create codegen.ts

* codegen moment

* create lab for generating other labs

* generate codegen lab

* add titles to example notes
  • Loading branch information
EthanThatOneKid authored May 8, 2024
1 parent 0c67e38 commit e80f98d
Show file tree
Hide file tree
Showing 12 changed files with 262 additions and 45 deletions.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![GitHub Actions](https://github.com/FartLabs/labs/actions/workflows/check.yaml/badge.svg)](https://github.com/FartLabs/labs/actions/workflows/check.yaml)

🧪 Labs by [**@FartLabs**](https://github.com/FartLabs)
🧪 Labs by [FartLabs](https://github.com/FartLabs)

## Overview

Expand Down Expand Up @@ -36,8 +36,8 @@ myLab.execute("links.link", {

- ["Your First Lab"](https://github.com/FartLabs/labs/discussions/2) is an
example that demonstrates the basic concepts of Labs.
- [example.ts](./example.ts) contains a more advanced example showcasing the
convenience of composing Labs.
- [./example/example.ts](./example/example.ts) contains a more advanced example
showcasing the convenience of composing Labs.

## Concepts

Expand Down Expand Up @@ -88,6 +88,15 @@ Run `deno fmt` to format the code.

Run `deno lint` to lint the code.

### Maintenance

Run `deno task generate` to generate the generated code for the project.

### Example

Run `deno task example` to run the example,
[./example/example.ts](./example/example.ts).

---

Developed with ❤️ [**@FartLabs**](https://github.com/FartLabs)
18 changes: 18 additions & 0 deletions codegen/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { generateLab } from "./generate.ts";

await Deno.writeTextFile(
"./codegen/codegen_lab.ts",
generateLab({
name: "codegenLab",
labsImportSource: "labs/labs.ts",
procedures: [
{
name: "codegen.lab",
import: {
name: "generateLab",
source: "labs/codegen/generate.ts",
},
},
],
}),
);
6 changes: 6 additions & 0 deletions codegen/codegen_lab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Generated lab file. DO NOT MODIFY.
import { Lab } from "labs/labs.ts";
import { generateLab } from "labs/codegen/generate.ts"

export const codegenLab = new Lab()
.procedure("codegen.lab", generateLab);
88 changes: 88 additions & 0 deletions codegen/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { importsOf } from "./imports.ts";
import type {
ExtendDescriptor,
ImportName,
ImportSource,
LabDescriptor,
ProcedureDescriptor,
VariableDescriptor,
} from "./types.ts";

export const LABS_IMPORT_SOURCE = "./labs.ts";

export function generateLab(descriptor: LabDescriptor): string {
const instructions = generateInstructions(descriptor);
return `// Generated lab file. DO NOT MODIFY.
import { Lab } from "${descriptor.labsImportSource ?? LABS_IMPORT_SOURCE}";
${generateImports(importsOf(descriptor))}
export const ${descriptor.name} = new Lab()${
instructions.length === 0 ? "" : `\n${indent(...instructions)}`
};\n`;
}

export function generateImports(
imports: Map<ImportSource, Set<ImportName>>,
): string {
const generated = Array.from(imports)
.toSorted(([a], [b]) => a.localeCompare(b))
.map(([importSource, names]) => {
return generateImport(Array.from(names).toSorted(), importSource);
})
.join("\n");
if (generated.length === 0) {
return "";
}

return `${generated}\n`;
}

export function generateImport(names: string[], importSource: string): string {
return `import { ${names.join(", ")} } from "${importSource}"`;
}

export function generateInstructions(descriptor: LabDescriptor): string[] {
const instructions: string[] = [];
if (descriptor.extends) {
instructions.push(...generateExtends(descriptor.extends));
}

if (descriptor.variables) {
instructions.push(...generateVariables(descriptor.variables));
}

if (descriptor.procedures) {
instructions.push(...generateProcedures(descriptor.procedures));
}

return instructions;
}

export function generateExtends(descriptors: ExtendDescriptor[]): string[] {
return descriptors.map((descriptor) => generateExtend(descriptor));
}

export function generateExtend(descriptor: ExtendDescriptor): string {
return `.extend(${descriptor.import.name})`;
}

export function generateVariables(descriptors: VariableDescriptor[]): string[] {
return descriptors.map((descriptor) => generateVariable(descriptor));
}

export function generateVariable(descriptor: VariableDescriptor): string {
return `.variable("${descriptor.name}", ${descriptor.value})`;
}

export function generateProcedures(
descriptors: ProcedureDescriptor[],
): string[] {
return descriptors.map((descriptor) => generateProcedure(descriptor));
}

export function generateProcedure(descriptor: ProcedureDescriptor): string {
return `.procedure("${descriptor.name}", ${descriptor.import.name})`;
}

export function indent(...lines: string[]): string {
return lines.map((line) => ` ${line}`).join("\n");
}
24 changes: 24 additions & 0 deletions codegen/imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ImportName, ImportSource, LabDescriptor } from "./types.ts";

export function importsOf(
descriptor: LabDescriptor,
): Map<ImportSource, Set<ImportName>> {
const imports = new Map<ImportSource, Set<ImportName>>();
for (const extend of descriptor.extends ?? []) {
if (!imports.has(extend.import.source)) {
imports.set(extend.import.source, new Set());
}

imports.get(extend.import.source)!.add(extend.import.name);
}

for (const procedure of descriptor.procedures ?? []) {
if (!imports.has(procedure.import.source)) {
imports.set(procedure.import.source, new Set());
}

imports.get(procedure.import.source)!.add(procedure.import.name);
}

return imports;
}
29 changes: 29 additions & 0 deletions codegen/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export interface LabDescriptor {
name: string;
labsImportSource?: ImportSource;
extends?: ExtendDescriptor[];
variables?: VariableDescriptor[];
procedures?: ProcedureDescriptor[];
}

export interface VariableDescriptor {
name: string;
value: string;
}

export interface ExtendDescriptor {
import: Importable;
}

export interface ProcedureDescriptor {
name: string;
import: Importable;
}

export interface Importable {
name: ImportName;
source: ImportSource;
}

export type ImportSource = string;
export type ImportName = string;
9 changes: 9 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"imports": {
"labs/": "./"
},
"tasks": {
"generate": "deno run -Ar https://deno.land/x/generate/cli/main.ts gen.ts",
"example": "deno run ./example/example.ts"
}
}
42 changes: 0 additions & 42 deletions example.ts

This file was deleted.

13 changes: 13 additions & 0 deletions example/codegen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { codegenLab } from "labs/codegen/codegen_lab.ts";

await Deno.writeTextFile(
"./example/my_lab.ts",
codegenLab.execute("codegen.lab", {
name: "myLab",
labsImportSource: "labs/labs.ts",
extends: [
{ import: { name: "notesLab", source: "labs/notes.ts" } },
{ import: { name: "linksLab", source: "labs/links.ts" } },
],
}),
);
53 changes: 53 additions & 0 deletions example/example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { myLab } from "./my_lab.ts";

if (import.meta.main) {
main();
}

function main() {
const note1 = myLab.execute(
"notes.add",
{ title: "Hi", content: "Hello, world!" },
);
const note2 = myLab.execute(
"notes.add",
{ title: "Bye", content: "Goodbye, world!" },
);
myLab.execute(
"links.link",
{ ids: [note1.id, note2.id] },
);

console.log(renderNote(note1.id));
}

function renderNote(id: string): string {
const note = myLab.execute("notes.get", { id });
if (!note) {
throw new Error(`Note not found: ${id}`);
}

const title = renderTitle(note.title);
const links = myLab.execute("links.get", { id });
return `# [${title}](${id})
${note.content ?? "No content."}
## Links
${
!links || links.links.length === 0 ? "None" : links.links.map(({ id }) => {
const linkedNote = myLab.execute("notes.get", { id });
if (!linkedNote) {
throw new Error(`Linked note not found: ${id}`);
}
return `- [${renderTitle(linkedNote.title)}](${id})`;
}).join("\n")
}
`;
}

function renderTitle(title?: string): string {
return title ?? "Untitled";
}
8 changes: 8 additions & 0 deletions example/my_lab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Generated lab file. DO NOT MODIFY.
import { Lab } from "labs/labs.ts";
import { linksLab } from "labs/links.ts"
import { notesLab } from "labs/notes.ts"

export const myLab = new Lab()
.extend(notesLab)
.extend(linksLab);
2 changes: 2 additions & 0 deletions gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
//deno:generate deno run --allow-write codegen/codegen.ts
//deno:generate deno run --allow-write example/codegen.ts

0 comments on commit e80f98d

Please sign in to comment.