-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
nullableObject? no longer returns null. Instead, it breakelses null.
Generally, null is treated equivalently to :else in breakelse features. This allows nullableObject?.else(...) and generally nullableObject? in if expressions. Also, .else now creates a breakelse targetable scope on its left.
- Loading branch information
1 parent
b501817
commit c355e74
Showing
8 changed files
with
187 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters