Skip to content

Commit

Permalink
Codegen: gen to emit trees for source mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Mar 18, 2024
1 parent badd005 commit bf8adb8
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 212 deletions.
8 changes: 4 additions & 4 deletions src/codegen/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Statement, UseExpr } from '../ast/statement'
import { FieldDef } from '../ast/type-def'
import { ParseNode } from '../parser'
import { unreachable } from '../util/todo'
import { indent } from './js'
import { indentStr } from './js'

export const emitDeclaration = (module: Module): string => {
return [
Expand Down Expand Up @@ -47,7 +47,7 @@ export const emitStatement = (statement: Statement, pubOnly = true): string | un
.map(s => emitStatement(s, false))
.filter(s => !!s)
.map(s => s!)
const block = statements.length > 0 ? `{\n${statements.map(s => indent(s)).join('\n')}\n}` : '{}'
const block = statements.length > 0 ? `{\n${statements.map(s => indentStr(s)).join('\n')}\n}` : '{}'
return `pub trait ${statement.name.value}${generics} ${block}`
}
case 'impl-def': {
Expand All @@ -64,7 +64,7 @@ export const emitStatement = (statement: Statement, pubOnly = true): string | un
.map(s => emitStatement(s))
.filter(s => !!s)
.map(s => s!)
const block = statements.length > 0 ? `{\n${statements.map(s => indent(s)).join('\n')}\n}` : '{}'
const block = statements.length > 0 ? `{\n${statements.map(s => indentStr(s)).join('\n')}\n}` : '{}'
return `impl ${generics}${emitParseNode(statement.identifier.parseNode)} ${block}`
}
case 'type-def': {
Expand All @@ -81,7 +81,7 @@ export const emitStatement = (statement: Statement, pubOnly = true): string | un
const fieldDefs = v.fieldDefs.length > 0 ? `(${fields})` : ''
return `${v.name.value}${fieldDefs}`
})
const block = variants.length > 0 ? ` {\n${variants.map(s => indent(s)).join(',\n')}\n}` : ''
const block = variants.length > 0 ? ` {\n${variants.map(s => indentStr(s)).join(',\n')}\n}` : ''
return `pub type ${statement.name.value}${generics}${block}`
}
default:
Expand Down
197 changes: 109 additions & 88 deletions src/codegen/js/expr.ts

Large diffs are not rendered by default.

74 changes: 26 additions & 48 deletions src/codegen/js/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,29 @@ import { Upcast } from '../../semantic/upcast'
import { VirtualType, virtualTypeToString } from '../../typecheck'
import { groupBy } from '../../util/array'
import { unreachable } from '../../util/todo'
import { emitExprToString } from './expr'
import { EmitNode, EmitToken, emitToken, emitTree } from './node'
import { emitStatement } from './statement'

export interface JsImport {
def: string
path: string
}

