Skip to content

Commit

Permalink
Semantic: desugar compose-op
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Nov 21, 2024
1 parent d1e33be commit 9c31a1d
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 47 deletions.
6 changes: 3 additions & 3 deletions src/ast/expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type UnaryExpr = BaseAstNode & {

export type BinaryExpr = BaseAstNode & {
kind: 'binary-expr'
binaryOp: BinaryOp
op: BinaryOp
lOperand: Expr
rOperand: Expr
}
Expand Down Expand Up @@ -62,7 +62,7 @@ export const buildExpr = (node: ParseNode, ctx: Context): Expr => {
exprStack.push({
kind: 'binary-expr',
parseNode: { kind: 'expr', nodes: [lExp, o2, rExp].map(e => e.parseNode).filter(n => !!n) },
binaryOp: o2,
op: o2,
lOperand: lExp,
rOperand: rExp
})
Expand All @@ -81,7 +81,7 @@ export const buildExpr = (node: ParseNode, ctx: Context): Expr => {
exprStack.push({
kind: 'binary-expr',
parseNode: { kind: 'expr', nodes: [lExp, op, rExp].map(e => e.parseNode).filter(n => !!n) },
binaryOp: op,
op: op,
lOperand: lExp,
rOperand: rExp
})
Expand Down
6 changes: 3 additions & 3 deletions src/codegen/js/expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const emitBinaryExpr = (binaryExpr: BinaryExpr, ctx: Context): EmitExpr =
const lOp = emitOperand(binaryExpr.lOperand, ctx)
const resultVar = nextVariable(ctx)
const rOp = emitOperand(binaryExpr.rOperand, ctx)
switch (binaryExpr.binaryOp.kind) {
switch (binaryExpr.op.kind) {
case 'assign-op': {
return {
emit: emitTree([
Expand All @@ -125,11 +125,11 @@ export const emitBinaryExpr = (binaryExpr: BinaryExpr, ctx: Context): EmitExpr =
}
}
default: {
const op = binaryExpr.binaryOp
const op = binaryExpr.op
const methodVid = operatorImplMap.get(op.kind)!
const trait = methodVid.names.at(-2)!
const method = methodVid.names.at(-1)!
const callerEmit = binaryExpr.binaryOp.impl ? jsRelName(binaryExpr.binaryOp.impl) : trait
const callerEmit = binaryExpr.op.impl ? jsRelName(binaryExpr.op.impl) : trait
const callEmit = jsVariable(
resultVar,
emitToken(`${callerEmit}().${method}(${lOp.resultVar}, ${rOp.resultVar})`)
Expand Down
3 changes: 2 additions & 1 deletion src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ExtractKeys } from './util/type'

export const debugAst = (
node: AstNode,
focusKinds: ExtractKeys<AstNode>[] = ['kind', 'type'],
focusKinds: ExtractKeys<AstNode>[] = ['kind', 'type', 'value'],
reportRecursive = false,
depth = 0
): any => {
Expand Down Expand Up @@ -40,6 +40,7 @@ export const debugAst = (
if (focusKinds.includes(<any>p)) {
return [p, v]
}
console.log(p, node.kind)
return undefined
})
.filter(t => t !== undefined)
Expand Down
2 changes: 1 addition & 1 deletion src/phase/impl-register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const registerImpl = (node: AstNode, ctx: Context): void => {
node.block.statements.forEach(s => registerImpl(s, ctx))
break
case 'impl-def':
if (!node.forTrait) return
if (!node.for) return
const m = ctx.moduleStack.at(-1)!
m.impls.push(node)
break
Expand Down
4 changes: 3 additions & 1 deletion src/phase/name-resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export const resolveName = (node: AstNode, ctx: Context): void => {
case 'binary-expr': {
resolveName(node.lOperand, ctx)
resolveName(node.rOperand, ctx)
resolveName(node.binaryOp, ctx)
resolveName(node.op, ctx)
break
}
case 'list-expr': {
Expand Down Expand Up @@ -218,6 +218,8 @@ export const resolveName = (node: AstNode, ctx: Context): void => {
break
}
case 'compose-op': {
addError(ctx, genericError(ctx, node), true)

unreachable()
break
}
Expand Down
129 changes: 107 additions & 22 deletions src/phase/sugar.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,126 @@
import { AstNode } from '../ast'
import { UnaryExpr } from '../ast/expr'
import { Context, idFromString } from '../scope'
import { assign } from '../util/object'
import { todo } from '../util/todo'

export type DesugarStage = 'init' | 'pre-name-resolve'

/**
* Desugar phase that runs before name resolution
* Does:
* - populates type of the `self` param
* - sets fnDef.instance
* - set fnDef.returnType to Unit if not specified
* Desugar phase
*/
export const desugar1 = (node: AstNode, ctx: Context, parent?: AstNode) => {
export const desugar = (node: AstNode, stage: DesugarStage, ctx: Context) => {
switch (node.kind) {
case 'module': {
node.block.statements.forEach(s => desugar1(s, ctx))
desugar(node.block, stage, ctx)
break
}
case 'trait-def':
case 'impl-def': {
node.block.statements.forEach(s => desugar1(s, ctx, node))
case 'block': {
node.statements.forEach(s => desugar(s, stage, ctx))
break
}
case 'fn-def': {
if (parent && (parent.kind === 'impl-def' || parent.kind === 'trait-def')) {
node.instance = parent
node.params.forEach((p, i) => {
if (i === 0 && !p.paramType && p.pattern.expr.kind === 'name' && p.pattern.expr.value === 'self') {
const selfType = idFromString('Self')
selfType.parseNode = p.parseNode
p.paramType = selfType
case 'var-def': {
if (node.expr) {
desugar(node.expr, stage, ctx)
}
break
}
case 'operand-expr': {
desugar(node.operand, stage, ctx)
break
}
case 'unary-expr': {
desugar(node.operand, stage, ctx)
desugar(node.op, stage, ctx)

if (stage === 'init') {
if (node.op.kind === 'compose-op') {
if (node.operand.kind === 'unary-expr' && node.operand.op.kind === 'call-op') {
// foo.bar(a, b, c) -> bar(foo, a, b, c)
const newNode = node.operand.op
// TODO: report error if there are named args, e.g. foo.bar(a = 4)
// since we cannot apply argument with a correnct name before typecheck phase
newNode.args.unshift({ kind: 'arg', parseNode: node.operand.parseNode, expr: node.operand })
assign(node, newNode)
} else if (node.operand.kind === 'operand-expr' && node.operand.operand.kind === 'identifier') {
// foo.bar -> bar(foo)
const newNode: UnaryExpr = {
kind: 'unary-expr',
operand: {
kind: 'operand-expr',
parseNode: node.op.operand.parseNode,
operand: node.op.operand
},
op: {
kind: 'call-op',
parseNode: node.parseNode,
args: [{ kind: 'arg', parseNode: node.operand.parseNode, expr: node.operand }]
}
}
assign(node, newNode)
} else {
// foo.{bar} -> fn() { bar(foo)) }
todo('complex compose-op')
}
})
}
}
node.returnType ??= ctx.stdTypeIds.unit ?? { kind: 'hole' }

break
}
case 'binary-expr': {
desugar(node.lOperand, stage, ctx)
desugar(node.rOperand, stage, ctx)
desugar(node.op, stage, ctx)
break
}
case 'compose-op': {
todo()
case 'fn-def': {
if (node.block) {
node.block.statements.forEach(s => desugar(s, stage, ctx))
}
break
}
case 'match-expr': {
desugar(node.expr, stage, ctx)
node.clauses.forEach(clause => desugar(clause, stage, ctx))
break
}
case 'match-clause': {
desugar(node.block, stage, ctx)
break
}
case 'call-op': {
node.args.forEach(arg => desugar(arg, stage, ctx))
break
}
case 'trait-def':
case 'impl-def': {
desugar(node.block, stage, ctx)
break
}
case 'trait-block': {
node.statements.forEach(s => desugar(s, stage, ctx))
break
}
case 'trait-statement': {
desugar(node.expr, stage, ctx)

if (stage === 'pre-name-resolve') {
if (node.expr.kind === 'operand-expr' && node.expr.operand.kind === 'fn-def') {
const fnDef = node.expr.operand
fnDef.params.forEach((p, i) => {
if (
i === 0 &&
!p.paramType &&
p.pattern.expr.kind === 'name' &&
p.pattern.expr.value === 'self'
) {
const selfType = idFromString('Self')
selfType.parseNode = p.parseNode
p.paramType = selfType
}
})
}
}
break
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/phase/type-bound.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,13 @@ export const collectTypeBounds = (node: AstNode, ctx: Context, parentBound?: Inf
break
}
case 'binary-expr': {
if (node.binaryOp.kind === 'assign-op') {
if (node.op.kind === 'assign-op') {
// TODO
break
}
collectTypeBounds(node.lOperand, ctx)
collectTypeBounds(node.rOperand, ctx)
const methodId = operatorImplMap.get(node.binaryOp.kind)
const methodId = operatorImplMap.get(node.op.kind)
assert(!!methodId)
const methodDef = findById(methodId!, ctx)
assert(!!methodDef)
Expand Down Expand Up @@ -235,7 +235,7 @@ export const collectTypeBounds = (node: AstNode, ctx: Context, parentBound?: Inf
case 'trait-def':
case 'impl-def': {
// TODO
if (node.kind === 'impl-def' && node.forTrait) {
if (node.kind === 'impl-def' && node.for) {
break
}
node.block.statements.forEach(s => collectTypeBounds(s, ctx))
Expand Down
23 changes: 13 additions & 10 deletions src/scope/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { TraitDef, TraitStatement } from '../ast/statement'
import { TypeParam } from '../ast/type'
import { FieldDef, TypeDef, Variant } from '../ast/type-def'
import { Config } from '../config'
import { prettySourceMessage } from '../error'
import { Package } from '../package'
import { ParseNode } from '../parser'
import { ParseNode, getSpan } from '../parser'
import { StdTypeIds } from '../phase/std-type'
import { SemanticError, duplicateDefError } from '../semantic/error'
import { typeToString } from '../typecheck'
Expand Down Expand Up @@ -107,15 +108,17 @@ export const pathToId = (path: string, packageName?: string): Identifier => {
return idFromString(dirs.join('::'))
}

export const addError = (ctx: Context, error: SemanticError): void => {
// console.trace(
// prettySourceMessage(
// error.message,
// error.source,
// error.node.parseNode ? getSpan(error.node.parseNode) : undefined,
// error.notes
// )
// )
export const addError = (ctx: Context, error: SemanticError, immediate = false): void => {
if (immediate) {
console.trace(
prettySourceMessage(
error.message,
error.source,
error.node.parseNode ? getSpan(error.node.parseNode) : undefined,
error.notes
)
)
}
ctx.errors.push(error)
}

Expand Down
8 changes: 5 additions & 3 deletions src/semantic/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
import { Module } from '../ast'
import { checkImpl } from '../phase/impl'
import { registerImpl } from '../phase/impl-register'
import { resolveImports, setExports } from '../phase/import-resolve'
import { resolveModuleScope } from '../phase/module-resolve'
import { resolveName } from '../phase/name-resolve'
import { setSelfBound } from '../phase/self-bound'
import { setStdTypeIds } from '../phase/std-type'
import { desugar1 } from '../phase/sugar'
import { desugar } from '../phase/sugar'
import { setTopScopeDefType, setTopScopeType } from '../phase/top-scope-type'
import { collectTypeBounds } from '../phase/type-bound'
import { unifyTypeBounds } from '../phase/type-unify'
import { Context, eachModule } from '../scope'

export const semanticCheck = (ctx: Context): void => {
const phases = [
const phases: ((module: Module, ctx: Context) => void)[] = [
(node, ctx) => desugar(node, 'init', ctx),
resolveModuleScope,
setExports,
resolveImports,
setStdTypeIds,
registerImpl,
desugar1,
(node, ctx) => desugar(node, 'pre-name-resolve', ctx),
resolveName,
setSelfBound,
setTopScopeDefType,
Expand Down

0 comments on commit 9c31a1d

Please sign in to comment.