diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1230ce300..b7dfc51ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,23 @@ jobs: - name: Checkout uses: actions/checkout@v2 + - name: Cache LLVM + id: cache-llvm + uses: actions/cache@v2 + with: + path: /home/runner/work/spice/llvm + key: llvm-12-0-1 + + - name: Setup LLVM + if: steps.cache-llvm.outputs.cache-hit != 'true' + run: | + cd .. + git clone https://github.com/llvm/llvm-project llvm + mkdir ./llvm/build + cd ./llvm/build + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS_RELEASE="-O2" -G "Unix Makefiles" ../llvm + cmake --build . + - name: Download Libs run: | mkdir -p ./compiler/lib @@ -23,8 +40,10 @@ jobs: git clone https://github.com/antlr/antlr4.git - name: Build + env: + LLVM_DIR: /home/runner/work/spice/llvm/build/lib/cmake/llvm run: | mkdir bin cd bin cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" -DWITH_TESTS=ON ../compiler - cmake --build . --target Spice_run -- -j 6 \ No newline at end of file + cmake --build . --target Spice_run -- -j 6 diff --git a/README.md b/README.md index 9217e03aa..d201b203a 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,15 @@ The Spice grammar can be found [here](./compiler/src/grammar/Spice.g4) as a ANTL - [x] Additive operators are only applied to some type combinations - [x] Multiplicative operators are only applied to some type combinations - [x] Prefix/postfix unary operators are only applied to integers +- [ ] Program contains main function +- [ ] `++` and `--` are only applied on identifiers ## Special language features - Something like `"Test" * 3` is valid and will evaluate to `"TestTestTest"` - Alternatively to the return statement in a function, you can also assign variable `result` with a value, which was automatically declared by the function header - Unary minus has to be applied without a space between (e.g.: `-3.4`) and binary minus has to be applied with a space beween (e.g.: `n - 5`) -- Default values of function/procedure parameters are possible e.g.: `f test(int param = 2) {}` \ No newline at end of file +- Default values of function/procedure parameters are possible e.g.: `f test(int param = 2) {}` + +## CMake instructions for building +`cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS_RELEASE="-O2" -G "CodeBlocks - MinGW Makefiles" ../llvm` +`cmake --build .` \ No newline at end of file diff --git a/build.bat b/build.bat index 3f8ef58e7..57387fc4a 100644 --- a/build.bat +++ b/build.bat @@ -2,7 +2,7 @@ mkdir bin 2> NUL cd bin -cmake -DCMAKE_BUILD_TYPE=Release -G "CodeBlocks - MinGW Makefiles" ../compiler +cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -G "CodeBlocks - MinGW Makefiles" ../compiler cmake --build . --target Spice_run -- -j 6 move src\Spice_run.exe spicec.exe diff --git a/compiler/.run/Spice_run.run.xml b/compiler/.run/Spice_run.run.xml new file mode 100644 index 000000000..9f13f8e79 --- /dev/null +++ b/compiler/.run/Spice_run.run.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/compiler/src/CMakeLists.txt b/compiler/src/CMakeLists.txt index 1976a7e6f..7111fe465 100644 --- a/compiler/src/CMakeLists.txt +++ b/compiler/src/CMakeLists.txt @@ -1,4 +1,5 @@ set(BINARY ${CMAKE_PROJECT_NAME}) +set(LLVM_DIR $ENV{LLVM_DIR}) LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) @@ -9,6 +10,15 @@ set(ANTLR_EXECUTABLE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/antlr/antlr-4.9.2-co find_package(ANTLR REQUIRED) antlr_target(Spice ${CMAKE_CURRENT_SOURCE_DIR}/grammar/Spice.g4 VISITOR) +find_package(LLVM REQUIRED CONFIG) + +message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") +message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") + +include_directories(${LLVM_INCLUDE_DIRS}) +separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) +add_definitions(${LLVM_DEFINITIONS_LIST}) + set(SOURCES main.cpp analyzer/AnalyzerVisitor.cpp @@ -17,14 +27,38 @@ set(SOURCES analyzer/SymbolTable.h analyzer/SymbolTableEntry.cpp analyzer/SymbolTableEntry.h + generator/GeneratorVisitor.cpp + generator/GeneratorVisitor.h exception/SemanticError.cpp - exception/SemanticError.h) + exception/SemanticError.h + exception/IRError.cpp + exception/IRError.h) add_executable(${BINARY}_run ${SOURCES} ${ANTLR_Spice_CXX_OUTPUTS}) +llvm_map_components_to_libnames(LLVM_LIBS aarch64asmparser aarch64codegen aarch64desc aarch64disassembler + aarch64info aarch64utils amdgpuasmparser amdgpucodegen amdgpudesc amdgpudisassembler + amdgpuinfo amdgpuutils armasmparser armcodegen armdesc armdisassembler arminfo armutils asmparser + asmprinter avrasmparser avrcodegen avrdesc avrdisassembler avrinfo + bpfasmparser bpfcodegen bpfdesc bpfdisassembler bpfinfo codegen core + hexagonasmparser hexagoncodegen hexagondesc hexagondisassembler hexagoninfo + irreader lanaiasmparser lanaicodegen lanaidesc lanaidisassembler lanaiinfo + mipsasmparser mipscodegen mipsdesc mipsdisassembler mipsinfo + mirparser msp430asmparser msp430codegen msp430desc msp430disassembler msp430info + nvptxcodegen nvptxdesc nvptxinfo + powerpcasmparser powerpccodegen powerpcdesc powerpcdisassembler powerpcinfo riscvasmparser + riscvcodegen riscvdesc riscvdisassembler riscvinfo sparcasmparser + sparccodegen sparcdesc sparcdisassembler sparcinfo support systemzasmparser systemzcodegen + systemzdesc systemzdisassembler systemzinfo + webassemblyasmparser webassemblycodegen webassemblydesc webassemblydisassembler webassemblyinfo + webassemblyutils x86asmparser x86codegen x86desc x86disassembler x86info xcorecodegen xcoredesc + xcoredisassembler xcoreinfo) + include_directories(${ANTLR4CPP_INCLUDE_DIRS}) include_directories(${ANTLR_Spice_OUTPUT_DIR}) include_directories(../lib/antlr4/runtime/Cpp/runtime/src) target_link_libraries(${BINARY}_run antlr4_static) -add_library(${BINARY}_lib STATIC ${SOURCES} exception/SemanticError.cpp exception/SemanticError.h analyzer/SymbolTableEntry.cpp analyzer/SymbolTableEntry.h) \ No newline at end of file +target_link_libraries(${BINARY}_run ${LLVM_LIBS}) + +add_library(${BINARY}_lib STATIC ${SOURCES} ${ANTLR_Spice_CXX_OUTPUTS}) \ No newline at end of file diff --git a/compiler/src/analyzer/AnalyzerVisitor.cpp b/compiler/src/analyzer/AnalyzerVisitor.cpp index c3485a5a1..e73d3d2f2 100644 --- a/compiler/src/analyzer/AnalyzerVisitor.cpp +++ b/compiler/src/analyzer/AnalyzerVisitor.cpp @@ -15,6 +15,29 @@ antlrcpp::Any AnalyzerVisitor::visitEntry(SpiceParser::EntryContext *ctx) { return currentScope; } +antlrcpp::Any AnalyzerVisitor::visitMainFunctionDef(SpiceParser::MainFunctionDefContext *ctx) { + // Insert function name into the root symbol table + currentScope->insert("main", TYPE_FUNCTION, INITIALIZED, true, false); + // Create a new scope + std::string scopeId = "f:main"; + currentScope = currentScope->createChildBlock(scopeId); + // Declare variable for the return value + SymbolType returnType = TYPE_INT; + currentScope->insert(RETURN_VARIABLE_NAME, returnType, DECLARED, false, false); + // Visit parameters + parameterMode = true; + if (ctx->paramLstDef()) visit(ctx->paramLstDef()); + parameterMode = false; + // Visit statements in new scope + visit(ctx->stmtLst()); + // Check if return variable is now initialized + if (currentScope->lookup(RETURN_VARIABLE_NAME)->getState() == DECLARED) + throw SemanticError(FUNCTION_WITHOUT_RETURN_STMT, "Function without return statement"); + // Return to old scope + currentScope = currentScope->getParent(); + return returnType; +} + antlrcpp::Any AnalyzerVisitor::visitFunctionDef(SpiceParser::FunctionDefContext *ctx) { // Insert function name into the root symbol table std::string functionName = ctx->IDENTIFIER()->toString(); @@ -146,15 +169,17 @@ antlrcpp::Any AnalyzerVisitor::visitFunctionCall(SpiceParser::FunctionCallContex SymbolTable* symbolTable = rootTable->getChild(scopeId); std::vector paramNames = symbolTable->getParamNames(); // Check if types match for parameter list - for (int i = 0; i < ctx->paramLstCall()->assignment().size(); i++) { - SymbolType type = visit(ctx->paramLstCall()->assignment()[i]).as(); - SymbolTableEntry* param = symbolTable->lookup(paramNames[i]); - if (!param) - throw SemanticError(REFERENCED_UNDEFINED_VARIABLE, - "Parameter '" + paramNames[i] + "' was not found in declaration"); - if (type != param->getType()) - throw SemanticError(PARAMETER_TYPES_DO_NOT_MATCH, - "Type of parameter '" + paramNames[i] + "' does not match the declaration"); + if (ctx->paramLstCall()) { + for (int i = 0; i < ctx->paramLstCall()->assignment().size(); i++) { + SymbolType type = visit(ctx->paramLstCall()->assignment()[i]).as(); + SymbolTableEntry* param = symbolTable->lookup(paramNames[i]); + if (!param) + throw SemanticError(REFERENCED_UNDEFINED_VARIABLE, + "Parameter '" + paramNames[i] + "' was not found in declaration"); + if (type != param->getType()) + throw SemanticError(PARAMETER_TYPES_DO_NOT_MATCH, + "Type of parameter '" + paramNames[i] + "' does not match the declaration"); + } } return symbolTable->lookup(RETURN_VARIABLE_NAME)->getType(); } @@ -180,6 +205,10 @@ antlrcpp::Any AnalyzerVisitor::visitReturnStmt(SpiceParser::ReturnStmtContext *c return returnType; } +antlrcpp::Any AnalyzerVisitor::visitPrintfStmt(SpiceParser::PrintfStmtContext *ctx) { + return SpiceBaseVisitor::visitPrintfStmt(ctx); +} + antlrcpp::Any AnalyzerVisitor::visitAssignment(SpiceParser::AssignmentContext *ctx) { // Check if there is an assign operator applied if (ctx->children.size() > 1) { diff --git a/compiler/src/analyzer/AnalyzerVisitor.h b/compiler/src/analyzer/AnalyzerVisitor.h index 1e39146ec..446a9843d 100644 --- a/compiler/src/analyzer/AnalyzerVisitor.h +++ b/compiler/src/analyzer/AnalyzerVisitor.h @@ -13,6 +13,7 @@ class AnalyzerVisitor : public SpiceBaseVisitor { public: // Public methods antlrcpp::Any visitEntry(SpiceParser::EntryContext *ctx) override; + antlrcpp::Any visitMainFunctionDef(SpiceParser::MainFunctionDefContext *ctx) override; antlrcpp::Any visitFunctionDef(SpiceParser::FunctionDefContext *ctx) override; antlrcpp::Any visitProcedureDef(SpiceParser::ProcedureDefContext *ctx) override; antlrcpp::Any visitForLoop(SpiceParser::ForLoopContext *ctx) override; @@ -23,6 +24,7 @@ class AnalyzerVisitor : public SpiceBaseVisitor { antlrcpp::Any visitFunctionCall(SpiceParser::FunctionCallContext *ctx) override; antlrcpp::Any visitImportStmt(SpiceParser::ImportStmtContext *ctx) override; antlrcpp::Any visitReturnStmt(SpiceParser::ReturnStmtContext *ctx) override; + antlrcpp::Any visitPrintfStmt(SpiceParser::PrintfStmtContext *ctx) override; antlrcpp::Any visitAssignment(SpiceParser::AssignmentContext *ctx) override; antlrcpp::Any visitTernary(SpiceParser::TernaryContext *ctx) override; antlrcpp::Any visitLogicalOrExpr(SpiceParser::LogicalOrExprContext *ctx) override; diff --git a/compiler/src/exception/IRError.cpp b/compiler/src/exception/IRError.cpp new file mode 100644 index 000000000..5674424e1 --- /dev/null +++ b/compiler/src/exception/IRError.cpp @@ -0,0 +1,7 @@ +// Copyright (c) 2021 ChilliBits. All rights reserved. + +#include "IRError.h" + +const char *IRError::what() const noexcept { + return errorMessage.c_str(); +} diff --git a/compiler/src/exception/IRError.h b/compiler/src/exception/IRError.h new file mode 100644 index 000000000..6f3067af3 --- /dev/null +++ b/compiler/src/exception/IRError.h @@ -0,0 +1,38 @@ +// Copyright (c) 2021 ChilliBits. All rights reserved. + +#pragma once + +#include +#include + +enum IRErrorType { + TARGET_NOT_AVAILABLE, + CANT_OPEN_OUTPUT_FILE, + WRONG_TYPE, +}; + +class IRError : public std::exception { +public: + // Constructors + explicit IRError(IRErrorType type, const std::string& message) { + std::string messagePrefix; + switch (type) { + case TARGET_NOT_AVAILABLE: + messagePrefix = "Selected target not available"; + break; + case CANT_OPEN_OUTPUT_FILE: + messagePrefix = "Could not open output file"; + break; + case WRONG_TYPE: + messagePrefix = "Wrong type of output file"; + break; + } + errorMessage = messagePrefix + ": " + message; + } + + // Public methods + const char * what() const noexcept override; +private: + // Members + std::string errorMessage {}; +}; \ No newline at end of file diff --git a/compiler/src/generator/GeneratorVisitor.cpp b/compiler/src/generator/GeneratorVisitor.cpp new file mode 100644 index 000000000..fdc6144d1 --- /dev/null +++ b/compiler/src/generator/GeneratorVisitor.cpp @@ -0,0 +1,627 @@ +// Copyright (c) 2021 ChilliBits. All rights reserved. + +#include "GeneratorVisitor.h" + +void GeneratorVisitor::init() { + // Initialize LLVM + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargets(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllAsmParsers(); + llvm::InitializeAllAsmPrinters(); +} + +void GeneratorVisitor::optimize() { + /*// Register optimization passes + std::unique_ptr functionPassManager = + llvm::make_unique(module.get()); + + // Promote allocas to registers. + functionPassManager->add(llvm::createPromoteMemoryToRegisterPass()); + // Do simple "peephole" optimizations + functionPassManager->add(llvm::createInstructionCombiningPass()); + // Reassociate expressions. + functionPassManager->add(llvm::createReassociatePass()); + // Eliminate Common SubExpressions. + functionPassManager->add(llvm::createGVNPass()); + // Simplify the control flow graph (deleting unreachable blocks etc). + functionPassManager->add(llvm::createCFGSimplificationPass()); + + functionPassManager->doInitialization(); + + for (auto &function : functions) { + llvm::Function *llvmFun = + module->getFunction(llvm::StringRef(function->functionName)); + functionPassManager->run(*llvmFun); + } + + llvm::Function *llvmMainFun = module->getFunction(llvm::StringRef("main")); + functionPassManager->run(*llvmMainFun);*/ +} + +void GeneratorVisitor::emit() { + // Configure output target + // ToDo: Make target customizable by setting an cli arg or similar + auto targetTriple = llvm::sys::getDefaultTargetTriple(); + module->setTargetTriple(targetTriple); + + // Search after selected target + std::string error; + auto target = llvm::TargetRegistry::lookupTarget(targetTriple, error); + if (!target) throw IRError(TARGET_NOT_AVAILABLE, "Selected target was not found: " + error); + + auto cpu = "generic"; + auto features = ""; + + llvm::TargetOptions opt; + auto rm = llvm::Optional(); + auto targetMachine = target->createTargetMachine(targetTriple, cpu, features, opt, rm); + + module->setDataLayout(targetMachine->createDataLayout()); + + // Open file output stream + std::string filename = "output.o"; + std::error_code errorCode; + llvm::raw_fd_ostream dest(filename, errorCode, llvm::sys::fs::OF_None); + if (errorCode) throw IRError(CANT_OPEN_OUTPUT_FILE, "File '" + filename + "' could not be opened"); + + llvm::legacy::PassManager pass; + auto FileType = llvm::CGFT_ObjectFile; + if (targetMachine->addPassesToEmitFile(pass, dest, nullptr, FileType)) + throw IRError(WRONG_TYPE, "Target machine can't emit a file of this type"); + + // Emit object file + pass.run(*module); + dest.flush(); +} + +void GeneratorVisitor::dumpIR() { + module->print(llvm::outs(), nullptr); +} + +antlrcpp::Any GeneratorVisitor::visitEntry(SpiceParser::EntryContext *ctx) { + // Generate code for external functions + initializeExternalFunctions(); + + return SpiceBaseVisitor::visitEntry(ctx); +} + +antlrcpp::Any GeneratorVisitor::visitMainFunctionDef(SpiceParser::MainFunctionDefContext *ctx) { + // Build function itself + auto mainType = llvm::FunctionType::get(llvm::IntegerType::getInt32Ty(*context), + std::vector(), false); + auto fct = llvm::Function::Create(mainType, llvm::Function::ExternalLinkage, "main", module.get()); + auto bMain = llvm::BasicBlock::Create(*context, "main_entry", fct); + builder->SetInsertPoint(bMain); + namedValues.clear(); + + // Generate IR for function body + visit(ctx->stmtLst()); + + // Verify function + llvm::verifyFunction(*fct); + + // Return true as result for the function definition + return llvm::ConstantInt::get(llvm::Type::getInt1Ty(*context), 1); +} + +antlrcpp::Any GeneratorVisitor::visitFunctionDef(SpiceParser::FunctionDefContext *ctx) { + std::string functionName = ctx->IDENTIFIER()->toString(); + // Create function itself + auto returnType = visit(ctx->dataType()).as(); + auto fctType = llvm::FunctionType::get(returnType, std::vector(), false); + auto fct = llvm::Function::Create(fctType, llvm::Function::ExternalLinkage, functionName, module.get()); + + // Create entry block + auto bEntry = llvm::BasicBlock::Create(*context, "entry"); + fct->getBasicBlockList().push_back(bEntry); + builder->SetInsertPoint(bEntry); + + // Store function params + namedValues.clear(); + for (auto& param : fct->args()) { + auto paramNo = param.getArgNo(); + std::string paramName = ctx->paramLstDef()->assignment()[paramNo]->IDENTIFIER()->toString(); + llvm::Type *paramType = fct->getFunctionType()->getParamType(paramNo); + namedValues[paramName] = builder->CreateAlloca(paramType, nullptr, paramName); + builder->CreateStore(¶m, namedValues[paramName]); + } + + // Generate IR for function body + visit(ctx->stmtLst()); + + // Verify function + llvm::verifyFunction(*fct); + + // Return true as result for the function definition + return llvm::ConstantInt::get((llvm::Type::getInt1Ty(*context)), 1); +} + +antlrcpp::Any GeneratorVisitor::visitProcedureDef(SpiceParser::ProcedureDefContext *ctx) { + auto procedureName = ctx->IDENTIFIER()->toString(); + // Create procedure itself + auto procType = llvm::FunctionType::get(llvm::Type::getVoidTy(*context), + std::vector(), false); + auto proc = llvm::Function::Create(procType, llvm::Function::ExternalLinkage, procedureName, module.get()); + + // Create entry block + auto bEntry = llvm::BasicBlock::Create(*context, "entry"); + proc->getBasicBlockList().push_back(bEntry); + builder->SetInsertPoint(bEntry); + + // Store procedure params + namedValues.clear(); + for (auto& param : proc->args()) { + auto paramNo = param.getArgNo(); + std::string paramName = ctx->paramLstDef()->assignment()[paramNo]->IDENTIFIER()->toString(); + llvm::Type *paramType = proc->getFunctionType()->getParamType(paramNo); + namedValues[paramName] = builder->CreateAlloca(paramType, nullptr, paramName); + builder->CreateStore(¶m, namedValues[paramName]); + } + + // Generate IR for procedure body + visit(ctx->stmtLst()); + + // Create return + builder->CreateRetVoid(); + + // Verify procedure + llvm::verifyFunction(*proc); + + // Return true as result for the function definition + return llvm::ConstantInt::get(llvm::Type::getInt1Ty(*context), 1); +} + +antlrcpp::Any GeneratorVisitor::visitForLoop(SpiceParser::ForLoopContext *ctx) { + auto parentFct = builder->GetInsertBlock()->getParent(); + + // Create blocks + auto bLoop = llvm::BasicBlock::Create(*context, "for"); + auto bLoopPost = llvm::BasicBlock::Create(*context, "for_post"); + auto bLoopEnd = llvm::BasicBlock::Create(*context, "for_end"); + + // Execute pre-loop stmts + visit(ctx->assignment()[0]); + // Check if entering the loop is necessary + auto conditionValue = visit(ctx->assignment()[1]).as(); + builder->CreateCondBr(conditionValue, bLoop, bLoopEnd); + + // Fill loop block + parentFct->getBasicBlockList().push_back(bLoop); + builder->SetInsertPoint(bLoop); + // Generate IR for nested statements + visit(ctx->stmtLst()); + // Check if condition is now false + conditionValue = visit(ctx->assignment()[1]).as(); + builder->CreateCondBr(conditionValue, bLoopPost, bLoopEnd); + + // Fill loop post block + parentFct->getBasicBlockList().push_back(bLoopPost); + builder->SetInsertPoint(bLoopPost); + visit(ctx->assignment()[2]); + builder->CreateBr(bLoop); + + // Fil loop end block + parentFct->getBasicBlockList().push_back(bLoopEnd); + builder->SetInsertPoint(bLoopEnd); + + // Return true as result for the loop + return llvm::ConstantInt::get(llvm::Type::getInt1Ty(*context), 1); +} + +antlrcpp::Any GeneratorVisitor::visitWhileLoop(SpiceParser::WhileLoopContext *ctx) { + auto conditionValue = visit(ctx->assignment()).as(); + auto parentFct = builder->GetInsertBlock()->getParent(); + + // Create blocks + auto bLoop = llvm::BasicBlock::Create(*context, "while"); + auto bLoopEnd = llvm::BasicBlock::Create(*context, "while_end"); + + // Check if entering the loop is necessary + builder->CreateCondBr(conditionValue, bLoop, bLoopEnd); + + // Fill loop block + parentFct->getBasicBlockList().push_back(bLoop); + builder->SetInsertPoint(bLoop); + // Generate IR for nested statements + visit(ctx->stmtLst()); + // Visit condition again + conditionValue = visit(ctx->assignment()).as(); + // Check if condition is now false + bLoop = builder->GetInsertBlock(); + builder->CreateCondBr(conditionValue, bLoop, bLoopEnd); + + // Fill loop end block + parentFct->getBasicBlockList().push_back(bLoopEnd); + builder->SetInsertPoint(bLoopEnd); + + // Return true as result for the loop + return llvm::ConstantInt::get(llvm::Type::getInt1Ty(*context), 1); +} + +antlrcpp::Any GeneratorVisitor::visitIfStmt(SpiceParser::IfStmtContext *ctx) { + auto conditionValue = visit(ctx->assignment()).as(); + auto parentFct = builder->GetInsertBlock()->getParent(); + + // Create blocks + auto bThen = llvm::BasicBlock::Create(*context, "then"); + auto bEnd = llvm::BasicBlock::Create(*context, "end"); + + // Check if if condition is fulfilled + builder->CreateCondBr(conditionValue, bThen, bEnd); + + // Fill then block + parentFct->getBasicBlockList().push_back(bThen); + builder->SetInsertPoint(bThen); + // Generate IR for nested statements + visit(ctx->stmtLst()); + builder->CreateBr(bEnd); + + // Fill end block + parentFct->getBasicBlockList().push_back(bEnd); + builder->SetInsertPoint(bEnd); + + // Return conditional value as result for the if stmt + return conditionValue; +} + +antlrcpp::Any GeneratorVisitor::visitDeclStmt(SpiceParser::DeclStmtContext *ctx) { + std::string varName = ctx->IDENTIFIER()->toString(); + + llvm::Type* varType = visit(ctx->dataType()).as(); + + llvm::Function* parentFunction = builder->GetInsertBlock()->getParent(); + llvm::IRBuilder<> tmpBuilder(&parentFunction->getEntryBlock(), parentFunction->getEntryBlock().begin()); + llvm::AllocaInst* var = tmpBuilder.CreateAlloca(varType, nullptr, varName); + namedValues[varName] = var; + return varName; +} + +antlrcpp::Any GeneratorVisitor::visitFunctionCall(SpiceParser::FunctionCallContext *ctx) { + auto fctName = ctx->IDENTIFIER()->toString(); + auto fct = module->getFunction(fctName); + auto fctType = fct->getFunctionType(); + std::vector argValues; + for (int i = 0; i < ctx->paramLstCall()->assignment().size(); i++) { + auto argValue = visit(ctx->paramLstCall()->assignment()[i]).as(); + auto argType = fctType->getParamType(i); + auto bitCastArgValue = builder->CreateBitCast(argValue, argType); + argValues.push_back(bitCastArgValue); + } + return builder->CreateCall(fct, argValues); +} + +antlrcpp::Any GeneratorVisitor::visitReturnStmt(SpiceParser::ReturnStmtContext *ctx) { + auto returnValue = visit(ctx->assignment()).as(); + // Build return value + builder->CreateRet(returnValue); + return returnValue; +} + +antlrcpp::Any GeneratorVisitor::visitPrintfStmt(SpiceParser::PrintfStmtContext *ctx) { + auto printf = module->getFunction("printf"); + std::vector printfArgs; + auto stringTemplate = ctx->STRING()->toString(); + stringTemplate = stringTemplate.erase(stringTemplate.size() -1).erase(0, 1); + printfArgs.push_back(builder->CreateGlobalStringPtr(stringTemplate)); + for (auto &arg : ctx->assignment()) { + auto argVal = visit(arg).as(); + if (argVal == nullptr) throw std::runtime_error("Printf has null arg"); + printfArgs.push_back(argVal); + } + return builder->CreateCall(printf, printfArgs); +} + +antlrcpp::Any GeneratorVisitor::visitAssignment(SpiceParser::AssignmentContext *ctx) { + if (ctx->declStmt() || ctx->IDENTIFIER()) { + std::string varName = ctx->declStmt() ? visit(ctx->declStmt()).as() : ctx->IDENTIFIER()->toString(); + + // Get value of left and right side + auto rhs = visit(ctx->ternary()).as(); + auto lhs = namedValues[varName]; + if (!lhs) throw std::runtime_error("Internal compiler error - Variable not found in code generation step"); + // Store right side on the left one + builder->CreateStore(rhs, lhs); + // Return value of the right side + return rhs; + } + return visit(ctx->ternary()); +} + +antlrcpp::Any GeneratorVisitor::visitTernary(SpiceParser::TernaryContext *ctx) { + if (ctx->logicalOrExpr().size() > 1) { + auto conditionValue = visit(ctx->logicalOrExpr()[0]).as(); + auto parentFct = builder->GetInsertBlock()->getParent(); + + // Create blocks + auto bThen = llvm::BasicBlock::Create(*context, "then"); + auto bElse = llvm::BasicBlock::Create(*context, "else"); + auto bEnd = llvm::BasicBlock::Create(*context, "end"); + + // Conditional jump to respective block + builder->CreateCondBr(conditionValue, bThen, bElse); + + // Fill then block + parentFct->getBasicBlockList().push_back(bThen); + builder->SetInsertPoint(bThen); + auto thenValue = visit(ctx->logicalOrExpr()[1]).as(); + builder->CreateBr(bEnd); + + // Fill else block + parentFct->getBasicBlockList().push_back(bElse); + builder->SetInsertPoint(bElse); + auto elseValue = visit(ctx->logicalOrExpr()[2]).as(); + builder->CreateBr(bEnd); + + // Fill end block + parentFct->getBasicBlockList().push_back(bEnd); + builder->SetInsertPoint(bEnd); + // if either is void or their types don't match (which indicates one of them + // returned the null value for void, then don't construct a phi node) + if (thenValue->getType() == llvm::Type::getVoidTy(*context) || + elseValue->getType() == llvm::Type::getVoidTy(*context) || + thenValue->getType() != elseValue->getType()) { + return llvm::Constant::getNullValue(llvm::Type::getInt32Ty(*context)); + } + // Setup phi value + auto phi = builder->CreatePHI(thenValue->getType(), 2, "phi"); + phi->addIncoming(thenValue, bThen); + phi->addIncoming(elseValue, bElse); + return (llvm::Value*) phi; + } + return visit(ctx->logicalOrExpr()[0]); +} + +antlrcpp::Any GeneratorVisitor::visitLogicalOrExpr(SpiceParser::LogicalOrExprContext *ctx) { + if (ctx->logicalAndExpr().size() > 1) { + auto lhs = visit(ctx->logicalAndExpr()[0]).as(); + for (int i = 1; i < ctx->logicalAndExpr().size(); i++) { + auto rhs = visit(ctx->logicalAndExpr()[i]).as(); + lhs = builder->CreateLogicalOr(lhs, rhs, "log_or"); + } + return lhs; + } + return visit(ctx->logicalAndExpr()[0]); +} + +antlrcpp::Any GeneratorVisitor::visitLogicalAndExpr(SpiceParser::LogicalAndExprContext *ctx) { + if (ctx->bitwiseOrExpr().size() > 1) { + auto lhs = visit(ctx->bitwiseOrExpr()[0]).as(); + for (int i = 1; i < ctx->bitwiseOrExpr().size(); i++) { + auto rhs = visit(ctx->bitwiseOrExpr()[i]).as(); + lhs = builder->CreateLogicalAnd(lhs, rhs, "log_and"); + } + return lhs; + } + return visit(ctx->bitwiseOrExpr()[0]); +} + +antlrcpp::Any GeneratorVisitor::visitBitwiseOrExpr(SpiceParser::BitwiseOrExprContext *ctx) { + if (ctx->bitwiseAndExpr().size() > 1) { + auto lhs = visit(ctx->bitwiseAndExpr()[0]).as(); + for (int i = 1; i < ctx->bitwiseAndExpr().size(); i++) { + auto rhs = visit(ctx->bitwiseAndExpr()[i]).as(); + lhs = builder->CreateOr(lhs, rhs, "bw_or"); + } + return lhs; + } + return visit(ctx->bitwiseAndExpr()[0]); +} + +antlrcpp::Any GeneratorVisitor::visitBitwiseAndExpr(SpiceParser::BitwiseAndExprContext *ctx) { + if (ctx->equalityExpr().size() > 1) { + auto lhs = visit(ctx->equalityExpr()[0]).as(); + for (int i = 1; i < ctx->equalityExpr().size(); i++) { + auto rhs = visit(ctx->equalityExpr()[i]).as(); + lhs = builder->CreateAnd(lhs, rhs, "bw_and"); + } + return lhs; + } + return visit(ctx->equalityExpr()[0]); +} + +antlrcpp::Any GeneratorVisitor::visitEqualityExpr(SpiceParser::EqualityExprContext *ctx) { + if (ctx->children.size() > 1) { + auto lhs = visit(ctx->relationalExpr()[0]).as(); + auto rhs = visit(ctx->relationalExpr()[1]).as(); + + // Equality expr is: relationalExpr EQUAL relationalExpr + if (ctx->EQUAL()) return builder->CreateICmpEQ(lhs, rhs, "eq"); + + // Equality expr is: relationalExpr NOT_EQUAL relationalExpr + return builder->CreateICmpNE(lhs, rhs, "ne"); + } + return visit(ctx->relationalExpr()[0]); +} + +antlrcpp::Any GeneratorVisitor::visitRelationalExpr(SpiceParser::RelationalExprContext *ctx) { + if (ctx->children.size() > 1) { + auto lhs = visit(ctx->additiveExpr()[0]).as(); + auto rhs = visit(ctx->additiveExpr()[1]).as(); + + // Relational expr is: additiveExpr LESS additiveExpr + if (ctx->LESS()) return builder->CreateICmpSLT(lhs, rhs, "lt"); + + // Relational expr is: additiveExpr GREATER additiveExpr + if (ctx->GREATER()) return builder->CreateICmpSGT(lhs, rhs, "gt"); + + // Relational expr is: additiveExpr LESS_EQUAL additiveExpr + if (ctx->LESS_EQUAL()) return builder->CreateICmpSLE(lhs, rhs, "le"); + + // Relational expr is: additiveExpr GREATER_EQUAL additiveExpr + return builder->CreateICmpSGE(lhs, rhs, "ge"); + } + return visit(ctx->additiveExpr()[0]); +} + +antlrcpp::Any GeneratorVisitor::visitAdditiveExpr(SpiceParser::AdditiveExprContext *ctx) { + if (ctx->multiplicativeExpr().size() > 1) { + auto lhs = visit(ctx->multiplicativeExpr()[0]).as(); + for (int i = 1; i < ctx->multiplicativeExpr().size(); i++) { + auto rhs = visit(ctx->multiplicativeExpr()[i]).as(); + if (ctx->PLUS()[i-1]) + lhs = builder->CreateAdd(lhs, rhs, "add"); + else + lhs = builder->CreateSub(lhs, rhs, "sub"); + } + return lhs; + } + return visit(ctx->multiplicativeExpr()[0]); +} + +antlrcpp::Any GeneratorVisitor::visitMultiplicativeExpr(SpiceParser::MultiplicativeExprContext *ctx) { + if (ctx->prefixUnary().size() > 1) { + auto lhs = visit(ctx->prefixUnary()[0]).as(); + for (int i = 1; i < ctx->prefixUnary().size(); i++) { + auto rhs = visit(ctx->prefixUnary()[i]).as(); + if (ctx->MUL()[i-1]) + lhs = builder->CreateMul(lhs, rhs, "mul"); + else + lhs = builder->CreateSDiv(lhs, rhs, "div"); + } + return lhs; + } + return visit(ctx->prefixUnary()[0]); +} + +antlrcpp::Any GeneratorVisitor::visitPrefixUnary(SpiceParser::PrefixUnaryContext *ctx) { + auto value = visit(ctx->postfixUnary()); + + // Prefix unary is: PLUS_PLUS postfixUnary + if (ctx->PLUS_PLUS()) { + auto llvmValue = value.as(); + auto rhs = builder->CreateAdd(llvmValue, builder->getInt32(1), "pre_pp"); + auto lhs = namedValues[ctx->postfixUnary()->atomicExpr()->value()->IDENTIFIER()->toString()]; + builder->CreateStore(rhs, lhs); + return lhs; + } + + // Prefix unary is: MINUS_MINUS postfixUnary + if (ctx->MINUS_MINUS()) { + auto llvmValue = value.as(); + auto rhs = builder->CreateSub(llvmValue, builder->getInt32(1), "pre_mm"); + auto lhs = namedValues[ctx->postfixUnary()->atomicExpr()->value()->IDENTIFIER()->toString()]; + builder->CreateStore(rhs, lhs); + return lhs; + } + + // Prefix unary is: NOT postfixUnary + if (ctx->NOT()) + return builder->CreateNot(value.as(), "not"); + + return value; +} + +antlrcpp::Any GeneratorVisitor::visitPostfixUnary(SpiceParser::PostfixUnaryContext *ctx) { + auto value = visit(ctx->atomicExpr()); + + // Postfix unary is: PLUS_PLUS atomicExpr + if (ctx->PLUS_PLUS()) { + auto llvmValue = value.as(); + auto rhs = builder->CreateAdd(llvmValue, builder->getInt32(1), "post_pp"); + auto lhs = namedValues[ctx->atomicExpr()->value()->IDENTIFIER()->toString()]; + builder->CreateStore(rhs, lhs); + return rhs; + } + + // Postfix unary is: MINUS_MINUS atomicExpr + if (ctx->MINUS_MINUS()) { + auto llvmValue = value.as(); + auto rhs = builder->CreateSub(llvmValue, builder->getInt32(1), "post_mm"); + auto lhs = namedValues[ctx->atomicExpr()->value()->IDENTIFIER()->toString()]; + builder->CreateStore(rhs, lhs); + return rhs; + } + + return value; +} + +antlrcpp::Any GeneratorVisitor::visitAtomicExpr(SpiceParser::AtomicExprContext *ctx) { + // Atomic expr is: LPAREN value RPAREN + if (ctx->LPAREN()) return visit(ctx->assignment()).as(); + + // Atomic expr is: value + return visit(ctx->value()); +} + +antlrcpp::Any GeneratorVisitor::visitValue(SpiceParser::ValueContext *ctx) { + // Value is a double constant + if (ctx->DOUBLE()) { + auto value = std::stod(ctx->DOUBLE()->toString()); + return (llvm::Value*) llvm::ConstantFP::get(*context, llvm::APFloat(value)); + } + + // Value is an integer constant + if (ctx->INTEGER()) { + auto value = std::stoi(ctx->INTEGER()->toString()); + return (llvm::Value*) llvm::ConstantInt::getSigned(llvm::Type::getInt32Ty(*context), value); + } + + // Value is a string constant + if (ctx->STRING()) { + std::string value = ctx->STRING()->toString(); + auto charType = llvm::IntegerType::get(*context, 8); + std::vector chars(value.size()); + for(unsigned int i = 0; i < value.size(); i++) chars[i] = llvm::ConstantInt::get(charType, value[i]); + return (llvm::Value*) llvm::ConstantArray::get(llvm::ArrayType::get(charType, chars.size()), chars); + } + + // Value is a boolean constant + if (ctx->TRUE() || ctx->FALSE()) + return (llvm::Value*) llvm::ConstantInt::getSigned(llvm::Type::getInt1Ty(*context), ctx->TRUE() ? 1 : 0); + + // Value is an identifier + if (ctx->IDENTIFIER()) { + auto variableName = ctx->IDENTIFIER()->toString(); + llvm::Value* var = namedValues[variableName]; + if (!var) throw std::runtime_error("Internal compiler error - Variable '" + variableName + + "' not found in code generation step"); + return (llvm::Value*) builder->CreateLoad(var->getType()->getPointerElementType(), var); + } + + // Value is a function call + auto calleeFun = module->getFunction(llvm::StringRef(ctx->functionCall()->IDENTIFIER()->toString())); + auto calleeFunTy = calleeFun->getFunctionType(); + std::vector argValues; + if (ctx->functionCall()->paramLstCall()) { + auto params = ctx->functionCall()->paramLstCall()->assignment(); + for (int i = 0; i < params.size(); i++) { + auto argVal = visit(params[i]).as(); + llvm::Type* paramTy = calleeFunTy->getParamType(i); + llvm::Value* bitCastArgVal = builder->CreateBitCast(argVal, paramTy); + argValues.push_back(bitCastArgVal); + } + } + return (llvm::Value*) builder->CreateCall(calleeFun, argValues); +} + +antlrcpp::Any GeneratorVisitor::visitDataType(SpiceParser::DataTypeContext *ctx) { + // Data type is double + if (ctx->TYPE_DOUBLE()) return (llvm::Type*) llvm::Type::getDoubleTy(*context); + + // Data type is int + if (ctx->TYPE_INT()) return (llvm::Type*) llvm::Type::getInt32Ty(*context); + + // Data type is string + if (ctx->TYPE_STRING()) return (llvm::Type*) llvm::Type::getInt8Ty(*context)->getPointerTo(); + + // Data type is bool + if (ctx->TYPE_BOOL()) return (llvm::Type*) llvm::Type::getInt1Ty(*context); + + // Data type is dyn + // ToDo: Add support for dyn + return (llvm::Type*) nullptr; +} + +std::string GeneratorVisitor::getIRString() { + std::string output; + llvm::raw_string_ostream oss(output); + module->print(oss, nullptr); + return oss.str(); +} + +void GeneratorVisitor::initializeExternalFunctions() { + module->getOrInsertFunction("printf", llvm::FunctionType::get( + llvm::IntegerType::getInt32Ty(*context), + llvm::Type::getInt8Ty(*context)->getPointerTo(), true)); +} diff --git a/compiler/src/generator/GeneratorVisitor.h b/compiler/src/generator/GeneratorVisitor.h new file mode 100644 index 000000000..6080035ac --- /dev/null +++ b/compiler/src/generator/GeneratorVisitor.h @@ -0,0 +1,76 @@ +// Copyright (c) 2021 ChilliBits. All rights reserved. + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class GeneratorVisitor : public SpiceBaseVisitor { +public: + // Public methods + void init(); + void optimize(); + void emit(); + void dumpIR(); + antlrcpp::Any visitEntry(SpiceParser::EntryContext *ctx) override; + antlrcpp::Any visitMainFunctionDef(SpiceParser::MainFunctionDefContext *ctx) override; + antlrcpp::Any visitFunctionDef(SpiceParser::FunctionDefContext *ctx) override; + antlrcpp::Any visitProcedureDef(SpiceParser::ProcedureDefContext *ctx) override; + antlrcpp::Any visitForLoop(SpiceParser::ForLoopContext *ctx) override; + /*antlrcpp::Any visitForeachLoop(SpiceParser::ForeachLoopContext *ctx) override;*/ + antlrcpp::Any visitWhileLoop(SpiceParser::WhileLoopContext *ctx) override; + antlrcpp::Any visitIfStmt(SpiceParser::IfStmtContext *ctx) override; + antlrcpp::Any visitDeclStmt(SpiceParser::DeclStmtContext *ctx) override; + antlrcpp::Any visitFunctionCall(SpiceParser::FunctionCallContext *ctx) override; + antlrcpp::Any visitReturnStmt(SpiceParser::ReturnStmtContext *ctx) override; + antlrcpp::Any visitPrintfStmt(SpiceParser::PrintfStmtContext *ctx) override; + antlrcpp::Any visitAssignment(SpiceParser::AssignmentContext *ctx) override; + antlrcpp::Any visitTernary(SpiceParser::TernaryContext *ctx) override; + antlrcpp::Any visitLogicalOrExpr(SpiceParser::LogicalOrExprContext *ctx) override; + antlrcpp::Any visitLogicalAndExpr(SpiceParser::LogicalAndExprContext *ctx) override; + antlrcpp::Any visitBitwiseOrExpr(SpiceParser::BitwiseOrExprContext *ctx) override; + antlrcpp::Any visitBitwiseAndExpr(SpiceParser::BitwiseAndExprContext *ctx) override; + antlrcpp::Any visitEqualityExpr(SpiceParser::EqualityExprContext *ctx) override; + antlrcpp::Any visitRelationalExpr(SpiceParser::RelationalExprContext *ctx) override; + antlrcpp::Any visitAdditiveExpr(SpiceParser::AdditiveExprContext *ctx) override; + antlrcpp::Any visitMultiplicativeExpr(SpiceParser::MultiplicativeExprContext *ctx) override; + antlrcpp::Any visitPrefixUnary(SpiceParser::PrefixUnaryContext *ctx) override; + antlrcpp::Any visitPostfixUnary(SpiceParser::PostfixUnaryContext *ctx) override; + antlrcpp::Any visitAtomicExpr(SpiceParser::AtomicExprContext *ctx) override; + antlrcpp::Any visitValue(SpiceParser::ValueContext *ctx) override; + antlrcpp::Any visitDataType(SpiceParser::DataTypeContext *ctx) override; +private: + // Members + std::unique_ptr context = std::make_unique(); + std::unique_ptr> builder = std::make_unique>(*context); + std::unique_ptr module = std::make_unique("Module", *context); + std::map namedValues; + + // Private methods + std::string getIRString(); + void initializeExternalFunctions(); +}; \ No newline at end of file diff --git a/compiler/src/grammar/Spice.g4 b/compiler/src/grammar/Spice.g4 index 5c1000bb0..67aaa4147 100644 --- a/compiler/src/grammar/Spice.g4 +++ b/compiler/src/grammar/Spice.g4 @@ -1,6 +1,7 @@ grammar Spice; -entry: (stmt | functionDef | procedureDef)*; +entry: (stmt | mainFunctionDef | functionDef | procedureDef)*; +mainFunctionDef: F LESS TYPE_INT GREATER MAIN LPAREN paramLstDef? RPAREN LBRACE stmtLst RBRACE; functionDef: F LESS dataType GREATER IDENTIFIER LPAREN paramLstDef? RPAREN LBRACE stmtLst RBRACE; procedureDef: P IDENTIFIER LPAREN paramLstDef? RPAREN LBRACE stmtLst RBRACE; forLoop: FOR assignment SEMICOLON assignment SEMICOLON assignment LBRACE stmtLst RBRACE; @@ -11,11 +12,12 @@ ifStmt: IF assignment LBRACE stmtLst RBRACE; stmtLst: (stmt | forLoop | /*foreachLoop |*/ whileLoop | ifStmt)*; paramLstDef: (declStmt | assignment) (COMMA (declStmt | assignment))*; paramLstCall: assignment (COMMA assignment)*; -stmt: (declStmt | assignment | functionCall | assignment | importStmt | returnStmt) SEMICOLON; +stmt: (declStmt | assignment | functionCall | assignment | importStmt | returnStmt | printfStmt) SEMICOLON; declStmt: CONST? dataType IDENTIFIER; functionCall: IDENTIFIER LPAREN paramLstCall? RPAREN; importStmt: IMPORT STRING; returnStmt: RETURN assignment; +printfStmt: PRINTF LPAREN STRING (COMMA assignment)* RPAREN; assignment: ((declStmt | IDENTIFIER) (ASSIGN_OP | PLUS_EQUAL | MINUS_EQUAL | MUL_EQUAL | DIV_EQUAL))? ternary; ternary: logicalOrExpr (QUESTION_MARK logicalOrExpr ':' logicalOrExpr)?; @@ -47,6 +49,8 @@ WHILE: 'while'; CONST: 'const'; IMPORT: 'import'; RETURN: 'return'; +MAIN: 'main'; +PRINTF: 'printf'; TRUE: 'true'; FALSE: 'false'; STRING: '"' (~["\\\r\n] | '\\' (. | EOF))* '"'; diff --git a/compiler/src/main.cpp b/compiler/src/main.cpp index 54dbf7d24..6fb2363d9 100644 --- a/compiler/src/main.cpp +++ b/compiler/src/main.cpp @@ -5,6 +5,7 @@ #include "SpiceLexer.h" #include "SpiceParser.h" #include "analyzer/AnalyzerVisitor.h" +#include "generator/GeneratorVisitor.h" using namespace antlr4; @@ -17,11 +18,19 @@ int main(int argc, char** argv) { // Parse input to AST SpiceLexer lexer(&input); antlr4::CommonTokenStream tokens((antlr4::TokenSource*) &lexer); - SpiceParser parser(&tokens); + SpiceParser parser(&tokens); // Check for syntax errors // Execute syntactical analysis antlr4::tree::ParseTree *tree = parser.entry(); - SymbolTable* symbolTable = AnalyzerVisitor().visit(tree).as(); + AnalyzerVisitor().visit(tree); // Check for semantic errors + + // Execute generator + GeneratorVisitor generator = GeneratorVisitor(); + generator.init(); // Initialize code generation + generator.visit(tree); // Generate IR code + generator.dumpIR(); + generator.optimize(); // Optimize IR code + generator.emit(); // Emit object file for specified platform // Return with positive result code return 0; diff --git a/media/fibonacci.spice b/media/fibonacci.spice index 72ff7456e..46a862da1 100644 --- a/media/fibonacci.spice +++ b/media/fibonacci.spice @@ -1,11 +1,10 @@ f fibo(int n) { - if n <=1 { - return n; - } + if n <=1 { return n; } return fibo(n - 1) + fibo(n - 2); } f main() { - fibo(5); + result = fibo(5); + printf("%d", result); return 0; } \ No newline at end of file diff --git a/media/test-code.spice b/media/test-code.spice index da86d1b9f..6d482fe23 100644 --- a/media/test-code.spice +++ b/media/test-code.spice @@ -27,5 +27,5 @@ p testProcedure(string stringParam, bool boolParam = false) { double result = testString == "" || testBool ? 5.0 : 1.4; if result == 5.0 { result = 3.0; } int result1 = testFunction(3.0); - testProcedure("test", false, 123); + testProcedure("test", false); } \ No newline at end of file diff --git a/media/test.spice b/media/test.spice new file mode 100644 index 000000000..ef04810ec --- /dev/null +++ b/media/test.spice @@ -0,0 +1,8 @@ +f main() { + int operand1 = 6; + int operand2 = 5; + int res = operand1 | operand2; + printf("Computation result is: %d", res); + + return 0; +} \ No newline at end of file