Adds semantic language validation.

This commit is contained in:
Felix Queissner 2015-08-14 17:12:08 +02:00
parent 3e2bf8ff10
commit 620f337bc2
11 changed files with 399 additions and 128 deletions

View file

@ -77,9 +77,9 @@ obj/vmm.o: src/vmm.c include/vmm.h include/pmm.h include/multiboot.h \
obj/tsvm.o: trainscript/tsvm.cpp include/stdlib.h include/varargs.h \ obj/tsvm.o: trainscript/tsvm.cpp include/stdlib.h include/varargs.h \
include/console.h trainscript/common.h trainscript/tsvm.hpp \ include/console.h trainscript/common.h trainscript/tsvm.hpp \
include/ker/string.hpp include/ker/vector.hpp include/ker/new.hpp \ include/ker/string.hpp include/ker/vector.hpp include/ker/new.hpp \
include/ker/dictionary.hpp include/ker/pair.hpp trainscript/typeid.hpp \ include/ker/dictionary.hpp include/kernel.h include/ker/pair.hpp \
trainscript/trainscript.tab.hpp trainscript/trainscript.l.h \ trainscript/typeid.hpp trainscript/trainscript.tab.hpp \
include/string.h trainscript/trainscript.l.h include/string.h
$(CXX) $(FLAGS) $(CXXFLAGS) -o $@ -c trainscript/tsvm.cpp $(CXX) $(FLAGS) $(CXXFLAGS) -o $@ -c trainscript/tsvm.cpp
# src/cplusplus.cpp # src/cplusplus.cpp
@ -91,7 +91,7 @@ obj/cplusplus.o: src/cplusplus.cpp include/stdlib.h include/varargs.h \
obj/vm.o: src/vm.cpp include/stdlib.h include/varargs.h include/timer.h \ obj/vm.o: src/vm.cpp include/stdlib.h include/varargs.h include/timer.h \
src/../trainscript/tsvm.hpp include/console.h include/ker/string.hpp \ src/../trainscript/tsvm.hpp include/console.h include/ker/string.hpp \
include/ker/vector.hpp include/ker/new.hpp include/ker/dictionary.hpp \ include/ker/vector.hpp include/ker/new.hpp include/ker/dictionary.hpp \
include/ker/pair.hpp src/../trainscript/typeid.hpp include/kernel.h include/ker/pair.hpp src/../trainscript/typeid.hpp
$(CXX) $(FLAGS) $(CXXFLAGS) -o $@ -c src/vm.cpp $(CXX) $(FLAGS) $(CXXFLAGS) -o $@ -c src/vm.cpp
# obj/trainscript.yy.cpp # obj/trainscript.yy.cpp
@ -99,15 +99,17 @@ obj/trainscript.yy.o: obj/trainscript.yy.cpp include/string.h \
include/stdlib.h include/varargs.h trainscript/common.h \ include/stdlib.h include/varargs.h trainscript/common.h \
trainscript/tsvm.hpp include/console.h include/ker/string.hpp \ trainscript/tsvm.hpp include/console.h include/ker/string.hpp \
include/ker/vector.hpp include/ker/new.hpp include/ker/dictionary.hpp \ include/ker/vector.hpp include/ker/new.hpp include/ker/dictionary.hpp \
include/ker/pair.hpp trainscript/typeid.hpp obj/trainscript.tab.hpp include/kernel.h include/ker/pair.hpp trainscript/typeid.hpp \
obj/trainscript.tab.hpp
$(CXX) -iquotetrainscript $(FLAGS) $(CXXFLAGS) -o $@ -c obj/trainscript.yy.cpp $(CXX) -iquotetrainscript $(FLAGS) $(CXXFLAGS) -o $@ -c obj/trainscript.yy.cpp
# obj/trainscript.tab.cpp # obj/trainscript.tab.cpp
obj/trainscript.tab.o: obj/trainscript.tab.cpp include/stdlib.h \ obj/trainscript.tab.o: obj/trainscript.tab.cpp include/stdlib.h \
include/varargs.h trainscript/common.h trainscript/tsvm.hpp \ include/varargs.h trainscript/common.h trainscript/tsvm.hpp \
include/console.h include/ker/string.hpp include/ker/vector.hpp \ include/console.h include/ker/string.hpp include/ker/vector.hpp \
include/ker/new.hpp include/ker/dictionary.hpp include/ker/pair.hpp \ include/ker/new.hpp include/ker/dictionary.hpp include/kernel.h \
trainscript/typeid.hpp trainscript/trainscript.l.h include/string.h include/ker/pair.hpp trainscript/typeid.hpp trainscript/trainscript.l.h \
include/string.h
$(CXX) -iquotetrainscript $(FLAGS) $(CXXFLAGS) -o $@ -c obj/trainscript.tab.cpp $(CXX) -iquotetrainscript $(FLAGS) $(CXXFLAGS) -o $@ -c obj/trainscript.tab.cpp
# asm/intr_common_handler.S # asm/intr_common_handler.S

