From 6a2554a2b8b0c97153249a6adb011d67d8d6ac92 Mon Sep 17 00:00:00 2001 From: ivanjermakov Date: Thu, 11 Apr 2024 23:14:03 +0200 Subject: [PATCH] Syntax: `await-op` --- nois.bnf | 10 +++++++-- src/ast/index.ts | 10 +++++++-- src/ast/op.ts | 11 +++++----- src/lexer/lexer.ts | 4 +++- src/parser/fns/index.ts | 21 +++++++++++++++++-- src/parser/fns/op.ts | 7 +++++-- src/parser/index.ts | 1 + src/scope/std.ts | 6 ++++++ src/semantic/expr.ts | 46 +++++++++++++++++++++++++++++++++-------- 9 files changed, 93 insertions(+), 23 deletions(-) diff --git a/nois.bnf b/nois.bnf index 3f8fa6e..5b34c13 100644 --- a/nois.bnf +++ b/nois.bnf @@ -58,7 +58,7 @@ statement ::= var-def | fn-def | trait-def | impl-def | type-def | r | bool | identifier ; - infix-op ::= add-op | sub-op | mult-op | div-op | exp-op | mod-op | access-op | eq-op | ne-op + infix-op ::= add-op | sub-op | mult-op | div-op | exp-op | mod-op | eq-op | ne-op | ge-op | le-op | gt-op | lt-op | and-op | or-op | assign-op; add-op ::= PLUS; sub-op ::= MINUS; @@ -77,7 +77,11 @@ statement ::= var-def | fn-def | trait-def | impl-def | type-def | r or-op ::= PIPE PIPE; assign-op ::= EQUALS; - postfix-op ::= call-op | unwrap-op | bind-op + postfix-op ::= method-call-op | field-access-op | call-op | unwrap-op | bind-op | await-op + ; + method-call-op ::= PERIOD NAME type-args? call-op + ; + field-access-op ::= PERIOD NAME ; call-op ::= O-PAREN (arg (COMMA arg)*)? COMMA? C-PAREN ; @@ -87,6 +91,8 @@ statement ::= var-def | fn-def | trait-def | impl-def | type-def | r ; bind-op ::= QMARK ; + await-op ::= PERIOD AWAIT-KEYWORD + ; identifier ::= (NAME COLON COLON)* NAME type-args? ; type-args ::= O-ANGLE (type (COMMA type)* COMMA?)? C-ANGLE diff --git a/src/ast/index.ts b/src/ast/index.ts index 24b52fb..ba94d3b 100644 --- a/src/ast/index.ts +++ b/src/ast/index.ts @@ -39,7 +39,6 @@ export const astInfixOpKinds = [ 'div-op', 'exp-op', 'mod-op', - 'access-op', 'eq-op', 'ne-op', 'ge-op', @@ -51,7 +50,14 @@ export const astInfixOpKinds = [ 'assign-op' ] -export const astPostfixOpKinds = ['method-call-op', 'field-access-op', 'call-op', 'unwrap-op', 'bind-op'] +export const astPostfixOpKinds = [ + 'method-call-op', + 'field-access-op', + 'call-op', + 'unwrap-op', + 'bind-op', + 'await-op' +] export const astKinds = [ 'module', diff --git a/src/ast/op.ts b/src/ast/op.ts index 61f3f2a..906726a 100644 --- a/src/ast/op.ts +++ b/src/ast/op.ts @@ -6,7 +6,7 @@ import { Arg, AstNode, AstNodeKind, buildArg } from './index' import { Name, buildName } from './operand' import { Type, buildType } from './type' -export type PostfixOp = MethodCallOp | FieldAccessOp | CallOp | UnwrapOp | BindOp +export type PostfixOp = MethodCallOp | FieldAccessOp | CallOp | UnwrapOp | BindOp | AwaitOp export const isPostfixOp = (op: AstNode): op is PostfixOp => { return ( @@ -14,7 +14,8 @@ export const isPostfixOp = (op: AstNode): op is PostfixOp => { op.kind === 'field-access-op' || op.kind === 'call-op' || op.kind === 'unwrap-op' || - op.kind === 'bind-op' + op.kind === 'bind-op' || + op.kind === 'await-op' ) } @@ -28,6 +29,7 @@ export const buildPostfixOp = (node: ParseNode): PostfixOp => { return buildCallOp(node) case 'unwrap-op': case 'bind-op': + case 'await-op': return { kind: node.kind, parseNode: node } default: throw Error(`expected postfix-op, got ${node.kind}`) @@ -41,7 +43,6 @@ export type BinaryOp = ( | DivOp | ExpOp | ModOp - | AccessOp | EqOp | NeOp | GeOp @@ -162,6 +163,8 @@ export interface UnwrapOp extends AstNode<'unwrap-op'> {} export interface BindOp extends AstNode<'bind-op'> {} +export interface AwaitOp extends AstNode<'await-op'> {} + export interface AddOp extends AstNode<'add-op'> {} export interface SubOp extends AstNode<'sub-op'> {} @@ -174,8 +177,6 @@ export interface ExpOp extends AstNode<'exp-op'> {} export interface ModOp extends AstNode<'mod-op'> {} -export interface AccessOp extends AstNode<'access-op'> {} - export interface EqOp extends AstNode<'eq-op'> {} export interface NeOp extends AstNode<'ne-op'> {} diff --git a/src/lexer/lexer.ts b/src/lexer/lexer.ts index 079a315..77c4373 100644 --- a/src/lexer/lexer.ts +++ b/src/lexer/lexer.ts @@ -48,7 +48,8 @@ export const lexerKeywordKinds = [ 'for-keyword', 'in-keyword', 'match-keyword', - 'pub-keyword' + 'pub-keyword', + 'await-keyword', ] export const lexerDynamicKinds = ['name', 'string-part', 'char', 'int', 'float', 'bool'] @@ -92,6 +93,7 @@ export const lexerKeywordMap: [TokenKind, string][] = [ ['in-keyword', 'in'], ['match-keyword', 'match'], ['pub-keyword', 'pub'], + ['await-keyword', 'await'], ['o-paren', '('], ['c-paren', ')'], diff --git a/src/parser/fns/index.ts b/src/parser/fns/index.ts index dd94055..9f6f824 100644 --- a/src/parser/fns/index.ts +++ b/src/parser/fns/index.ts @@ -1,5 +1,5 @@ import { Parser } from '..' -import { TokenKind, lexerKeywordKinds } from '../../lexer/lexer' +import { TokenKind } from '../../lexer/lexer' import { parseBlock, parseParam, parseStatement, parseUseStmt } from './statement' import { parseTypeAnnot } from './type' @@ -7,7 +7,24 @@ import { parseTypeAnnot } from './type' * Tokens that can be used as a name AST node depending on context. * Includes 'name' itself and all keyword tokens */ -export const nameLikeTokens: TokenKind[] = ['name', ...lexerKeywordKinds] +export const nameLikeTokens: TokenKind[] = [ + 'name', + 'use-keyword', + 'type-keyword', + 'trait-keyword', + 'impl-keyword', + 'let-keyword', + 'fn-keyword', + 'if-keyword', + 'else-keyword', + 'return-keyword', + 'break-keyword', + 'while-keyword', + 'for-keyword', + 'in-keyword', + 'match-keyword', + 'pub-keyword' +] export const infixOpFirstTokens: TokenKind[] = [ 'ampersand', diff --git a/src/parser/fns/op.ts b/src/parser/fns/op.ts index 3e748d7..5790bf4 100644 --- a/src/parser/fns/op.ts +++ b/src/parser/fns/op.ts @@ -80,7 +80,7 @@ export const parseInfixOp = (parser: Parser): void => { } /** - * postfix-op ::= call-op | unwrap-op | bind-op + * postfix-op ::= method-call-op | field-access-op | call-op | unwrap-op | bind-op | await-op */ export const parsePostfixOp = (parser: Parser): void => { if (parser.at('o-paren')) { @@ -88,7 +88,10 @@ export const parsePostfixOp = (parser: Parser): void => { return } if (parser.at('period')) { - if ( + if (parser.nth(1) === 'await-keyword') { + const mark = parser.open() + parser.close(mark, 'await-op') + } else if ( parser.nth(2) === 'o-paren' || (parser.nth(2) === 'o-angle' && parser.encounter('c-angle', [...nameLikeTokens, 'comma', 'o-angle', 'underscore'], 2)) diff --git a/src/parser/index.ts b/src/parser/index.ts index 2062eb0..3eeef04 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -53,6 +53,7 @@ export const treeKinds = [ 'call-op', 'unwrap-op', 'bind-op', + 'await-op', 'access-op', 'arg', 'closure-expr', diff --git a/src/scope/std.ts b/src/scope/std.ts index a3a03a3..3700a33 100644 --- a/src/scope/std.ts +++ b/src/scope/std.ts @@ -4,6 +4,7 @@ import { vidFromString } from './util' export const preludeVid = vidFromString('std::prelude') +// TODO: lack of std types must throw notFoundError export const bool: VidType = { kind: 'vid-type', identifier: vidFromString('std::bool::Bool'), typeArgs: [] } export const string: VidType = { kind: 'vid-type', identifier: vidFromString('std::string::String'), typeArgs: [] } @@ -21,3 +22,8 @@ export const unwrap: VidType = { identifier: vidFromString('std::unwrap::Unwrap'), typeArgs: [holeType] } +export const future: VidType = { + kind: 'vid-type', + identifier: vidFromString('std::future::Future'), + typeArgs: [holeType] +} diff --git a/src/semantic/expr.ts b/src/semantic/expr.ts index babe1e6..16035be 100644 --- a/src/semantic/expr.ts +++ b/src/semantic/expr.ts @@ -5,7 +5,7 @@ import { MatchExpr } from '../ast/match' import { CallOp } from '../ast/op' import { ClosureExpr, ForExpr, Identifier, IfExpr, IfLetExpr, ListExpr, Name, Operand, WhileExpr } from '../ast/operand' import { Context, Scope, addError, fnDefScope, instanceScope } from '../scope' -import { bool, iter, iterable, show, string, unwrap } from '../scope/std' +import { bool, future, iter, iterable, show, string, unwrap } from '../scope/std' import { InstanceRelation, getConcreteTrait, @@ -15,7 +15,7 @@ import { typeDefToVirtualType } from '../scope/trait' import { idToVid, vidEq, vidFromString, vidToString } from '../scope/util' -import { MethodDef, VariantDef, VirtualIdentifierMatch, resolveVid, typeKinds } from '../scope/vid' +import { MethodDef, VariantDef, VirtualIdentifier, VirtualIdentifierMatch, resolveVid, typeKinds } from '../scope/vid' import { VidType, VirtualFnType, @@ -189,6 +189,9 @@ export const checkUnaryExpr = (unaryExpr: UnaryExpr, ctx: Context): void => { case 'bind-op': checkBind(unaryExpr, ctx) return + case 'await-op': + checkAwait(unaryExpr, ctx) + return } } @@ -674,7 +677,7 @@ export const checkCall_ = (call: CallOp, operand: Operand, args: Expr[], ctx: Co export const checkUnwrap = (unaryExpr: UnaryExpr, ctx: Context): void => { const operand = unaryExpr.operand checkOperand(operand, ctx) - const unwrapType = findUnwrapInnerType(operand.type!, ctx) + const unwrapType = findTraitInnerType(operand.type!, unwrap.identifier, ctx) if (!unwrapType) { addError(ctx, typeError(ctx, unaryExpr, operand.type!, unwrap)) unaryExpr.type = unknownType @@ -689,7 +692,7 @@ export const checkUnwrap = (unaryExpr: UnaryExpr, ctx: Context): void => { export const checkBind = (unaryExpr: UnaryExpr, ctx: Context): void => { const operand = unaryExpr.operand checkOperand(operand, ctx) - const unwrapType = findUnwrapInnerType(operand.type!, ctx) + const unwrapType = findTraitInnerType(operand.type!, unwrap.identifier, ctx) if (!unwrapType) { addError(ctx, typeError(ctx, unaryExpr, operand.type!, unwrap)) unaryExpr.type = unknownType @@ -708,17 +711,42 @@ export const checkBind = (unaryExpr: UnaryExpr, ctx: Context): void => { upcast(operand, operand.type!, unwrap, ctx) } -export const findUnwrapInnerType = (type: VirtualType, ctx: Context): VirtualType | undefined => { - const unwrapRel = ctx.impls.find( +export const checkAwait = (unaryExpr: UnaryExpr, ctx: Context): void => { + const operand = unaryExpr.operand + checkOperand(operand, ctx) + if (!(operand.type?.kind === 'vid-type' && vidEq(operand.type.identifier, future.identifier))) { + addError(ctx, typeError(ctx, unaryExpr.op, operand.type!, future)) + unaryExpr.type = unknownType + return + } + const innerType = operand.type.typeArgs[0] + unaryExpr.type = innerType + + const scope = fnDefScope(ctx) + if (!scope) { + addError(ctx, notInFnScopeError(ctx, unaryExpr.op)) + return + } + scope.returns.push(operand) + + upcast(operand, operand.type!, future, ctx) +} + +export const findTraitInnerType = ( + type: VirtualType, + trait: VirtualIdentifier, + ctx: Context +): VirtualType | undefined => { + const traitRel = ctx.impls.find( i => - vidEq(i.implDef.vid, unwrap.identifier) && + vidEq(i.implDef.vid, trait) && // TODO: properly handle generics isAssignable(type, replaceGenericsWithHoles(i.forType), ctx) ) - if (!unwrapRel) { + if (!traitRel) { return undefined } - return getConcreteTrait(type, unwrapRel, ctx) + return getConcreteTrait(type, traitRel, ctx) } export const variantCallRef = (operand: Operand, ctx: Context): VirtualIdentifierMatch | undefined => {