End of Stage 1
This commit is contained in:
@@ -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<String, Construct>
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
BIN
c/return_2
BIN
c/return_2
Binary file not shown.
@@ -1,4 +0,0 @@
|
||||
int main() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
35
c/return_2.s
35
c/return_2.s
@@ -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
|
||||
Reference in New Issue
Block a user