View file

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <kernel.h>
#include "pair.hpp" #include "pair.hpp"
#include "vector.hpp" #include "vector.hpp"
@ -10,7 +12,8 @@ namespace ker
{ {
public: public:
typedef Pair<Key, Value> Entry; typedef Pair<Key, Value> Entry;
public: static Value _default;
public:
Vector<Entry> contents; Vector<Entry> contents;
public: public:
Dictionary() : Dictionary() :
@ -45,16 +48,21 @@ namespace ker
return pair.second; return pair.second;
} }
} }
die("Key not found in dictionary.");
return _default;
} }
const Value &at(const Key &key) const const Value &at(const Key &key) const
{ {
static Value _default;
for(auto &&pair : this->contents) for(auto &&pair : this->contents)
{ {
if(pair.first == key) { if(pair.first == key) {
return pair.second; return pair.second;
} }
} }
die("Key not found in dictionary.");
return _default;
} }
Value get(const Key &key) const Value get(const Key &key) const
@ -122,4 +130,7 @@ namespace ker
return this->contents.end(); return this->contents.end();
} }
}; };
template<typename Key, typename Value>
Value Dictionary<Key, Value>::_default = Value();
} }

View file

@ -30,13 +30,7 @@ namespace ker
{ {
other.mText = nullptr; other.mText = nullptr;
other.mLength = 0; other.mLength = 0;
} }
String & operator = (const String &other)
{
this->copyFrom(other.mText, other.mLength);
return *this;
}
String(const char *text) : String(const char *text) :
mText(nullptr), mText(nullptr),
@ -52,6 +46,12 @@ namespace ker
this->copyFrom(bytes, length); this->copyFrom(bytes, length);
} }
String & operator = (const String &other)
{
this->copyFrom(other.mText, other.mLength);
return *this;
}
~String() ~String()
{ {
if(this->mText != nullptr) { if(this->mText != nullptr) {
@ -72,6 +72,21 @@ namespace ker
return memcmp(this->mText, other.mText, this->mLength) == 0; return memcmp(this->mText, other.mText, this->mLength) == 0;
} }
String append(const String &other) const
{
uint8_t *data = (uint8_t*)malloc(this->mLength + other.mLength);
memcpy(&data[0], this->mText, this->mLength);
memcpy(&data[this->mLength], other.mText, other.mLength);
String cat(data, this->mLength + other.mLength);
free(data);
return cat;
}
static String concat(const String &lhs, const String &rhs)
{
return lhs.append(rhs);
}
const uint8_t *text() const const uint8_t *text() const
{ {
static const uint8_t empty[] = { 0 }; static const uint8_t empty[] = { 0 };
@ -128,4 +143,15 @@ namespace ker
this->mText[this->mLength] = 0; // last byte is always 0 this->mText[this->mLength] = 0; // last byte is always 0
} }
}; };
static String operator + (const char *lhs, const String &rhs)
{
return String::concat(lhs, rhs);
}
static String operator + (const String &lhs, const char *rhs)
{
return String::concat(lhs, rhs);
}
}; };

View file

