diff --git a/src/main.nt b/src/main.nt index 144462e..2c46914 100644 --- a/src/main.nt +++ b/src/main.nt @@ -30,7 +30,7 @@ import package(compiler).std.file; import package(compiler).std.json; import package(compiler).std.string; import package(compiler).std.thread; -import std.argparse; +import package(compiler).std.argparse; import c_header("sys/inotify.h"); extern(C) int getpid(); diff --git a/src/neat/base.nt b/src/neat/base.nt index 90bb4ca..27c4525 100644 --- a/src/neat/base.nt +++ b/src/neat/base.nt @@ -882,6 +882,10 @@ abstract class CompilerBase abstract (nullable ASTSymbol | fail Error) parseExpression( Parser parser, LexicalContext lexicalContext); + // Parse an expression that may be arithmetic, but not ternary-if. + abstract (nullable ASTSymbol | fail Error) parseArithmetic( + Parser parser, LexicalContext lexicalContext); + abstract (nullable ASTSymbol | fail Error) parseExpressionLeaf( Parser parser, LexicalContext lexicalContext); diff --git a/src/neat/compiler.nt b/src/neat/compiler.nt index fded12b..f5e94d5 100644 --- a/src/neat/compiler.nt +++ b/src/neat/compiler.nt @@ -90,6 +90,14 @@ class CompilerImpl : CompilerBase return .parseExpression(parser.instanceOf(ParserImpl).notNull, lexicalContext); } + override (nullable ASTSymbol | fail Error) parseArithmetic(Parser parser, LexicalContext lexicalContext) + { + auto leaf = parseExpressionLeaf(parser, lexicalContext)? + .case(null: return parser.fail("Expression leaf expected")); + + return .parseArithmetic(parser.instanceOf(ParserImpl).notNull, lexicalContext, leaf, 0); + } + override (nullable ASTSymbol | fail Error) parseExpressionLeaf(Parser parser, LexicalContext lexicalContext) { return .parseExpressionLeaf(parser.instanceOf(ParserImpl).notNull, lexicalContext); diff --git a/src/neat/elseexpr.nt b/src/neat/elseexpr.nt deleted file mode 100644 index 6db1580..0000000 --- a/src/neat/elseexpr.nt +++ /dev/null @@ -1,127 +0,0 @@ -module neat.elseexpr; - -macro import std.macro.listcomprehension; -macro import std.macro.quasiquoting; - -import neat.base; -import neat.bottom; -import neat.either; -import neat.expr; -import neat.statements; -import neat.util; - -/** - * Is this type a type that should trigger a breakelse? - * - :else - * - nullptr_t - */ -bool isElseTriggerType(Type type) { - if (type.instanceOf(SymbolIdentifierType).case(null: breakelse).name == "else") { - return true; - } - if (type.instanceOf(NullPointer)) return true; - return false; -} - -class ASTElseExpr : ASTSymbol -{ - ASTSymbol base; - - ASTSymbol else_; - - this(this.base, this.else_, super) { } - - override (Symbol | fail Error) compile(Context context) { - /** - * - make label - * - LabelIfScope - * - preallocate var - * if (true) { - * var = base.case(:else: breakelse) - * } else { - * var = else_ - * } - * - var - */ - auto ifLabel = context.getLabel; - auto context = context.withNamespace(new LabelIfScope(ifLabel, hasElse=true, context.namespace)); - auto base = this.base.compile(context)?.beExpressionImplCall(context, base.locRange)?; - auto else_ = this.else_.compile(context)?.beExpressionImplCall(context, else_.locRange)?; - // won't be visible post-merge - bool elseIsBottom = !!else_.type.instanceOf(Bottom); - (Expression | fail Error) baseEither() { - if (auto either = base.type.instanceOf(Either)) { - return base; - } - if (auto canTreatAsEither = base.type.instanceOf(CanTreatAsEither)) { - return canTreatAsEither.toEitherType(context, base)?.case(null: breakelse); - } - return this.locRange.fail( - "'else' operator cannot be applied to non-sumtype $(base.type.repr)"); - } - auto baseEither = baseEither?; - auto baseEitherType = baseEither.type.instanceOf(Either).notNull; - if (![any a.type.isElseTriggerType for a in baseEitherType.types]) { - return this.locRange.fail( - "'else' operator cannot be applied to sumtype without :else member"); - } - auto merger = new TypeMerger; - mut nullable Type triggerType; - for (auto entry in baseEitherType.types) { - if (entry.type.isElseTriggerType) { - this.locRange.assert(!triggerType, "internal error: double trigger type")?; - triggerType = entry.type; - } else { - merger.add(new NullExpr(entry.type), __RANGE__, context)?; - } - } - merger.add(else_, __RANGE__, context)?; - auto mergedType = merger.type(context).notNull; - print("merged to $(mergedType) from $(baseEitherType.types) and $(else_.type)"); - /** - * uninitialized mergedType result; - * if (true) { result = baseEither.case(:else: breakelse); } - * else { result = else_; } - */ - auto astBaseEither = new ASTSymbolHelper(baseEither); - auto astTriggerType = new ASTSymbolHelper(triggerType); - auto astBaseBreakElse = context.compiler.$expr $astBaseEither.case($astTriggerType: breakelse); - auto baseBreakElse = astBaseBreakElse.compile(context)?.beExpression(__RANGE__)?; - auto lifetimeBaseBreakElse = baseBreakElse if elseIsBottom else baseBreakElse.take(context, __RANGE__)?; - auto baseBreakElseConvert = expectImplicitConvertTo( - context, lifetimeBaseBreakElse, mergedType, this.locRange)?; - auto elseConvert = expectImplicitConvertTo(context, else_, mergedType, this.locRange)?; - // If else is bottom, we can just use the lifetime of base directly. - auto lifetime = baseEither.info.lifetime if elseIsBottom else Lifetime.gifted; - auto result = new PairedTemporary(mergedType, lifetime, context.getUniqueId); - auto init = new UninitializeTemporaryStatement(result); - auto ifTrue = new AssignStatement(result, baseBreakElseConvert, __RANGE__); - auto ifFalse = new AssignStatement(result, elseConvert, __RANGE__); - auto trueTest = new BoolLiteral(true); - auto ifStmt = new IfStatement(ifLabel, trueTest, then=ifTrue, else_=ifFalse, __RANGE__); - return context.compiler.(wrap(sequence(init, ifStmt), result, null)); - } - - override string repr() => "($(base)).else($else_)"; -} - -// expr.else(expr) -(nullable ASTSymbol | fail Error) parseElseExpr(Parser parser, LexicalContext lexicalContext, ASTSymbol current) { - parser.begin; - auto from = parser.from; - if (!(parser.acceptToken(TokenType.dot) - && parser.acceptIdentifier("else") - && parser.acceptToken(TokenType.lparen))) - { - parser.revert; - return null; - } - parser.commit; - - auto elseExpr = lexicalContext.compiler.parseExpression(parser, lexicalContext)?; - auto range = parser.to(from); - range.assert(!!elseExpr, () => "expression expected")?; - auto elseExpr = elseExpr.notNull; - parser.expectToken(TokenType.rparen)?; - return new ASTElseExpr(base=current, else_=elseExpr, range); -} diff --git a/src/neat/stuff.nt b/src/neat/stuff.nt index c361439..93d430e 100644 --- a/src/neat/stuff.nt +++ b/src/neat/stuff.nt @@ -16,7 +16,6 @@ import neat.bottom; import neat.class_; import neat.decl; import neat.delegate_; -import neat.elseexpr; import neat.either; import neat.enums; import neat.expr; @@ -35,6 +34,7 @@ import neat.runtime : die; import neat.statements; import neat.struct_; import neat.templ; +import neat.ternary; import neat.traits; import neat.tuples; import neat.types; @@ -1244,10 +1244,13 @@ class ASTPropagateFailureExpr : ASTSymbol mut ASTEitherCaseExprCase[] cases; for (k, v in either.types) { ASTSymbol expr() { - auto a = new ASTIdentifier("a", false, __RANGE__); - if (v.type.isElseTriggerType) { + if (v.type.instanceOf(SymbolIdentifierType).case(null: breakelse).name == "else") { + return new ASTBreakElse(this.locRange); + } + if (v.type.instanceOf(NullPointer)) { return new ASTBreakElse(this.locRange); } + auto a = new ASTIdentifier("a", false, __RANGE__); if (v.fail) return new ASTReturn(a, this.locRange); return a; } @@ -1321,10 +1324,6 @@ class ASTPropagateFailureExpr : ASTSymbol current = propagateExpr; continue; } - if (ASTSymbol elseExpr = parser.parseElseExpr(lexicalContext, current)?) { - current = elseExpr; - continue; - } if (ASTSymbol prop = parser.parseParenPropertyExpression(lexicalContext, current)?) { current = prop; continue; @@ -2084,59 +2083,19 @@ class ASTIota : ASTSymbol return left; } -class ASTTernaryIf : ASTSymbol -{ - ASTSymbol cond, left, right; - - this(this.cond, this.left, this.right, this.locRange) { } - - override (Symbol | fail Error) compile(Context context) - { - Expression left = this.left.compile(context)?.beExpressionImplCall(context, this.left.locRange)?; - Expression right = this.right.compile(context)?.beExpressionImplCall(context, this.right.locRange)?; - auto merge = mergePair(context, left, right, this.right.locRange)?; - auto left = new ASTSymbolHelper(merge.left); - auto right = new ASTSymbolHelper(merge.right); - auto impl = context.compiler.$expr ({ - mut uninitialized typeof($left) result; - if ($cond) result = $left; - else result = $right; - result; - }); - return impl.compile(context); - } - - override string repr() { return "$(left.repr) if $(cond.repr) else $(right.repr)"; } -} - -(ASTSymbol | fail Error) parseTernaryOp(ParserImpl parser, LexicalContext lexicalContext, mut ASTSymbol then, int myLevel) { - with (parser.transaction) { - auto from = parser.from; - if (!parser.acceptIdentifier("if")) return then; - auto cond = parseExpression(parser, lexicalContext)?; - if (!cond) return then; - if (!parser.acceptIdentifier("else")) - return parser.fail("'else' expected"); - auto else_ = expectArithmetic(parser, lexicalContext, 0)?; - commit; - return new ASTTernaryIf(cond.notNull, then, else_, parser.to(from)); - } -} - (ASTSymbol | fail Error) parseArithmetic(ParserImpl parser, LexicalContext lexicalContext, mut ASTSymbol left, int level) { - // & ^ <<>> | */ +- <> && || if - if (level <= 10) left = parseBitAnd(parser, lexicalContext, left, 10)?; - if (level <= 9) left = parseBitXor(parser, lexicalContext, left, 9)?; - if (level <= 8) left = parseBitShift(parser, lexicalContext, left, 8)?; - if (level <= 7) left = parseBitOr(parser, lexicalContext, left, 7)?; - if (level <= 6) left = parseMulDiv(parser, lexicalContext, left, 6)?; - if (level <= 5) left = parseAddSubCat(parser, lexicalContext, left, 5)?; - if (level <= 4) left = parseIota(parser, lexicalContext, left, 4)?; - if (level <= 3) left = parseComparison(parser, lexicalContext, left, 3)?; - if (level <= 2) left = parseBoolAnd(parser, lexicalContext, left, 2)?; - if (level <= 1) left = parseBoolOr(parser, lexicalContext, left, 1)?; - if (level <= 0) left = parseTernaryOp(parser, lexicalContext, left, 0)?; + // & ^ <<>> | */ +- <> && || + if (level <= 9) left = parseBitAnd(parser, lexicalContext, left, 9)?; + if (level <= 8) left = parseBitXor(parser, lexicalContext, left, 8)?; + if (level <= 7) left = parseBitShift(parser, lexicalContext, left, 7)?; + if (level <= 6) left = parseBitOr(parser, lexicalContext, left, 6)?; + if (level <= 5) left = parseMulDiv(parser, lexicalContext, left, 5)?; + if (level <= 4) left = parseAddSubCat(parser, lexicalContext, left, 4)?; + if (level <= 3) left = parseIota(parser, lexicalContext, left, 3)?; + if (level <= 2) left = parseComparison(parser, lexicalContext, left, 2)?; + if (level <= 1) left = parseBoolAnd(parser, lexicalContext, left, 1)?; + if (level <= 0) left = parseBoolOr(parser, lexicalContext, left, 0)?; return left; } @@ -2148,12 +2107,45 @@ class ASTTernaryIf : ASTSymbol return parseArithmetic(parser, lexicalContext, leaf.notNull, level); } -(nullable ASTSymbol | fail Error) parseExpression(ParserImpl parser, LexicalContext lexicalContext) -{ - if (auto leaf = parseExpressionLeaf(parser, lexicalContext)?) { - return parseArithmetic(parser, lexicalContext, leaf, 0); +(nullable ASTSymbol | fail Error) parseArithmeticExpr(ParserImpl parser, LexicalContext lexicalContext) { + auto leaf = parseExpressionLeaf(parser, lexicalContext)?.case(null: return null); + return parseArithmetic(parser, lexicalContext, leaf, 0); +} + +/** + * Ternary expressions have two forms: + * - foo if bar else baz: ternary if + * - foo else baz: short ternary if :) + * - In this case, the condition is held to be true. + * So the else block can only be hit via breakelse. + */ +(nullable ASTSymbol | fail Error) parseTernaryOp(ParserImpl parser, LexicalContext lexicalContext) { + with (parser.transaction) { + auto from = parser.from; + auto base = parseArithmeticExpr(parser, lexicalContext)?.case(null: return null); + mut nullable ASTSymbol test = null; + if (parser.acceptIdentifier("if")) { + test = parseArithmeticExpr(parser, lexicalContext)?; + if (!test) { + commit; + return base; + } + } + if (!parser.acceptIdentifier("else")) { + if (test) + return parser.fail("'else' expected"); + commit; + return base; + } + auto else_ = parseExpression(parser, lexicalContext)? + .case(null: return parser.fail("'else' expression expected")); + commit; + return new ASTTernaryIf(test=test, then=base, else_=else_, parser.to(from)); } - return null; +} + +(nullable ASTSymbol | fail Error) parseExpression(ParserImpl parser, LexicalContext lexicalContext) { + return parseTernaryOp(parser, lexicalContext); } class ASTReturn : ASTSymbol @@ -2847,6 +2839,10 @@ class ASTVarDeclStatement : ASTStatement else { if (auto ident = parser.parseIdentifierSymbol(lexicalContext)?) { + if (reserved(ident)) { + parser.revert; + return null; + } name = ident; } else { parser.revert; @@ -3405,6 +3401,12 @@ class ASTForLoop : ASTStatement return new ASTIdentifierQuote(lexicalContext.quoteScope, token, parser.to(from)); } +bool reserved(ASTIdentifierSymbol symbol) { + ASTIdentifier ident = symbol.instanceOf(ASTIdentifier).case(null: return false); + // Bit short... TODO can this be used elsewhere? + return ident.name_ == "else"; +} + /** * As above, but this time the quote is allowed to resolve to any ASTSymbol. * TODO: Better name? diff --git a/src/neat/ternary.nt b/src/neat/ternary.nt new file mode 100644 index 0000000..4be3f88 --- /dev/null +++ b/src/neat/ternary.nt @@ -0,0 +1,75 @@ +module neat.ternary; + +macro import package(compiler).std.macro.listcomprehension; +macro import package(compiler).std.macro.quasiquoting; + +import neat.base; +import neat.bottom; +import neat.either; +import neat.expr; +import neat.statements; +import neat.util; + +class ASTTernaryIf : ASTSymbol +{ + nullable ASTSymbol test; + + ASTSymbol then; + + ASTSymbol else_; + + this(this.test, this.then, this.else_, super) { } + + override (Symbol | fail Error) compile(Context context) { + /** + * - make label + * - LabelIfScope + * - preallocate var + * if (true) { + * var = then.case(:else: breakelse) + * } else { + * var = else_ + * } + * - var + */ + auto ifLabel = context.getLabel; + auto context = context.withNamespace(new LabelIfScope(ifLabel, hasElse=true, context.namespace)); + (Expression | fail Error) test() => this.test + .case(null: return new BoolLiteral(true)) + .compile(context)? + .beExpressionImplCall(context, this.test.locRange)? + .(truthy(context, that, this.test.locRange)?); + + auto test = test?; + auto then = this.then.compile(context)?.beExpressionImplCall(context, this.then.locRange)?; + auto else_ = this.else_.compile(context)?.beExpressionImplCall(context, this.else_.locRange)?; + // won't be visible post-merge + bool elseIsBottom = !!else_.type.instanceOf(Bottom); + auto merger = new TypeMerger; + merger.add(then, __RANGE__, context)?; + merger.add(else_, __RANGE__, context)?; + auto mergedType = merger.type(context).notNull; + /** + * uninitialized mergedType result; + * if (test) { result = then; } + * else { result = else_; } + */ + auto lifetimeThen = then if elseIsBottom else then.take(context, __RANGE__)?; + auto thenConvert = expectImplicitConvertTo( + context, lifetimeThen, mergedType, this.locRange)?; + auto elseConvert = expectImplicitConvertTo(context, else_, mergedType, this.locRange)?; + // If else is bottom, we can just use the lifetime of `then` directly. + auto lifetime = then.info.lifetime if elseIsBottom else Lifetime.gifted; + auto result = new PairedTemporary(mergedType, lifetime, context.getUniqueId); + auto init = new UninitializeTemporaryStatement(result); + auto ifTrue = new AssignStatement(result, thenConvert, __RANGE__); + auto ifFalse = new AssignStatement(result, elseConvert, __RANGE__); + auto ifStmt = new IfStatement(ifLabel, test=test, then=ifTrue, else_=ifFalse, __RANGE__); + return context.compiler.(wrap(sequence(init, ifStmt), result, null)); + } + + override string repr() { + if (test) return "$then if $test else $else_"; + return "$then else $else_"; + } +} diff --git a/src/std/macro/listcomprehension.nt b/src/std/macro/listcomprehension.nt index ecbf1fe..1ec8ee0 100644 --- a/src/std/macro/listcomprehension.nt +++ b/src/std/macro/listcomprehension.nt @@ -62,8 +62,6 @@ class ASTListComprehension : ASTSymbol mut auto type = astType.compile(context)?; bool defaultIsNullExpr() { - // TODO - // return default_?.else(return false) return default_.case(null: return false) .(compiler.destructAstIdentifier(that)) .case(string name: name == "null", :none: false); @@ -425,14 +423,14 @@ class ListComprehension : Macro } } parser.expect("in")?; - auto source = compiler.parseExpression(parser, lexicalContext)?; + auto source = compiler.parseArithmetic(parser, lexicalContext)?; if (!source) { return parser.fail("source expression expected"); } auto source = notNull!ASTSymbol(source); mut nullable ASTSymbol where; if (parser.acceptIdentifier("where")) { - where = compiler.parseExpression(parser, lexicalContext)?; + where = compiler.parseArithmetic(parser, lexicalContext)?; if (!where) { return parser.fail("where expression expected"); } diff --git a/test/runnable/breakelse.nt b/test/runnable/breakelse.nt index 3022b71..f4e5249 100644 --- a/test/runnable/breakelse.nt +++ b/test/runnable/breakelse.nt @@ -35,7 +35,7 @@ void findtest() { } else { assert(false); } - assert("Helloworld".find("owo").else(return) == 4); + assert("Helloworld".find("owo").(that else return) == 4); } void main() { diff --git a/test/runnable/eithertest.nt b/test/runnable/eithertest.nt index 6e54f02..52a3ced 100644 --- a/test/runnable/eithertest.nt +++ b/test/runnable/eithertest.nt @@ -121,7 +121,7 @@ void testObjHack() { assert(test3(new Foo)); void test4(nullable Baz nullableBaz, (Foo | bool) outcome) { - assert(nullableBaz?.foo.else(false) == outcome); + assert((nullableBaz?.foo? else false) == outcome); } auto foo = new Foo; test4(null, false); diff --git a/test/runnable/lifetime.nt b/test/runnable/lifetime.nt index 850326f..b3e95b1 100644 --- a/test/runnable/lifetime.nt +++ b/test/runnable/lifetime.nt @@ -608,7 +608,7 @@ void test_else_prop() string trace; { nullable C c = new C(S("S", &trace, 0)); - auto value = c.else(false); + auto value = c? else false; assert(trace == "+S1 -S0"); } assert(trace == "+S1 -S0 -S1"); @@ -616,13 +616,13 @@ void test_else_prop() { string trace; (int | :else) test = :else; - test.else(new C(S("S", &trace, 0))); + test? else new C(S("S", &trace, 0)); assert(trace == "+S1 -S0 -S1"); } { string trace; (int | :else) test = 0; - test.else(new C(S("S", &trace, 0))); + test? else new C(S("S", &trace, 0)); // else block is never evaluated assert(trace == ""); }