diff --git a/.gitignore b/.gitignore index 931f755..16481c3 100644 --- a/.gitignore +++ b/.gitignore @@ -129,7 +129,6 @@ dmypy.json .pyre/ #cbLang test files -*.cb Testing.py cbLangBootstrap/* diff --git a/README.md b/README.md index ead06c9..a3f7564 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ So, how does it work? } *^This is a basic `Hello World` program.* -To run this, you will need the CBLang interpreter that you can either obtain by running `.\build.bat` or downloading it from the [releases section](https://github.com/Ceebox/cbLang/releases). +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). ⠀ ⠀ diff --git a/error.cb b/error.cb new file mode 100644 index 0000000..dc3104c --- /dev/null +++ b/error.cb @@ -0,0 +1,8 @@ +from native reference sys; + +class Error() +{ + function Start(error): + print(error); + sys.exit(); +} \ No newline at end of file diff --git a/interpreter.cb b/interpreter.cb new file mode 100644 index 0000000..1dd5389 --- /dev/null +++ b/interpreter.cb @@ -0,0 +1,142 @@ +from native reference os.path; +from native reference subprocess; +from native reference sys; +from native reference shutil; + +include parse; +include error; + +class Interpreter() +{ + function Interpret(code) + { + subprocess.call(["python", "output.py"]); + } +} +class Main() +{ + function GetCode(filePath) + { + if os.path.isfile(filePath) + { + with open(filePath, 'r') as file + { + return file.read(); + } + } + else + { + Error("Input file not found"); + } + } + + function HandleArgs() + { + if sys.argv[1] == "--help" or sys.argv[1] == "-h" + { + Error("Command line arguments: \n--help -h: Prints this 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"); + } + else if sys.argv[1] == "--run" or sys.argv[1] == "-r" + { + if len(sys.argv) < 3 + { + Error("Invalid number of arguments"); + } + else + { + if os.path.isfile(sys.argv[2]) + { + parser = Parser(this.GetCode((sys.argv[2]))); + interpreter = Interpreter(); + interpreter.Interpret(parser.code); + } + else + { + Error("File not found"); + } + } + } + else if os.path.isfile(sys.argv[1]) + { + parser = Parser(this.GetCode(sys.argv[1])); + interpreter = Interpreter(); + interpreter.Interpret(parser.code); + } + else if sys.argv[1] == "--transpile" or sys.argv[1] == "-t" + { + if len(sys.argv) < 4 + { + Error("Invalid number of arguments"); + } + else + { + if os.path.isfile(sys.argv[2]) + { + parser = Parser(this.GetCode((sys.argv[2]))); + with open(sys.argv[3], "w") as f + { + f.write(parser.code); + } + } + else + { + Error("Input file not found"); + } + } + } + else if sys.argv[1] == "--compile" or sys.argv[1] == "-c" + { + if len(sys.argv) < 4 + { + Error("Invalid number of arguments"); + } + else + { + if os.path.isfile(sys.argv[2]) + { + parser = Parser(this.GetCode((sys.argv[2]))); + fileName = sys.argv[3].split(".")[0]; + with open(fileName + ".py", "w") as f + { + f.write(parser.code); + } + if (os.path.isfile(fileName + ".exe")) + { + os.remove(fileName + ".exe"); + } + subprocess.call(["PyInstaller", fileName + ".py", "--onefile", "--log-level", "ERROR"]); + os.rename("dist/{}".format(fileName+".exe"),"./"+sys.argv[3]); + os.remove(fileName + ".py"); + os.remove(fileName + ".spec"); + shutil.rmtree("build"); + shutil.rmtree("dist"); + } + else + { + Error("File not found"); + } + } + } + else + { + Error("Invalid argument"); + } + + if (os.path.isfile("output.py")) + { + os.remove("output.py"); + } + } + + function CheckArgs() + { + if len(sys.argv) < 2: + Error("Invalid number of arguments"); + this.HandleArgs(); + } + + function Main() + { + this.CheckArgs(); + } +} \ No newline at end of file diff --git a/parse.cb b/parse.cb new file mode 100644 index 0000000..66878bf --- /dev/null +++ b/parse.cb @@ -0,0 +1,575 @@ +include error; + +class Parser +{ + function Start(code : str) + { + //Pass in code + this.code = code; + //Parse code + this.code = this.Parse(this.code); + } + + function Parse(code: str) is str + { + //Parse everything into normal python + code = this.ParseInclude(code); + code = this.ParseComments(code); + code = this.ParseKeyWords(code); + code = this.ParseEOL(code); + code = this.ParseBraces(code); + code = this.ParseFunctions(code); + code = this.CleanCode(code); + code = this.AddEntryPoint(code); + + //Dump code to file + with open("output.py", "w") as f + { + f.write(code); + } + return code; + } + + function ParseComments(code: str) is str + { + for line in code.splitlines() + { + if "//" in line + { + if not this.IsInString("//", line) + { + if list(line)[0] == "/" + { + if list(line)[1] == "/" + { + code = code.replace(line, ""); + } + } + else + { + newLine = line.partition("//")[0]; + code = code.replace(line, newLine); + } + } + } + } + return code; + } + + function ParseInclude(code: str) is str + { + includeName = ""; + for line in code.splitlines() + { + words = line.split(); + for wordNo, word in enumerate(words) + { + if words[wordNo] == "from" + { + if not this.IsInString(words[wordNo], line) + { + if words[wordNo + 1]== "native" + { + if words[wordNo + 2] == "include" + { + words[wordNo] = f"from {words[wordNo + 3]} import *"; + } + } + } + } + } + } + for line in code.splitlines() + { + words = line.split(); + for wordNo, word in enumerate(words) + { + if word == "include" + { + if not this.IsInString(word, line) + { + includeName = words[wordNo + 1]; + code = code.replace(line, ""); + with open(includeName.removesuffix(";") + ".cb", "r") as file + { + code = file.read() + "\n" + code; + } + } + } + } + } + for line in code.splitlines() + { + if "from native reference " in line + { + if this.IsInString("from native reference ", line, true) + { + continue; + } + code = code.replace(line, line.replace("from native reference ", "import ")); + words = line.split(); + newLine = ""; + for wordNo, word in enumerate(words) + { + if words[wordNo] == "from" + { + if not this.IsInString(words[wordNo], line) + { + if words[wordNo + 1] == "native" + { + if words[wordNo + 2] == "reference" + { + words[wordNo] = "import"; + words[wordNo] = ""; + words[wordNo + 2] = ""; + newLine = " ".join(words); + } + } + } + } + } + if newLine != "" + { + code = code.replace(line, newLine); + } + } + } + return code; + } + + function ParseKeyWords(code: str) is str + { + for line in code.splitlines() + { + if "this" in line + { + if not this.IsInString("this", line) + { + code = code.replace(line, line.replace("this", "self")); + } + } + } + for line in code.splitlines() + { + if "true" in line + { + if not this.IsInString("true", line) + { + code = code.replace(line, line.replace("true", "True")); + } + } + } + for line in code.splitlines() + { + if "false" in line + { + if not this.IsInString("false", line) + { + code = code.replace(line, line.replace("false", "False")); + } + } + } + for line in code.splitlines() + { + if "null" in line + { + if not this.IsInString("null", line) + { + code = code.replace(line, line.replace("null", "None")); + } + } + } + for line in code.splitlines() + { + if "else if" in line + { + if not this.IsInString("else if", line, false): + code = code.replace(line, line.replace("else if", "elif")); + } + } + return code; + } + + function ParseEOL(code: str) is str + { + code = "".join([s for s in code.splitlines(true) if s.strip("\r\n")]); + + for line in code.splitlines() + { + skipLine = false; + for token in ("function", "while", "for", "if", "else", "elif", "with", "from") + { + if token in line + { + if not this.IsInString(token, line) + { + skipLine = true; + } + } + } + if ''.join(line.split()).startswith(("{", "}", "\n", "class")) + { + skipLine = true; + } + else if line.strip() == "" + { + skipLine = true; + } + if skipLine + { + continue; + } + if ";" in line + { + if not this.IsInString(";", line) + { + lineChars = list(line); + stringCount = 0; + for i in range(len(lineChars)) + { + if lineChars[i] == '"' or lineChars[i] == "'" + { + stringCount += 1; + } + if lineChars[i] == ";" + { + if stringCount % 2 == 0 + { + lineChars[i] = "\n"; + break; + } + } + } + } + } + + else if line.endswith((":")) + { + Error(f"Syntax error in: \n{line}"); + } + else + { + Error(f"Missing semicolon in: \n{line}"); + } + if line.endswith((":")) + { + Error(f"Syntax error in: \n{line}"); + } + } + + return code; + } + + function ParseBraces(code: str) is str + { + leftBracesAmount = 0; + for line in code.splitlines() + { + if "{" in line + { + lineChars = list(line); + stringCount = 0; + for i in range(len(lineChars)) + { + if lineChars[i] == '"' or lineChars[i] == "'" + { + stringCount += 1; + } + if lineChars[i] == "{" + { + if stringCount % 2 == 0 + { + if stringCount != 0 + { + leftBracesAmount += 1; + break; + } + } + } + } + } + } + rightBracesAmount = 0; + for line in code.splitlines() + { + if "}" in line + { + lineChars = list(line); + stringCount = 0; + for i in range(len(lineChars)) + { + if lineChars[i] == '"' or lineChars[i] == "'" + { + stringCount += 1; + } + if lineChars[i] == "}" + { + if stringCount % 2 == 0 + { + if stringCount != 0 + { + rightBracesAmount += 1; + break; + } + } + } + } + } + } + + if leftBracesAmount != rightBracesAmount + { + Error(("Braces amount is not equal")); + } + + newCode = ""; + splitLines = code.splitlines(); + for line in splitLines + { + if ";" in line + { + if not this.IsInString(";", line) + { + lineChars = list(line); + stringCount = 0; + for i in range(len(lineChars)) + { + if lineChars[i] == '"' or lineChars[i] == "'" + { + stringCount += 1; + } + if lineChars[i] == ";" + { + if stringCount % 2 == 0 + { + lineChars[i] = "\n"; + break; + } + } + } + line = "".join(lineChars); + } + } + if "class" in line + { + if not this.IsInString("class", line) + { + line = "\n"+" ".join(line.split()); + } + } + if "function" in line + { + if not this.IsInString("function", line, false) + { + line = line.replace("function", "def"); + } + } + leftBraceExpression = ''.join(line.split()); + if not this.IsInString("{", leftBraceExpression) + { + if ''.join(line.split()).startswith(("{")) + { + newCode += ":\n"; + } + } + if not this.IsInString("}", line) + { + line = line.replace("}", "#endindent"); + } + if not this.IsInString("{", line) + { + line = line.replace("{", "#startindent"); + } + line += "\n"; + newCode += line; + line += "\n"; + } + + return newCode; + } + + function ParseFunctions(code: str) is str + { + code = code; + for line in code.splitlines() + { + if "function" in line + { + if not this.IsInString("function", line, false) + { + code = code.replace(line, line.replace("function", "def")); + } + } + } + for line in code.splitlines() + { + if "def Start" in line + { + if not this.IsInString("def Start", line) + { + code = code.replace(line, line.replace("def Start", "def __init__")); + } + } + } + for line in code.splitlines() + { + if ") is" in line + { + if not this.IsInString(") is", line) + { + code = code.replace(line, line.replace(") is", ") ->")); + } + } + } + for line in code.splitlines() + { + if "def" in line + { + if (line.partition("def")[0].strip() == "") + { + code = code.replace(line, line.replace("(", "(self,")); + } + } + } + + return code; + } + + function CleanCode(code : str) is str + { + //I'm not going to lie, I made a lot of mistakes. Let's hope these hacks fix it. + + splitLines = code.splitlines(); + for lineNo, line in enumerate(splitLines) + { + if line.startswith(":") + { + splitLines[lineNo - 1] = splitLines[lineNo - 1] + ":"; + splitLines[lineNo] = ""; + } + } + newCode = ""; + for line in splitLines + { + newCode += line + "\n"; + } + code = newCode; + + splitLines = code.splitlines(); + newCode = ""; + for lineNo, line in enumerate(splitLines) + { + i = 0; + indentCount = 0; + while i <= lineNo + { + if "#endindent" in splitLines[i] + { + if not this.IsInString("#endindent", splitLines[i], true) + { + indentCount -= 1; + } + } + + else if "#startindent" in splitLines[i] + { + if not this.IsInString("#startindent", splitLines[i], true) + { + if not this.IsInString("#startindent", splitLines[i]) + { + indentCount += 1; + } + } + } + i += 1; + } + newCode += (" " * indentCount) + line + "\n"; + } + code = newCode; + + //Remove indent helpers + newCode = ""; + for line in code.splitlines() + { + if "#endindent" in line + { + if not this.IsInString("#endindent", line) + { + line = line.replace(line, ""); + } + } + if "#startindent" in line + { + if not this.IsInString("#startindent", line) + { + line = line.replace(line, ""); + } + } + newCode += line + "\n"; + } + code = newCode; + + //Tidy code by removing empty lines + newCode = ""; + for line in code.splitlines() + { + if line.strip("\t\r\n") == "" + { + line = line.replace(line, line.strip("\t\r\n")); + newCode += line; + } + else + { + newCode += line + "\n"; + } + } + code = newCode; + + code = "\n".join([ll.rstrip() for ll in code.splitlines() if ll.strip()]); + + return code; + } + + function AddEntryPoint(code: str) is str + { + code += "\n"; + code += "if __name__ == \"__main__\":\n\tmain = Main()\n\tmain.Main()"; + + return code; + } + + function IsInString(phrase : str, line : str, returnIfMultiple = false) is bool + { + if not phrase in line + { + return false; + } + if line.count(phrase) > 1 + { + return returnIfMultiple; + } + leftSide = line.partition(phrase)[0]; + if leftSide.count("\"") > 0 + { + if leftSide.count("\"") % 2 == 0 + { + return false; + } + else + { + return true; + } + } + if leftSide.count("\'") > 0 + { + if leftSide.count("\'") % 2 == 0 + { + return false; + } + else + { + return true; + } + } + } +} \ No newline at end of file