プログラミング言語の作成(Swiftで)
独自のプログラミング言語を作成するには、コンピューターサイエンスの学位を取得する必要はありません。3つの基本的な手順を理解すれば十分です。
言語:ムー(μ)
Muは、後置演算子、二項演算、および「1桁」の数字を含む最小限の言語です。
例: (s 2 4)または(s(s 4 5)4)または(s(s 4 5)(s 3 2))...
手順:
- レクサー
- パーサー
- 通訳
レクサー
コンピューターサイエンスでは、字句解析は、「トークン」と呼ばれる文字のシーケンス(単語の文字のグループなど)を取得するために、文字の入力シーケンス(たとえば、プログラミング言語のソースコードなど)を解析的に解析するプロセスです。
プロセスの出力でトークンとして識別される入力シーケンスの文字のグループは、トークンと呼ばれます。 字句解析のプロセスでは、文字の入力シーケンスからのトークンの認識と選択が実行されます。
-ウィキペディア
例:
Mu
非常に小さいため(演算子と数字は1つだけです)、入力を反復処理して各文字をチェックできます。
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)
パーサー
パーサーまたはパーサーは、入力データ(通常はテキスト)を構造化形式に変換するプログラムの一部です。 パーサーはテキストを解析します。
文法:
expression: parensOpen operator primaryExpression primaryExpression parensClose primaryExpression: expression | number parensOpen: "(" parensClose: ")" operator: "s" number: [0-9]
文法
Mu
は、文脈に依存しない文法です。つまり、言語の文字列のすべての可能なバリエーションを記述します。 パーサーは最上部(生成されたツリーのルート)から開始し、最下部のノードに移動します。
ヒント:コードは、文法を直接表現するものでなければなりません。
func parseExpression() -> ExpressionNode { ... firstPrimaryExpression = parsePrimaryExpression() secondPrimaryExpression = parsePrimaryExpression() ... } func parseExpression() -> PrimaryExpressionNode { return parseExpression() || parseNumber() }
indirect enum PrimaryExpressionNode { case number(Int) case expression(ExpressionNode) } struct ExpressionNode { var op: String var firstExpression: PrimaryExpressionNode var secondExpression: PrimaryExpressionNode } struct Parser { var index = 0 let tokens: [Token] 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 parse() throws -> ExpressionNode { return try parseExpression() } mutating func parseExpression() throws -> ExpressionNode { guard case .parensOpen = popToken() else { throw ParsingError.unexpectedToken } guard case let Token.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, firstExpression: firstExpression, secondExpression: secondExpression) } mutating func parsePrimaryExpression() throws -> PrimaryExpressionNode { switch peekToken() { case .number: return try parseNumber() case .parensOpen: let expressionNode = try parseExpression() return PrimaryExpressionNode.expression(expressionNode) default: throw ParsingError.unexpectedToken } } mutating func parseNumber() throws -> PrimaryExpressionNode { guard case let Token.number(n) = popToken() else { throw ParsingError.unexpectedToken } return PrimaryExpressionNode.number(n) } } //MARK: Utils extension ExpressionNode: CustomStringConvertible { public var description: String { return "\(op) -> [\(firstExpression), \(secondExpression)]" } } extension PrimaryExpressionNode: CustomStringConvertible { public var description: String { switch self { case .number(let n): return n.description case .expression(let exp): return exp.description } } } let input = "(s 2 (s 3 5))" let tokens = Lexer.tokenize(input) var parser = Parser(tokens: tokens) var ast = try! parser.parse()
通訳
コンピューターサイエンスでは、インタープリターは、プログラミング言語またはスクリプト言語で記述された命令を、最初にマシンコードにコンパイルすることなく順次実行するプログラムです。 (ウィキペディア)
例:
Muインタープリターは、ASTを通過して値を計算し、子ノードに演算子を適用します。
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)
おわりに
- 入力
let input = "(s (s 4 5) 4)
- トークン配列を抽出(Lexing)
let tokens = Lexer.tokenize(input)
- 受信したトークンをツリーに解析します(解析)。
var parser = Parser(tokens: tokens) let ast = try! parser.parse()
- ツリー内を歩いて、ノード内の値を計算します(解釈)。
let result = try! Interpreter.eval(ast)
資源
- ruslanspivak.com/lsbasi-part1
- www.amazon.com/Compilers-Principles-Techniques-Tools-2nd/dp/0321486811
- llvm.org/docs/tutorial
出版物サポート-Edison 、 アスファルトプラントの自動化 と支払いシステムと端末の開発を専門とする会社。