Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/david518yang/Lang
Browse files Browse the repository at this point in the history
  • Loading branch information
Mattmart42 committed Apr 28, 2024
2 parents 40628e3 + a4443b9 commit 057d305
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 175 deletions.
318 changes: 159 additions & 159 deletions src/generator.js
Original file line number Diff line number Diff line change
@@ -1,166 +1,166 @@
// The code generator exports a single function, generate(program), which
// accepts a program representation and returns the JavaScript translation
// as a string.
// // The code generator exports a single function, generate(program), which
// // accepts a program representation and returns the JavaScript translation
// // as a string.

import { voidType, standardLibrary } from "./core.js"
// import { voidType, standardLibrary } from "./core.js"

export default function generate(program) {
// When generating code for statements, we'll accumulate the lines of
// the target code here. When we finish generating, we'll join the lines
// with newlines and return the result.
const output = []
// export default function generate(program) {
// // When generating code for statements, we'll accumulate the lines of
// // the target code here. When we finish generating, we'll join the lines
// // with newlines and return the result.
// const output = []

const standardFunctions = new Map([
[standardLibrary.print, x => `console.log(${x})`]
])
// const standardFunctions = new Map([
// [standardLibrary.print, x => `console.log(${x})`]
// ])

// Variable and function names in JS will be suffixed with _1, _2, _3,
// etc. This is because "switch", for example, is a legal name in Carlos,
// but not in JS. So, the Carlos variable "switch" must become something
// like "switch_1". We handle this by mapping each name to its suffix.
const targetName = (mapping => {
return entity => {
if (!mapping.has(entity)) {
mapping.set(entity, mapping.size + 1)
}
return `${entity.name}_${mapping.get(entity)}`
}
})(new Map())
// // Variable and function names in JS will be suffixed with _1, _2, _3,
// // etc. This is because "switch", for example, is a legal name in Carlos,
// // but not in JS. So, the Carlos variable "switch" must become something
// // like "switch_1". We handle this by mapping each name to its suffix.
// const targetName = (mapping => {
// return entity => {
// if (!mapping.has(entity)) {
// mapping.set(entity, mapping.size + 1)
// }
// return `${entity.name}_${mapping.get(entity)}`
// }
// })(new Map())

const gen = node => generators?.[node?.kind]?.(node) ?? node
// const gen = node => generators?.[node?.kind]?.(node) ?? node

const generators = {
// Key idea: when generating an expression, just return the JS string; when
// generating a statement, write lines of translated JS to the output array.
Program(p) {
p.statements.forEach(gen)
},
VariableDeclaration(d) {
// We don't care about const vs. let in the generated code! The analyzer has
// already checked that we never updated a const, so let is always fine.
output.push(`let ${gen(d.variable)} = ${gen(d.initializer)};`)
},
TypeDeclaration(d) {
// The only type declaration in Carlos is the struct! Becomes a JS class.
output.push(`class ${gen(d.type)} {`)
output.push(`constructor(${d.type.fields.map(gen).join(",")}) {`)
for (let field of d.type.fields) {
output.push(`this[${JSON.stringify(gen(field))}] = ${gen(field)};`)
}
output.push("}")
output.push("}")
},
StructType(t) {
return targetName(t)
},
Field(f) {
return targetName(f)
},
FunctionDeclaration(d) {
output.push(`function ${gen(d.fun)}(${d.params.map(gen).join(", ")}) {`)
d.body.forEach(gen)
output.push("}")
},
Variable(v) {
return targetName(v)
},
Function(f) {
return targetName(f)
},
Assignment(s) {
output.push(`${gen(s.target)} = ${gen(s.source)};`)
},
BreakStatement(s) {
output.push("break;")
},
ReturnStatement(s) {
output.push(`return ${gen(s.expression)};`)
},
ShortReturnStatement(s) {
output.push("return;")
},
IfStatement(s) {
output.push(`if (${gen(s.test)}) {`)
s.consequent.forEach(gen)
if (s.alternate?.kind?.endsWith?.("IfStatement")) {
output.push("} else")
gen(s.alternate)
} else {
output.push("} else {")
s.alternate.forEach(gen)
output.push("}")
}
},
ShortIfStatement(s) {
output.push(`if (${gen(s.test)}) {`)
s.consequent.forEach(gen)
output.push("}")
},
WhileStatement(s) {
output.push(`while (${gen(s.test)}) {`)
s.body.forEach(gen)
output.push("}")
},
ForRangeStatement(s) {
const i = targetName(s.iterator)
const op = s.op === ".."
output.push(`for (let ${i} = ${gen(s.low)}; ${i} ${op} ${gen(s.high)}; ${i}++) {`)
s.body.forEach(gen)
output.push("}")
},
ForStatement(s) {
output.push(`for (let ${gen(s.iterator)} of ${gen(s.collection)}) {`)
s.body.forEach(gen)
output.push("}")
},
Conditional(e) {
return `((${gen(e.test)}) ? (${gen(e.consequent)}) : (${gen(e.alternate)}))`
},
BinaryExpression(e) {
const op = { "==": "===", "!=": "!==" }[e.op] ?? e.op
return `(${gen(e.left)} ${op} ${gen(e.right)})`
},
UnaryExpression(e) {
const operand = gen(e.operand)
if (e.op === "some") {
return operand
} else if (e.op === "#") {
return `${operand}.length`
} else if (e.op === "random") {
return `((a=>a[~~(Math.random()*a.length)])(${operand}))`
}
return `${e.op}(${operand})`
},
SubscriptExpression(e) {
return `${gen(e.array)}[${gen(e.index)}]`
},
ArrayExpression(e) {
return `[${e.elements.map(gen).join(",")}]`
},
EmptyArray(e) {
return "[]"
},
MemberExpression(e) {
const object = gen(e.object)
const field = JSON.stringify(gen(e.field))
const chain = e.op === "." ? "" : e.op
return `(${object}${chain}[${field}])`
},
FunctionCall(c) {
const targetCode = standardFunctions.has(c.callee)
? standardFunctions.get(c.callee)(c.args.map(gen))
: `${gen(c.callee)}(${c.args.map(gen).join(", ")})`
// Calls in expressions vs in statements are handled differently
if (c.callee.type.returnType !== voidType) {
return targetCode
}
output.push(`${targetCode};`)
},
ConstructorCall(c) {
return `new ${gen(c.callee)}(${c.args.map(gen).join(", ")})`
},
}
// const generators = {
// // Key idea: when generating an expression, just return the JS string; when
// // generating a statement, write lines of translated JS to the output array.
// Program(p) {
// p.statements.forEach(gen)
// },
// VariableDeclaration(d) {
// // We don't care about const vs. let in the generated code! The analyzer has
// // already checked that we never updated a const, so let is always fine.
// output.push(`let ${gen(d.variable)} = ${gen(d.initializer)};`)
// },
// TypeDeclaration(d) {
// // The only type declaration in Carlos is the struct! Becomes a JS class.
// output.push(`class ${gen(d.type)} {`)
// output.push(`constructor(${d.type.fields.map(gen).join(",")}) {`)
// for (let field of d.type.fields) {
// output.push(`this[${JSON.stringify(gen(field))}] = ${gen(field)};`)
// }
// output.push("}")
// output.push("}")
// },
// StructType(t) {
// return targetName(t)
// },
// Field(f) {
// return targetName(f)
// },
// FunctionDeclaration(d) {
// output.push(`function ${gen(d.fun)}(${d.params.map(gen).join(", ")}) {`)
// d.body.forEach(gen)
// output.push("}")
// },
// Variable(v) {
// return targetName(v)
// },
// Function(f) {
// return targetName(f)
// },
// Assignment(s) {
// output.push(`${gen(s.target)} = ${gen(s.source)};`)
// },
// BreakStatement(s) {
// output.push("break;")
// },
// ReturnStatement(s) {
// output.push(`return ${gen(s.expression)};`)
// },
// ShortReturnStatement(s) {
// output.push("return;")
// },
// IfStatement(s) {
// output.push(`if (${gen(s.test)}) {`)
// s.consequent.forEach(gen)
// if (s.alternate?.kind?.endsWith?.("IfStatement")) {
// output.push("} else")
// gen(s.alternate)
// } else {
// output.push("} else {")
// s.alternate.forEach(gen)
// output.push("}")
// }
// },
// ShortIfStatement(s) {
// output.push(`if (${gen(s.test)}) {`)
// s.consequent.forEach(gen)
// output.push("}")
// },
// WhileStatement(s) {
// output.push(`while (${gen(s.test)}) {`)
// s.body.forEach(gen)
// output.push("}")
// },
// ForRangeStatement(s) {
// const i = targetName(s.iterator)
// const op = s.op === ".."
// output.push(`for (let ${i} = ${gen(s.low)}; ${i} ${op} ${gen(s.high)}; ${i}++) {`)
// s.body.forEach(gen)
// output.push("}")
// },
// ForStatement(s) {
// output.push(`for (let ${gen(s.iterator)} of ${gen(s.collection)}) {`)
// s.body.forEach(gen)
// output.push("}")
// },
// Conditional(e) {
// return `((${gen(e.test)}) ? (${gen(e.consequent)}) : (${gen(e.alternate)}))`
// },
// BinaryExpression(e) {
// const op = { "==": "===", "!=": "!==" }[e.op] ?? e.op
// return `(${gen(e.left)} ${op} ${gen(e.right)})`
// },
// UnaryExpression(e) {
// const operand = gen(e.operand)
// if (e.op === "some") {
// return operand
// } else if (e.op === "#") {
// return `${operand}.length`
// } else if (e.op === "random") {
// return `((a=>a[~~(Math.random()*a.length)])(${operand}))`
// }
// return `${e.op}(${operand})`
// },
// SubscriptExpression(e) {
// return `${gen(e.array)}[${gen(e.index)}]`
// },
// ArrayExpression(e) {
// return `[${e.elements.map(gen).join(",")}]`
// },
// EmptyArray(e) {
// return "[]"
// },
// MemberExpression(e) {
// const object = gen(e.object)
// const field = JSON.stringify(gen(e.field))
// const chain = e.op === "." ? "" : e.op
// return `(${object}${chain}[${field}])`
// },
// FunctionCall(c) {
// const targetCode = standardFunctions.has(c.callee)
// ? standardFunctions.get(c.callee)(c.args.map(gen))
// : `${gen(c.callee)}(${c.args.map(gen).join(", ")})`
// // Calls in expressions vs in statements are handled differently
// if (c.callee.type.returnType !== voidType) {
// return targetCode
// }
// output.push(`${targetCode};`)
// },
// ConstructorCall(c) {
// return `new ${gen(c.callee)}(${c.args.map(gen).join(", ")})`
// },
// }

gen(program)
return output.join("\n")
}
// gen(program)
// return output.join("\n")
// }
32 changes: 16 additions & 16 deletions test/generator.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import assert from "node:assert/strict"
import parse from "../src/parser.js"
import analyze from "../src/analyzer.js"
import optimize from "../src/optimizer.js"
import generate from "../src/generator.js"
// import assert from "node:assert/strict"
// import parse from "../src/parser.js"
// import analyze from "../src/analyzer.js"
// import optimize from "../src/optimizer.js"
// import generate from "../src/generator.js"

