-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 34d21fb
Showing
19 changed files
with
565 additions
and
0 deletions.
There are no files selected for viewing
38 changes: 38 additions & 0 deletions
38
WriteYourLanguage.playground/Pages/Conclusion.xcplaygroundpage/Contents.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
//: [Previous](@previous) | ||
/*: | ||
# Conclusion | ||
|
||
![Alt text](complete-flow.png) | ||
|
||
- Given an input | ||
*/ | ||
|
||
let input = "(s (s 4 5) 4)" | ||
|
||
/*: | ||
- Extract an array of tokens (Lexing); | ||
*/ | ||
|
||
let tokens = Lexer.tokenize(input) | ||
|
||
/*: | ||
- Parse the given tokens into a tree (Parsing); | ||
*/ | ||
|
||
var parser = Parser(tokens: tokens) | ||
let ast = try! parser.parse() | ||
|
||
/*: | ||
- And walk through this tree, and compute the values contained inside a node (Interpreting); | ||
*/ | ||
let result = try! Interpreter.eval(ast) | ||
|
||
|
||
/*: | ||
|
||
### Resources | ||
|
||
- https://ruslanspivak.com/lsbasi-part1/ | ||
- https://www.amazon.com/Compilers-Principles-Techniques-Tools-2nd/dp/0321486811 | ||
- http://llvm.org/docs/tutorial/ | ||
*/ |
Binary file added
BIN
+256 KB
...nguage.playground/Pages/Conclusion.xcplaygroundpage/Resources/complete-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
126 changes: 126 additions & 0 deletions
126
WriteYourLanguage.playground/Pages/Conclusion.xcplaygroundpage/Sources/Mu.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
|
||
public enum Token { | ||
case parensOpen | ||
case op(String) | ||
case number(Int) | ||
case parensClose | ||
} | ||
|
||
public struct Lexer { | ||
public static func tokenize(_ input: String) -> [Token] { | ||
return input.characters.flatMap { | ||
switch $0 { | ||
case "(": return Token.parensOpen | ||
case ")": return Token.parensClose | ||
case "s": return Token.op($0.description) | ||
default: | ||
if "0"..."9" ~= $0 { | ||
return Token.number(Int($0.description)!) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
} | ||
|
||
public indirect enum PrimaryExpressionNode { | ||
case number(Int) | ||
case expression(ExpressionNode) | ||
} | ||
|
||
public struct ExpressionNode { | ||
public var op: String | ||
public var first: PrimaryExpressionNode | ||
public var second: PrimaryExpressionNode | ||
} | ||
|
||
public enum ParsingError: Error { | ||
case unexpectedToken | ||
} | ||
public struct Parser { | ||
|
||
var index = 0 | ||
let tokens: [Token] | ||
|
||
public init(tokens: [Token]) { | ||
self.tokens = tokens | ||
} | ||
|
||
mutating func popToken() -> Token { | ||
let token = tokens[index] | ||
index += 1 | ||
|
||
return token | ||
} | ||
|
||
mutating func peekToken() -> Token { | ||
return tokens[index] | ||
} | ||
|
||
|
||
mutating func parsePrimaryExpression() throws -> PrimaryExpressionNode { | ||
switch peekToken() { | ||
case .number(let n): | ||
_ = popToken() // Removing number | ||
return PrimaryExpressionNode.number(n) | ||
case .parensOpen: | ||
let expressionNode = try parseExpression() | ||
|
||
return PrimaryExpressionNode.expression(expressionNode) | ||
default: | ||
throw ParsingError.unexpectedToken | ||
} | ||
} | ||
|
||
mutating func parseExpression() throws -> ExpressionNode { | ||
guard case .parensOpen = popToken() else { | ||
throw ParsingError.unexpectedToken | ||
} | ||
guard case let .op(_operator) = popToken() else { | ||
throw ParsingError.unexpectedToken | ||
} | ||
|
||
let firstExpression = try parsePrimaryExpression() | ||
let secondExpression = try parsePrimaryExpression() | ||
|
||
guard case .parensClose = popToken() else { | ||
throw ParsingError.unexpectedToken | ||
} | ||
|
||
return ExpressionNode(op: _operator, first: firstExpression, second: secondExpression) | ||
} | ||
|
||
public mutating func parse() throws -> ExpressionNode { | ||
return try parseExpression() | ||
} | ||
} | ||
|
||
enum InterpreterError: Error { | ||
case unknownOperator | ||
} | ||
|
||
public struct Interpreter { | ||
public static func eval(_ expression: ExpressionNode) throws -> Int { | ||
let firstEval = try eval(expression.first) | ||
let secEval = try eval(expression.second) | ||
|
||
if expression.op == "s" { | ||
return firstEval + secEval | ||
} | ||
|
||
throw InterpreterError.unknownOperator | ||
} | ||
|
||
static func eval(_ prim: PrimaryExpressionNode) throws -> Int { | ||
switch prim { | ||
case .expression(let exp): | ||
return try eval(exp) | ||
case .number(let n): | ||
return Int(n) | ||
} | ||
} | ||
|
||
} | ||
|
||
|
50 changes: 50 additions & 0 deletions
50
WriteYourLanguage.playground/Pages/Interpreter.xcplaygroundpage/Contents.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
//: [Previous](@previous) | ||
/*: | ||
# Interpreter | ||
|
||
*"In computer science, an interpreter is a computer program that directly executes, i.e. performs, instructions written in a programming or scripting language, without previously compiling them into a machine language program."* *-Wikipedia* | ||
|
||
|
||
## Example: | ||
`Mu`'s interpreter will walk through its A.S.T and compute a value by applying an operator to the children nodes. | ||
|
||
|
||
![Alt text](simple-ast.png) | ||
|
||
*/ | ||
enum InterpreterError: Error { | ||
case unknownOperator | ||
} | ||
|
||
struct Interpreter { | ||
static func eval(_ expression: ExpressionNode) throws -> Int { | ||
let firstEval = try eval(expression.first) | ||
let secEval = try eval(expression.second) | ||
|
||
if expression.op == "s" { | ||
return firstEval + secEval | ||
} | ||
|
||
throw InterpreterError.unknownOperator | ||
} | ||
|
||
static func eval(_ prim: PrimaryExpressionNode) throws -> Int { | ||
switch prim { | ||
case .expression(let exp): | ||
return try eval(exp) | ||
case .number(let n): | ||
return Int(n) | ||
} | ||
} | ||
|
||
} | ||
|
||
let input = "(s (s 5 2) 4)" | ||
let tokens = Lexer.tokenize(input) | ||
var parser = Parser(tokens: tokens) | ||
|
||
let ast = try! parser.parse() | ||
try! Interpreter.eval(ast) | ||
|
||
|
||
//: [Next](@next) |
Binary file added
BIN
+101 KB
...Language.playground/Pages/Interpreter.xcplaygroundpage/Resources/simple-ast.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions
26
WriteYourLanguage.playground/Pages/Interpreter.xcplaygroundpage/Sources/Lexer.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
|
||
public enum Token { | ||
case parensOpen | ||
case op(String) | ||
case number(Int) | ||
case parensClose | ||
} | ||
|
||
public struct Lexer { | ||
public static func tokenize(_ input: String) -> [Token] { | ||
return input.characters.flatMap { | ||
switch $0 { | ||
case "(": return Token.parensOpen | ||
case ")": return Token.parensClose | ||
case "s": return Token.op($0.description) | ||
default: | ||
if "0"..."9" ~= $0 { | ||
return Token.number(Int($0.description)!) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
} | ||
|
71 changes: 71 additions & 0 deletions
71
WriteYourLanguage.playground/Pages/Interpreter.xcplaygroundpage/Sources/Parser.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
public indirect enum PrimaryExpressionNode { | ||
case number(Int) | ||
case expression(ExpressionNode) | ||
} | ||
|
||
public struct ExpressionNode { | ||
public var op: String | ||
public var first: PrimaryExpressionNode | ||
public var second: PrimaryExpressionNode | ||
} | ||
|
||
public enum ParsingError: Error { | ||
case unexpectedToken | ||
} | ||
public struct Parser { | ||
|
||
var index = 0 | ||
let tokens: [Token] | ||
|
||
public init(tokens: [Token]) { | ||
self.tokens = tokens | ||
} | ||
|
||
mutating func popToken() -> Token { | ||
let token = tokens[index] | ||
index += 1 | ||
|
||
return token | ||
} | ||
|
||
mutating func peekToken() -> Token { | ||
return tokens[index] | ||
} | ||
|
||
|
||
mutating func parsePrimaryExpression() throws -> PrimaryExpressionNode { | ||
switch peekToken() { | ||
case .number(let n): | ||
_ = popToken() // Removing number | ||
return PrimaryExpressionNode.number(n) | ||
case .parensOpen: | ||
let expressionNode = try parseExpression() | ||
|
||
return PrimaryExpressionNode.expression(expressionNode) | ||
default: | ||
throw ParsingError.unexpectedToken | ||
} | ||
} | ||
|
||
mutating func parseExpression() throws -> ExpressionNode { | ||
guard case .parensOpen = popToken() else { | ||
throw ParsingError.unexpectedToken | ||
} | ||
guard case let .op(_operator) = popToken() else { | ||
throw ParsingError.unexpectedToken | ||
} | ||
|
||
let firstExpression = try parsePrimaryExpression() | ||
let secondExpression = try parsePrimaryExpression() | ||
|
||
guard case .parensClose = popToken() else { | ||
throw ParsingError.unexpectedToken | ||
} | ||
|
||
return ExpressionNode(op: _operator, first: firstExpression, second: secondExpression) | ||
} | ||
|
||
public mutating func parse() throws -> ExpressionNode { | ||
return try parseExpression() | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
WriteYourLanguage.playground/Pages/Intro.xcplaygroundpage/Contents.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/*: | ||
# Writing Your Own Programming Language | ||
|
||
You don't need a CS degree to write a programing language, you just need to understand 3 basic steps. | ||
|
||
## The Language: **Mu(μ)** | ||
Mu is a minimal language, that is consisted by a postfix operator, a binary operation and one digit numbers. | ||
|
||
### Examples: | ||
`(s 2 4)` or `(s (s 4 5) 4)` or `(s (s 4 5) (s 3 2))`... | ||
|
||
## The Steps: | ||
* Lexer | ||
* Parser | ||
* Interpreter | ||
|
||
![Alt text](flow.png) | ||
*/ | ||
|
||
let input = "(s (s 6 6) 6)" // Should return 18 | ||
|
||
//: [Lexer ->](@next) |
Binary file added
BIN
+1.57 MB
WriteYourLanguage.playground/Pages/Intro.xcplaygroundpage/Resources/flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions
45
WriteYourLanguage.playground/Pages/Lexer.xcplaygroundpage/Contents.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
//: [Previous](@previous) | ||
/*: | ||
|
||
# Lexer | ||
|
||
*"In computer science, lexical analysis is the process of converting a sequence of characters into a sequence of tokens (strings with an identified "meaning"). A program that performs lexical analysis may be called a lexer, tokenizer,[1] or scanner (though "scanner" is also used to refer to the first stage of a lexer). Such a lexer is generally combined with a parser, which together analyze the syntax of programming languages..."* *-Wikipedia* | ||
|
||
|
||
## Example: | ||
![Alt text](lexer.png) | ||
|
||
Because `Mu` is so small--only one character operator and numbers--you can simply iterate over the input and check each one character at the time. | ||
|
||
*/ | ||
|
||
enum Token { | ||
case parensOpen | ||
case op(String) | ||
case number(Int) | ||
case parensClose | ||
} | ||
|
||
struct Lexer { | ||
|
||
static func tokenize(_ input: String) -> [Token] { | ||
return input.characters.flatMap { | ||
switch $0 { | ||
case "(": return Token.parensOpen | ||
case ")": return Token.parensClose | ||
case "s": return Token.op($0.description) | ||
default: | ||
if "0"..."9" ~= $0 { | ||
return Token.number(Int($0.description)!) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
} | ||
|
||
let input = "(s (s 4 5) 4)" | ||
let tokens = Lexer.tokenize(input) | ||
|
||
//: [Next](@next) |
Binary file added
BIN
+118 KB
WriteYourLanguage.playground/Pages/Lexer.xcplaygroundpage/Resources/lexer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Oops, something went wrong.