diff --git a/Sources/rxcc/rxcc.swift b/Sources/rxcc/rxcc.swift index b90e410..43bafa7 100644 --- a/Sources/rxcc/rxcc.swift +++ b/Sources/rxcc/rxcc.swift @@ -1,22 +1,5 @@ import Foundation -enum Constant { - case Integer(value: Int) -} - -struct Return { - let value: Constant -} - -struct Function { - let name: String - let statement: Return -} - -struct Program { - let contents: Function -} - enum TokenType { case BRACE_OPEN case BRACE_CLOSE @@ -27,10 +10,15 @@ enum TokenType { case RETURN case IDENTIFIER case LITERAL_INTEGER + case NEGATION + case BITWISE_COMPLIMENT + case LOGICAL_NEGATION case UNDEFINED } +let UNARY_OPERATORS: [TokenType] = [.NEGATION, .BITWISE_COMPLIMENT, .LOGICAL_NEGATION] + struct Token { let content: Substring let type: TokenType @@ -39,36 +27,74 @@ struct Token { typealias Construct = [Element] enum Element { - case Construct(type: Construct) - case Token(type: TokenType) + case Construct(type: ConstructDefinitions); + case Token(types: [TokenType]) } +enum ConstructType { + case Expression + case Statement + case Function + case Program +} +struct ConstructDefinitions { + var type: ConstructType + var variants: Dictionary +} -let expression: Construct = [ - .Token(type: .LITERAL_INTEGER) -] -let statement: Construct = [ - .Token(type: .RETURN), - .Construct(type: expression), - .Token(type: .SEMICOLON) -] -let function: Construct = [ - .Token(type: .INT), - .Token(type: .IDENTIFIER), - .Token(type: .PARENTHESIS_OPEN), - .Token(type: .PARENTHESIS_CLOSE), - .Token(type: .BRACE_OPEN), - .Construct(type: statement), - .Token(type: .BRACE_CLOSE) -] -let program: Construct = [ - .Construct(type: function) -] +let expression: ConstructDefinitions = ConstructDefinitions( + type: .Expression, + variants: [ + "LiteralInteger": [ + .Token(types: [.LITERAL_INTEGER]) + ], + "UnaryOperator": [ + .Token(types: [.NEGATION]), + .Token(types: UNARY_OPERATORS) + ], + ] +) + +let statement: ConstructDefinitions = ConstructDefinitions( + type: .Statement, + variants: [ + "ReturnInteger": [ + .Token(types: [.RETURN]), + .Construct(type: expression), + .Token(types: [.SEMICOLON]) + ] + ] +) + +let function: ConstructDefinitions = ConstructDefinitions( + type: .Function, + variants: [ + "Integer": [ + .Token(types: [.INT]), + .Token(types: [.IDENTIFIER]), + .Token(types: [.PARENTHESIS_OPEN]), + .Token(types: [.PARENTHESIS_CLOSE]), + .Token(types: [.BRACE_OPEN]), + .Construct(type: statement), + .Token(types: [.BRACE_CLOSE]) + ] + ] +) + +let program: ConstructDefinitions = ConstructDefinitions( + type: .Function, + variants: [ + "Function": [ + .Construct(type: function) + ] + ] +) struct TestFile { var name: String + var path: String var contents: String var valid: Bool } @@ -76,12 +102,50 @@ struct TestFile { @main struct rxcc { static func main() { - for testFile in getTestFiles() { - print("Testing \(testFile.valid ? "valid" : "invalid") file \(testFile.name):") - print(testFile.contents) - let lexed: [Substring] = lex(string: testFile.contents) - parse(lexed: lexed) - print() + if CommandLine.arguments.count < 2 { + for testFile in getTestFiles() { + print("Testing \(testFile.valid ? "valid" : "invalid") file \(testFile.name):") + print(testFile.contents) + let lexed: [Substring] = lex(string: testFile.contents) + let output: String = parse(lexed: lexed) + + if output.isEmpty { + print() + continue + } + + do { + try output.write(to: URL(fileURLWithPath: "bin").appendingPathComponent("\(testFile.name.dropLast())s"), atomically: true, encoding: .utf8) + } catch { + print("Couldn't write file \"\(testFile.name.dropLast())s\"") + } + + print() + } + } else { + var text = "" + do { + text = try String(contentsOf: URL(fileURLWithPath: CommandLine.arguments[1]), encoding: .utf8) + } catch { + print("Error reading file from command line argument") + } + print(CommandLine.arguments) + + let lexed: [Substring] = lex(string: text) + let output: String = parse(lexed: lexed) + + if output.isEmpty { + print("Failed to compile") + } + + var fileName = String(CommandLine.arguments[1].dropLast() + "s") + fileName = URL(fileURLWithPath: fileName).lastPathComponent + + do { + try output.write(to: URL(fileURLWithPath: fileName), atomically: true, encoding: .utf8) + } catch { + print("Couldn't write file \"\(CommandLine.arguments[1].dropLast())s\"") + } } } } @@ -90,23 +154,28 @@ func getTestFiles() -> [TestFile] { var testFiles: [TestFile] = [TestFile]() let fileManager = FileManager.default - let path = "c/tests/stage_1" + let path = "c/tests/stage_2" do { let validItems = try fileManager.contentsOfDirectory(atPath: path + "/valid") - for item in validItems { + if item.last?.lowercased() != "c" { + continue + } let fileURL = URL(fileURLWithPath: path + "/valid").appendingPathComponent(item) let text = try String(contentsOf: fileURL, encoding: .utf8) - testFiles.append(TestFile(name: item, contents: text, valid: true)) + testFiles.append(TestFile(name: item, path: path + "/valid/", contents: text, valid: true)) } let invalidItems = try fileManager.contentsOfDirectory(atPath: path + "/invalid") for item in invalidItems { + if item.last?.lowercased() != "c" { + continue + } let fileURL = URL(fileURLWithPath: path + "/invalid").appendingPathComponent(item) let text = try String(contentsOf: fileURL, encoding: .utf8) - testFiles.append(TestFile(name: item, contents: text, valid: false)) + testFiles.append(TestFile(name: item, path: path + "/invalid/", contents: text, valid: false)) } } catch { print("You think I'm going to handle errors? You are mistaken.") @@ -123,9 +192,14 @@ func lex(string: String) -> [Substring] { line = line.replacing("(", with: " ( ") line = line.replacing(")", with: " ) ") line = line.replacing(";", with: " ; ") + line = line.replacing("-", with: " - ") + line = line.replacing("~", with: " ~ ") + line = line.replacing("!", with: " ! ") do { let tokens: [Substring] = line.split(separator: try Regex(" +")) + print("Tokens:") + print(tokens) return tokens } catch { print("Regex did something wrong") @@ -133,7 +207,7 @@ func lex(string: String) -> [Substring] { return [] } -func parse(lexed: [Substring]) { +func parse(lexed: [Substring]) -> String { var tokens: [Token] = [Token]() for token: Substring in lexed { let tokenType: TokenType = categorizeToken(token: token) @@ -141,32 +215,66 @@ func parse(lexed: [Substring]) { } tokens = tokens.reversed() - if validateConstruct(program, tokens: &tokens) { + var output: String = "" + + if validateConstruct(program.variants["Function"]!, constructType: .Function, tokens: &tokens, output: &output) { print("Success") - return + print("Assembly:") + print(output) + print() + + return output } print("Distinct lack of success") + return "" } -func validateConstruct(_ construct: Construct, tokens: inout [Token]) -> Bool { +func validateConstruct(_ construct: Construct, constructType: ConstructType, tokens: inout [Token], output: inout String) -> Bool { for element in construct { switch element { case .Construct(let type): - print("Begin validate subconstruct") - if !validateConstruct(type, tokens: &tokens) { + print("Begin validate subconstruct (type \"\(type.type)\")") + + var valid: Bool = false + var validVariant: String = "" + for key in type.variants.keys { + if !validateConstruct(type.variants[key]!, constructType: type.type, tokens: &tokens, output: &output) { + continue + } + valid = true + validVariant = key + break + } + if !valid { print("Subconstruct validation failed") return false } - print("End validate subconstruct") + print("End validate subconstruct (variant \"\(validVariant)\")") break case .Token(let type): if let token: Token = tokens.popLast() { - if type != token.type { + if !type.contains(token.type) { print("VALIDATION FAILED FOR TOKEN \"\(token.content)\"") return false } + + switch constructType { + case .Function: + if token.type == .IDENTIFIER { + output.append(" .globl \(token.content)\n\(token.content):\n") + } + break + case .Expression: + if token.type == .LITERAL_INTEGER { + output.append(" movl $\(token.content), %eax\n ret\n") + } + break + default: + break + } + } else { print("RAN OUT OF TOKENS") return false @@ -187,6 +295,9 @@ func categorizeToken(token: Substring) -> TokenType { else if token.firstMatch(of: /^return$/) != nil { return .RETURN } else if token.firstMatch(of: /^[a-zA-Z]\w*$/) != nil { return .IDENTIFIER } else if token.firstMatch(of: /^[0-9]+$/) != nil { return .LITERAL_INTEGER } + else if token.firstMatch(of: /^-$/) != nil { return .NEGATION } + else if token.firstMatch(of: /^~$/) != nil { return .BITWISE_COMPLIMENT } + else if token.firstMatch(of: /^!$/) != nil { return .LOGICAL_NEGATION } return .UNDEFINED } diff --git a/c/a.out b/c/a.out deleted file mode 100755 index 90f76cf..0000000 Binary files a/c/a.out and /dev/null differ diff --git a/c/return_2 b/c/return_2 deleted file mode 100755 index 096aa21..0000000 Binary files a/c/return_2 and /dev/null differ diff --git a/c/return_2.c b/c/return_2.c deleted file mode 100644 index 76598f0..0000000 --- a/c/return_2.c +++ /dev/null @@ -1,4 +0,0 @@ -int main() { - return 2; -} - diff --git a/c/return_2.s b/c/return_2.s deleted file mode 100644 index b949824..0000000 --- a/c/return_2.s +++ /dev/null @@ -1,35 +0,0 @@ - .file "return_2.c" - .text - .globl main - .type main, @function -main: -.LFB0: - .cfi_startproc - pushl %ebp - .cfi_def_cfa_offset 8 - .cfi_offset 5, -8 - movl %esp, %ebp - .cfi_def_cfa_register 5 - call __x86.get_pc_thunk.ax - addl $_GLOBAL_OFFSET_TABLE_, %eax - movl $2, %eax - popl %ebp - .cfi_restore 5 - .cfi_def_cfa 4, 4 - ret - .cfi_endproc -.LFE0: - .size main, .-main - .section .text.__x86.get_pc_thunk.ax,"axG",@progbits,__x86.get_pc_thunk.ax,comdat - .globl __x86.get_pc_thunk.ax - .hidden __x86.get_pc_thunk.ax - .type __x86.get_pc_thunk.ax, @function -__x86.get_pc_thunk.ax: -.LFB1: - .cfi_startproc - movl (%esp), %eax - ret - .cfi_endproc -.LFE1: - .ident "GCC: (GNU) 15.2.1 20260103" - .section .note.GNU-stack,"",@progbits