Skip to content

Commit

Permalink
Codegen: list & for exprs; other codegen fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Mar 5, 2024
1 parent 6fcf4b0 commit c6b7cd6
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 55 deletions.
104 changes: 73 additions & 31 deletions src/codegen/js/expr.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { extractValue, indent, jsRelName, jsString, jsTodo, jsVariable, nextVariable } from '.'
import { emitLines, extractValue, indent, jsError, jsRelName, jsString, jsVariable, nextVariable } from '.'
import { Module, Param } from '../../ast'
import { BinaryExpr, Expr, OperandExpr, UnaryExpr } from '../../ast/expr'
import { MatchExpr, PatternExpr } from '../../ast/match'
import { MatchExpr, Pattern, PatternExpr } from '../../ast/match'
import { Operand } from '../../ast/operand'
import { Context } from '../../scope'
import { relTypeName } from '../../scope/trait'
import { operatorImplMap } from '../../semantic/op'
import { todo, unreachable } from '../../util/todo'
import { emitBlock } from './statement'
import { unreachable } from '../../util/todo'
import { emitBlock, emitBlockStatements } from './statement'

export interface EmitExpr {
emit: string
Expand Down Expand Up @@ -49,21 +49,21 @@ export const emitUnaryExpr = (unaryExpr: UnaryExpr, module: Module, ctx: Context
const variantName = `${variantDef.typeDef.name.value}.${variantDef.variant.name.value}`
const call = jsVariable(resultVar, `${variantName}(${args.map(a => a.resultVar).join(', ')})`)
return {
emit: [...args.map(a => a.emit), call, ...impls].join('\n'),
emit: emitLines([...args.map(a => a.emit), call, ...impls]),
resultVar
}
} else {
const operand = emitOperand(unaryExpr.operand, module, ctx)
const call = jsVariable(resultVar, `${operand.resultVar}(${args.map(a => a.resultVar).join(', ')})`)
return {
emit: [operand.emit, ...args.map(a => a.emit), call, ...impls].join('\n'),
emit: emitLines([operand.emit, ...args.map(a => a.emit), call, ...impls]),
resultVar
}
}
case 'unwrap-op':
return { emit: jsTodo('unwrap-op'), resultVar }
return { emit: jsError('unwrap-op'), resultVar }
case 'bind-op':
return { emit: jsTodo('bind'), resultVar }
return { emit: jsError('bind'), resultVar }
}
}