export const emitModule = (module: Module, ctx: Context, mainFn?: string): string => {
export const emitModule = (module: Module, ctx: Context, mainFn?: string): EmitNode => {
const imports = emitImports(module, ctx)
const statements = module.block.statements
.map(s => emitStatement(s, module, ctx))
.map(emitExprToString)
.filter(s => s.length > 0)
.join('\n\n')
const statements = emitTree(
module.block.statements.map(s => emitStatement(s, module, ctx)).map(s => ('resultVar' in s ? s.emit : s))
)
const mainFnInvoke =
mainFn !== undefined
? [
`\
try {
${mainFn}();
} catch (e) {
console.error(\`\${e.message}\\n\${e.stack.split("\\n").slice(1).join("\\n")}\`);
}`
]
: []
return [imports, statements, ...mainFnInvoke].filter(s => s.length > 0).join('\n\n')
? emitToken(
`try{${mainFn}();}catch(e){console.error(\`\${e.message}\${e.stack.split("\\n").slice(1).join("\\n")}\`);}`
)
: undefined
return emitTree([imports, statements, mainFnInvoke], module.parseNode)
}

export const emitImports = (module: Module, ctx: Context): string => {
export const emitImports = (module: Module, ctx: Context): EmitNode => {
const relImports = module.relImports
.filter(i => i.module !== module)
.map(i => makeJsImport(concatVid(i.module.identifier, vidFromString(jsRelName(i))), i.module, module, ctx))
Expand All @@ -52,13 +45,11 @@ export const emitImports = (module: Module, ctx: Context): string => {
return makeJsImport(vid, i.module, module, ctx)
})
imports_.push(...relImports)
const imports = [...groupBy(imports_, i => i.path).entries()]
.map(([path, is]) => {
const defs = [...new Set(is.map(i => i.def))].toSorted()
return `import { ${defs.join(', ')} } from "${path}.js";`
})
.join('\n')
return imports
const imports = [...groupBy(imports_, i => i.path).entries()].map(([path, is]) => {
const defs = [...new Set(is.map(i => i.def))].toSorted()
return emitToken(`import{${defs.join(',')}}from"${path}.js";`)
})
return emitTree(imports)
}

const makeJsImport = (vid: VirtualIdentifier, importModule: Module, module: Module, ctx: Context): JsImport => {
Expand All @@ -72,6 +63,14 @@ const makeJsImport = (vid: VirtualIdentifier, importModule: Module, module: Modu
return { def, path: [...vid.names.slice(0, -1), ...(importModule.mod ? ['mod'] : [])].join('/') }
}

export const emitUpcasts = (resultVar: string, upcasts: Map<string, Upcast>): EmitToken => {
const args = [...upcasts.entries()].map(
([k, v]) => `[${[...v.traits.entries()].map(([tk, tv]) => `[${jsString(tk)}, ${jsRelName(tv)}]`).join(',')}]`
)
args.unshift(resultVar)
return emitToken(`${resultVar}.upcast(${args.join(',')});`)
}

export const extractValue = (str: string): string => {
return `${str}.value`
}
Expand All @@ -80,24 +79,11 @@ export const jsString = (str: string): string => {
return JSON.stringify(str)
}

export const jsVariable = (name: string, emit?: string, pub = false): string => {
const assign = `let ${name}${emit !== undefined ? ` = ${emit}` : ''}`
return `${pub ? 'export ' : ''}${assign};`
}

export const indent = (str: string, level = 1): string => {
return str.replace(/^/gm, ' '.repeat(4 * level))
}

export const nextVariable = (ctx: Context): string => {
ctx.variableCounter++
return `$${ctx.variableCounter}`
}

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

export const jsRelName = (rel: InstanceRelation): string => {
if (rel.instanceDef.kind === 'trait-def') {
return relTypeName(rel)
Expand All @@ -108,10 +94,6 @@ export const jsRelName = (rel: InstanceRelation): string => {
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')
}

export const jsGenericTypeName = (type: VirtualType): string => {
switch (type.kind) {
case 'generic':
Expand All @@ -127,10 +109,6 @@ export const jsGenericTypeName = (type: VirtualType): string => {
}
}

export const emitUpcasts = (resultVar: string, upcasts: Map<string, Upcast>): string => {
const args = [...upcasts.entries()].map(
([k, v]) => `[${[...v.traits.entries()].map(([tk, tv]) => `[${jsString(tk)}, ${jsRelName(tv)}]`).join(', ')}]`
)
args.unshift(resultVar)
return `${resultVar}.upcast(${args.join(', ')})`
export const indentStr = (str: string, level = 1): string => {
return str.replace(/^/gm, ' '.repeat(4 * level))
}
74 changes: 74 additions & 0 deletions src/codegen/js/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { jsString } from '.'
import { ParseNode } from '../../parser'

export type EmitNode = EmitToken | EmitTree

export interface EmitToken {
kind: 'token'
value: string
parseNode?: ParseNode
}

export const emitToken = (value: string, parseNode?: ParseNode): EmitToken => {
return {
kind: 'token',
parseNode,
value
}
}

export interface EmitTree {
kind: 'node'
nodes: EmitNode[]
parseNode?: ParseNode
}

export const emitTree = (nodes: (EmitNode | undefined)[], parseNode?: ParseNode): EmitTree => {
return {
kind: 'node',
nodes: nodes.filter(n => n).map(n => n!),
parseNode
}
}

export const emitAppendStr = (node: EmitNode, str: string): EmitNode => {
if (node.kind === 'node') {
node.nodes.push(emitToken(str))
} else {
node.value += str
}
return node
}

export const jsVariable = (name: string, emit?: EmitNode, pub = false): EmitNode => {
const pubStr = pub ? 'export ' : ''
if (emit) {
return emitTree([emitToken(`${pubStr}let ${name} = `), emit, emitToken(';')])
} else {
return emitToken(`${pubStr}let ${name};`)
}
}

export const jsError = (message?: string): EmitToken => {
return emitToken(`(() => { throw Error(${jsString(message ?? '')}); })()`)
}

export const emitIntersperse = (
lines: (EmitNode | undefined)[],
separator: string,
parseNode?: ParseNode
): EmitTree => {
return emitTree(
lines
.filter(l => !!l)
.map(l => l!)
.filter(l => l.kind === 'node' || l.value.length > 0)
.map((l, i, ls) => {
if (i !== ls.length - 1) {
return emitAppendStr(l, separator)
}
return l
}),
parseNode
)
}
Loading

0 comments on commit bf8adb8

Please sign in to comment.