├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── Old ├── Resources │ └── cbIcon.ico ├── build.bat ├── error.py ├── interpreter.py └── parse.py ├── README.md ├── error.cb ├── interpreter.cb └── parse.cb /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | #cbLang test files 132 | output.py 133 | Testing.py 134 | cbLangBootstrap/* 135 | 136 | #Any compiled files 137 | *.exe -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "black" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Chadderbox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Old/Resources/cbIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ceebox/cbLang/6a3865b9e3e17c523afe4972b202ab6a2159f4bb/Old/Resources/cbIcon.ico -------------------------------------------------------------------------------- /Old/build.bat: -------------------------------------------------------------------------------- 1 | pyinstaller --icon="Resources/cbIcon.ico" --onefile --log-level ERROR interpreter.py 2 | move .\dist\interpreter.exe .\ 3 | @RD /S /Q .\dist 4 | @RD /S /Q .\build 5 | del cbLang.exe 6 | del interpreter.spec 7 | rename interpreter.exe cbLang.exe -------------------------------------------------------------------------------- /Old/error.py: -------------------------------------------------------------------------------- 1 | import sys 2 | class Error(): 3 | def __init__(self, error) -> None: 4 | print(error) 5 | sys.exit() -------------------------------------------------------------------------------- /Old/interpreter.py: -------------------------------------------------------------------------------- 1 | #Native 2 | import os.path 3 | import subprocess 4 | import sys 5 | import shutil 6 | 7 | #PyPy 8 | pyInstallerInstalled = True 9 | 10 | try: 11 | import PyInstaller.__main__ as PyInstaller 12 | except ImportError: 13 | pyInstallerInstalled = False 14 | 15 | #Custom 16 | from parse import Parser 17 | from error import Error 18 | 19 | version = "0.0.1" 20 | 21 | class Interpreter: 22 | def Interpret(self, code : str) -> None: 23 | subprocess.call(["python", "output.py"]) 24 | 25 | 26 | def GetCode(filePath) -> str: 27 | if os.path.isfile(filePath): 28 | with open(filePath, 'r') as file: 29 | return file.read() 30 | else: 31 | Error("Input file not found") 32 | 33 | def HandleArgs() -> None: 34 | if sys.argv[1] == "--help" or sys.argv[1] == "-h": 35 | Error(''' 36 | Command line arguments: 37 | --help -h: Prints this message 38 | --version -b: Prints the version of the interpreter 39 | --run -r (default) [file]: Runs the interpreter on the file specified 40 | --transpile -t [file] [address]: Converts the file specified into python code and saves it to the address specified 41 | --compile -c [file] [address]: Compiles the file specified into an executable and saves it to the address specified 42 | ''') 43 | elif sys.argv[1] == "--run" or sys.argv[1] == "-r": 44 | if len(sys.argv) < 3: 45 | Error("Invalid number of arguments") 46 | else: 47 | if os.path.isfile(sys.argv[2]): 48 | parser = Parser(GetCode((sys.argv[2]))) 49 | interpreter = Interpreter() 50 | interpreter.Interpret(parser.code) 51 | else: 52 | Error("File not found") 53 | elif os.path.isfile(sys.argv[1]): 54 | parser = Parser(GetCode(sys.argv[1])) 55 | interpreter = Interpreter() 56 | interpreter.Interpret(parser.code) 57 | elif sys.argv[1] == "--transpile" or sys.argv[1] == "-t": 58 | if len(sys.argv) < 4: 59 | Error("Invalid number of arguments") 60 | else: 61 | if os.path.isfile(sys.argv[2]): 62 | parser = Parser(GetCode((sys.argv[2]))) 63 | with open(sys.argv[3], "w") as f: 64 | f.write(parser.code) 65 | else: 66 | Error("Input file not found") 67 | elif sys.argv[1] == "--compile" or sys.argv[1] == "-c": 68 | if pyInstallerInstalled == False: 69 | Error("PyInstaller is not installed \n Please run \"pip install PyInstaller\"") 70 | if len(sys.argv) < 4: 71 | Error("Invalid number of arguments") 72 | else: 73 | if os.path.isfile(sys.argv[2]): 74 | parser = Parser(GetCode((sys.argv[2]))) 75 | fileName = sys.argv[3].split(".")[0] 76 | with open(fileName + ".py", "w") as f: 77 | f.write(parser.code) 78 | if (os.path.isfile(fileName + ".exe")): 79 | os.remove(fileName + ".exe") 80 | subprocess.call(["PyInstaller", fileName + ".py", "--onefile"]) 81 | os.rename("dist/{}".format(fileName+".exe"), "./"+sys.argv[3]) 82 | os.remove(fileName + ".py") 83 | os.remove(fileName + ".spec") 84 | shutil.rmtree("build") 85 | shutil.rmtree("dist") 86 | else: 87 | Error("File not found") 88 | else: 89 | Error("Invalid argument") 90 | 91 | if (os.path.isfile("output.py")): 92 | os.remove("output.py") 93 | 94 | def CheckArgs() -> str: 95 | if len(sys.argv) < 2: 96 | Error("Invalid number of arguments") 97 | HandleArgs() 98 | 99 | if __name__ == "__main__": 100 | CheckArgs() 101 | -------------------------------------------------------------------------------- /Old/parse.py: -------------------------------------------------------------------------------- 1 | from error import Error 2 | 3 | class Parser: 4 | def __init__(self, code: str): 5 | #Pass in code 6 | self.code = code 7 | #Parse code 8 | self.code = self.Parse(self.code) 9 | 10 | def Parse(self, code: str) -> str: 11 | #Parse code into normal python 12 | code = self.ParseInclude(code) 13 | code = self.ParseComments(code) 14 | code = self.ParseKeyWords(code) 15 | code = self.ParseEOL(code) 16 | code = self.ParseBraces(code) 17 | code = self.ParseFunctions(code) 18 | code = self.CleanCode(code) 19 | code = self.AddEntryPoint(code) 20 | 21 | #Dump code to file 22 | with open("output.py", "w") as f: 23 | f.write(code) 24 | return code 25 | 26 | def ParseComments(self, code: str) -> str: 27 | for line in code.splitlines(): 28 | if "//" in line: 29 | if not self.IsInString("//", line): 30 | if list(line)[0] == "/" and list(line)[1] == "/": 31 | code = code.replace(line, "") 32 | else: 33 | newLine = line.partition("//")[0] 34 | code = code.replace(line, newLine) 35 | return code 36 | 37 | def ParseInclude(self, code: str) -> str: 38 | includeName = "" 39 | for line in code.splitlines(): 40 | words = line.split() 41 | for wordNo, word in enumerate(words): 42 | if words[wordNo] == "from" and not self.IsInString(words[wordNo], line): 43 | if words[wordNo + 1]== "native": 44 | if words[wordNo + 2] == "include": 45 | words[wordNo] = f"from {words[wordNo + 3]} import *" 46 | for line in code.splitlines(): 47 | words = line.split() 48 | for wordNo, word in enumerate(words): 49 | if word == "include" and not self.IsInString(word, line): 50 | includeName = words[wordNo + 1] 51 | code = code.replace(line, "") 52 | with open(includeName.removesuffix(";") + ".cb", "r") as file: 53 | code = file.read() + "\n" + code 54 | for line in code.splitlines(): 55 | if "from native reference " in line: 56 | if self.IsInString("from native reference ", line, True): 57 | continue 58 | code = code.replace(line, line.replace("from native reference ", "import ")) 59 | words = line.split() 60 | newLine = "" 61 | for wordNo, word in enumerate(words): 62 | if words[wordNo] == "from" and not self.IsInString(words[wordNo], line): 63 | if words[wordNo + 1] == "native": 64 | if words[wordNo + 2] == "reference": 65 | words[wordNo] = "import" 66 | words[wordNo] = "" 67 | words[wordNo + 2] = "" 68 | newLine = " ".join(words) 69 | if newLine != "": 70 | code = code.replace(line, newLine) 71 | 72 | return code 73 | 74 | def ParseKeyWords(self, code: str) -> str: 75 | for line in code.splitlines(): 76 | if "this" in line and not self.IsInString("this", line): 77 | code = code.replace(line, line.replace("this", "self")) 78 | for line in code.splitlines(): 79 | if "true" in line and not self.IsInString("true", line): 80 | code = code.replace(line, line.replace("true", "True")) 81 | for line in code.splitlines(): 82 | if "false" in line and not self.IsInString("false", line): 83 | code = code.replace(line, line.replace("false", "False")) 84 | for line in code.splitlines(): 85 | if "null" in line and not self.IsInString("null", line): 86 | code = code.replace(line, line.replace("null", "None")) 87 | for line in code.splitlines(): 88 | if "else if" in line and not self.IsInString("else if", line): 89 | code = code.replace(line, line.replace("else if", "elif")) 90 | return code 91 | 92 | def ParseEOL(self, code: str) -> str: 93 | code = "".join([s for s in code.splitlines(True) if s.strip("\r\n")]) 94 | 95 | for line in code.splitlines(): 96 | skipLine = False 97 | for token in ("function", "while", "for", "if", "else", "elif", "with", "from"): 98 | if token in line and not self.IsInString(token, line): 99 | skipLine = True 100 | if ''.join(line.split()).startswith(("{", "}", "\n", "class")): 101 | skipLine = True 102 | elif line.strip() == "": 103 | skipLine = True 104 | if skipLine: 105 | continue 106 | if ";" in line and not self.IsInString(";", line): 107 | lineChars = list(line) 108 | stringCount = 0 109 | for i in range(len(lineChars)): 110 | if lineChars[i] == '"' or lineChars[i] == "'": 111 | stringCount += 1 112 | if lineChars[i] == ";": 113 | if stringCount % 2 == 0: 114 | lineChars[i] = "\n" 115 | break 116 | 117 | elif line.endswith((":")): 118 | Error(f"Syntax error in: \n{line}") 119 | else: 120 | Error(f"Missing semicolon in: \n{line}") 121 | if line.endswith((":")): 122 | Error(f"Syntax error in: \n{line}") 123 | 124 | return code 125 | 126 | def ParseBraces(self, code: str) -> str: 127 | leftBracesAmount = 0 128 | for line in code.splitlines(): 129 | if "{" in line: 130 | lineChars = list(line) 131 | stringCount = 0 132 | for i in range(len(lineChars)): 133 | if lineChars[i] == '"' or lineChars[i] == "'": 134 | stringCount += 1 135 | if lineChars[i] == "{": 136 | if stringCount % 2 == 0 and stringCount != 0: 137 | leftBracesAmount += 1 138 | break 139 | rightBracesAmount = 0 140 | for line in code.splitlines(): 141 | if "}" in line: 142 | lineChars = list(line) 143 | stringCount = 0 144 | for i in range(len(lineChars)): 145 | if lineChars[i] == '"' or lineChars[i] == "'": 146 | stringCount += 1 147 | if lineChars[i] == "}": 148 | if stringCount % 2 == 0 and stringCount != 0: 149 | rightBracesAmount += 1 150 | break 151 | 152 | if leftBracesAmount != rightBracesAmount: 153 | Error(("Braces amount is not equal")) 154 | 155 | newCode = "" 156 | splitLines = code.splitlines(); 157 | for line in splitLines: 158 | if ";" in line and not self.IsInString(";", line): 159 | lineChars = list(line) 160 | stringCount = 0 161 | for i in range(len(lineChars)): 162 | if lineChars[i] == '"' or lineChars[i] == "'": 163 | stringCount += 1 164 | if lineChars[i] == ";": 165 | if stringCount % 2 == 0: 166 | lineChars[i] = "\n" 167 | break 168 | line = "".join(lineChars) 169 | if "class" in line: 170 | if not self.IsInString("class", line): 171 | line = "\n"+" ".join(line.split()) 172 | if "function" in line: 173 | if line.partition("function")[0].count("\"") != 0 and line.partition("function")[0].count("\"") % 2 == 0: 174 | words = line.split() 175 | for wordNo, word in enumerate(words): 176 | if word == "function": 177 | speechCount = line.partition("function")[2].count("\"") 178 | otherCount = line.partition("function")[2].count("'") 179 | if speechCount % 2 == 0 and otherCount % 2 == 0: 180 | words[wordNo] = "def" 181 | break 182 | line = " ".join(words) 183 | leftBraceExpression = ''.join(line.split()) 184 | if not self.IsInString("{", leftBraceExpression): 185 | if ''.join(line.split()).startswith(("{")): 186 | newCode += ":\n" 187 | if not self.IsInString("}", line): 188 | line = line.replace("}", "#endindent") 189 | if not self.IsInString("{", line): 190 | line = line.replace("{", "#startindent") 191 | line += "\n" 192 | newCode += line 193 | line += "\n" 194 | 195 | return newCode 196 | 197 | def ParseFunctions(self, code: str) -> str: 198 | code = code 199 | for line in code.splitlines(): 200 | if "function" in line and not self.IsInString("function", line): 201 | code = code.replace(line, line.replace("function", "def")) 202 | for line in code.splitlines(): 203 | if "def Start" in line and not self.IsInString("def Start", line): 204 | code = code.replace(line, line.replace("def Start", "def __init__")) 205 | for line in code.splitlines(): 206 | if ") is" in line and not self.IsInString(") is", line): 207 | code = code.replace(line, line.replace(") is", ") ->")) 208 | for line in code.splitlines(): 209 | if "def" in line: 210 | if (line.partition("def")[0].strip() == ""): 211 | code = code.replace(line, line.replace("(", "(self,")) 212 | return code 213 | 214 | def CleanCode(self, code : str) -> str: 215 | #I'm not going to lie, I made a lot of mistakes. Let's hope these hacks fix it. 216 | 217 | splitLines = code.splitlines() 218 | for lineNo, line in enumerate(splitLines): 219 | if line.startswith(":"): 220 | splitLines[lineNo - 1] = splitLines[lineNo - 1] + ":" 221 | splitLines[lineNo] = "" 222 | newCode = "" 223 | for line in splitLines: 224 | newCode += line + "\n" 225 | code = newCode 226 | 227 | splitLines = code.splitlines() 228 | newCode = "" 229 | for lineNo, line in enumerate(splitLines): 230 | i = 0 231 | indentCount = 0 232 | while i <= lineNo: 233 | if "#endindent" in splitLines[i]: 234 | if not self.IsInString("#endindent", splitLines[i], True): 235 | indentCount -= 1 236 | 237 | elif "#startindent" in splitLines[i] and not self.IsInString("#startindent", splitLines[i], True): 238 | if not self.IsInString("#startindent", splitLines[i]): 239 | indentCount += 1 240 | i += 1 241 | newCode += (" " * indentCount) + line + "\n" 242 | code = newCode 243 | 244 | #Remove indent helpers 245 | newCode = "" 246 | for line in code.splitlines(): 247 | if "#endindent" in line: 248 | if not self.IsInString("#endindent", line): 249 | line = line.replace(line, "") 250 | if "#startindent" in line: 251 | if not self.IsInString("#startindent", line): 252 | line = line.replace(line, "") 253 | newCode += line + "\n" 254 | code = newCode 255 | 256 | #Tidy code by removing empty lines 257 | newCode = "" 258 | for line in code.splitlines(): 259 | if line.strip("\t\r\n") == "": 260 | line = line.replace(line, line.strip("\t\r\n")) 261 | newCode += line 262 | else: 263 | newCode += line + "\n" 264 | code = newCode 265 | 266 | code = "\n".join([ll.rstrip() for ll in code.splitlines() if ll.strip()]) 267 | 268 | return code 269 | 270 | def AddEntryPoint(self, code: str) -> str: 271 | code += "\n" 272 | code += ''' 273 | if __name__ == "__main__": 274 | main = Main() 275 | main.Main() 276 | ''' 277 | 278 | return code 279 | 280 | def IsInString(self, phrase : str, line : str, returnIfMultiple = False) -> bool: 281 | if not phrase in line: 282 | return False 283 | if line.count(phrase) > 1: 284 | return returnIfMultiple 285 | leftSide = line.partition(phrase)[0] 286 | if leftSide.count("\"") > 0: 287 | if leftSide.count("\"") % 2 == 0: 288 | return False 289 | else: 290 | return True 291 | if leftSide.count("\'") > 0: 292 | if leftSide.count("\'") % 2 == 0: 293 | return False 294 | else: 295 | return True -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CBLang 2 | ## A bad programming language made in Python. 3 | 4 | CBLang is a programming language aiming to fix most of my problems with Python (this means that you likely don't need it), while still keeping it similar and as effective. 5 | 6 | CBLang can be either interpreted, transpiled to python, or compiled into an exe using [PyInstaller](https://pyinstaller.readthedocs.io/en/stable/). 7 | 8 | CBLang can run without any dependencies (including PyInstaller - *you just won't be able to compile your programs*, but they will run and transpile fine). 9 | 10 | CBLang is only tested on Windows, but I'm sure it wouldn't be hard at all to get working on Linux. 11 | 12 | Be warned that everything is basically held together by hopes and dreams, *so you are going to get a load of super random errors*. 13 | 14 | So, how does it work? 15 | 16 | ## Using the language 17 | 18 | class Main() 19 | { 20 | function Main() 21 | { 22 | //Here is a comment! 23 | print("Hello World"); 24 | } 25 | } 26 | *^This is a basic `Hello World` program.* 27 | 28 | To run this, you will need the CBLang interpreter that you can either obtain by downloading it from the [releases section](https://github.com/Ceebox/cbLang/releases) or running `.\build.bat` (this will give you a depricated version of the interpreter based on the old Python code). 29 | 30 | ⠀ 31 | ## Running, transpiling and 'compiling' 32 | CBLang supports lots of methods of running your code, either having it directly interpreted, transpiling it to Python and spitting out the file for you to run when you want, or using PyInstaller to 'compile' (a more technically correct word would be 'pack') your code to an exe. 33 | 34 | To run code directly, use the command `cbLang.exe fileName.cb` or `cbLang.exe -r fileName.cb`.⠀ 35 | To transpile code, use the command `cbLang.exe -t fileName.cb outputfile.py`.⠀ 36 | To compile code, use the command `cbLang.exe -c filename.cb output.exe`. *(This requires PyInstaller to be installed)*.⠀ 37 | 38 | ⠀ 39 | ⠀ 40 | **More advanced behaviour:** 41 | 42 | //main.cb 43 | 44 | //Include python libraries 45 | from native reference sys; 46 | 47 | //Include other cbLang code 48 | include OtherFile; 49 | 50 | class Main() 51 | { 52 | function Main() 53 | { 54 | TryQuit(false); 55 | 56 | instance = OtherClass(); 57 | print(instance.Add(1, 2)); 58 | 59 | TryQuit(true); 60 | print(instance.Add(2, 3)); 61 | //^This line will never be reached 62 | } 63 | function Quit(shouldQuit : bool) 64 | { 65 | if (shouldQuit == true) 66 | { 67 | print("Quitting"); 68 | sys.exit(); 69 | } 70 | else 71 | { 72 | print("Did not quit"); 73 | } 74 | } 75 | } 76 | ⠀ 77 | 78 | //OtherFile.cb 79 | class OtherClass 80 | { 81 | //Constructor 82 | function Start() 83 | { 84 | print("Created a new instance of OtherClass!"); 85 | } 86 | 87 | function Add(a, b) is int 88 | { 89 | return a + b; 90 | } 91 | } 92 | 93 | *^This is a basic example of importing external code, along with demonstrating other concepts in the programming language.* 94 | 95 | class Main 96 | { 97 | function Main() 98 | { 99 | Other.Test(); 100 | } 101 | } 102 | 103 | class Other 104 | { 105 | static function Test() 106 | { 107 | print("This code was printed from a static method in Other"); 108 | } 109 | } 110 | *^This is a basic example of static methods.* 111 | -------------------------------------------------------------------------------- /error.cb: -------------------------------------------------------------------------------- 1 | from native reference sys; 2 | 3 | class Error() 4 | { 5 | function Start(error): 6 | print(error); 7 | sys.exit(); 8 | } -------------------------------------------------------------------------------- /interpreter.cb: -------------------------------------------------------------------------------- 1 | from native reference os.path; 2 | from native reference subprocess; 3 | from native reference sys; 4 | from native reference shutil; 5 | 6 | include parse; 7 | include error; 8 | 9 | class Interpreter() 10 | { 11 | function Interpret(code) 12 | { 13 | subprocess.call(["python", "output.py"]); 14 | } 15 | } 16 | class Main() 17 | { 18 | function GetCode(filePath) 19 | { 20 | if os.path.isfile(filePath) 21 | { 22 | with open(filePath, 'r') as file 23 | { 24 | return file.read(); 25 | } 26 | } 27 | else 28 | { 29 | Error("Input file not found"); 30 | } 31 | } 32 | 33 | function HandleArgs() 34 | { 35 | if sys.argv[1] == "--help" or sys.argv[1] == "-h" 36 | { 37 | Error("Command line arguments: \n--help -h: Prints help message \n--version -b: Prints the version of the interpreter \n--run -r (default) [file]: Runs the interpreter on the file specified \n--transpile -t [file] [address]: Converts the file specified into python code and saves it to the address specified \n--compile -c [file] [address]: Compiles the file specified into an executable and saves it to the address specified"); 38 | } 39 | else if sys.argv[1] == "--run" or sys.argv[1] == "-r" 40 | { 41 | if len(sys.argv) < 3 42 | { 43 | Error("Invalid number of arguments"); 44 | } 45 | else 46 | { 47 | if os.path.isfile(sys.argv[2]) 48 | { 49 | parser = Parser(this.GetCode((sys.argv[2]))); 50 | interpreter = Interpreter(); 51 | interpreter.Interpret(parser.code); 52 | } 53 | else 54 | { 55 | Error("File not found"); 56 | } 57 | } 58 | } 59 | else if os.path.isfile(sys.argv[1]) 60 | { 61 | parser = Parser(this.GetCode(sys.argv[1])); 62 | interpreter = Interpreter(); 63 | interpreter.Interpret(parser.code); 64 | } 65 | else if sys.argv[1] == "--transpile" or sys.argv[1] == "-t" 66 | { 67 | if len(sys.argv) < 4 68 | { 69 | Error("Invalid number of arguments"); 70 | } 71 | else 72 | { 73 | if os.path.isfile(sys.argv[2]) 74 | { 75 | parser = Parser(this.GetCode((sys.argv[2]))); 76 | with open(sys.argv[3], "w") as f 77 | { 78 | f.write(parser.code); 79 | } 80 | } 81 | else 82 | { 83 | Error("Input file not found"); 84 | } 85 | } 86 | } 87 | else if sys.argv[1] == "--compile" or sys.argv[1] == "-c" 88 | { 89 | if len(sys.argv) < 4 90 | { 91 | Error("Invalid number of arguments"); 92 | } 93 | else 94 | { 95 | if os.path.isfile(sys.argv[2]) 96 | { 97 | parser = Parser(this.GetCode((sys.argv[2]))); 98 | fileName = sys.argv[3].split(".")[0]; 99 | with open(fileName + ".py", "w") as f 100 | { 101 | f.write(parser.code); 102 | } 103 | if (os.path.isfile(fileName + ".exe")) 104 | { 105 | os.remove(fileName + ".exe"); 106 | } 107 | subprocess.call(["PyInstaller", fileName + ".py", "--onefile", "--log-level", "ERROR"]); 108 | os.rename("dist/{}".format(fileName+".exe"),"./"+sys.argv[3]); 109 | os.remove(fileName + ".py"); 110 | os.remove(fileName + ".spec"); 111 | shutil.rmtree("build"); 112 | shutil.rmtree("dist"); 113 | } 114 | else 115 | { 116 | Error("File not found"); 117 | } 118 | } 119 | } 120 | else 121 | { 122 | Error("Invalid argument"); 123 | } 124 | 125 | if (os.path.isfile("output.py")) 126 | { 127 | os.remove("output.py"); 128 | } 129 | } 130 | 131 | function CheckArgs() 132 | { 133 | if len(sys.argv) < 2: 134 | Error("Invalid number of arguments"); 135 | this.HandleArgs(); 136 | } 137 | 138 | function Main() 139 | { 140 | this.CheckArgs(); 141 | } 142 | } -------------------------------------------------------------------------------- /parse.cb: -------------------------------------------------------------------------------- 1 | include error; 2 | 3 | class Parser 4 | { 5 | function Start(code : str) 6 | { 7 | //Pass in code 8 | this.code = code; 9 | //Parse code 10 | this.code = this.Parse(this.code); 11 | } 12 | 13 | function Parse(code: str) is str 14 | { 15 | //Parse everything into normal python 16 | code = this.ParseInclude(code); 17 | code = this.ParseComments(code); 18 | code = this.ParseKeyWords(code); 19 | code = this.ParseEOL(code); 20 | code = this.ParseBraces(code); 21 | code = this.ParseFunctions(code); 22 | code = this.CleanCode(code); 23 | code = this.AddEntryPoint(code); 24 | 25 | //Dump code to file 26 | with open("output.py", "w") as f 27 | { 28 | f.write(code); 29 | } 30 | return code; 31 | } 32 | 33 | function ParseComments(code: str) is str 34 | { 35 | for line in code.splitlines() 36 | { 37 | if "//" in line 38 | { 39 | if not this.IsInString("//", line) 40 | { 41 | if list(line)[0] == "/" 42 | { 43 | if list(line)[1] == "/" 44 | { 45 | code = code.replace(line, ""); 46 | } 47 | } 48 | else 49 | { 50 | newLine = line.partition("//")[0]; 51 | code = code.replace(line, newLine); 52 | } 53 | } 54 | } 55 | } 56 | return code; 57 | } 58 | 59 | function ParseInclude(code: str) is str 60 | { 61 | includeName = ""; 62 | for line in code.splitlines() 63 | { 64 | words = line.split(); 65 | for wordNo, word in enumerate(words) 66 | { 67 | if words[wordNo] == "from" 68 | { 69 | if not this.IsInString(words[wordNo], line) 70 | { 71 | if words[wordNo + 1]== "native" 72 | { 73 | if words[wordNo + 2] == "include" 74 | { 75 | words[wordNo + 3] = words[wordNo + 3].replace(";", ""); 76 | code = code.replace(line, (f"from {words[wordNo + 3]} import *")); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } 83 | for line in code.splitlines() 84 | { 85 | words = line.split(); 86 | for wordNo, word in enumerate(words) 87 | { 88 | if "from" in line 89 | { 90 | continue; 91 | } 92 | if word == "include" 93 | { 94 | if not this.IsInString(word, line) 95 | { 96 | includeName = words[wordNo + 1]; 97 | code = code.replace(line, ""); 98 | with open(includeName.removesuffix(";") + ".cb", "r") as file 99 | { 100 | code = file.read() + "\n" + code; 101 | } 102 | } 103 | } 104 | } 105 | } 106 | for line in code.splitlines() 107 | { 108 | if "from native reference " in line 109 | { 110 | if this.IsInString("from native reference ", line, true) 111 | { 112 | continue; 113 | } 114 | code = code.replace(line, line.replace("from native reference ", "import ")); 115 | words = line.split(); 116 | newLine = ""; 117 | for wordNo, word in enumerate(words) 118 | { 119 | if words[wordNo] == "from" 120 | { 121 | if not this.IsInString(words[wordNo], line) 122 | { 123 | if words[wordNo + 1] == "native" 124 | { 125 | if words[wordNo + 2] == "reference" 126 | { 127 | words[wordNo] = "import"; 128 | words[wordNo] = ""; 129 | words[wordNo + 2] = ""; 130 | newLine = " ".join(words); 131 | } 132 | } 133 | } 134 | } 135 | } 136 | if newLine != "" 137 | { 138 | code = code.replace(line, newLine); 139 | } 140 | } 141 | } 142 | return code; 143 | } 144 | 145 | function ParseKeyWords(code: str) is str 146 | { 147 | for line in code.splitlines() 148 | { 149 | if "true" in line 150 | { 151 | if not this.IsInString("true", line) 152 | { 153 | code = code.replace(line, line.replace("true", "True")); 154 | } 155 | } 156 | } 157 | for line in code.splitlines() 158 | { 159 | if "false" in line 160 | { 161 | if not this.IsInString("false", line) 162 | { 163 | code = code.replace(line, line.replace("false", "False")); 164 | } 165 | } 166 | } 167 | for line in code.splitlines() 168 | { 169 | if "null" in line 170 | { 171 | if not this.IsInString("null", line) 172 | { 173 | code = code.replace(line, line.replace("null", "None")); 174 | } 175 | } 176 | } 177 | for line in code.splitlines() 178 | { 179 | if "else if" in line 180 | { 181 | if not this.IsInString("else if", line, false): 182 | code = code.replace(line, line.replace("else if", "elif")); 183 | } 184 | } 185 | return code; 186 | } 187 | 188 | function ParseEOL(code: str) is str 189 | { 190 | code = "".join([s for s in code.splitlines(true) if s.strip("\r\n")]); 191 | 192 | for line in code.splitlines() 193 | { 194 | skipLine = false; 195 | for token in ("function", "while", "for", "if", "else", "elif", "with", "from") 196 | { 197 | if token in line 198 | { 199 | if not this.IsInString(token, line) 200 | { 201 | skipLine = true; 202 | } 203 | } 204 | } 205 | if ''.join(line.split()).startswith(("{", "}", "\n", "class")) 206 | { 207 | skipLine = true; 208 | } 209 | else if line.strip() == "" 210 | { 211 | skipLine = true; 212 | } 213 | if skipLine 214 | { 215 | continue; 216 | } 217 | if ";" in line 218 | { 219 | if not this.IsInString(";", line) 220 | { 221 | lineChars = list(line); 222 | stringCount = 0; 223 | for i in range(len(lineChars)) 224 | { 225 | if lineChars[i] == '"' or lineChars[i] == "'" 226 | { 227 | stringCount += 1; 228 | } 229 | if lineChars[i] == ";" 230 | { 231 | if stringCount % 2 == 0 232 | { 233 | lineChars[i] = "\n"; 234 | break; 235 | } 236 | } 237 | } 238 | } 239 | } 240 | 241 | else if line.endswith((":")) 242 | { 243 | Error(f"Syntax error in: \n{line}"); 244 | } 245 | else 246 | { 247 | Error(f"Missing semicolon in: \n{line}"); 248 | } 249 | if line.endswith((":")) 250 | { 251 | Error(f"Syntax error in: \n{line}"); 252 | } 253 | } 254 | 255 | return code; 256 | } 257 | 258 | function ParseBraces(code: str) is str 259 | { 260 | leftBracesAmount = 0; 261 | for line in code.splitlines() 262 | { 263 | if "{" in line 264 | { 265 | lineChars = list(line); 266 | stringCount = 0; 267 | for i in range(len(lineChars)) 268 | { 269 | if lineChars[i] == '"' or lineChars[i] == "'" 270 | { 271 | stringCount += 1; 272 | } 273 | if lineChars[i] == "{" 274 | { 275 | if stringCount % 2 == 0 276 | { 277 | if stringCount != 0 278 | { 279 | leftBracesAmount += 1; 280 | break; 281 | } 282 | } 283 | } 284 | } 285 | } 286 | } 287 | rightBracesAmount = 0; 288 | for line in code.splitlines() 289 | { 290 | if "}" in line 291 | { 292 | lineChars = list(line); 293 | stringCount = 0; 294 | for i in range(len(lineChars)) 295 | { 296 | if lineChars[i] == '"' or lineChars[i] == "'" 297 | { 298 | stringCount += 1; 299 | } 300 | if lineChars[i] == "}" 301 | { 302 | if stringCount % 2 == 0 303 | { 304 | if stringCount != 0 305 | { 306 | rightBracesAmount += 1; 307 | break; 308 | } 309 | } 310 | } 311 | } 312 | } 313 | } 314 | 315 | if leftBracesAmount != rightBracesAmount 316 | { 317 | Error(("Braces amount is not equal")); 318 | } 319 | 320 | newCode = ""; 321 | splitLines = code.splitlines(); 322 | for line in splitLines 323 | { 324 | if ";" in line 325 | { 326 | if not this.IsInString(";", line) 327 | { 328 | lineChars = list(line); 329 | stringCount = 0; 330 | for i in range(len(lineChars)) 331 | { 332 | if lineChars[i] == '"' or lineChars[i] == "'" 333 | { 334 | stringCount += 1; 335 | } 336 | if lineChars[i] == ";" 337 | { 338 | if stringCount % 2 == 0 339 | { 340 | lineChars[i] = "\n"; 341 | break; 342 | } 343 | } 344 | } 345 | line = "".join(lineChars); 346 | } 347 | } 348 | if "class" in line 349 | { 350 | if not this.IsInString("class", line) 351 | { 352 | line = "\n"+" ".join(line.split()); 353 | } 354 | } 355 | if "function" in line 356 | { 357 | if not this.IsInString("function", line, false) 358 | { 359 | line = line.replace("function", "def"); 360 | } 361 | } 362 | leftBraceExpression = ''.join(line.split()); 363 | if not this.IsInString("{", leftBraceExpression) 364 | { 365 | if ''.join(line.split()).startswith(("{")) 366 | { 367 | newCode += ":\n"; 368 | } 369 | } 370 | if not this.IsInString("}", line) 371 | { 372 | line = line.replace("}", "#endindent"); 373 | } 374 | if not this.IsInString("{", line) 375 | { 376 | line = line.replace("{", "#startindent"); 377 | } 378 | line += "\n"; 379 | newCode += line; 380 | line += "\n"; 381 | } 382 | 383 | return newCode; 384 | } 385 | 386 | function ParseFunctions(code: str) is str 387 | { 388 | code = code; 389 | for line in code.splitlines() 390 | { 391 | if "function" in line 392 | { 393 | if not this.IsInString("function", line, false) 394 | { 395 | code = code.replace(line, line.replace("function", "def")); 396 | } 397 | } 398 | } 399 | for line in code.splitlines() 400 | { 401 | if "def Start" in line 402 | { 403 | if not this.IsInString("def Start", line) 404 | { 405 | code = code.replace(line, line.replace("def Start", "def __init__")); 406 | } 407 | } 408 | } 409 | for line in code.splitlines() 410 | { 411 | if ") is" in line 412 | { 413 | if not this.IsInString(") is", line) 414 | { 415 | code = code.replace(line, line.replace(") is", ") ->")); 416 | } 417 | } 418 | } 419 | for line in code.splitlines() 420 | { 421 | if "def" in line 422 | { 423 | if (line.partition("def")[0].strip() == "") 424 | { 425 | code = code.replace(line, line.replace("(", "(this,")); 426 | } 427 | } 428 | } 429 | newCode = []; 430 | for count, line in enumerate(code.splitlines()) 431 | { 432 | if "static " in line 433 | { 434 | if not this.IsInString("static ", line, false) 435 | { 436 | newCode.insert(count - 1, " @staticmethod"); 437 | line = line.replace(line, line.replace("static ", "")); 438 | } 439 | } 440 | newCode.append(line); 441 | } 442 | code = "\n".join(newCode); 443 | 444 | return code; 445 | } 446 | 447 | function CleanCode(code : str) is str 448 | { 449 | //I'm not going to lie, I made a lot of mistakes. Let's hope these hacks fix it. 450 | 451 | newCode = ""; 452 | for line in code.splitlines() 453 | { 454 | chars = ""; 455 | for c in line 456 | { 457 | if "t"+"his" in line 458 | { 459 | if not this.IsInString(("t"+"his"), line) 460 | { 461 | line = line.replace(("t"+"his"), "self"); 462 | } 463 | } 464 | } 465 | newCode += line + "\n"; 466 | } 467 | code = newCode; 468 | 469 | splitLines = code.splitlines(); 470 | for lineNo, line in enumerate(splitLines) 471 | { 472 | if line.startswith(":") 473 | { 474 | splitLines[lineNo - 1] = splitLines[lineNo - 1] + ":"; 475 | splitLines[lineNo] = ""; 476 | } 477 | } 478 | newCode = ""; 479 | for line in splitLines 480 | { 481 | newCode += line + "\n"; 482 | } 483 | code = newCode; 484 | 485 | splitLines = code.splitlines(); 486 | newCode = ""; 487 | for lineNo, line in enumerate(splitLines) 488 | { 489 | i = 0; 490 | indentCount = 0; 491 | while i <= lineNo 492 | { 493 | if "#endindent" in splitLines[i] 494 | { 495 | if not this.IsInString("#endindent", splitLines[i], true) 496 | { 497 | indentCount -= 1; 498 | } 499 | } 500 | 501 | else if "#startindent" in splitLines[i] 502 | { 503 | if not this.IsInString("#startindent", splitLines[i], true) 504 | { 505 | if not this.IsInString("#startindent", splitLines[i]) 506 | { 507 | indentCount += 1; 508 | } 509 | } 510 | } 511 | i += 1; 512 | } 513 | newCode += (" " * indentCount) + line + "\n"; 514 | } 515 | code = newCode; 516 | 517 | //Remove indent helpers 518 | newCode = ""; 519 | for line in code.splitlines() 520 | { 521 | if "#endindent" in line 522 | { 523 | if not this.IsInString("#endindent", line) 524 | { 525 | line = line.replace(line, ""); 526 | } 527 | } 528 | if "#startindent" in line 529 | { 530 | if not this.IsInString("#startindent", line) 531 | { 532 | line = line.replace(line, ""); 533 | } 534 | } 535 | newCode += line + "\n"; 536 | } 537 | code = newCode; 538 | 539 | //Tidy code by removing empty lines 540 | newCode = ""; 541 | for line in code.splitlines() 542 | { 543 | if line.strip("\t\r\n") == "" 544 | { 545 | line = line.replace(line, line.strip("\t\r\n")); 546 | newCode += line; 547 | } 548 | else 549 | { 550 | newCode += line + "\n"; 551 | } 552 | } 553 | code = newCode; 554 | 555 | code = "\n".join([ll.rstrip() for ll in code.splitlines() if ll.strip()]); 556 | 557 | return code; 558 | } 559 | 560 | function AddEntryPoint(code: str) is str 561 | { 562 | code += "\n"; 563 | code += "if __name__ == \"__main__\":\n\tmain = Main()\n\tmain.Main()"; 564 | 565 | return code; 566 | } 567 | 568 | function IsInString(phrase : str, line : str, returnIfMultiple = false, returnIfNotInLine = false) is bool 569 | { 570 | if not phrase in line 571 | { 572 | return returnIfNotInLine; 573 | } 574 | if line.count(phrase) > 1 575 | { 576 | return returnIfMultiple; 577 | } 578 | 579 | leftSide = line.partition(phrase)[0]; 580 | if leftSide.count("\"") > 0 581 | { 582 | if leftSide.count("\"") % 2 == 0 583 | { 584 | return false; 585 | } 586 | else 587 | { 588 | return true; 589 | } 590 | } 591 | 592 | if leftSide.count("\'") > 0 593 | { 594 | if leftSide.count("\'") % 2 == 0 595 | { 596 | return false; 597 | } 598 | else 599 | { 600 | return true; 601 | } 602 | } 603 | } 604 | } --------------------------------------------------------------------------------