Expand All @@ -74,7 +74,7 @@ export const emitBinaryExpr = (binaryExpr: BinaryExpr, module: Module, ctx: Cont
if (binaryExpr.rOperand.kind === 'identifier') {
const accessor = binaryExpr.rOperand.names.at(-1)!.value
return {
emit: [lOp.emit, jsVariable(resultVar, `${lOp.resultVar}.${accessor}`)].join('\n'),
emit: emitLines([lOp.emit, jsVariable(resultVar, `${lOp.resultVar}.${accessor}`)]),
resultVar
}
}
Expand All @@ -86,29 +86,31 @@ export const emitBinaryExpr = (binaryExpr: BinaryExpr, module: Module, ctx: Cont
const args = callOp.args.map(a => emitExpr(a.expr, module, ctx))
const argsEmit = (methodDef.fn.static ? args : [lOp.resultVar, ...args.map(a => a.resultVar)]).join(', ')
return {
emit: [
emit: emitLines([
lOp.emit,
...args.map(a => a.emit).join('\n'),
emitLines(args.map(a => a.emit)),
jsVariable(resultVar, `${lOp.resultVar}.${traitName}.${methodName}(${argsEmit})`)
].join('\n'),
]),
resultVar
}
}
return {
emit: jsTodo('unwrap/bind ops'),
emit: jsError('unwrap/bind ops'),
resultVar
}
}
const rOp = emitOperand(binaryExpr.rOperand, module, ctx)
if (binaryExpr.binaryOp.kind === 'assign-op') {
return {
emit: [lOp.emit, rOp.emit, `${extractValue(lOp.resultVar)} = ${extractValue(rOp.resultVar)}`].join('\n'),
emit: emitLines([lOp.emit, rOp.emit, `${extractValue(lOp.resultVar)} = ${extractValue(rOp.resultVar)}`]),
resultVar
}
}
const trait = operatorImplMap.get(binaryExpr.binaryOp.kind)!.names.at(-2)!
const method = operatorImplMap.get(binaryExpr.binaryOp.kind)!.names.at(-1)!
const assign = jsVariable(resultVar, `${lOp.resultVar}.${trait}.${method}(${lOp.resultVar}, ${rOp.resultVar})`)
return {
emit: [lOp.emit, rOp.emit, jsVariable(resultVar, `${lOp.resultVar}.${method}(${rOp.resultVar})`)].join('\n'),
emit: emitLines([lOp.emit, rOp.emit, assign]),
resultVar
}
}
Expand All @@ -121,36 +123,68 @@ export const emitOperand = (operand: Operand, module: Module, ctx: Context): Emi
const thenBlock = emitBlock(operand.thenBlock, module, ctx, resultVar)
const elseBlock = operand.elseBlock ? `else ${emitBlock(operand.elseBlock, module, ctx, resultVar)}` : ''
return {
emit: [`let ${resultVar};`, cEmit, `if (${extractValue(cVar)}) ${thenBlock} ${elseBlock}`].join('\n'),
emit: emitLines([jsVariable(resultVar), cEmit, `if (${extractValue(cVar)}) ${thenBlock} ${elseBlock}`]),
resultVar
}
}
case 'if-let-expr':
return { emit: jsTodo('if-let'), resultVar }
return { emit: jsError('if-let'), resultVar }
case 'while-expr': {
const { emit: cEmit, resultVar: cVar } = emitExpr(operand.condition, module, ctx)
const block = emitBlock(operand.block, module, ctx)
return { emit: [cEmit, `while (${extractValue(cVar)}) ${block}`].join('\n'), resultVar }
return { emit: emitLines([cEmit, `while (${extractValue(cVar)}) ${block}`]), resultVar }
}
case 'for-expr': {
const expr = emitExpr(operand.expr, module, ctx)
const iteratorVar = nextVariable(ctx)
const iterator = {
emit: emitLines([
expr.emit,
jsVariable(iteratorVar, `${expr.resultVar}.Iterable.iter(${expr.resultVar})`)
]),
resultVar: iteratorVar
}
const iterateeVar = nextVariable(ctx)
const thenBlock = emitLines([
emitPattern(operand.pattern, module, ctx, `${iterateeVar}.value`),
...emitBlockStatements(operand.block, module, ctx)
])
const block = emitLines([
jsVariable(iterateeVar, `${iterator.resultVar}.Iter.next(${iterator.resultVar})`),
`if (${iterateeVar}.$noisVariant === "Some") {`,
indent(thenBlock),
`} else {`,
indent(`break;`),
`}`
])
const forEmit = emitLines([iterator.emit, 'while (true) {', indent(block), '}'])
return { emit: emitLines([jsVariable(resultVar), forEmit]), resultVar: jsError('no use') }
}
case 'for-expr':
return { emit: jsTodo('for'), resultVar }
case 'match-expr':
return emitMatchExpr(operand, module, ctx, resultVar)
case 'closure-expr':
case 'closure-expr': {
const params = operand.params.map(p => emitParam(p, module, ctx)).join(', ')
const block = emitBlock(operand.block, module, ctx)
return { emit: jsVariable(resultVar, `function(${params}) ${block}`), resultVar }
}
case 'operand-expr':
case 'unary-expr':
case 'binary-expr':
return emitExpr(operand, module, ctx)
case 'list-expr':
const items = operand.exprs.map(e => emitExpr(e, module, ctx))
const impls: string[] = []
if (operand.impls !== undefined) {
for (const impl of operand.impls) {
impls.push(`${resultVar}.${relTypeName(impl)} = ${jsRelName(impl)};`)
}
}
return {
emit: [
emit: emitLines([
...items.map(i => i.emit),
jsVariable(resultVar, `List(${items.map(i => i.resultVar).join(', ')})`)
].join('\n'),
jsVariable(resultVar, `List.List(${items.map(i => i.resultVar).join(', ')})`),
...impls
]),
resultVar
}
case 'string-literal':
Expand All @@ -160,7 +194,7 @@ export const emitOperand = (operand: Operand, module: Module, ctx: Context): Emi
case 'bool-literal':
return emitLiteral(operand, module, ctx, resultVar)
case 'identifier':
return { emit: jsVariable(resultVar, operand.names.at(-1)!.value), resultVar }
return { emit: '', resultVar: operand.names.at(-1)!.value }
}
}