@ -2,65 +2,10 @@ VAR global : INT;
PUB main() | i : INT PUB main() | i : INT
BEGIN BEGIN
afraid(15, 34) i; fun(1) -> i;
0 -> i;
WHILE i < 5 DO
BEGIN
print(i);
i + 1 -> i;
END
print(factorial(4), fibonacci(8), problem1(10));
REPEAT
BEGIN
print(1);
sleep'(5);
END
END END
# Sleep implementation PUB fun() -> i : INT
PRI sleep'(time : INT) | init : INT
BEGIN BEGIN
timer_get() init; 10 -> i;
WHILE (timer_get() - init) < time DO
0; # do nothing
END
# Calculates factorial (number!)
PRI factorial(number : INT) result : INT
BEGIN
IF number > 1 THEN
number * factorial(number - 1) result;
ELSE
1 result;
END
# Recursive test
PRI fibonacci(n : INT) f : INT
BEGIN
IF n = 0 THEN
0 f;
ELSEIF n = 1 THEN
1 f;
ELSE
fibonacci(n - 1) + fibonacci(n - 2) f;
END
# If we list all the natural numbers below 10 that are
# multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of
# these multiples is 23.
# Find the sum of all the multiples of 3 or 5 below 1000.
PRI problem1(max : INT) r : INT | iter : INT
BEGIN
1 iter;
0 r;
WHILE iter < max DO
BEGIN
IF (iter % 5) = 0 THEN
r + iter r;
ELSEIF (iter % 3) = 0 THEN
r + iter r;
iter + 1 iter;
END
END END

View file

