Skip to content

Commit

Permalink
Add breakelse, :else propagation and .else().
Browse files Browse the repository at this point in the history
  • Loading branch information
FeepingCreature committed Sep 30, 2023
1 parent 16dd6a8 commit fe3a998
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/neat/compiler.nt
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@ class CompilerImpl : CompilerBase

override Statement ifStatement(Expression test, Statement then, nullable Statement else_)
{
return new IfStatement(test, then, else_, :none);
return new IfStatement(:none, test, then, else_, :none);
}

override Statement loopStatement(
Expand Down
39 changes: 37 additions & 2 deletions src/neat/statements.nt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class ReturnStatement : Statement

class IfStatement : Statement
{
(string | :none) label;

Expression test;

Statement then;
Expand All @@ -77,7 +79,7 @@ class IfStatement : Statement

(LocRange | :none) locRange;

this(this.test, this.then, this.else_, this.locRange) { }
this(this.label, this.test, this.then, this.else_, this.locRange) { }

override void emit(Generator output)
{
Expand All @@ -87,7 +89,7 @@ class IfStatement : Statement
}
int reg = this.test.emit(output);

string label = output.fun.getLabel;
string label = this.label.case(:none: output.fun.getLabel);
if (this.else_)
output.fun.testBranch(reg, label ~ "_then", label ~ "_else");
else
Expand Down Expand Up @@ -120,6 +122,23 @@ class IfStatement : Statement
}
}

class LabelIfScope : IfScope
{
string label;

bool hasElse;

this(this.label, this.hasElse, this.parent) { this.isContextScope = true; }

override (Statement | fail Error) breakElseFrom(Context context, LocRange locRange) {
auto branch = new Branch(this.label ~ ("_else" if hasElse else "_fin"), locRange);

return sequence2(context.compiler.unwindScope(context, this, new NoopStatement)?, branch);
}

override string repr() => "LabelIfScope";
}

class ASTBreak : ASTSymbol
{
this(this.locRange) { }
Expand Down Expand Up @@ -152,6 +171,22 @@ class ASTContinue : ASTSymbol
override string repr() { return "continue"; }
}

class ASTBreakElse : ASTSymbol
{
this(this.locRange) { }

override (Symbol | fail Error) compile(Context context)
{
auto ifScope = findParent!IfScope(context.namespace);
locRange.assert(!!ifScope, () => "Cannot 'breakelse': not in an if statement.")?;
auto stmt = ifScope.breakElseFrom(context, locRange)?;

return new StatementExpression(stmt, new UnreachableExpr, gifted=false);
}

override string repr() { return "breakelse"; }
}