Expand Down Expand Up @@ -191,7 +225,7 @@ export const emitLiteral = (operand: Operand, module: Module, ctx: Context, resu
impls.push(`${resultVar}.${relTypeName(impl)} = ${jsRelName(impl)};`)
}
}
return { emit: [jsVariable(resultVar, constructorEmit), ...impls].join('\n'), resultVar }
return { emit: emitLines([jsVariable(resultVar, constructorEmit), ...impls]), resultVar }
}

export const emitMatchExpr = (matchExpr: MatchExpr, module: Module, ctx: Context, resultVar: string): EmitExpr => {
Expand All @@ -200,12 +234,12 @@ export const emitMatchExpr = (matchExpr: MatchExpr, module: Module, ctx: Context
return { emit: '', resultVar }
}
const clauses = matchExpr.clauses.map(clause => {
if (clause.patterns.length !== 1) return jsTodo('union clause')
if (clause.patterns.length !== 1) return jsError('union clause')
const pattern = clause.patterns[0]
const cond = emitPatternExprCondition(pattern.expr, module, ctx, sVar)
const block = emitBlock(clause.block, module, ctx, resultVar)
// TODO: inject aliases and fields in block's scope
return [cond.emit, `if (${cond.resultVar}) ${block}`].join('\n')
return emitLines([cond.emit, `if (${cond.resultVar}) ${block}`])
})
let ifElseChain = clauses[0]
for (let i = 1; i < clauses.length; i++) {
Expand All @@ -216,7 +250,7 @@ export const emitMatchExpr = (matchExpr: MatchExpr, module: Module, ctx: Context
ifElseChain += `\n${indent('}', i)}`
}
return {
emit: [jsVariable(resultVar), sEmit, ifElseChain].join('\n'),
emit: emitLines([jsVariable(resultVar), sEmit, ifElseChain]),
resultVar
}
}
Expand All @@ -241,7 +275,7 @@ export const emitPatternExprCondition = (
case 'int-literal':
case 'float-literal':
case 'bool-literal':
return { emit: jsVariable(resultVar, jsTodo('literal')), resultVar }
return { emit: jsVariable(resultVar, jsError('literal')), resultVar }
case 'list-expr':
case 'operand-expr':
case 'unary-expr':
Expand All @@ -260,7 +294,15 @@ export const emitPatternExprCondition = (

export const emitParam = (param: Param, module: Module, ctx: Context): string => {
if (param.pattern.expr.kind !== 'name') {
return todo('destructuring')
return jsError('destructuring')
}
return param.pattern.expr.value
}

export const emitPattern = (pattern: Pattern, module: Module, ctx: Context, assignVar: string): string => {
if (pattern.expr.kind !== 'name') {
return jsError('destructuring')
}
const name = pattern.expr.value
return jsVariable(name, assignVar)
}
13 changes: 8 additions & 5 deletions src/codegen/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,18 @@ export const nextVariable = (ctx: Context): string => {
return `$${ctx.variableCounter}`
}

export const jsTodo = (message?: string): string => {
const msg = 'todo' + (message ? `: ${message}` : '')
return `(() => { throw Error(${jsString(msg)}); })()`
export const jsError = (message?: string): string => {
return `(() => { throw Error(${jsString(message ?? '')}); })()`
}

export const jsRelName = (rel: InstanceRelation): string => {
if (rel.inherent) {
return `impl${virtualTypeToString(rel.implType)}`.replace(/[:<>,]/g, '')
return `impl_${virtualTypeToString(rel.implType)}`.replace(/[:<>, ]/g, '')
} else {
return `impl${virtualTypeToString(rel.implType)}${virtualTypeToString(rel.forType)}`.replace(/[:<>,]/g, '')
return `impl_${virtualTypeToString(rel.implType)}_${virtualTypeToString(rel.forType)}`.replace(/[:<>, ]/g, '')
}
}

export const emitLines = (lines: string[]): string => {
return lines.filter(l => l.length > 0).join('\n')
}
37 changes: 20 additions & 17 deletions src/codegen/js/statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ export const emitStatement = (statement: Statement, module: Module, ctx: Context
case 'fn-def':
return emitFnDef(statement, module, ctx)
case 'trait-def':
return emitTraitDef(statement, module, ctx)
case 'impl-def':
return emitImplDef(statement, module, ctx)
return emitInstanceDef(statement, module, ctx)
case 'type-def':
return emitTypeDef(statement, module, ctx)
case 'return-stmt':
Expand Down Expand Up @@ -52,15 +51,9 @@ export const emitFnDef = (fnDef: FnDef, module: Module, ctx: Context, asProperty
}
}

export const emitTraitDef = (traitDef: TraitDef, module: Module, ctx: Context): string => {
const name = traitDef.name.value
const impl = emitInstance(traitDef, module, ctx)
return jsVariable(name, impl, true)
}

export const emitImplDef = (implDef: ImplDef, module: Module, ctx: Context): string => {
const rel = ctx.impls.find(i => i.instanceDef === implDef)!
const impl = emitInstance(implDef, module, ctx)
export const emitInstanceDef = (instanceDef: ImplDef | TraitDef, module: Module, ctx: Context): string => {
const rel = ctx.impls.find(i => i.instanceDef === instanceDef)!
const impl = emitInstance(instanceDef, module, ctx)
return jsVariable(jsRelName(rel), impl, true)
}

Expand Down Expand Up @@ -92,18 +85,28 @@ export const emitInstance = (instance: ImplDef | TraitDef, module: Module, ctx:
return `{${fns}}`
}

export const emitBlock = (block: Block, module: Module, ctx: Context, resultVar?: boolean | string): string => {
const statements_ = block.statements.map(s => emitStatement(s, module, ctx))
const last = statements_.at(-1)
export const emitBlockStatements = (
block: Block,
module: Module,
ctx: Context,
resultVar?: boolean | string
): string[] => {
const statements = block.statements.map(s => emitStatement(s, module, ctx))
const last = statements.at(-1)
if (resultVar !== undefined && typeof last === 'object') {
if (typeof resultVar === 'string') {
statements_.push(`${resultVar} = ${last.resultVar};`)
statements.push(`${resultVar} = ${last.resultVar};`)
}
if (resultVar === true) {
statements_.push(`return ${last.resultVar};`)
statements.push(`return ${last.resultVar};`)
}
}
const statements = statements_.length > 0 ? `\n${indent(statements_.map(emitExprToString).join('\n'))}\n` : ''
return statements.map(emitExprToString)
}

export const emitBlock = (block: Block, module: Module, ctx: Context, resultVar?: boolean | string): string => {
const statements_ = emitBlockStatements(block, module, ctx, resultVar)
const statements = statements_.length > 0 ? `\n${indent(statements_.join('\n'))}\n` : ''
return `{${statements}}`
}

Expand Down
11 changes: 9 additions & 2 deletions src/semantic/expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ export const checkOperand = (operand: Operand, ctx: Context): void => {
break
case 'list-expr':
checkListExpr(operand, ctx)
operand.impls = resolveImplsForType(operand.type!, operand, ctx)
break
case 'string-literal': {
const vid = vidFromString('std::string::String')
Expand Down Expand Up @@ -610,7 +609,15 @@ export const checkListExpr = (listExpr: ListExpr, ctx: Context): void => {
addError(ctx, typeError(expr, otherType, itemType, ctx))
}
}
listExpr.type = { kind: 'vid-type', identifier: vidFromString('std::list::List'), typeArgs: [itemType] }
const listVid = vidFromString('std::list::List')
const ref = resolveVid(listVid, ctx, ['type-def'])
if (!ref || ref.def.kind !== 'type-def') {
addError(ctx, notFoundError(ctx, listExpr, vidToString(listVid)))
listExpr.type = unknownType
return
}
listExpr.type = { kind: 'vid-type', identifier: listVid, typeArgs: [itemType] }
listExpr.impls = resolveImplsForType(listExpr.type, listExpr, ctx)
}

export const checkAssignExpr = (binaryExpr: BinaryExpr, ctx: Context): void => {
Expand Down

0 comments on commit c6b7cd6

Please sign in to comment.