@ -183,7 +183,6 @@ void kprintf(const char *format, ...)
{ {
c = *(format++); c = *(format++);
int i; int i;
float f;
char *str; char *str;
switch(c) switch(c)
{ {

View file

@ -13,13 +13,6 @@ extern "C" {
void cpp_test(); void cpp_test();
class PrintMethod :
public Method
{
public:
Variable invoke(Vector<Variable> arguments) override;
};
class NativeMethod : class NativeMethod :
public Method public Method
{ {
@ -33,18 +26,24 @@ public:
} }
Variable invoke(Vector<Variable> arguments) override; Variable invoke(Vector<Variable> arguments) override;
};
Variable PrintMethod::invoke(Vector<Variable> arguments) bool validate(ker::String &errorCode) const
{ {
for(auto &var : arguments) if(this->function == nullptr) {
{ errorCode = "Native method with nullptr interface.";
var.printval(); return false;
kprintf(" "); }
} return true;
kprintf("\n"); }
return Variable::Void;
} Vector<Type> arguments() const {
return Vector<Type>();
}
Type returnType() const {
return Type::Int;
}
};
static void copyCode(uint8_t **ptr, const uint8_t *src, size_t length) static void copyCode(uint8_t **ptr, const uint8_t *src, size_t length)
{ {
@ -169,9 +168,11 @@ extern "C" void vm_start()
kprintf("Module successfully loaded :)\n"); kprintf("Module successfully loaded :)\n");
module->methods.add("print", new PrintMethod()); String errorCode;
if(module->validate(errorCode) == false) {
module->methods.add("afraid", new NativeMethod(reinterpret_cast<void*>(&cCodeFunction))); kprintf("Module validation failed: %s\n", errorCode.str());
return;
}
NativeModuleDef *mod = methods; NativeModuleDef *mod = methods;
while(mod->name != nullptr) { while(mod->name != nullptr) {

View file

@ -39,7 +39,7 @@ struct VariableDeclaration
struct LocalVariable struct LocalVariable
{ {
char *name; char *name;
trainscript::Variable variable; trainscript::Type type;
LocalVariable *next; LocalVariable *next;
}; };

View file

@ -1,9 +1,66 @@
VAR global : INT;
VAR res : INT; PUB main() | i : INT
PUB main(x : INT) c : INT
BEGIN BEGIN
# factorial(10) res; afraid(15, 34) i;
# fibonacci(7) res; 0 -> i;
problem1(1000) res; WHILE i < 5 DO
BEGIN
print(i);
i + 1 -> i;
END
print(factorial(4), fibonacci(8), problem1(10));
REPEAT
BEGIN
print(1);
sleep'(5);
END
END
# Sleep implementation
PRI sleep'(time : INT) | init : INT
BEGIN
timer_get() init;
WHILE (timer_get() - init) < time DO
0; # do nothing
END
# Calculates factorial (number!)
PRI factorial(number : INT) result : INT
BEGIN
IF number > 1 THEN
number * factorial(number - 1) result;
ELSE
1 result;
END
# Recursive test
PRI fibonacci(n : INT) f : INT
BEGIN
IF n = 0 THEN
0 f;
ELSEIF n = 1 THEN
1 f;
ELSE
fibonacci(n - 1) + fibonacci(n - 2) f;
END
# If we list all the natural numbers below 10 that are
# multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of
# these multiples is 23.
# Find the sum of all the multiples of 3 or 5 below 1000.
PRI problem1(max : INT) r : INT | iter : INT
BEGIN
1 iter;
0 r;
WHILE iter < max DO
BEGIN
IF (iter % 5) = 0 THEN
r + iter r;
ELSEIF (iter % 3) = 0 THEN
r + iter r;
iter + 1 iter;
END
END END

View file

@ -154,18 +154,18 @@ input:
ScriptMethod *method = new ScriptMethod(mod, $2.body); ScriptMethod *method = new ScriptMethod(mod, $2.body);
method->isPublic = $2.header.isPublic; method->isPublic = $2.header.isPublic;
if($2.header.returnValue) { if($2.header.returnValue) {
method->returnValue = ker::Pair<ker::String, Variable>( method->mReturnValue = ker::Pair<ker::String, Type>(
$2.header.returnValue->name, $2.header.returnValue->name,
$2.header.returnValue->variable); $2.header.returnValue->type);
} }
LocalVariable *local = $2.header.locals; LocalVariable *local = $2.header.locals;
while(local) { while(local) {
method->locals.add( local->name, local->variable ); method->mLocals.add( local->name, local->type );
local = local->next; local = local->next;
} }
LocalVariable *arg = $2.header.arguments; LocalVariable *arg = $2.header.arguments;
while(arg) { while(arg) {
method->arguments.append( { arg->name, arg->variable } ); method->mArguments.append( { arg->name, arg->type} );
arg = arg->next; arg = arg->next;
} }
@ -277,8 +277,7 @@ argument:
IDENTIFIER COLON typeName { IDENTIFIER COLON typeName {
$$ = new LocalVariable(); $$ = new LocalVariable();
$$->name = $1; $$->name = $1;
$$->variable.type = $3; $$->type = $3;
$$->variable.integer = 0; // zero value
$$->next = nullptr; $$->next = nullptr;
} }
; ;

View file

@ -28,6 +28,17 @@ namespace trainscript
const Variable Variable::Text = { Type::Text, 0 }; const Variable Variable::Text = { Type::Text, 0 };
const Variable Variable::Boolean = { Type::Boolean, 0 }; const Variable Variable::Boolean = { Type::Boolean, 0 };
bool Module::validate(ker::String &errorCode) const
{
errorCode = "";
for(auto method : this->methods) {
if(method.second->validate(errorCode) == false) {
return false;
}
}
return true;
}
Module *VM::load(const void *buffer, size_t length) Module *VM::load(const void *buffer, size_t length)
{ {
void *internalStorage = malloc(length); void *internalStorage = malloc(length);
@ -85,28 +96,78 @@ namespace trainscript
context.add(var.first, var.second); context.add(var.first, var.second);
} }
if(this->returnValue.second.type.usable()) { Variable returnVariable = {
context.add(this->returnValue.first, &this->returnValue.second); this->mReturnValue.second, 0
};
if(this->mReturnValue.second.usable()) {
context.add(this->mReturnValue.first, &returnVariable);
} }
if(arguments.length() != this->arguments.length()) { if(arguments.length() != this->mArguments.length()) {
return Variable::Invalid; return Variable::Invalid;
} }
for(size_t i = 0; i < this->arguments.length(); i++) { for(size_t i = 0; i < this->mArguments.length(); i++) {
if(this->arguments[i].second.type != arguments[i].type) { if(this->mArguments[i].second != arguments[i].type) {
return Variable::Invalid; return Variable::Invalid;
} }
context.add(this->arguments[i].first, new Variable(arguments[i])); context.add(this->mArguments[i].first, new Variable(arguments[i]));
} }
for(auto local : this->locals) { for(auto local : this->mLocals) {
context.add(local.first, new Variable(local.second)); context.add(local.first, new Variable { local.second, 0 });
} }
this->block->execute(context); this->block->execute(context);
return this->returnValue.second; return returnVariable;
} }
bool ScriptMethod::validate(ker::String &errorCode) const
{
if(this->block == nullptr) {
errorCode = "Method block is not set.";
return false;
}
LocalContext context(this->module);
for(auto var : this->module->variables)
{
context.add(var.first, var.second);
}
Variable returnVariable = {
this->mReturnValue.second, 0
};
if(this->mReturnValue.second.usable()) {
if(context.get(this->mReturnValue.first) != nullptr) {
errorCode = "Return variable overlaps a variable.";
return false;
}
context.add(this->mReturnValue.first, &returnVariable);
}
for(size_t i = 0; i < this->mArguments.length(); i++) {
if(context.get(this->mArguments[i].first) != nullptr) {
errorCode = "Parameter overlaps a variable.";
return false;
}
context.add(this->mArguments[i].first, new Variable { this->mArguments[i].second, 0 });
}
for(auto local : this->mLocals) {
if(context.get(local.first) != nullptr) {
errorCode = "Local variable overlaps a variable.";
return false;
}
context.add(local.first, new Variable { local.second, 0 });
}
if(this->block->validate(context, errorCode) == false) {
return false;
}
return true;
}
namespace ops namespace ops
{ {

View file

@ -5,15 +5,10 @@ extern "C" {
#include <console.h> #include <console.h>
} }
// #include <map>
#include <ker/string.hpp> #include <ker/string.hpp>
#include <ker/vector.hpp> #include <ker/vector.hpp>
#include <ker/dictionary.hpp> #include <ker/dictionary.hpp>
// #include <vector>
// #include <string.h>
#include "typeid.hpp" #include "typeid.hpp"
namespace trainscript namespace trainscript
@ -144,6 +139,15 @@ namespace trainscript
virtual ~Instruction() { } virtual ~Instruction() { }
virtual Variable execute(LocalContext &context) const = 0; virtual Variable execute(LocalContext &context) const = 0;
virtual Type expectedResult(LocalContext &) const {
return Type::Void;
}
virtual bool validate(LocalContext &, ker::String &errorCode) const {
errorCode = "";
return true;
}
}; };
class Block : class Block :
@ -156,6 +160,15 @@ namespace trainscript
for(auto *instr : instructions) delete instr; for(auto *instr : instructions) delete instr;
} }
bool validate(LocalContext &context, ker::String &errorCode) const {
errorCode = "";
for(auto *instr : instructions) {
if(instr->validate(context, errorCode) == false)
return false;
}
return true;
}
Variable execute(LocalContext &context) const override { Variable execute(LocalContext &context) const override {
for(auto *instr : instructions) { for(auto *instr : instructions) {
instr->execute(context); instr->execute(context);
@ -167,8 +180,15 @@ namespace trainscript
class Method class Method
{ {
public: public:
virtual ~Method() { } virtual ~Method() { }
virtual Variable invoke(ker::Vector<Variable> arguments) = 0;
virtual Variable invoke(ker::Vector<Variable> arguments) = 0;
virtual bool validate(ker::String &errorCode) const = 0;
virtual ker::Vector<Type> arguments() const = 0;
virtual Type returnType() const = 0;
}; };
class ScriptMethod : class ScriptMethod :
@ -178,9 +198,9 @@ namespace trainscript
Module *module; Module *module;
Instruction *block; Instruction *block;
bool isPublic; bool isPublic;
ker::Vector<ker::Pair<ker::String, Variable>> arguments; ker::Vector<ker::Pair<ker::String, Type>> mArguments;
ker::Dictionary<ker::String, Variable> locals; ker::Dictionary<ker::String, Type> mLocals;
ker::Pair<ker::String, Variable> returnValue; ker::Pair<ker::String, Type> mReturnValue;
ScriptMethod(Module *module, Instruction *block) : module(module), block(block) ScriptMethod(Module *module, Instruction *block) : module(module), block(block)
{ {
@ -188,6 +208,22 @@ namespace trainscript
} }
Variable invoke(ker::Vector<Variable> arguments) override; Variable invoke(ker::Vector<Variable> arguments) override;
bool validate(ker::String &errorCode) const override;
ker::Vector<Type> arguments() const override
{
ker::Vector<Type> args(this->mArguments.length());
for(size_t i = 0; i < args.length(); i++) {
args[i] = this->mArguments[i].second;
}
return args;
}
Type returnType() const override
{
return this->mReturnValue.second;
}
}; };
class Module class Module
@ -208,6 +244,8 @@ namespace trainscript
{ {
return this->variables.get(name); return this->variables.get(name);
} }
bool validate(ker::String &errorCode) const;
}; };
class VM class VM
@ -231,9 +269,13 @@ namespace trainscript
Variable value; Variable value;
ConstantExpression(Variable value) : value(value) { } ConstantExpression(Variable value) : value(value) { }
Variable execute(LocalContext &context) const override { Variable execute(LocalContext &) const override {
return this->value; return this->value;
} }
Type expectedResult(LocalContext &) const override {
return this->value.type;
}
}; };
class VariableExpression : class VariableExpression :
@ -250,6 +292,24 @@ namespace trainscript
} }
return *var; return *var;
} }
bool validate(LocalContext &context, ker::String &errorCode) const override {
errorCode = "";
if(context.get(this->variableName) == nullptr) {
errorCode = "Variable " + this->variableName + " not found.";
return false;
}
return true;
}
Type expectedResult(LocalContext &context) const override {
Variable *var = context.get(this->variableName);
if(var == nullptr) {
return Type::Invalid;
} else {
return var->type;
}
}
}; };
@ -290,6 +350,43 @@ namespace trainscript
return result; return result;
} }
bool validate(LocalContext &context, ker::String &errorCode) const override {
errorCode = "";
if(this->expression == nullptr) {
errorCode = "Missing expression.";
return false;
}
Type result = this->expression->expectedResult(context);
if(result == Type::Invalid) {
errorCode = "Expression returns invalid type.";
return false;
}
if(result == Type::Void) {
errorCode = "Void cannot be assigned to a variable";
return false;
}
Variable *var = context.get(this->variableName);
if(var == nullptr) {
errorCode = "Variable " + this->variableName + " not found.";
return false;
}
if(var->type != result) {
errorCode = "Variable assignment has invalid type.";
return false;
}
if(this->expression->validate(context, errorCode) == false)
return false;
return true;
}
Type expectedResult(LocalContext &context) const override {
if(this->expression == nullptr) {
return Type::Invalid;
} else {
return this->expression->expectedResult(context);
}
}
}; };
class MethodInvokeExpression : class MethodInvokeExpression :
@ -320,6 +417,38 @@ namespace trainscript
return method->invoke(vars); return method->invoke(vars);
} }
bool validate(LocalContext &context, ker::String &errorCode) const override {
Method *method = context.module->method(this->methodName.str());
if(method == nullptr) {
errorCode = "The method " + this->methodName + " does not exist.";
return false;
}
ker::Vector<Type> arguments = method->arguments();
if(arguments.length() != this->parameters.length()) {
errorCode = "Argument count mismatch.";
return false;
}
for(size_t i = 0; i < arguments.length(); i++) {
if(this->parameters[i]->expectedResult(context) != arguments[i]) {
errorCode = "Argument type mismatch.";
return false;
}
if(this->parameters[i]->validate(context, errorCode) == false)
return false;
}
return true;
}
Type expectedResult(LocalContext &context) const override {
Method *method = context.module->method(this->methodName.str());
if(method == nullptr) {
return Type::Invalid;
}
return method->returnType();
}
}; };
template<Variable (*OP)(Variable, Variable)> template<Variable (*OP)(Variable, Variable)>
@ -353,6 +482,47 @@ namespace trainscript
return OP(left, right); return OP(left, right);
} }
bool validate(LocalContext &context, ker::String &errorCode) const override {
errorCode = "";
if(this->lhs == nullptr) {
errorCode = "Left part of operand is missing.";
return false;
}
if(this->rhs == nullptr) {
errorCode = "Right part of operand is missing.";
return false;
}
Type lhsType = this->lhs->expectedResult(context);
Type rhsType = this->rhs->expectedResult(context);
if(lhsType != rhsType) {
errorCode = "Types of operands do not match.";
return false;
}
if(lhsType == Type::Invalid) {
errorCode = "Invalid type can't be used for operand.";
return false;
}
if(rhsType == Type::Void) {
errorCode = "VOID type can't be used for operand.";
return false;
}
if(this->lhs->validate(context, errorCode) == false)
return false;
if(this->rhs->validate(context, errorCode) == false)
return false;
return true;
}
Type expectedResult(LocalContext &context) const override {
if(this->lhs == nullptr) {
return Type::Invalid;
} else {
return this->lhs->expectedResult(context);
}
}
}; };
class IfExpression : class IfExpression :