class Branch : Statement
{
string label;
Expand Down
46 changes: 41 additions & 5 deletions src/neat/stuff.nt
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,13 @@ class ASTPropagateFailureExpr : ASTSymbol
for (k, v in either.types) {
ASTSymbol expr() {
auto a = new ASTIdentifier("a", false, __RANGE__);
// Aaaaaaaugh!
// if (v.type.instanceOf(SymbolIdentifierType).case(null: breakelse).name == "else") {
if (auto asIdentifierType = v.type.instanceOf(SymbolIdentifierType)) {
if (asIdentifierType.name == "else") {
return new ASTBreakElse(__RANGE__);
}
}
if (v.fail) return new ASTReturn(a, __RANGE__);
return a;
}
Expand Down Expand Up @@ -1278,6 +1285,26 @@ class ASTPropagateFailureExpr : ASTSymbol
return new ASTPropagateFailureExpr(left, parser.to(from));
}

// expr.else(expr)
(nullable ASTSymbol | fail Error) parseElseExpr(Parser parser, LexicalContext lexicalContext, ASTSymbol current) {
parser.begin;
if (!(parser.acceptToken(TokenType.dot)
&& parser.acceptIdentifier("else")
&& parser.acceptToken(TokenType.lparen)))
{
parser.revert;
return null;
}
auto from = parser.from;
parser.commit;

auto elseExpr = lexicalContext.compiler.parseExpression(parser, lexicalContext)?;
parser.to(from).assert(!!elseExpr, () => "expression expected")?;
auto elseExpr = elseExpr.notNull;
parser.expectToken(TokenType.rparen)?;
return lexicalContext.compiler.$expr $current.case(:else: $elseExpr);
}

(nullable ASTSymbol | fail Error) parseProperties(ParserImpl parser, LexicalContext lexicalContext)
{
mut (:inc | :dec | :none) preincdec = :none, postincdec = :none;
Expand Down Expand Up @@ -1325,6 +1352,10 @@ 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;
Expand Down Expand Up @@ -2326,6 +2357,9 @@ class ASTIfStatement : ASTStatement
mut nullable Statement prelude; // var decl statement
mut nullable Expression test;
mut nullable Statement epilog; // either decl value assignment
auto ifLabel = context.getLabel;
auto ifLabelNamespace = new LabelIfScope(ifLabel, hasElse=!!this.else_, context.namespace);
auto context = context.withNamespace(ifLabelNamespace);
mut auto testSucceedsNamespace = context.namespace;
this.test.case {
ASTSymbol symbol: {
Expand Down Expand Up @@ -2431,7 +2465,7 @@ class ASTIfStatement : ASTStatement
}

auto test = truthy(context, test.notNull, this.locRange)?;
auto ifStmt = new IfStatement(test, sequence2(epilog, then), else_, this.locRange);
auto ifStmt = new IfStatement(ifLabel, test, sequence2(epilog, then), else_, this.locRange);

return StatementCompileResult(sequence2(prelude, ifStmt), context.namespace);
}
Expand Down Expand Up @@ -2467,16 +2501,18 @@ class ASTIfStatement : ASTStatement
parser.begin;
auto from = parser.from;
string identifier = parser.parseIdentifier;
if (identifier == "break")
{
if (identifier == "break") {
parser.commit;
return new ASTBreak(parser.to(from));
}
if (identifier == "continue")
{
if (identifier == "continue") {
parser.commit;
return new ASTContinue(parser.to(from));
}
if (identifier == "breakelse") {
parser.commit;
return new ASTBreakElse(parser.to(from));
}
parser.revert;
return null;
}
Expand Down
18 changes: 16 additions & 2 deletions src/neat/util.nt
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,27 @@ class ASTImportStatement : ASTImportStatementBase
}

/**
* Delineates a region of control flow in which 'break;' and 'continue;' may be used.
* Delineates a region of control flow in which 'break' and 'continue' may be used.
*/
abstract class LoopScope : Namespace
{
abstract (Statement | fail Error) breakFrom(Context context, LocRange locRange);
abstract (Statement | fail Error) continueFrom(Context context, LocRange locRange);
// purely a marker
// this class is purely a marker
override (nullable Symbol | fail Error) lookup(
string name, Context context, LookupReason reason, LocRange locRange)
{
return this.parent.lookup(name, context, reason, locRange);
}
}

/**
* Delineates a region of control flow in which 'breakelse' may be used.
*/
abstract class IfScope : Namespace
{
abstract (Statement | fail Error) breakElseFrom(Context context, LocRange locRange);
// this class is purely a marker
override (nullable Symbol | fail Error) lookup(
string name, Context context, LookupReason reason, LocRange locRange)
{
Expand Down
2 changes: 1 addition & 1 deletion src/std/argparse.nt
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ unittest
}
}

alias assertEqual = (a, b) => assert(a == b);
private alias assertEqual = (a, b) => assert(a == b);

/**
* The kind of a command-line argument. This determines how many further
Expand Down
44 changes: 44 additions & 0 deletions test/runnable/breakelse.nt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module breakelse;

macro import std.macro.assert;

void basetest() {
void check((bool | :test) value, bool ifTaken) {
if (bool b = value.case(:test: breakelse)) {
assert(ifTaken);
} else {
assert(!ifTaken);
}

if (bool b = value.case(:test: breakelse)) {
assert(ifTaken);
return;
}
assert(!ifTaken);
}
check(value=false, ifTaken=false);
check(value=true, ifTaken=true);
check(value=:test, ifTaken=false);
}

void findtest() {
(size_t | :else) find(string text, string marker) {
for (mut size_t i = 0; i <= text.length - marker.length; i++) {
if (text[i .. i + marker.length] == marker)
return i;
}
return :else;
}

if (size_t pos = "Helloworld".find("owo")?) {
assert(pos == 4);
} else {
assert(false);
}
assert("Helloworld".find("owo").else(return) == 4);
}

void main() {
basetest;
findtest;
}

0 comments on commit fe3a998

Please sign in to comment.