diff --git a/.github/workflows/cmake-linux-x86.yml b/.github/workflows/cmake-linux-x86.yml
index 90e329a..856dc30 100644
--- a/.github/workflows/cmake-linux-x86.yml
+++ b/.github/workflows/cmake-linux-x86.yml
@@ -23,6 +23,19 @@ jobs:
steps:
- uses: actions/checkout@v4
+ - name: Cache LLVM build
+ uses: actions/cache@v3
+ id: cache-dependencies
+ with:
+ # Adjust these paths to match where LLVM is built and installed
+ path: |
+ Asa-LLVM-Compiled
+ /usr/lib
+ /usr/include
+ key: llvm-build-${{ hashFiles('Asa-LLVM-Compiled/llvm/**', 'Asa-LLVM-Compiled/build/CMakeCache.txt') }}
+ restore-keys: |
+ llvm-build-
+
- name: Set up Ninja
uses: ashutoshvarma/setup-ninja@93f8b9763516f1fb9b4d9840b12d844bee17791f
@@ -34,23 +47,30 @@ jobs:
version: latest
platform: x64
- - name: Setup LLVM
- # You may pin to the exact commit or the version.
- uses: ZhongRuoyu/setup-llvm@v0.1.1
- with:
- llvm-version: 20
-
-
# - name: Setup LLVM
-# run: |
+# # You may pin to the exact commit or the version.
+# uses: ZhongRuoyu/setup-llvm@v0.1.1
+# with:
+# llvm-version: 20
+
+
+
+ - name: Setup LLVM
+ if: steps.cache-dependencies.outputs.cache-hit != 'true'
+ run: |
+ git clone --depth 1 https://github.com/sam-astro/Asa-LLVM-Compiled
+ cd Asa-LLVM-Compiled
+ mkdir build
+ cmake -S llvm -B build -G Ninja -DLLVM_TARGETS_TO_BUILD="X86" -DCMAKE_BUILD_TYPE=MinSizeRel
+ cd build
+ ninja
+ sudo ninja install
+ cd ../../
# git config --add remote.origin.fetch '^refs/heads/users/*'
# git config --add remote.origin.fetch '^refs/heads/revert-*'
-# git clone --depth 1 https://github.com/llvm/llvm-project.git
# cd llvm-project
# git fetch origin 7615503409f19ad7e2e2f946437919d0689d4b3e
# cmake -S llvm -B build -G Ninja -DLLVM_TARGETS_TO_BUILD="X86" -DCMAKE_BUILD_TYPE=MinSizeRel
-# cmake --build build
-# cmake --install build
- name: Configure CMake
@@ -69,5 +89,7 @@ jobs:
working-directory: ${{github.workspace}}/build
# Execute tests defined by the CMake configuration.
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
- run: ./${{github.workspace}}/build/asa
+ run: |
+ cd ${{github.workspace}}/build
+ ./asa -V
diff --git a/README.md b/README.md
index 9456cbf..2249157 100644
--- a/README.md
+++ b/README.md
@@ -2,9 +2,7 @@
-
+
@@ -14,6 +12,8 @@
This repository contains all of the source code for the Asa programming language compiler and standard libraries.
+> There are no releases currently, and much of the current code is subject to change
+
[License]: LICENSE
diff --git a/examples/start/main.asa b/examples/start/main.asa
index cbf82dc..4da6923 100644
--- a/examples/start/main.asa
+++ b/examples/start/main.asa
@@ -7,6 +7,8 @@
#import Builtin.PipeOperatorTest;
+#import Rendering.Window;
+
//#import GameLogic;
//#file "./function_redefine.asa";
@@ -28,25 +30,25 @@ create::string() #inline; #replaceable; {
return *(#new string);
}
create :: string(c : *char) {
- s : string = *(#new string);
+ s : string = string();
puts("Creating new string from *char\n");
- len : uint32 = 10;
- //for(i : 0..4_294_967_296){
- // //if(c[i] == '\0')
- // // break;
- // len++;
+ //len : uint32 = 10;
+ ////for(i : 0..4_294_967_296){
+ //// //if(c[i] == '\0')
+ //// // break;
+ //// len++;
+ ////}
+ //s.address = malloc(10*2);
+ //s.length = 10;
+ ////len = 5;
+ ////s.address = malloc(5);
+ //for(i : 0..len){
+ // s.address[i] = 'A';
+ // //s.address[i] = c[i];
//}
- s.address = malloc(10*2);
- s.length = 10;
- //len = 5;
- //s.address = malloc(5);
- for(i : 0..len){
- s.address[i] = 'A';
- //s.address[i] = c[i];
- }
- //s.length = len;
- //s.address = "String value example";
+ ////s.length = len;
+ ////s.address = "String value example";
puts("Done!\n");
return s;
}
@@ -73,7 +75,7 @@ TEST_MACRO :: {printl("Macro works!")};
//}
printint:: (n : uint64) #hideast {
- putchar(48+n);
+ putchar(int(48+n));
}
modifyReference::(n : ref int) #hideast {
@@ -103,11 +105,13 @@ main :: (){
//var : bool;
+ pipeVal = pipeTest(1);
+ printint(pipeVal);
+ newline();
+
s : string = string();
s = string("This works");
- pipeTest(1);
-
//s.length = 8;
//printint(s.length);
//s.size();
@@ -116,6 +120,7 @@ main :: (){
printint(s.length);
newline();
s.print();
+ newline();
puts("Done!");
newline();
@@ -132,7 +137,7 @@ main :: (){
//x = x+1;
//putchar(*x_ptr);
//putchar(32);
-
+ //
//arr : *int = malloc(8*4);
//arr[2] = 72;
diff --git a/modules/Builtin/asa.asa b/modules/Builtin/asa.asa
index 8fd1e01..9545128 100644
--- a/modules/Builtin/asa.asa
+++ b/modules/Builtin/asa.asa
@@ -178,10 +178,10 @@ Strings :: module{
PipeOperatorTest :: module{
fnX :: int(x : int){
- return x*5;
+ return x*3;
}
fnY :: int(y : int){
- return y*5;
+ return y*2;
}
pipeTest::int(z : int){
return fnX(z) -> fnY(%);
diff --git a/modules/Rendering/window.asa b/modules/Rendering/window.asa
new file mode 100644
index 0000000..5b269e5
--- /dev/null
+++ b/modules/Rendering/window.asa
@@ -0,0 +1,4 @@
+
+Window :: module{
+
+}
diff --git a/src/codegen.cpp b/src/codegen.cpp
index 2414ff4..9fa0b99 100644
--- a/src/codegen.cpp
+++ b/src/codegen.cpp
@@ -20,6 +20,7 @@ llvm::Value* castValue(llvm::Value* value, llvm::Type* destType, bool isSrcSigne
std::unordered_map unresolvedTypes;
std::stack lastRetrievedElementType;
+std::stack pipeOperationValue;
std::unordered_map typeSigns = {
{"int128", true},
@@ -90,7 +91,10 @@ struct functionID {
}
void print()
{
- console::Write(returnType + " ", console::blueFGColor);
+ if (uses == 0)
+ return;
+ if (returnType != "")
+ console::Write(returnType + " ", console::blueFGColor);
console::Write(name, console::greenFGColor);
console::Write("(");
for (int i = 0; i < arguments.size(); i++) {
@@ -100,7 +104,13 @@ struct functionID {
if (i < arguments.size() - 1)
console::Write(", ");
}
- console::WriteLine(")");
+ console::Write(")");
+ if (isStructReturn) {
+ console::Write(" (");
+ console::Write("returns struct", console::yellowFGColor);
+ console::Write(")");
+ }
+ console::WriteLine();
}
bool compareASTNodeTypes(ASTNodeType& a, ASTNodeType& b, bool wereTypesInferred = false)
{
@@ -253,6 +263,12 @@ structType* getStructTypeFromLLVMType(Type*& t)
return nullptr;
}
+void printFunctionPrototypes()
+{
+ for (const auto& f : functionIDs)
+ f->print();
+}
+
functionID* getFunctionFromID(std::vector& fnIDs, std::string& name, argumentList& arguments, tokenPair*& t, bool wereTypesInferred = false, bool isMemberFunction = false)
{
functionID* best;
@@ -967,7 +983,7 @@ void* ASTNode::generateVariableExpression(int pass)
return Builder->CreateLoad(targetPtr->getAllocatedType(), targetPtr, token->first + "_load");
}
AllocaInst* A = (AllocaInst*)(val->val);
- baseType = A->getAllocatedType();
+ baseType = val->val->getType();
if (isRef || lvalue)
return A;
@@ -987,43 +1003,45 @@ void* ASTNode::generateReturn(int pass)
if (wasError) {
exit(1);
}
+ Function* currentFunc = Builder->GetInsertBlock()->getParent();
+ functionID* fnID = getFunctionIDFromFunctionPointer(functionIDs, currentFunc);
// Check if we're returning a struct
Type* returnType = Builder->GetInsertBlock()->getParent()->getReturnType();
- if (returnType->isStructTy()) {
- // For struct returns, we need to handle this specially
- // Option 1: If the function uses sret, copy to the sret parameter
- Function* currentFunc = Builder->GetInsertBlock()->getParent();
- if (currentFunc->hasStructRetAttr()) {
- // Get the sret parameter (first parameter)
- Value* sretPtr = &*currentFunc->arg_begin();
-
- // Copy the struct value to the sret location
- if (RetVal->getType()->isPointerTy()) {
- // If RetVal is a pointer to struct, memcpy from it
- Value* structSize = ConstantInt::get(Type::getInt64Ty(*TheContext),
- TheModule->getDataLayout().getTypeAllocSize(returnType));
-
- // Create memcpy call
- Function* memcpyFunc = Intrinsic::getDeclaration(TheModule.get(),
- Intrinsic::memcpy, {sretPtr->getType(), RetVal->getType(), Type::getInt64Ty(*TheContext)});
- Builder->CreateCall(memcpyFunc, {sretPtr, RetVal, structSize, ConstantInt::get(Type::getInt1Ty(*TheContext), 0)});
- }
- else {
- // If RetVal is a struct value, store it
- Builder->CreateStore(RetVal, sretPtr);
- }
-
-
- Builder->CreateRetVoid();
+ if (fnID->isStructReturn) {
+ ////if (returnType->isStructTy()) {
+ //// For struct returns, we need to handle this specially
+ //// Option 1: If the function uses sret, copy to the sret parameter
+ //if (currentFunc->hasStructRetAttr()) {
+ // Get the sret parameter (first parameter)
+ Value* sretPtr = &*currentFunc->arg_begin();
+
+ // Copy the struct value to the sret location
+ if (RetVal->getType()->isPointerTy()) {
+ // If RetVal is a pointer to struct, memcpy from it
+ Value* structSize = ConstantInt::get(Type::getInt64Ty(*TheContext),
+ TheModule->getDataLayout().getTypeAllocSize(returnType));
+
+ // Create memcpy call
+ Function* memcpyFunc = Intrinsic::getDeclaration(TheModule.get(),
+ Intrinsic::memcpy, {sretPtr->getType(), RetVal->getType(), Type::getInt64Ty(*TheContext)});
+ Builder->CreateCall(memcpyFunc, {sretPtr, RetVal, structSize, ConstantInt::get(Type::getInt1Ty(*TheContext), 0)});
}
else {
- // Option 2: Direct struct return (for small structs)
- if (RetVal->getType()->isPointerTy()) {
- // Load the struct value from the pointer
- RetVal = Builder->CreateLoad(returnType, RetVal, "struct_ret_load");
- }
- Builder->CreateRet(RetVal);
+ // If RetVal is a struct value, store it
+ Builder->CreateStore(RetVal, sretPtr);
}
+
+
+ Builder->CreateRetVoid();
+ //}
+ //else {
+ // // Option 2: Direct struct return (for small structs)
+ // if (RetVal->getType()->isPointerTy()) {
+ // // Load the struct value from the pointer
+ // RetVal = Builder->CreateLoad(returnType, RetVal, "struct_ret_load");
+ // }
+ // Builder->CreateRet(RetVal);
+ //}
}
else {
// Non-struct return, handle normally
@@ -1397,6 +1415,19 @@ void* ASTNode::generateBinaryExpression(int pass)
return nullptr;
}
+ // Check if it is the pipe operator first
+ if (nodeType == Pipe_Operation) {
+ // add L to stack
+ Value* L = (Value*)(childNodes[0]->*(childNodes[0]->codegen))(pass);
+ pipeOperationValue.push(L);
+ // then process R
+ Value* R = (Value*)(childNodes[1]->*(childNodes[1]->codegen))(pass);
+ // pop stack
+ pipeOperationValue.pop();
+
+ return R;
+ }
+
Value* L = (Value*)(childNodes[0]->*(childNodes[0]->codegen))(pass);
Value* R = (Value*)(childNodes[1]->*(childNodes[1]->codegen))(pass);
@@ -1412,14 +1443,26 @@ void* ASTNode::generateBinaryExpression(int pass)
if (L->getType() != R->getType()) {
printTokenWarning(token, "Operand type mismatch, performing implicit conversion");
castToHighestAccuracy(L, R, token);
+ if (L->getType() != R->getType()) {
+ printTokenError(token, "Operands to multiply are not the same type (after automatic cast)");
+ return nullptr;
+ }
}
ValueCategory category = getValueCategory(L->getType());
switch (category) {
case ValueCategory::Integer:
+ if (!L->getType()->isIntegerTy() || !R->getType()->isIntegerTy()) {
+ printTokenError(token, "Multiply: operands are not both integers");
+ return nullptr;
+ }
return generateIntegerBinaryOp(L, R);
case ValueCategory::Float:
+ if (!L->getType()->isFloatTy() || !R->getType()->isFloatTy()) {
+ printTokenError(token, "Multiply: operands are not both integers");
+ return nullptr;
+ }
return generateFloatBinaryOp(L, R);
case ValueCategory::Pointer:
return generatePointerBinaryOp(L, R);
@@ -1489,6 +1532,15 @@ Value* ASTNode::generatePointerBinaryOp(Value* L, Value* R)
return nullptr;
}
+void* ASTNode::generatePipePlaceholder(int pass)
+{
+ if (pipeOperationValue.size() > 0) {
+ return pipeOperationValue.top();
+ }
+ printTokenError(token, "Pipe operation placeholder '%' can only be used after a pipe operation");
+ return nullptr;
+}
+
bool ASTNode::checkForOperatorOverload()
{
std::string operatorName = "operator." + tokenAsString(token->second);
@@ -1856,7 +1908,17 @@ void* ASTNode::generateMemberAccess(int pass)
isCallMemberFunction = false;
baseType = lastRetrievedElementType.top();
- return Builder->CreateCall(CalleeF, ArgsV, "calltmp");
+ Value* callResult = nullptr;
+ Type* retType = CalleeF->getReturnType();
+
+ // Create the call
+ if (retType->isVoidTy())
+ Builder->CreateCall(CalleeF, ArgsV);
+ else
+ callResult = Builder->CreateCall(CalleeF, ArgsV, "calltmp");
+ return callResult;
+
+ //return Builder->CreateCall(CalleeF, ArgsV, "calltmp");
}
}
// If left is not pointer, assume another member access or index operator
@@ -2001,7 +2063,17 @@ void* ASTNode::generateMemberAccess(int pass)
isCallMemberFunction = false;
baseType = lastRetrievedElementType.top();
- return Builder->CreateCall(CalleeF, ArgsV, "calltmp");
+ Value* callResult = nullptr;
+ Type* retType = CalleeF->getReturnType();
+
+ // Create the call
+ if (retType->isVoidTy())
+ Builder->CreateCall(CalleeF, ArgsV);
+ else
+ callResult = Builder->CreateCall(CalleeF, ArgsV, "calltmp");
+ return callResult;
+
+ //return Builder->CreateCall(CalleeF, ArgsV, "calltmp");
}
}
@@ -2221,8 +2293,14 @@ void* ASTNode::generateCallExpression(int pass)
ArgsV.insert(ArgsV.begin(), sretAlloc);
}
- // Create the call (returns void for struct returns)
- Value* callResult = Builder->CreateCall(CalleeF, ArgsV, "calltmp");
+ Value* callResult = nullptr;
+ Type* retType = CalleeF->getReturnType();
+
+ // Create the call
+ if (retType->isVoidTy())
+ Builder->CreateCall(CalleeF, ArgsV);
+ else
+ callResult = Builder->CreateCall(CalleeF, ArgsV, "calltmp");
// For struct returns, return the loaded struct value (or pointer if lvalue)
if (isStructReturn) {
@@ -2982,11 +3060,18 @@ int outputObjectFile(std::string& objectFilePath)
return 0;
}
-int generateExecutable(const std::string& objectFilePath, const std::string& exeFilePath)
+int generateExecutable(const std::string& irFilePath, const std::string& exeFilePath)
{
- // Example using clang as the linker
- std::string command = "clang -o " + exeFilePath + " " + objectFilePath;
- int result = std::system(command.c_str());
+ // llc to convert .ll to assembly
+ std::string commandLLC = "llc -relocation-model=pic " + irFilePath + " -o " + irFilePath + ".s";
+ int result = std::system(commandLLC.c_str());
+ if (result != 0)
+ exit(1);
+ // clang as the linker
+ std::string commandClang = "clang -fPIE -o " + exeFilePath + " " + irFilePath + ".s -g";
+ result = std::system(commandClang.c_str());
+ if (result != 0)
+ exit(1);
return result;
}
diff --git a/src/codegen.h b/src/codegen.h
index 357193f..08bdf59 100644
--- a/src/codegen.h
+++ b/src/codegen.h
@@ -22,5 +22,6 @@ extern bool wasError;
void initializeCodeGenerator();
int outputObjectFile(std::string& objectFilePath);
-int generateExecutable(const std::string& objectFilePath, const std::string& exeFilePath);
+int generateExecutable(const std::string& irFilePath, const std::string& exeFilePath);
void removeUnusedPrototypes();
+void printFunctionPrototypes();
diff --git a/src/main.cpp b/src/main.cpp
index f4bfbca..0311bce 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -34,9 +34,10 @@ int main(int argc, char** argv)
{"output", required_argument, 0, 'o'},
{"optimize", required_argument, 0, 'O'},
{"compilerdebug", no_argument, 0, 'd'},
+ {"version", no_argument, 0, 'V'},
{0, 0, 0, 0}};
- c = getopt_long(argc, argv, "cvqdf:o:O:0",
+ c = getopt_long(argc, argv, "cvqdVf:o:O:0",
long_options, &option_index);
if (c == -1)
break;
@@ -88,6 +89,10 @@ int main(int argc, char** argv)
compilerDebug = true;
break;
+ case 'V':
+ console::ResetColor();
+ exit(0);
+
case '?':
console::ResetColor();
exit(1);
@@ -219,18 +224,47 @@ int main(int argc, char** argv)
TheModule->print(errs(), nullptr);
}
- //// Verify the module
- //if (llvm::verifyModule(*TheModule, &llvm::errs())) {
- // std::cerr << "Module verification failed!\n";
- // abort();
- //}
+ // Write IR to /build/.ll
+ std::string irFilePath = projectDirectory + "build/" + baseFileName + ".ll";
+ std::error_code EC;
+ llvm::raw_fd_ostream OS(irFilePath, EC, llvm::sys::fs::OF_None);
+ if (EC) {
+ // Handle error
+ llvm::errs() << "Could not open file: " << EC.message() << "\n";
+ exit(1);
+ }
+ TheModule->print(OS, nullptr);
+ OS.close();
+
+
+ // Verify the module
+ if (compilerDebug) {
+ console::WriteLine("\nVerifying code:");
+ if (llvm::verifyModule(*TheModule, &llvm::errs())) {
+ std::cerr << "Module verification failed!\n";
+ abort();
+ }
+ }
+
+ // Print out all function prototypes
+ if (verbosity >= 4)
+ printFunctionPrototypes();
- // Output the object file in project's build directory
- std::string objectFilePath = outputFileName + ".o";
- outputObjectFile(objectFilePath);
+ //// Output the object file in project's build directory
+ //std::string objectFilePath = outputFileName + ".o";
+ //outputObjectFile(objectFilePath);
// Link the object file into executable
- generateExecutable(objectFilePath, outputFileName);
+ generateExecutable(irFilePath, outputFileName);
if (verbosity >= 1)
- console::WriteLine("\n\nWrote executable to " + outputFileName);
+ console::WriteLine("\nWrote executable to " + outputFileName);
+
+
+ // Cleanup by deleting files only used for codegen.
+ if (verbosity >= 3) {
+ console::WriteLine("Cleaning up files: " + irFilePath);
+ console::WriteLine("Cleaning up files: " + irFilePath + ".s");
+ }
+ std::filesystem::remove(irFilePath);
+ std::filesystem::remove(irFilePath + ".s");
}
diff --git a/src/parser.cpp b/src/parser.cpp
index 3b15408..c2a37fb 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -780,6 +780,7 @@ ASTNode* generateAST(const std::vector& tokens, int depth, ASTNode*
case At_At: {
bool isUnaryR = false; // Operates on right
bool isUnaryL = false; // Left
+ bool noOp = false;
bool isGeneralOperator = false;
if (operatorDefaultNodeType.find(tokenType) != operatorDefaultNodeType.end()) {
node->nodeType = operatorDefaultNodeType[tokenType];
@@ -890,6 +891,12 @@ ASTNode* generateAST(const std::vector& tokens, int depth, ASTNode*
node->nodeType = Pointer_Node;
isUnaryL = true;
}
+ // No expression operator % when used in conjunction with pipe operator
+ else if (isUnaryR && tokenType == Percent) {
+ node->nodeType = Pipe_Placeholder;
+ node->codegen = &ASTNode::generatePipePlaceholder;
+ noOp = true;
+ }
else {
printTokenError(token, "Operator expected right argument");
exit(1);
@@ -899,10 +906,12 @@ ASTNode* generateAST(const std::vector& tokens, int depth, ASTNode*
secondTerm = generateAST(subTokens, depth + 1)->childNodes[0];
//secondTerm->nodeType = Expression_Term;
- if (!isUnaryR)
- node->childNodes.push_back(firstTerm);
- if (!isUnaryL)
- node->childNodes.push_back(secondTerm);
+ if (!noOp) {
+ if (!isUnaryR)
+ node->childNodes.push_back(firstTerm);
+ if (!isUnaryL)
+ node->childNodes.push_back(secondTerm);
+ }
if (isLeaf)
goto addNodeAsLeaf;
break;
diff --git a/src/parser.h b/src/parser.h
index 366b9c1..ace6865 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -70,6 +70,7 @@ enum ASTNodeType {
Bitwise_Shift_Left,
Bitwise_Shift_Right,
Pipe_Operation,
+ Pipe_Placeholder,
Range_Node,
@@ -176,6 +177,7 @@ const std::string ASTNodeTypeStrings[] = {
"Bitwise_Shift_Left",
"Bitwise_Shift_Right",
"Pipe_Operation",
+ "Pipe_Placeholder",
"Range_Node",
@@ -272,6 +274,7 @@ struct ASTNode {
void* generateIterator(int pass = 0);
void* generateUnaryExpression(int pass = 0);
void* generateBinaryExpression(int pass = 0);
+ void* generatePipePlaceholder(int pass = 0);
void* generateAccessOperation(int pass = 0);
void* generateMemberAccess(int pass = 0);
void* generateScopeBody(int pass = 0);
diff --git a/src/run.sh b/src/run.sh
index f664658..b1eee5e 100644
--- a/src/run.sh
+++ b/src/run.sh
@@ -8,4 +8,4 @@ cmake -G Ninja ../src;
echo -e "\nRunning Ninja...";
ninja -j12;
echo -e "\nStarting asa...";
-./asa;
+./asa -V;
diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp
index c73f797..71f7cc0 100644
--- a/src/tokenizer.cpp
+++ b/src/tokenizer.cpp
@@ -176,7 +176,7 @@ int tokenize(std::string& rawFile, std::vector& tokens, std::string&
fileNames.push_back(new std::string(fileName));
// Then start making tokens
- int lineNumber = 1;
+ int lineNumber = 0;
int indexInLine = 0;
int startIndexInLine = 0;
std::string* lineValue = new std::string("");