Skip to content

Commit

Permalink
Allow method calls on strings (#211)
Browse files Browse the repository at this point in the history
* Code improvements

* Rework runtime module mechanism

* Add test for string methods

* Add `contains(string)` method for strings

* Add `substring(int,int)` method for strings

* Add tests for `substring` method

* Fix #212

* Fix tests
  • Loading branch information
marcauberer committed Oct 10, 2022
1 parent 210f9b8 commit bc3a579
Show file tree
Hide file tree
Showing 52 changed files with 1,157 additions and 580 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ jobs:
mkdir ./llvm/build
cd ./llvm/build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS_RELEASE="-O2" -GNinja -Wno-dev -Wattributes ../llvm
cmake --build . -j4
cmake --build . -j %NUMBER_OF_PROCESSORS%
- name: Download Libs
run: .\setup-libs.bat
Expand All @@ -146,7 +146,7 @@ jobs:
mkdir ./bin
cd ./bin
cmake -GNinja -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DSPICE_LINK_STATIC=ON -DSPICE_VERSION="${{ steps.get_version.outputs.version }}" -DSPICE_BUILT_BY="ghactions" -DCMAKE_CXX_FLAGS_RELEASE="-O2" ..
cmake --build . --target spice -j$(nproc)
cmake --build . --target spice -j %NUMBER_OF_PROCESSORS%
- name: Process build output
working-directory: bin
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ spice
/coverage
/cmake-build-debug
/cmake-build-release
/cmake-build-relwithdebinfo

# Third party libs
/lib
Expand Down
2 changes: 1 addition & 1 deletion .run/spicetest.run.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="spicetest" type="CMakeGoogleTestRunConfigurationType" factoryName="Google Test" PROGRAM_PARAMS="true" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Spice" TARGET_NAME="spicetest" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="Spice" RUN_TARGET_NAME="spicetest" TEST_MODE="SUITE_TEST">
<configuration default="false" name="spicetest" type="CMakeGoogleTestRunConfigurationType" factoryName="Google Test" PROGRAM_PARAMS="false" REDIRECT_INPUT="false" ELEVATE="false" USE_EXTERNAL_CONSOLE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="Spice" TARGET_NAME="spicetest" CONFIG_NAME="Debug" RUN_TARGET_PROJECT_NAME="Spice" RUN_TARGET_NAME="spicetest" TEST_MODE="SUITE_TEST">
<envs>
<env name="RUN_TESTS" value="ON" />
<env name="SPICE_STD_DIR" value="$PROJECT_DIR$/std" />
Expand Down
2 changes: 1 addition & 1 deletion dev-setup.bat
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ REM - Build LLVM
echo [Step 3] Building LLVM (Could take a whole while, please be patient) ...
mkdir .\llvm\build-release
pushd .\llvm\build-release
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS_RELEASE="-O2" -GNinja ../llvm
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS_RELEASE="-O2" -GNinja ../llvm
cmake --build .
popd
echo done.
Expand Down
2 changes: 1 addition & 1 deletion dev-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ colored_echo "[Step 3] Building LLVM (Could take a whole while, please be patien
mkdir ./llvm/build-release 2>/dev/null
(
cd ./llvm/build-release || exit
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS_RELEASE="-O2" -GNinja ../llvm
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS_RELEASE="-O2" -GNinja ../llvm
cmake --build .
)
colored_echo "done."
Expand Down
56 changes: 17 additions & 39 deletions media/test-project/os-test.spice
Original file line number Diff line number Diff line change
@@ -1,43 +1,21 @@
type Stamp struct {
double value
bool glued
}

p Stamp.print() {
printf("Value: %f, glued: %d", this.value, this.glued);
}

type Letter struct {
string content
Stamp stamp
}

f<string> Letter.getContent() {
return this.content;
}

p Letter.setContent(string text) {
this.content = text;
}

f<Stamp> Letter.getStamp() {
return this.stamp;
}

p Letter.setStamp(Stamp stamp) {
this.stamp = stamp;
}

f<int> main() {
dyn letter = Letter{ "", Stamp{ 3.4, false } };
printf("Stamp glued: %f\n", letter.getStamp().value);
letter.getStamp().print();
}

/*import "std/runtime/string_rt" as _rt_str;

f<int> main() {
printf("%d", _rt_str::String("Test").isEmpty());
string test = "Tested";
printf("%s\n", test.getRaw());
printf("%d\n", test.getLength());
printf("%d\n", test.getCapacity());
}

/*f<int> main() {
int v1 = 10;
int v2 = v1;
printf("v1: %d, v2: %d\n", v1, v2);
v2++;
printf("v1: %d, v2: %d\n", v1, v2);

unsigned long startIndex = 10l;
for unsigned long charIndex = startIndex; charIndex < 20l; charIndex++ {
printf("Char index: %d, start index: %d\n", charIndex, startIndex);
}
}*/

/*import "std/net/http" as http;
Expand Down
2 changes: 2 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ set(SOURCES
ast/AstNodes.h
dependency/SourceFile.cpp
dependency/SourceFile.h
dependency/RuntimeModuleManager.cpp
dependency/RuntimeModuleManager.h
exception/CliError.cpp
exception/CliError.h
exception/SemanticError.cpp
Expand Down
46 changes: 36 additions & 10 deletions src/analyzer/AnalyzerVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <utility>

#include <cli/CliInterface.h>
#include <dependency/RuntimeModuleManager.h>
#include <dependency/SourceFile.h>
#include <exception/SemanticError.h>
#include <symbol/Function.h>
Expand All @@ -15,9 +16,9 @@
#include <util/CommonUtil.h>
#include <util/CompilerWarning.h>

AnalyzerVisitor::AnalyzerVisitor(const llvm::LLVMContext *context, const llvm::IRBuilder<> *builder, const SourceFile &sourceFile,
CliOptions &options, RuntimeModules &runtimeModules, bool requiresMainFct, bool isStdFile)
: context(context), builder(builder), runtimeModules(runtimeModules), requiresMainFct(requiresMainFct), isStdFile(isStdFile) {
AnalyzerVisitor::AnalyzerVisitor(const llvm::LLVMContext *context, const llvm::IRBuilder<> *builder, SourceFile &sourceFile,
CliOptions &options, bool requiresMainFct, bool isStdFile)
: context(context), builder(builder), sourceFile(sourceFile), requiresMainFct(requiresMainFct), isStdFile(isStdFile) {
// Retrieve symbol table
this->currentScope = this->rootScope = sourceFile.symbolTable.get();

Expand Down Expand Up @@ -1689,6 +1690,19 @@ std::any AnalyzerVisitor::visitPostfixUnaryExpr(PostfixUnaryExprNode *node) {
throw SemanticError(node->codeLoc, MEMBER_ACCESS_ONLY_STRUCTS, "Cannot apply member access operator on " + lhs.getName());

PostfixUnaryExprNode *rhs = node->postfixUnaryExpr()[memberAccessCounter++];

// Propagate strings to string objects
if (lhs.is(TY_STRING)) {
insertAnonStringStructSymbol(rhs);
lhs = SymbolType(TY_STRUCT, STRING_RT_IMPORT_NAME + std::string(".String"));
SymbolTable *stringRuntimeScope = sourceFile.getRuntimeModuleScope(STRING_RT);
stringRuntimeScope = stringRuntimeScope->getChild(STRUCT_SCOPE_PREFIX + std::string("String"));
assert(stringRuntimeScope != nullptr);
scopePath.clear();
scopePath.pushFragment(STRING_RT_IMPORT_NAME, stringRuntimeScope);
}
currentThisType = lhs;

lhs = any_cast<SymbolType>(visit(rhs)); // Visit rhs
break;
}
Expand Down Expand Up @@ -1906,12 +1920,24 @@ std::any AnalyzerVisitor::visitFunctionCall(FunctionCallNode *node) {
std::string identifier = node->functionNameFragments[i];
SymbolTableEntry *symbolEntry = accessScope->lookup(identifier);

SymbolType symbolBaseType;
if (symbolEntry != nullptr) {
if (symbolEntry->type.getBaseType().is(TY_STRING)) {
insertAnonStringStructSymbol(node);
symbolBaseType = SymbolType(TY_STRUCT, std::string("String"));
accessScope = sourceFile.getRuntimeModuleScope(STRING_RT);
assert(accessScope != nullptr);
} else {
symbolBaseType = symbolEntry->type.getBaseType();
}
}

if (i < node->functionNameFragments.size() - 1) {
if (!symbolEntry)
throw SemanticError(node->codeLoc, REFERENCED_UNDEFINED_FUNCTION,
"Symbol '" + scopePath.getScopePrefix() + identifier + "' was used before defined");
thisType = symbolEntry->type.getBaseType();
} else if (symbolEntry != nullptr && symbolEntry->type.getBaseType().is(TY_STRUCT)) { // last fragment is a struct
thisType = symbolBaseType;
} else if (symbolEntry != nullptr && symbolBaseType.is(TY_STRUCT)) {
// Get the concrete template types
std::vector<SymbolType> concreteTemplateTypes;
if (node->isGeneric) {
Expand All @@ -1933,7 +1959,7 @@ std::any AnalyzerVisitor::visitFunctionCall(FunctionCallNode *node) {
if (accessScope->isImported(currentScope))
thisType = initExtStruct(accessScope, scopePath.getScopePrefix(true), identifier, concreteTemplateTypes, node->codeLoc);
else
thisType = symbolEntry->type.getBaseType();
thisType = symbolBaseType;

functionName = CTOR_VARIABLE_NAME;
constructorCall = true;
Expand Down Expand Up @@ -2008,7 +2034,7 @@ std::any AnalyzerVisitor::visitFunctionCall(FunctionCallNode *node) {
// Add anonymous symbol to keep track of de-allocation
currentScope->insertAnonymous(thisType, node);
// Return struct type on constructor call
return currentThisType = node->setEvaluatedSymbolType(thisType);
return node->setEvaluatedSymbolType(thisType);
}

// If the callee is a procedure, return type bool
Expand All @@ -2029,11 +2055,11 @@ std::any AnalyzerVisitor::visitFunctionCall(FunctionCallNode *node) {
std::string scopePrefix = scopePathBackup.getScopePrefix(!spiceFunc->isGenericSubstantiation);
SymbolType symbolType =
initExtStruct(currentScope, scopePrefix, returnType.getSubType(), returnType.getTemplateTypes(), node->codeLoc);
return currentThisType = node->setEvaluatedSymbolType(symbolType);
return node->setEvaluatedSymbolType(symbolType);
}
}

return currentThisType = node->setEvaluatedSymbolType(returnType);
return node->setEvaluatedSymbolType(returnType);
}

std::any AnalyzerVisitor::visitArrayInitialization(ArrayInitializationNode *node) {
Expand Down Expand Up @@ -2310,7 +2336,7 @@ SymbolType AnalyzerVisitor::insertAnonStringStructSymbol(const AstNode *declNode
currentScope->insertAnonymous(stringStructType, declNode);

// Enable string runtime
runtimeModules.stringRuntime = true;
sourceFile.requestRuntimeModule(STRING_RT);

return stringStructType;
}
Expand Down
8 changes: 4 additions & 4 deletions src/analyzer/AnalyzerVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class LinkerInterface;
class SymbolTable;
class SymbolTableEntry;
class SourceFile;
struct RuntimeModules;
struct RuntimeModuleManager;

/**
* Visitor for analyzing a source file.
Expand All @@ -48,8 +48,8 @@ struct RuntimeModules;
class AnalyzerVisitor : public AstVisitor {
public:
// Constructors
explicit AnalyzerVisitor(const llvm::LLVMContext *context, const llvm::IRBuilder<> *builder, const SourceFile &sourceFile,
CliOptions &options, RuntimeModules &runtimeModules, bool requiresMainFct, bool stdFile);
explicit AnalyzerVisitor(const llvm::LLVMContext *context, const llvm::IRBuilder<> *builder, SourceFile &sourceFile,
CliOptions &options, bool requiresMainFct, bool stdFile);

// Friend classes
friend class OpRuleManager;
Expand Down Expand Up @@ -113,7 +113,7 @@ class AnalyzerVisitor : public AstVisitor {
const llvm::LLVMContext *context;
const llvm::IRBuilder<> *builder;
std::unique_ptr<OpRuleManager> opRuleManager;
RuntimeModules &runtimeModules;
SourceFile &sourceFile;
bool requiresMainFct = true;
bool hasMainFunction = false;
bool isStdFile = false;
Expand Down
5 changes: 3 additions & 2 deletions src/analyzer/PreAnalyzerVisitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ std::any PreAnalyzerVisitor::visitImportStmt(ImportStmtNode *node) {
}
CommonUtil::replaceAll(importPath, "/", std::string(1, FileUtil::DIR_SEPARATOR));

// Visit the imported file
sourceFile.addDependency(node, node->importName, importPath, isImportStd);
// Create the imported source file
const auto moduleSourceFile = sourceFile.createSourceFile(node->importName, importPath, isImportStd);
sourceFile.addDependency(moduleSourceFile, node, node->importName, importPath);

return nullptr;
}
52 changes: 52 additions & 0 deletions src/dependency/RuntimeModuleManager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) 2021-2022 ChilliBits. All rights reserved.

#include "RuntimeModuleManager.h"

#include <dependency/SourceFile.h>
#include <symbol/SymbolTable.h>
#include <util/FileUtil.h>

#include <ast/AstNodes.h>

SourceFile *RuntimeModuleManager::requestModule(SourceFile *sourceFile, const RuntimeModuleName &moduleName) {
// Make the module available
if (!isModuleAvailable(moduleName))
addModule(sourceFile, moduleName);

return modules.at(moduleName);
}

SymbolTable *RuntimeModuleManager::getModuleScope(const RuntimeModuleName &moduleName) const {
assert(modules.contains(moduleName));
return modules.at(moduleName)->symbolTable.get();
}

bool RuntimeModuleManager::isModuleAvailable(const RuntimeModuleName &module) const { return modules.contains(module); }

void RuntimeModuleManager::addModule(SourceFile *parentSourceFile, const RuntimeModuleName &moduleName) {
std::string importName;
std::string fileName;
switch (moduleName) {
case STRING_RT:
importName = "__rt_string";
fileName = "string_rt";
break;
case THREAD_RT:
importName = "__rt_thread";
fileName = "thread_rt";
break;
default:
throw std::runtime_error("Internal compiler error: Requested unknown runtime module");
}
std::string filePath = FileUtil::getStdDir() + "runtime" + FileUtil::DIR_SEPARATOR + fileName + ".spice";
const auto moduleSourceFile = parentSourceFile->createSourceFile(importName, filePath, true);
parentSourceFile->addDependency(moduleSourceFile, parentSourceFile->ast.get(), importName, filePath);

const auto runtimeFile = parentSourceFile->dependencies.at(importName).first;
runtimeFile->buildAST();
runtimeFile->preAnalyze();
runtimeFile->analyze();
runtimeFile->reAnalyze();

modules.emplace(moduleName, runtimeFile.get());
}
29 changes: 29 additions & 0 deletions src/dependency/RuntimeModuleManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2021-2022 ChilliBits. All rights reserved.

#pragma once

#include <map>

// Forward declaration
class SourceFile;
class SymbolTable;

const char *const STRING_RT_IMPORT_NAME = "__rt_string";
const char *const THREAD_RT_IMPORT_NAME = "__rt_thread";

enum RuntimeModuleName { STRING_RT, THREAD_RT };

class RuntimeModuleManager {
public:
// Public methods
SourceFile *requestModule(SourceFile *sourceFile, const RuntimeModuleName &moduleName);
[[nodiscard]] SymbolTable *getModuleScope(const RuntimeModuleName &moduleName) const;
[[nodiscard]] bool isModuleAvailable(const RuntimeModuleName &moduleName) const;

private:
// Private methods
void addModule(SourceFile *parentSourceFile, const RuntimeModuleName &moduleName);

// Private fields
std::map<RuntimeModuleName, SourceFile *> modules;
};

0 comments on commit bc3a579

Please sign in to comment.