diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..1905e0c --- /dev/null +++ b/.clang-format @@ -0,0 +1,66 @@ +# Use LLVM as a base and customize from there +BasedOnStyle: LLVM +Language: Cpp + +# Indentation and spacing +IndentWidth: 4 +TabWidth: 4 +UseTab: Never +ContinuationIndentWidth: 8 +ColumnLimit: 80 +AccessModifierOffset: -4 +NamespaceIndentation: All +IndentCaseLabels: true +IndentGotoLabels: false +IndentPPDirectives: AfterHash + +# Braces and layout +BreakBeforeBraces: Allman +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false + +# Spaces +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpacesInParentheses: false +SpacesInAngles: false +SpacesInSquareBrackets: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpaceBeforeAssignmentOperators: true + +# Pointers and references +DerivePointerAlignment: false +PointerAlignment: Left # "int* ptr" rather than "int *ptr" +ReferenceAlignment: Left + +# Includes +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^<.*>$' + Priority: 1 + - Regex: '^".*"$' + Priority: 2 +SortIncludes: true + +# Line breaking +BreakConstructorInitializers: BeforeColon +ConstructorInitializerAllOnOneLineOrOnePerLine: true +BinPackParameters: false +BinPackArguments: false +PenaltyBreakBeforeFirstCallParameter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 + +# C++ specific +Standard: Latest +Cpp11BracedListStyle: true +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveDeclarations: Consecutive +AlignOperands: Align +AlignAfterOpenBracket: Align diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml new file mode 100644 index 0000000..31f2582 --- /dev/null +++ b/.github/workflows/cmake-multi-platform.yml @@ -0,0 +1,76 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: CMake on multiple platforms + +on: + push: + branches: [ "First" ] + pull_request: + branches: [ "First" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Release] + c_compiler: [gcc, clang, cl] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + exclude: + - os: windows-latest + c_compiler: gcc + - os: windows-latest + c_compiler: clang + - os: ubuntu-latest + c_compiler: cl + + steps: + - uses: actions/checkout@v4 + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} + + - name: Upload Build Artifact + uses: actions/upload-artifact@v4 + with: + name: build-${{ matrix.os }}-${{ matrix.c_compiler }}-${{ matrix.build_type }} + path: ${{ steps.strings.outputs.build-output-dir }} + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..87ee204 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.vs/ +build/ +bin/ + +**/Thumbs.db +**/desktop.ini + +*.swp +*~ + +examples/* +!examples/*.cppsp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ca61313 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.15) +project(cppsp) +option(CPPSP_BUILD_EXAMPLES "Build cppsp examples" OFF) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +add_executable(cppsp_compiler "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp") +if(CPPSP_BUILD_EXAMPLES) +add_custom_command( + TARGET cppsp_compiler + POST_BUILD + COMMAND "$" ../examples/helloworld.cppsp + COMMAND "$" ../examples/advanced.cppsp + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + COMMENT "Building examples..." +) +endif() diff --git a/README.md b/README.md index 7e787fb..bd11f33 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,65 @@ -# cppspcppsp logo -cppsp -a scripting language base on c++ +# cppspcppsp logo + +cppsp -- a transpiled language based on C++ + ## Install -Download the cppsp_compiler.exe or compiler the sourcecode by yourself -* Requirement:prepare your own c++ compiler and set it's folder to environment path(environment variable) -* Requirement:a 64bits c++ compiler to make sure exe can be open -* Optional: put the folder path of exe to environment + +Build from source using CMake or download a release. Keep it somewhere and optionally add it to your environment variables. + ## Usage -* Use cmd or other console to compiler .cppsp file: -cppsp_compiler(if not in environment path:.\cppsp_compiler.exe or c:\...\cppsp_compiler.exe) script.cppsp -* Setting c++ include/lib folder by .ini file + +- Use a terminal to build .cppsp file: + +`cppsp_compiler script.cppsp` + +- Setting c++ include/lib folder by .ini file include.ini:C:\...\include1,c:\...\include2 lib.ini:C:\...\lib1,c:\...\lib2 -## Feature -* can compile when there is only print("hello world") in .cppsp -* can use almost c++ header by import -* can use c++ code by @inject and @function -* enable indentation and multi-line after v1.3 -## Keyword -* #useclang or #usegcc : use clang++ or g++ compile command -* @command("..."): add command when compile like:-Os、-m64 -* #overwrite:make @command() overwrite g++ .... or clang++ compile command like @command("g++ -Os -m64 -nostdlib -shared -o dll.dll dll.cpp") and add "*/" in the end of int main{..} but you'll need @funcion<> to make comment work -* import :import header in c++ and accept import x,y,..... -* @funcuion<<...>>: inject everything(void()、int()、bool()、even #define and using namespace) in <<...>> to the space under #include above int main() -* @inject(...) :inject everything in (...) to int main{...} -* print(): print content to console like print("12\n"," ",1," ",2.1,true,false," ") -* input(): input data to variables,but need @inject() to declare varibles -* //:comment + +## Features + +- Can compile with only `print("hello world")` in .cppsp +- Can use almost any C++ header by import +- Can use C++ code by @inject and @function +- Enable indentation and multi-line after v1.3 + +## Keywords + +- `#useclang` or `#usegcc` : use `clang++` or `g++` compile command +- `@command("...")`: add command when compile like: `-Os、-m64` +- #overwrite:make @command() overwrite g++ .... or clang++ compile command like @command("g++ -Os -m64 -nostdlib -shared -o dll.dll dll.cpp") and add "*/" in the end of int main{..} but you'll need @funcion<> to make comment work +- `import`: import header in c++ and accept import x,y,..... +- @funcuion<<...>>: inject everything(void()、int()、bool()、even #define and using namespace) in <<...>> to the space under #include above int main() +- `@inject(...)`: inject everything in (...) to int main{...} +- print(): print content to console like print("12\n"," ",1," ",2.1,true,false," ") +- `input()`: input data to variables, but need `@inject()` to declare varibles +- `//`:comment + ### Warning ⚠️ -* Cannot accept any space/blank before keyword before v1.2! -* No multi-line before v1.3! -* @command() will never be multi-line but you can use following as an alternative + +- Cannot accept any space/blank before keyword before v1.2! +- No multi-line before v1.3! +- `@command()` will never be multi-line but you can use following as an alternative: + ``` @command("-f1 -f2 ..... -f5") @command("-f6 -f7 ....-f10") ``` + under #overwritender #overwrite + ``` @command("g++ -Os -m64 -nostdlib -shared ") @command(" -o dll.dll dll.cpp") - ``` -## Example -```cpp +``` + +## Examples + +``` print("hello world") ``` -* another exmaple: -```cpp + +``` @command("-mtune=native -fomit-frame-pointer -static-libgcc -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--as-needed -s -Wl,--strip-all -Os -m64") import iostream,vector @function<> @@ -57,8 +72,7 @@ input(x,y,z) @function< cars = {"Volvo", "BMW", "Ford", "Mazda"};};>> print(x+y+z) ``` -* simple dll -```cpp +``` #overwrite @command("g++ -Os -m64 -nostdlib -shared -o dll.dll dll.cpp") @function<> diff --git a/cppsp_compiler.cpp b/cppsp_compiler.cpp deleted file mode 100644 index 4f427f8..0000000 --- a/cppsp_compiler.cpp +++ /dev/null @@ -1,409 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -bool isWindows=false;bool isMac=false;bool isLinux =false; - #if defined(_WIN32) || defined(_WIN64) - #define isWindows 1 - #elif defined(__APPLE__) && defined(__MACH__) - #define isMac 1 - #elif defined(__linux__) - #define isLinux 1 - #endif -//檢查dll依賴:objdump -p cppsp_compiler.exe | findstr ".dll" - -namespace fs = std::filesystem; -bool Ifiostream=0;bool commentInReg=false; -// ====== 新增:萬用語法指令註冊器 ====== -std::unordered_map> cpsCommands; -std::unordered_map g_vars; // 全域變數 -std::unordered_mapfunc_head_end ; -void registerCommand(const std::string& name,const std::string& start,const std::string& end, - std::function handler) { - cpsCommands[name] = handler; - func_head_end[start]=end; -} -//註解 -bool isComment(const std::string& line) { - bool in_string = false; - bool escape = false; - - for (size_t i = 0; i + 1 < line.size(); ++i) { - char c = line[i]; - - if (escape) { - escape = false; - continue; - } - - if (c == '\\') { - escape = true; - continue; - } - - if (c == '"') { - in_string = !in_string; - continue; - } - - // 第一個非空白字元前遇到 // - if (!in_string) { - if (c == ' ' || c == '\t') - continue; - - if (c == '/' && line[i + 1] == '/') - return true; - - // 一旦遇到有效字元,就不可能是註解行,直接返回 false所以不會跑下一個字 - return false; - } - } - return false; -} -//去空格 -inline std::string noblank(const std::string& token) { - // 去掉前後空格 - std::string v = token; - auto trim = [](std::string& s){ - while (!s.empty() && isspace(s.front())) s.erase(s.begin()); - while (!s.empty() && isspace(s.back())) s.pop_back(); - }; trim(v); - // 空字串直接跳過 - if (v.empty()) return ""; - // 布林判斷 - if (v == "true") return "printf(\"true\");\n"; if (v == "false") return "printf(\"false\");\n"; - // 判斷數字 - char* end; - double num = std::strtod(v.c_str(), &end); - if (*end == '\0') {// 判斷整數或浮點 - if (num == (int)num) return "{ auto _t = (int)" + v + "; printf(\"%d\", _t); }\n"; - else return "{ auto _t = " + v + "; printf(\"%g\", _t); }\n"; } - // 判斷字串(含 "abc")或 fallback - if (!v.empty() && v.front()=='"' && v.back()=='"') return "printf(" + v + ");\n"; - // 其他情況也當字串 - return "printf(\"" + v + "\");\n";} - -size_t findMatchingEnd(const std::string& line, size_t start, const std::string& h, const std::string& e) { - int depth = 1; - // 確保 start 不越界 - if (start >= line.size()) return std::string::npos; - - for (size_t i = start; i + e.length() <= line.size(); ++i) { - // 先檢查是否遇到新的起始符號 (嵌套) - if (line.compare(i, h.length(), h) == 0) { - depth++; - i += h.length() - 1; // 跳過符號長度 - } - // 再檢查是否遇到結束符號 - else if (line.compare(i, e.length(), e) == 0) { - depth--; - if (depth == 0) - return i; - i += e.length() - 1; // 跳過符號長度 - } - } - return std::string::npos; -} -// 萃取 print("abc",1,2) 裡面的參數部分以及其他() 內容 -std::string extractArgs(const std::string& line, const std::string& h, const std::string& e, bool& keywordEnd) { - keywordEnd = true; // 預設為已結束 - size_t l = line.find(h); - - // 如果連頭都找不到,直接回傳空或原字串(視需求而定,這裡假設若沒頭則不處理) - if (l == std::string::npos) { - keywordEnd = true; // 沒頭不算未完成,算找不到 - return ""; - } - - // 尋找對應的結尾 - size_t r = findMatchingEnd(line, l + h.length(), h, e); - - if (r == std::string::npos) { - // 找不到結尾,表示多行模式,回傳 header 之後的所有內容 - keywordEnd = false; - return line.substr(l + h.length()); - } else { - // 找到結尾,回傳中間的內容 - return line.substr(l + h.length(), r - (l + h.length())); - } -} - -// 通用呼叫,用於主迴圈:直接給一行程式碼,它會自動選擇指令 -// 通用呼叫,用於主迴圈:直接給一行程式碼,它會自動選擇指令 -std::string runCommand(const std::string& line,bool iffunc) { - // [新增] 靜態變數,用於記憶多行狀態 - static std::string pendingBuffer = ""; // 累積的程式碼 - static std::string pendingCmdKey = ""; // 正在等待的指令關鍵字 (如 print) - static std::string pendingHead = ""; // 正在等待的起始符 (如 "(") - static std::string pendingEnd = ""; // 正在等待的結束符 (如 ")") - // 假設 cpsCommands 值的型別是 std::function - static std::function pendingFunc = nullptr; - // 判斷是否處於多行模式 - bool inMultiLine = !pendingBuffer.empty(); - // 如果是註解中,或是空行,且不在多行模式下,直接略過 - if (!inMultiLine && (line.find("//") == 0 || line.empty())) return ""; - // 注意:原本的 commentInReg 若是全域變數請保留使用,此處僅示範邏輯 - std::string currentLine; - if (inMultiLine) { - // 多行模式:將新的一行接在緩衝區後面 (加上換行符號) - pendingBuffer += "\n" + line; currentLine = pendingBuffer; - } else { currentLine = line; } - // 如果在多行模式,直接使用記憶中的參數進行檢查,不需要重跑迴圈 - if (inMultiLine) { - bool keywordEnd = false; - // 嘗試在累積的字串中萃取參數 - std::string args = extractArgs(currentLine, pendingHead, pendingEnd, keywordEnd); - if (keywordEnd) { - // 找到了結尾!執行指令 - std::string result = pendingFunc(args); - // 清空靜態狀態,回到單行模式 - pendingBuffer = ""; pendingCmdKey = ""; pendingHead = ""; pendingEnd = ""; pendingFunc = nullptr; - return result; - } else { return ""; } } - // --- 以下為新指令的偵測邏輯 (原本的迴圈) --- - - for (auto& p : cpsCommands) { - // 優化:先檢查是否包含指令,避免無效搜尋 - size_t cmdPos = line.find(p.first); if (cmdPos == std::string::npos) continue; - // 確保指令不是變數的一部分 (簡單邊界檢查,可選) - // if (cmdPos > 0 && isalnum(line[cmdPos-1])) continue; - if (commentInReg) return ""; // 假設這是全域變數 - for (auto& note : func_head_end) { - const std::string& hd = note.first; const std::string& ed = note.second; - // 檢查這一行是否有對應的起始符號 (例如 "(") - if (line.find(hd) == std::string::npos) continue; - bool isFuncCmd = (p.first == "@function"); - bool shouldRun = (isFuncCmd && iffunc) || (!isFuncCmd && !iffunc); - bool keywordEnd = false;std::string args = extractArgs(line, hd, ed, keywordEnd); - if (!keywordEnd) { - // [變更] 發現沒閉合,進入多行模式 - if (shouldRun) { - pendingBuffer = line; // 存入緩衝區 - pendingCmdKey = p.first; - pendingFunc = p.second; // 記住要執行的函數 - pendingHead = hd; // 記住符號 - pendingEnd = ed; - return ""; // 等待下一行 - } - } else { - // 單行直接完成,執行並回傳 - if (shouldRun) { - return p.second(args); - } - return ""; // 雖然解析成功,但當前模式不執行 - } } } - return ""; // 找不到指令就忽略 -} - - -// 讀 ini 檔,用逗號分割,轉成 -I 或 -L 參數 -std::string parseIni(const std::string& path, const std::string& flag) { - std::ifstream infile(path); - if (!infile) return ""; - - std::string line; - std::string result; - while(std::getline(infile, line)){ - std::stringstream ss(line); - std::string token; - while (std::getline(ss, token, ',')) { - if (!token.empty()) { - result += flag+ "\"" + token + "\" "; - } - } - - } - return result; -} -// 判斷字串是否為布林值 -auto is_bool = [](const std::string& s){ - return s == "true" || s == "false"; -}; -auto is_number = [](const std::string& s){ - char* end; - std::strtod(s.c_str(), &end); - return *end == '\0'; -}; - // 去掉前後空白 - auto trim = [](std::string s) { - while (!s.empty() && (s.front() == ' ' || s.front() == '\t')) s.erase(s.begin()); - while (!s.empty() && (s.back() == ' ' || s.back() == '\t')) s.pop_back(); - return s; - }; - -int main(int argc, char* argv[]) { - bool enableclang =false; - //註冊 - registerCommand("print","(",")", [](const std::string& args) { - std::stringstream ss(args); - std::string tok; - std::vector v; - while (std::getline(ss, tok, ',')) v.push_back(tok); - std::string out;double iorf; - //處理非" "的參數 - if(v.empty()) {for (size_t i =0; i < v.size(); ++i)v[i]= noblank(v[i]);} - if (!v.empty()) { - for (size_t i =0; i < v.size(); ++i) { - std::string cur = v[i]; - if ( cur == "true") { out += "printf(\"true\");\n";} - else if (cur == "false") { out += "printf(\"false\");\n"; } - else if (is_number(cur)) { - double iorf = std::stod(cur); - if (iorf == (int)iorf) out += "{ int _t = " + cur + "; printf(\"%d\", _t); }\n"; - else out += "{ double _t = " + cur + "; printf(\"%g\", _t); }\n"; } - else if(cur[0]== 'L') out +=(Ifiostream)? "std::wcout<<"+cur+";\n" :"wprintf("+cur+");\n"; - else if(Ifiostream==true) out +="std::cout<<"+cur+";\n"; - else { out += "printf(" + cur + ");\n";} -} - } - - return out; -}); - -registerCommand("println","(",")", [](const std::string& args) { - return "printf(" + args + "); printf(\"\\n\");\n"; -}); - -registerCommand("input","(",")", [](const std::string& args) { - std::stringstream ss(args); - std::string tok,out; - std::vector v; - while (std::getline(ss, tok, ',')) v.push_back(tok); - if(Ifiostream==false) out= "printf(\"need import iostream\")"; - if (!v.empty()) { - for(int i=0;i>", [](const std::string& args) { - return args; -}); -//////// - if (argc < 2) { - std::cerr << "Usage: cppsp_compiler(if not in environment path:.\\cppsp_compiler.exe or c:\\...\\cppsp_compiler.exe) script.cppsp\ninclude.ini:C:\\...\\include1,c:\\...\\include2\nlib.ini:C:\\...\\lib1,c:\\...\\lib2\n"; - return 1; - } - - fs::path cpsPath(argv[1]); - if (!fs::exists(cpsPath)) { - std::cerr << "File not found.\n"; - return 1; - } - - std::ifstream infile(cpsPath);std::ifstream funcfile(cpsPath); - if (!infile) { - std::cerr << "Cannot open file.\n"; - return 1; - } - - fs::path cppPath = cpsPath.parent_path() / (cpsPath.stem().string() + ".cpp"); - std::ofstream outfile(cppPath); - if (!outfile) { - std::cerr << "Cannot create cpp file.\n"; - return 1; - } - - outfile << "#include \n"; - std::string importline; std::ifstream fileinclude(cpsPath); -while (std::getline(fileinclude, importline)) { - bool comment=isComment(importline); -if (!comment && importline.find("import ") != std::string::npos) { - size_t pos = importline.find("import "); - std::string imports = importline.substr(pos + 7); // "import " 長度 7 - - imports = trim(imports); - - // 拆成多個標頭 - std::stringstream ss(imports); - std::string header; - while (std::getline(ss, header, ',')) { - header = trim(header); // 每個標頭也要去掉空白 - if (header.empty()) continue; - - fs::path importFile = cpsPath.parent_path() / header; - outfile << "#include \"" << importFile.lexically_relative(cpsPath.parent_path()).string() << "\"\n"; - - if (importFile.filename().string() == "iostream") { - Ifiostream = true; - } - } -} -} - std::string funcline; - while (std::getline(funcfile, funcline)) { - - /* if(funcline != std::string::npos){ size_t pos = funcline.find("@function<<"); - std::string funcname = funcline.substr(pos + 11); // "@function(" 長度 10 - funcname = funcname.substr(0, funcname.find(">>"));*/ - std::string funcname = runCommand(funcline,true); - bool comment=isComment(funcline); - if (comment) {continue; } - if (funcname.empty()) continue; - outfile < start) - extraFlags += " " + line.substr(start + 1, end - start - 1); - } - if(line.find("#useclang")!= std::string::npos){enableclang=true;} - if(line.find("#usegcc")!= std::string::npos){enableclang=false;} - if(line.find("#overwrite")!= std::string::npos){enableoverwrite=true;} - } - - outfile << "\nreturn 0;\n}\n"; - if(enableoverwrite) outfile << "*/"; - outfile.close(); -std::string local=(isMac || isLinux)? "./":""; - fs::path exePath = cpsPath.parent_path() / (local+cpsPath.stem().string() );// .exe後綴 : + ".exe"); - - // 讀 include.ini 和 lib.ini - std::string includeFlags = parseIni("include.ini", "-I"); - std::string libFlags = parseIni("lib.ini", "-L"); - - std::string gppCommand = "g++ \"" + cppPath.string() + "\" -o \"" + exePath.string() + "\" " - + extraFlags + " " - + includeFlags + " " - + libFlags; - if(enableclang) gppCommand = "clang++ \"" + cppPath.string() + "\" -o \"" + exePath.string() + "\" " - + extraFlags + " " - + includeFlags + " " - + libFlags; - if(enableoverwrite) gppCommand = extraFlags + " " + includeFlags + " " + libFlags; - - std::cout << "Compiling: " << gppCommand << "\n"; - int ret = system(gppCommand.c_str()); - - if (ret != 0) { - std::cerr << "Compilation failed!\n"; - return 1; - } - - if(!enableoverwrite) std::cout << "Compilation succeeded! Executable: " << exePath.string() << "\n"; - if(enableoverwrite) std::cout << "Compilation succeeded!\n"; - if(!enableoverwrite) int runexe= system(exePath.string().c_str()); - return 0; -} diff --git a/cppsp_compiler.exe b/cppsp_compiler.exe deleted file mode 100644 index 7a93667..0000000 Binary files a/cppsp_compiler.exe and /dev/null differ diff --git a/cppsp_compiler_linux b/cppsp_compiler_linux deleted file mode 100644 index cb9f4c1..0000000 Binary files a/cppsp_compiler_linux and /dev/null differ diff --git a/cppsp_compiler_mac b/cppsp_compiler_mac deleted file mode 100644 index 704a338..0000000 Binary files a/cppsp_compiler_mac and /dev/null differ diff --git a/dll/libgcc_s_seh-1.dll b/dll/libgcc_s_seh-1.dll deleted file mode 100644 index 78857d3..0000000 Binary files a/dll/libgcc_s_seh-1.dll and /dev/null differ diff --git a/dll/libstdc++-6.dll b/dll/libstdc++-6.dll deleted file mode 100644 index bda377c..0000000 Binary files a/dll/libstdc++-6.dll and /dev/null differ diff --git a/dll/libwinpthread-1.dll b/dll/libwinpthread-1.dll deleted file mode 100644 index a3e7a60..0000000 Binary files a/dll/libwinpthread-1.dll and /dev/null differ diff --git a/examples/advanced.cppsp b/examples/advanced.cppsp new file mode 100644 index 0000000..e968728 --- /dev/null +++ b/examples/advanced.cppsp @@ -0,0 +1,11 @@ +@command("-mtune=native -fomit-frame-pointer -static-libgcc -ffunction-sections -fdata-sections -Wl,--gc-sections -Wl,--as-needed -s -Wl,--strip-all -Os -m64") +import iostream,vector +@function<> +print("12\n"," ",1," ",2.1,true,false," ") +print( "abc") +print(1,"\n") //abv +//print(1.1) +@inject(int x=1;int y=2;int z=3; auto is_bool = [](const std::string& s){ return s == "true" || s == "false";};) +input(x,y,z) +@function< cars = {"Volvo", "BMW", "Ford", "Mazda"};};>> +print(x+y+z) diff --git a/examples/helloworld.cppsp b/examples/helloworld.cppsp new file mode 100644 index 0000000..9c05391 --- /dev/null +++ b/examples/helloworld.cppsp @@ -0,0 +1 @@ +print("Hello world!\n") diff --git a/cppsp.png b/res/cppsp.png similarity index 100% rename from cppsp.png rename to res/cppsp.png diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..603a9e8 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,587 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +bool isWindows = false; +bool isMac = false; +bool isLinux = false; +#ifdef _WIN32 +# define isWindows 1 +#elif defined(__APPLE__) && defined(__MACH__) +# define isMac 1 +#elif defined(__linux__) +# define isLinux 1 +#endif +// 檢查dll依賴:objdump -p cppsp_compiler.exe | findstr ".dll" + +namespace fs = std::filesystem; +bool Ifiostream = 0; +bool commentInReg = false; +// ====== 新增:萬用語法指令註冊器 ====== +std::unordered_map> + cpsCommands; +std::unordered_map g_vars; // 全域變數 +std::unordered_map func_head_end; +void registerCommand(const std::string& name, + const std::string& start, + const std::string& end, + std::function handler) +{ + cpsCommands[name] = handler; + func_head_end[start] = end; +} +// 註解 +bool isComment(const std::string& line) +{ + bool in_string = false; + bool escape = false; + + for (size_t i = 0; i + 1 < line.size(); ++i) + { + char c = line[i]; + + if (escape) + { + escape = false; + continue; + } + + if (c == '\\') + { + escape = true; + continue; + } + + if (c == '"') + { + in_string = !in_string; + continue; + } + + // 第一個非空白字元前遇到 // + if (!in_string) + { + if (c == ' ' || c == '\t') + continue; + + if (c == '/' && line[i + 1] == '/') + return true; + + // 一旦遇到有效字元,就不可能是註解行,直接返回 + // false所以不會跑下一個字 + return false; + } + } + return false; +} +// 去空格 +inline std::string noblank(const std::string& token) +{ + // 去掉前後空格 + std::string v = token; + auto trim = [](std::string& s) + { + while (!s.empty() && isspace(s.front())) + s.erase(s.begin()); + while (!s.empty() && isspace(s.back())) + s.pop_back(); + }; + trim(v); + // 空字串直接跳過 + if (v.empty()) + return ""; + // 布林判斷 + if (v == "true") + return "printf(\"true\");\n"; + if (v == "false") + return "printf(\"false\");\n"; + // 判斷數字 + char* end; + double num = std::strtod(v.c_str(), &end); + if (*end == '\0') + { // 判斷整數或浮點 + if (num == (int)num) + return "{ auto _t = (int)" + v + "; printf(\"%d\", _t); }\n"; + else + return "{ auto _t = " + v + "; printf(\"%g\", _t); }\n"; + } + // 判斷字串(含 "abc")或 fallback + if (!v.empty() && v.front() == '"' && v.back() == '"') + return "printf(" + v + ");\n"; + // 其他情況也當字串 + return "printf(\"" + v + "\");\n"; +} + +size_t findMatchingEnd(const std::string& line, + size_t start, + const std::string& h, + const std::string& e) +{ + int depth = 1; + // 確保 start 不越界 + if (start >= line.size()) + return std::string::npos; + + for (size_t i = start; i + e.length() <= line.size(); ++i) + { + // 先檢查是否遇到新的起始符號 (嵌套) + if (line.compare(i, h.length(), h) == 0) + { + depth++; + i += h.length() - 1; // 跳過符號長度 + } + // 再檢查是否遇到結束符號 + else if (line.compare(i, e.length(), e) == 0) + { + depth--; + if (depth == 0) + return i; + i += e.length() - 1; // 跳過符號長度 + } + } + return std::string::npos; +} +// 萃取 print("abc",1,2) 裡面的參數部分以及其他() 內容 +std::string extractArgs(const std::string& line, + const std::string& h, + const std::string& e, + bool& keywordEnd) +{ + keywordEnd = true; // 預設為已結束 + size_t l = line.find(h); + + // 如果連頭都找不到,直接回傳空或原字串(視需求而定,這裡假設若沒頭則不處理) + if (l == std::string::npos) + { + keywordEnd = true; // 沒頭不算未完成,算找不到 + return ""; + } + + // 尋找對應的結尾 + size_t r = findMatchingEnd(line, l + h.length(), h, e); + + if (r == std::string::npos) + { + // 找不到結尾,表示多行模式,回傳 header 之後的所有內容 + keywordEnd = false; + return line.substr(l + h.length()); + } + else + { + // 找到結尾,回傳中間的內容 + return line.substr(l + h.length(), r - (l + h.length())); + } +} + +// 通用呼叫,用於主迴圈:直接給一行程式碼,它會自動選擇指令 +// 通用呼叫,用於主迴圈:直接給一行程式碼,它會自動選擇指令 +std::string runCommand(const std::string& line, bool iffunc) +{ + // [新增] 靜態變數,用於記憶多行狀態 + static std::string pendingBuffer = ""; // 累積的程式碼 + static std::string pendingCmdKey = ""; // 正在等待的指令關鍵字 (如 print) + static std::string pendingHead = ""; // 正在等待的起始符 (如 "(") + static std::string pendingEnd = ""; // 正在等待的結束符 (如 ")") + // 假設 cpsCommands 值的型別是 std::function + static std::function pendingFunc = nullptr; + // 判斷是否處於多行模式 + bool inMultiLine = !pendingBuffer.empty(); + // 如果是註解中,或是空行,且不在多行模式下,直接略過 + if (!inMultiLine && (line.find("//") == 0 || line.empty())) + return ""; + // 注意:原本的 commentInReg 若是全域變數請保留使用,此處僅示範邏輯 + std::string currentLine; + if (inMultiLine) + { + // 多行模式:將新的一行接在緩衝區後面 (加上換行符號) + pendingBuffer += "\n" + line; + currentLine = pendingBuffer; + } + else + { + currentLine = line; + } + // 如果在多行模式,直接使用記憶中的參數進行檢查,不需要重跑迴圈 + if (inMultiLine) + { + bool keywordEnd = false; + // 嘗試在累積的字串中萃取參數 + std::string args = + extractArgs(currentLine, pendingHead, pendingEnd, keywordEnd); + if (keywordEnd) + { + // 找到了結尾!執行指令 + std::string result = pendingFunc(args); + // 清空靜態狀態,回到單行模式 + pendingBuffer = ""; + pendingCmdKey = ""; + pendingHead = ""; + pendingEnd = ""; + pendingFunc = nullptr; + return result; + } + else + { + return ""; + } + } + // --- 以下為新指令的偵測邏輯 (原本的迴圈) --- + + for (auto& p : cpsCommands) + { + // 優化:先檢查是否包含指令,避免無效搜尋 + size_t cmdPos = line.find(p.first); + if (cmdPos == std::string::npos) + continue; + // 確保指令不是變數的一部分 (簡單邊界檢查,可選) + // if (cmdPos > 0 && isalnum(line[cmdPos-1])) continue; + if (commentInReg) + return ""; // 假設這是全域變數 + for (auto& note : func_head_end) + { + const std::string& hd = note.first; + const std::string& ed = note.second; + // 檢查這一行是否有對應的起始符號 (例如 "(") + if (line.find(hd) == std::string::npos) + continue; + bool isFuncCmd = (p.first == "@function"); + bool shouldRun = (isFuncCmd && iffunc) || (!isFuncCmd && !iffunc); + bool keywordEnd = false; + std::string args = extractArgs(line, hd, ed, keywordEnd); + if (!keywordEnd) + { + // [變更] 發現沒閉合,進入多行模式 + if (shouldRun) + { + pendingBuffer = line; // 存入緩衝區 + pendingCmdKey = p.first; + pendingFunc = p.second; // 記住要執行的函數 + pendingHead = hd; // 記住符號 + pendingEnd = ed; + return ""; // 等待下一行 + } + } + else + { + // 單行直接完成,執行並回傳 + if (shouldRun) + { + return p.second(args); + } + return ""; // 雖然解析成功,但當前模式不執行 + } + } + } + return ""; // 找不到指令就忽略 +} + +// 讀 ini 檔,用逗號分割,轉成 -I 或 -L 參數 +std::string parseIni(const std::string& path, const std::string& flag) +{ + std::ifstream infile(path); + if (!infile) + return ""; + + std::string line; + std::string result; + while (std::getline(infile, line)) + { + std::stringstream ss(line); + std::string token; + while (std::getline(ss, token, ',')) + { + if (!token.empty()) + { + result += flag + "\"" + token + "\" "; + } + } + } + return result; +} +// 判斷字串是否為布林值 +auto is_bool = [](const std::string& s) { return s == "true" || s == "false"; }; +auto is_number = [](const std::string& s) +{ + char* end; + std::strtod(s.c_str(), &end); + return *end == '\0'; +}; +// 去掉前後空白 +auto trim = [](std::string s) +{ + while (!s.empty() && (s.front() == ' ' || s.front() == '\t')) + s.erase(s.begin()); + while (!s.empty() && (s.back() == ' ' || s.back() == '\t')) + s.pop_back(); + return s; +}; + +int main(int argc, char* argv[]) +{ + bool enableclang = false; + // 註冊 + registerCommand("print", + "(", + ")", + [](const std::string& args) + { + std::stringstream ss(args); + std::string tok; + std::vector v; + while (std::getline(ss, tok, ',')) + v.push_back(tok); + std::string out; + double iorf; + // 處理非" "的參數 + if (v.empty()) + { + for (size_t i = 0; i < v.size(); ++i) + v[i] = noblank(v[i]); + } + if (!v.empty()) + { + for (size_t i = 0; i < v.size(); ++i) + { + std::string cur = v[i]; + if (cur == "true") + { + out += "printf(\"true\");\n"; + } + else if (cur == "false") + { + out += "printf(\"false\");\n"; + } + else if (is_number(cur)) + { + double iorf = std::stod(cur); + if (iorf == (int)iorf) + out += "{ int _t = " + cur + + "; printf(\"%d\", _t); }\n"; + else + out += "{ double _t = " + cur + + "; printf(\"%g\", _t); }\n"; + } + else if (cur[0] == 'L') + out += (Ifiostream) + ? "std::wcout<<" + cur + + ";\n" + : "wprintf(" + cur + ");\n"; + else if (Ifiostream == true) + out += "std::cout<<" + cur + ";\n"; + else + { + out += "printf(" + cur + ");\n"; + } + } + } + + return out; + }); + + registerCommand("println", + "(", + ")", + [](const std::string& args) + { return "printf(" + args + "); printf(\"\\n\");\n"; }); + + registerCommand("input", + "(", + ")", + [](const std::string& args) + { + std::stringstream ss(args); + std::string tok, out; + std::vector v; + while (std::getline(ss, tok, ',')) + v.push_back(tok); + if (Ifiostream == false) + out = "printf(\"need import iostream\")"; + if (!v.empty()) + { + for (int i = 0; i < v.size(); i++) + { + out += "std::cin>>" + v[i] + ";\n"; + } + } + return out; + }); + + registerCommand("@inject", + "(", + ")", + [](const std::string& args) { return args; }); + registerCommand("@function", + "<<", + ">>", + [](const std::string& args) { return args; }); + //////// + if (argc < 2) + { + std::cerr << "Usage: cppsp_compiler \n"; + return 1; + } + + fs::path cpsPath(argv[1]); + if (!fs::exists(cpsPath)) + { + std::cerr << "File not found.\n"; + return 1; + } + + std::ifstream infile(cpsPath); + std::ifstream funcfile(cpsPath); + if (!infile) + { + std::cerr << "Cannot open file.\n"; + return 1; + } + + fs::path cppPath = + cpsPath.parent_path() / (cpsPath.stem().string() + ".cpp"); + std::ofstream outfile(cppPath); + if (!outfile) + { + std::cerr << "Cannot create cpp file.\n"; + return 1; + } + + outfile << "#include \n"; + std::string importline; + std::ifstream fileinclude(cpsPath); + while (std::getline(fileinclude, importline)) + { + bool comment = isComment(importline); + if (!comment && importline.find("import ") != std::string::npos) + { + size_t pos = importline.find("import "); + std::string imports = + importline.substr(pos + 7); // "import " 長度 7 + + imports = trim(imports); + + // 拆成多個標頭 + std::stringstream ss(imports); + std::string header; + while (std::getline(ss, header, ',')) + { + header = trim(header); // 每個標頭也要去掉空白 + if (header.empty()) + continue; + + fs::path importFile = cpsPath.parent_path() / header; + outfile << "#include \"" + << importFile.lexically_relative(cpsPath.parent_path()) + .string() + << "\"\n"; + + if (importFile.filename().string() == "iostream") + { + Ifiostream = true; + } + } + } + } + std::string funcline; + while (std::getline(funcfile, funcline)) + { + + /* if(funcline != std::string::npos){ size_t pos = + funcline.find("@function<<"); std::string funcname = + funcline.substr(pos + 11); // "@function(" 長度 10 funcname = + funcname.substr(0, funcname.find(">>"));*/ + std::string funcname = runCommand(funcline, true); + bool comment = isComment(funcline); + if (comment) + { + continue; + } + if (funcname.empty()) + continue; + outfile << funcname + "\n"; + } + bool enableoverwrite = false; + if (enableoverwrite) + outfile << "/*"; + outfile << "int main() {\n"; + std::string line; + std::string extraFlags = ""; // 存 @command() 的內容 + while (std::getline(infile, line)) + { + commentInReg = false; + commentInReg = isComment(line); + std::string code = runCommand(line, false); + if (!code.empty()) + outfile << code; + if (commentInReg) + { + continue; + } + if (line.find("@command(") != std::string::npos) + { + size_t start = line.find("\""); + size_t end = line.rfind("\""); + if (start != std::string::npos && end != std::string::npos && + end > start) + extraFlags += " " + line.substr(start + 1, end - start - 1); + } + if (line.find("#useclang") != std::string::npos) + { + enableclang = true; + } + if (line.find("#usegcc") != std::string::npos) + { + enableclang = false; + } + if (line.find("#overwrite") != std::string::npos) + { + enableoverwrite = true; + } + } + + outfile << "\nreturn 0;\n}\n"; + if (enableoverwrite) + outfile << "*/"; + outfile.close(); + std::string local = (isMac || isLinux) ? "./" : ""; + fs::path exePath = + cpsPath.parent_path() / + (local + cpsPath.stem().string()); // .exe後綴 : + ".exe"); + + // 讀 include.ini 和 lib.ini + std::string includeFlags = parseIni("include.ini", "-I"); + std::string libFlags = parseIni("lib.ini", "-L"); + + std::string gppCommand = "g++ \"" + cppPath.string() + "\" -o \"" + + exePath.string() + "\" " + extraFlags + " " + + includeFlags + " " + libFlags; + if (enableclang) + gppCommand = "clang++ \"" + cppPath.string() + "\" -o \"" + + exePath.string() + "\" " + extraFlags + " " + + includeFlags + " " + libFlags; + if (enableoverwrite) + gppCommand = extraFlags + " " + includeFlags + " " + libFlags; + + std::cout << "Compiling: " << gppCommand << "\n"; + int ret = system(gppCommand.c_str()); + + if (ret != 0) + { + std::cerr << "Compilation failed!\n"; + return 1; + } + + if (!enableoverwrite) + std::cout << "Compilation succeeded! Executable: " << exePath.string() + << "\n"; + if (enableoverwrite) + std::cout << "Compilation succeeded!\n"; + if (!enableoverwrite) + int runexe = system(exePath.string().c_str()); + return 0; +}