function dedent(s) {
return `${s}`.replace(/(?<=\n)\s+/g, "").trim()
}
// function dedent(s) {
// return `${s}`.replace(/(?<=\n)\s+/g, "").trim()
// }

const fixtures = [
{
Expand Down Expand Up @@ -169,11 +169,11 @@ const fixtures = [
},
]

describe("The code generator", () => {
for (const fixture of fixtures) {
it(`produces expected js output for the ${fixture.name} program`, () => {
const actual = generate(optimize(analyze(parse(fixture.source))))
assert.deepEqual(actual, fixture.expected)
})
}
})
// describe("The code generator", () => {
// for (const fixture of fixtures) {
// it(`produces expected js output for the ${fixture.name} program`, () => {
// const actual = generate(optimize(analyze(parse(fixture.source))))
// assert.deepEqual(actual, fixture.expected)
// })
// }
// })
3 changes: 3 additions & 0 deletions test/optimizer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ const tests = [
["optimizes in subscripts", sub(a, onePlusTwo), sub(a, 3)],
["optimizes in array literals", array(0, onePlusTwo, 9), array(0, 3, 9)],
["optimizes in arguments", callIdentity([times(3, 5)]), callIdentity([15])],
["optimizes ConstructorCall", core.constructorCall(identity, [times(3, 5)]), core.constructorCall(identity, [15])],
["optimizes MemberExpression", core.memberExpression(core.constructorCall(identity, [times(3, 5)]), ".", "f"), core.memberExpression(core.constructorCall(identity, [15]), ".", "f")],
[
"passes through nonoptimizable constructs",
...Array(2).fill([
Expand All @@ -83,6 +85,7 @@ const tests = [
core.forStatement(x, array(1, 2, 3), []),
]),
],
["optimizes whileStatement", core.whileStatement(true, [returnX]), core.whileStatement(true, [returnX])],
]

describe("The optimizer", () => {
Expand Down

0 comments on commit 057d305

Please sign in to comment.