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 \
include/console.h trainscript/common.h trainscript/tsvm.hpp \
include/ker/string.hpp include/ker/vector.hpp include/ker/new.hpp \
include/ker/dictionary.hpp include/ker/pair.hpp trainscript/typeid.hpp \
trainscript/trainscript.tab.hpp trainscript/trainscript.l.h \
include/string.h
include/ker/dictionary.hpp include/kernel.h include/ker/pair.hpp \
trainscript/typeid.hpp trainscript/trainscript.tab.hpp \
trainscript/trainscript.l.h include/string.h
$(CXX) $(FLAGS) $(CXXFLAGS) -o $@ -c trainscript/tsvm.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 \
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/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
# 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 \
trainscript/tsvm.hpp include/console.h include/ker/string.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
# obj/trainscript.tab.cpp
obj/trainscript.tab.o: obj/trainscript.tab.cpp include/stdlib.h \
include/varargs.h trainscript/common.h trainscript/tsvm.hpp \
include/console.h include/ker/string.hpp include/ker/vector.hpp \
include/ker/new.hpp include/ker/dictionary.hpp include/ker/pair.hpp \
trainscript/typeid.hpp trainscript/trainscript.l.h include/string.h
include/ker/new.hpp include/ker/dictionary.hpp include/kernel.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
# asm/intr_common_handler.S

View file

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

View file

@ -32,12 +32,6 @@ namespace ker
other.mLength = 0;
}
String & operator = (const String &other)
{
this->copyFrom(other.mText, other.mLength);
return *this;
}
String(const char *text) :
mText(nullptr),
mLength(0)
@ -52,6 +46,12 @@ namespace ker
this->copyFrom(bytes, length);
}
String & operator = (const String &other)
{
this->copyFrom(other.mText, other.mLength);
return *this;
}
~String()
{
if(this->mText != nullptr) {
@ -72,6 +72,21 @@ namespace ker
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
{
static const uint8_t empty[] = { 0 };
@ -128,4 +143,15 @@ namespace ker
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
BEGIN
afraid(15, 34) i;
0 -> i;
WHILE i < 5 DO
BEGIN
print(i);
i + 1 -> i;
fun(1) -> i;
END
print(factorial(4), fibonacci(8), problem1(10));
REPEAT
PUB fun() -> i : INT
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
10 -> i;
END

View file

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

View file

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

View file

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

View file

@ -1,9 +1,66 @@
VAR global : INT;
VAR res : INT;
PUB main(x : INT) c : INT
PUB main() | i : INT
BEGIN
# factorial(10) res;
# fibonacci(7) res;
problem1(1000) res;
afraid(15, 34) 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
# 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

View file

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

View file

@ -28,6 +28,17 @@ namespace trainscript
const Variable Variable::Text = { Type::Text, 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)
{
void *internalStorage = malloc(length);
@ -85,28 +96,78 @@ namespace trainscript
context.add(var.first, var.second);
}
if(this->returnValue.second.type.usable()) {
context.add(this->returnValue.first, &this->returnValue.second);
Variable returnVariable = {
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;
}
for(size_t i = 0; i < this->arguments.length(); i++) {
if(this->arguments[i].second.type != arguments[i].type) {
for(size_t i = 0; i < this->mArguments.length(); i++) {
if(this->mArguments[i].second != arguments[i].type) {
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) {
context.add(local.first, new Variable(local.second));
for(auto local : this->mLocals) {
context.add(local.first, new Variable { local.second, 0 });
}
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
{

View file

@ -5,15 +5,10 @@ extern "C" {
#include <console.h>
}
// #include <map>
#include <ker/string.hpp>
#include <ker/vector.hpp>
#include <ker/dictionary.hpp>
// #include <vector>
// #include <string.h>
#include "typeid.hpp"
namespace trainscript
@ -144,6 +139,15 @@ namespace trainscript
virtual ~Instruction() { }
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 :
@ -156,6 +160,15 @@ namespace trainscript
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 {
for(auto *instr : instructions) {
instr->execute(context);
@ -168,7 +181,14 @@ namespace trainscript
{
public:
virtual ~Method() { }
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 :
@ -178,9 +198,9 @@ namespace trainscript
Module *module;
Instruction *block;
bool isPublic;
ker::Vector<ker::Pair<ker::String, Variable>> arguments;
ker::Dictionary<ker::String, Variable> locals;
ker::Pair<ker::String, Variable> returnValue;
ker::Vector<ker::Pair<ker::String, Type>> mArguments;
ker::Dictionary<ker::String, Type> mLocals;
ker::Pair<ker::String, Type> mReturnValue;
ScriptMethod(Module *module, Instruction *block) : module(module), block(block)
{
@ -188,6 +208,22 @@ namespace trainscript
}
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
@ -208,6 +244,8 @@ namespace trainscript
{
return this->variables.get(name);
}
bool validate(ker::String &errorCode) const;
};
class VM
@ -231,9 +269,13 @@ namespace trainscript
Variable value;
ConstantExpression(Variable value) : value(value) { }
Variable execute(LocalContext &context) const override {
Variable execute(LocalContext &) const override {
return this->value;
}
Type expectedResult(LocalContext &) const override {
return this->value.type;
}
};
class VariableExpression :
@ -250,6 +292,24 @@ namespace trainscript
}
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;
}
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 :
@ -320,6 +417,38 @@ namespace trainscript
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)>
@ -353,6 +482,47 @@ namespace trainscript
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 :