It's a playground explaining how to create a tiny programming language (Mu).
You can download the playground here or check the source code live here
Or follow the tutorial below.
You don't need a CS degree to write a programing language, you just need to understand 3 basic steps.
Mu is a minimal language, that is consisted by a postfix operator, a binary operation and one digit numbers.
(s 2 4) or (s (s 4 5) 4) or (s (s 4 5) (s 3 2))...
"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
The idea is to transform an array of charaters into an array of tokens (strings with an identified "meaning")
Because Mu is so small--only one character operator and numbers--you can simply iterate over the input and check each character.
enumToken{case parensOpen
case op(String)case number(Int)case parensClose
}structLexer{staticfunc tokenize(_ input:String)->[Token]{return input.characters.flatMap{switch $0 {case"(":returnToken.parensOpen
case")":returnToken.parensClose
case"s":returnToken.op(String($0))default:if"0"..."9"~= $0 {returnToken.number(Int(String($0))!)}}returnnil}}}letinput="(s (s 4 5) 4)"lettokens=Lexer.tokenize(input)Parsing or syntactic analysis is the process of analysing a string of symbols, either in natural language or in computer languages, conforming to the rules of a formal grammar...-Wikipedia
expression: parensOpen operator primaryExpression primaryExpression parensClose
primaryExpression: expression | number
parensOpen: "("
parensClose: ")"
operator: "s"
number: [0-9]
Mu's grammar is a context-free grammar, that means it describes all possible strings in the language.
The parser will start from the top (root of the generated tree) and it will go until the lowest node.
Tip: the code should be a direct representation of the grammar
func parseExpression() -> ExpressionNode {
...
firstPrimaryExpression = parsePrimaryExpression()
secondPrimaryExpression = parsePrimaryExpression()
...
}
func parsePrimaryExpression() -> PrimaryExpressionNode {
return parseExpression() || parseNumber()
}
indirectenumPrimaryExpressionNode{case number(Int)case expression(ExpressionNode)}structExpressionNode{varop:StringvarfirstExpression:PrimaryExpressionNodevarsecondExpression:PrimaryExpressionNode}structParser{varindex=0lettokens:[Token]init(tokens:[Token]){self.tokens = tokens
}mutatingfunc popToken()->Token{lettoken=tokens[index]
index +=1return token
}mutatingfunc peekToken()->Token{returntokens[index]}mutatingfunc parse()throws->ExpressionNode{returntryparseExpression()}mutatingfunc parseExpression()throws->ExpressionNode{guard case .parensOpen =popToken()else{throwParsingError.unexpectedToken
}guard case letToken.op(_operator)=popToken()else{throwParsingError.unexpectedToken
}letfirstExpression=tryparsePrimaryExpression()letsecondExpression=tryparsePrimaryExpression()guard case .parensClose =popToken()else{throwParsingError.unexpectedToken
}returnExpressionNode(op: _operator, firstExpression: firstExpression, secondExpression: secondExpression)}mutatingfunc parsePrimaryExpression()throws->PrimaryExpressionNode{switchpeekToken(){case.number:returntryparseNumber()case.parensOpen:letexpressionNode=tryparseExpression()returnPrimaryExpressionNode.expression(expressionNode)default:throwParsingError.unexpectedToken
}}mutatingfunc parseNumber()throws->PrimaryExpressionNode{guard case letToken.number(n)=popToken()else{throwParsingError.unexpectedToken }returnPrimaryExpressionNode.number(n)}}
//MARK: Utils
extensionExpressionNode:CustomStringConvertible{publicvardescription:String{return"\(op) -> [\(firstExpression), \(secondExpression)]"}}extensionPrimaryExpressionNode:CustomStringConvertible{publicvardescription:String{switchself{case.number(let n):return n.description
case.expression(let exp):return exp.description
}}}letinput="(s 2 (s 3 5))"lettokens=Lexer.tokenize(input)varparser=Parser(tokens: tokens)varast=try! parser.parse()"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
Mu's interpreter will walk through its A.S.T and compute a value by applying an operator to the children nodes.
enumInterpreterError:Error{case unknownOperator
}structInterpreter{staticfunc eval(_ expression:ExpressionNode)throws->Int{letfirstEval=tryeval(expression.first)letsecEval=tryeval(expression.second)if expression.op =="s"{return firstEval + secEval
}throwInterpreterError.unknownOperator
}staticfunc eval(_ prim:PrimaryExpressionNode)throws->Int{switch prim {case.expression(let exp):returntryeval(exp)case.number(let n):returnInt(n)}}}letinput="(s (s 5 2) 4)"lettokens=Lexer.tokenize(input)varparser=Parser(tokens: tokens)letast=try! parser.parse()try!Interpreter.eval(ast)- 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)




