From 8b276aeb158a6787e51df611a4d4406c46366d75 Mon Sep 17 00:00:00 2001 From: ivanjermakov Date: Thu, 21 Nov 2024 12:10:18 +0100 Subject: [PATCH] Semantic: desugar `compose-op` --- nois.bnf | 2 +- src/phase/sugar.ts | 135 ++++++++++++++++++++++++++++-------- src/phase/top-scope-type.ts | 21 +++--- 3 files changed, 115 insertions(+), 43 deletions(-) diff --git a/nois.bnf b/nois.bnf index b261bcc..8534bff 100644 --- a/nois.bnf +++ b/nois.bnf @@ -68,7 +68,7 @@ statement ::= var-def | fn-def | trait-def | impl-def | type-def | r postfix-op ::= compose-op | call-op | unwrap-op | bind-op | await-op ; - compose-op ::= PERIOD operand + compose-op ::= PERIOD sub-expr ; call-op ::= O-PAREN (arg (COMMA arg)*)? COMMA? C-PAREN ; diff --git a/src/phase/sugar.ts b/src/phase/sugar.ts index 59b8fe6..a87fd7e 100644 --- a/src/phase/sugar.ts +++ b/src/phase/sugar.ts @@ -1,8 +1,10 @@ import { AstNode } from '../ast' import { UnaryExpr } from '../ast/expr' -import { Context, idFromString } from '../scope' +import { printAst } from '../debug' +import { Context, addError, idFromString } from '../scope' +import { genericError } from '../semantic/error' import { assign } from '../util/object' -import { todo } from '../util/todo' +import { assert } from '../util/todo' export type DesugarStage = 'init' | 'pre-name-resolve' @@ -35,33 +37,7 @@ export const desugar = (node: AstNode, stage: DesugarStage, ctx: Context) => { 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') - } + desugarComposeOp(node, ctx) } } @@ -73,9 +49,10 @@ export const desugar = (node: AstNode, stage: DesugarStage, ctx: Context) => { desugar(node.op, stage, ctx) break } + case 'fn-def': { if (node.block) { - node.block.statements.forEach(s => desugar(s, stage, ctx)) + desugar(node.block, stage, ctx) } break } @@ -92,6 +69,14 @@ export const desugar = (node: AstNode, stage: DesugarStage, ctx: Context) => { node.args.forEach(arg => desugar(arg, stage, ctx)) break } + case 'arg': { + desugar(node.expr, stage, ctx) + break + } + case 'compose-op': { + desugar(node.operand, stage, ctx) + break + } case 'trait-def': case 'impl-def': { desugar(node.block, stage, ctx) @@ -123,5 +108,95 @@ export const desugar = (node: AstNode, stage: DesugarStage, ctx: Context) => { } break } + case 'list-expr': { + node.exprs.forEach(expr => desugar(expr, stage, ctx)) + break + } + case 'while-expr': { + desugar(node.condition, stage, ctx) + desugar(node.block, stage, ctx) + break + } + case 'for-expr': { + desugar(node.pattern, stage, ctx) + desugar(node.expr, stage, ctx) + desugar(node.block, stage, ctx) + break + } + case 'use-expr': + case 'variant': + case 'return-stmt': + case 'break-stmt': + case 'param': + case 'fn-type': + case 'param-type': + case 'type-param': + case 'pattern': + case 'con-pattern': + case 'list-pattern': + case 'field-pattern': + case 'hole': + case 'identifier': + case 'name': + case 'string-interpolated': + case 'type-def': + case 'field-def': + case 'string-literal': + case 'char-literal': + case 'int-literal': + case 'float-literal': + case 'bool-literal': + case 'add-op': + case 'sub-op': + case 'mult-op': + case 'div-op': + case 'exp-op': + case 'mod-op': + case 'eq-op': + case 'ne-op': + case 'ge-op': + case 'le-op': + case 'gt-op': + case 'lt-op': + case 'and-op': + case 'or-op': + case 'assign-op': + case 'bind-op': + case 'await-op': + break + } +} + +export const desugarComposeOp = (node: UnaryExpr, ctx: Context) => { + if (node.op.kind !== 'compose-op') return assert(false) + const left = node.operand + const right = node.op.operand + if (right.kind === 'unary-expr' && right.op.kind === 'call-op') { + // foo.bar(a, b, c) -> bar(foo, a, b, c) + const newNode = right + // 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 + right.op.args.unshift({ kind: 'arg', parseNode: left.parseNode, expr: left }) + assign(node, newNode) + } else if (right.kind === 'operand-expr') { + // foo.bar -> bar(foo) + const newNode: UnaryExpr = { + kind: 'unary-expr', + parseNode: node.parseNode, + operand: { + kind: 'operand-expr', + parseNode: right.operand.parseNode, + operand: right.operand + }, + op: { + kind: 'call-op', + parseNode: right.operand.parseNode, + args: [{ kind: 'arg', parseNode: node.parseNode, expr: node.operand }] + } + } + assign(node, newNode) + } else { + printAst(node) + addError(ctx, genericError(ctx, node, 'unknown `compose-op` structure'), true) } } diff --git a/src/phase/top-scope-type.ts b/src/phase/top-scope-type.ts index 6928432..7b62379 100644 --- a/src/phase/top-scope-type.ts +++ b/src/phase/top-scope-type.ts @@ -14,14 +14,14 @@ export const setTopScopeDefType = (node: AstNode, ctx: Context) => { break } case 'trait-def': { - node.generics.forEach(g => setTopScopeDefType(g, ctx)) + node.typeParams.forEach(g => setTopScopeDefType(g, ctx)) break } case 'type-def': { - node.generics.forEach(g => setTopScopeDefType(g, ctx)) + node.typeParams.forEach(g => setTopScopeDefType(g, ctx)) break } - case 'generic': { + case 'type-param': { node.type = makeTypeParam(node) break } @@ -44,16 +44,13 @@ export const setTopScopeType = (node: AstNode, ctx: Context) => { break } case 'fn-def': { - if (node.instance) { - node.generics.push(...node.instance.generics) - } - node.generics.forEach(g => setTopScopeType(g, ctx)) + node.typeParams.forEach(g => setTopScopeType(g, ctx)) node.params.forEach(p => setTopScopeType(p, ctx)) assert(!!node.returnType) setTopScopeType(node.returnType!, ctx) node.type = { kind: 'fn-type', - typeParams: node.generics, + typeParams: node.typeParams, paramTypes: node.params.map(p => p.paramType!), returnType: node.returnType! } @@ -70,7 +67,7 @@ export const setTopScopeType = (node: AstNode, ctx: Context) => { kind: 'identifier', parseNode: node.parseNode, names: [node.typeDef!.name], - typeArgs: node.typeDef!.generics.map(g => ({ + typeArgs: node.typeDef!.typeParams.map(g => ({ kind: 'identifier', names: [g.name], typeArgs: [], @@ -80,7 +77,7 @@ export const setTopScopeType = (node: AstNode, ctx: Context) => { } const fnType = { kind: 'fn-type', - generics: node.typeDef!.generics, + generics: node.typeDef!.typeParams, paramTypes: node.fields.map(f => f.fieldType), returnType: typeDefId } @@ -97,7 +94,7 @@ export const setTopScopeType = (node: AstNode, ctx: Context) => { } case 'trait-def': case 'impl-def': { - node.generics.forEach(g => setTopScopeType(g, ctx)) + node.typeParams.forEach(g => setTopScopeType(g, ctx)) node.block.statements.forEach(s => setTopScopeType(s, ctx)) break } @@ -130,7 +127,7 @@ export const setTopScopeType = (node: AstNode, ctx: Context) => { node.type = { kind: 'hole' } break } - case 'generic': { + case 'type-param': { node.type = makeTypeParam(node) break }