paint-brush
ゼロから独自のプログラミング言語を構築する: パート VIII - ネストされたクラス@alexandermakeev
669 測定値
669 測定値

ゼロから独自のプログラミング言語を構築する: パート VIII - ネストされたクラス

Alexander Makeev9m2023/01/16
Read on Terminal Reader

長すぎる; 読むには

独自のプログラミング言語を作成するこの部分では、ネストされたクラスを実装します
featured image - ゼロから独自のプログラミング言語を構築する: パート VIII - ネストされたクラス
Alexander Makeev HackerNoon profile picture

独自のプログラミング言語を作成するこの部分では、ネストされたクラスを実装し、前の部分で紹介したクラスをわずかにアップグレードして、言語を改善し続けます。前の部分をチェックしてください:


  1. ゼロから独自のプログラミング言語を構築する
  2. ゼロから独自のプログラミング言語を構築する: パート II - ダイクストラの 2 スタック アルゴリズム
  3. 独自のプログラミング言語を構築する パート III: 正規表現先読みによる字句解析の改善
  4. ゼロから独自のプログラミング言語を構築する パート IV: 関数の実装
  5. 独自のプログラミング言語をゼロから構築する: パート V - 配列
  6. ゼロから独自のプログラミング言語を構築する: パート VI - ループ
  7. ゼロから独自のプログラミング言語を構築する: パート VII - クラス


完全なソース コードはGitHubで入手できます。

1 字句解析

最初のセクションでは、字句解析について説明します。つまり、ソースコードをキーワード、変数、演算子などの言語語彙素に分割するプロセスです。


前の部分から、すべての語彙素タイプを定義するためにTokenType列挙型にリストされた正規表現を使用していたことを覚えているかもしれません。


 package org.example.toylanguage.token; public enum TokenType { Comment("\\#.*"), LineBreak("[\\n\\r]"), Whitespace("[\\s\\t]"), Keyword("(if|elif|else|end|print|input|class|fun|return|loop|in|by|break|next)(?=\\s|$)"), GroupDivider("(\\[|\\]|\\,|\\{|}|[.]{2})"), Logical("(true|false)(?=\\s|$)"), Numeric("([-]?(?=[.]?[0-9])[0-9]*(?![.]{2})[.]?[0-9]*)"), Null("(null)(?=,|\\s|$)"), This("(this)(?=,|\\s|$)"), Text("\"([^\"]*)\""), Operator("(\\+|-|\\*|/{1,2}|%|>=|>|<=|<{1,2}|={1,2}|!=|!|:{2}|\\(|\\)|(new|and|or)(?=\\s|$))"), Variable("[a-zA-Z_]+[a-zA-Z0-9_]*"); private final String regex; }


このネストされたクラスのプロトタイプを調べて、不足している正規表現をTokenType語彙素に追加してみましょう。


ネストされたクラスのインスタンスを作成するときは、2 つの式とそれらの間に 1 つの演算子を使用して、次の構造を使用します。

左の式class_instanceは、ネストされたクラスを取得するために参照しているクラスのインスタンスです。右側の式NestedClass [args]は、インスタンスを作成するためのプロパティを持つネストされたクラスです。


最後に、ネストされたクラスを作成する演算子として、次の式を使用します: :: new 。これは、2 つのコロン::演算子でクラス インスタンス プロパティを参照し、次にnewでインスタンスを作成することを意味します。オペレーター。


現在の語彙素のセットでは、 :: new演算子の正規表現を追加するだけです。この演算子は、次の正規表現で検証できます。

 :{2}\\s+new


クラス プロパティへのアクセスを表す:{2}部分の前に、OR 式としてOperator語彙素にこの式を追加しましょう。

 public enum TokenType { ... Operator("(\\+|-|\\*|/{1,2}|%|>=|>|<=|<{1,2}|={1,2}|!=|!|:{2}\\s+new|:{2}|\\(|\\)|(new|and|or)(?=\\s|$))"), ... }


2 構文解析

2 番目のセクションでは、語彙アナライザーから受け取った語彙素を、言語規則に従って最終ステートメントに変換します。

2.1 演算子式

数式を評価するために、 ダイクストラの Two-Stack アルゴリズムを使用しています。このアルゴリズムの各演算は、1 つのオペランドを持つ単項演算子、またはそれぞれ 2 つのオペランドを持つ 2 項演算子によって表すことができます。



ネストされたクラスのインスタンス化は、左のオペランドが、ネストされたクラスが定義されているクラスを参照するために使用するクラス インスタンスであり、2 番目のオペランドが、インスタンスを作成するネストされたクラスであるバイナリ演算です。


拡張してNestedClassInstanceOperator実装を作成しましょう

BinaryOperatorExpression :

 package org.example.toylanguage.expression.operator; public class NestedClassInstanceOperator extends BinaryOperatorExpression { public NestedClassInstanceOperator(Expression left, Expression right) { super(left, right); } @Override public Value<?> evaluate() { ... } }


次に、ネストされたクラスのインスタンス化を実行するevaluate()メソッドを完成させる必要があります。

まず、左側のオペランドの式をValue式に評価します。

 @Override public Value<?> evaluate() { // ClassExpression -> ClassValue Value<?> left = getLeft().evaluate(); }


次に、正しいオペランドをevaluate()する必要があります。この場合、ネストされたクラスの定義が親クラスの DefinitionScope (左側のオペランド) で宣言されているため、 Expression#evaluate()を直接呼び出すことはできません。


ネストされたクラスの定義にアクセスするには、左側のオペランドを取り、その DefinitionScope を使用してネストされたクラス定義にアクセスし、次のインスタンスを作成する補助的なClassExpression#evaluate(ClassValue)メソッドを作成する必要があります。

 @Override public Value<?> evaluate() { Value<?> left = getLeft().evaluate(); if (left instanceof ClassValue && getRight() instanceof ClassExpression) { // instantiate nested class // new Class [] :: new NestedClass [] return ((ClassExpression) getRight()).evaluate((ClassValue) left); } else { throw new ExecutionException(String.format("Unable to access class's nested class `%s``", getRight())); } }


最後に、欠落しているClassExpression#evaluate(ClassValue)メソッドを実装しましょう。


この実装はClassExpression#evaluate()メソッドに似ていますが、唯一の違いは、ネストされたクラス定義を取得するためにClassDefinition#getDefinitionScope()を設定する必要があることです。

 package org.example.toylanguage.expression; … public class ClassExpression implements Expression { private final String name; private final List<Expression> argumentExpressions; @Override public Value<?> evaluate() { //initialize class arguments List<Value<?>> values = argumentExpressions.stream().map(Expression::evaluate).collect(Collectors.toList()); return evaluate(values); } /** * Evaluate nested class * * @param classValue instance of the parent class */ public Value<?> evaluate(ClassValue classValue) { //initialize class arguments List<Value<?>> values = argumentExpressions.stream().map(Expression::evaluate).collect(Collectors.toList()); //set parent class's definition ClassDefinition classDefinition = classValue.getValue(); DefinitionContext.pushScope(classDefinition.getDefinitionScope()); try { return evaluate(values); } finally { DefinitionContext.endScope(); } } private Value<?> evaluate(List<Value<?>> values) { //get class's definition and statement ClassDefinition definition = DefinitionContext.getScope().getClass(name); ClassStatement classStatement = definition.getStatement(); //set separate scope MemoryScope classScope = new MemoryScope(null); MemoryContext.pushScope(classScope); try { //initialize constructor arguments ClassValue classValue = new ClassValue(definition, classScope); ClassInstanceContext.pushValue(classValue); IntStream.range(0, definition.getArguments().size()).boxed() .forEach(i -> MemoryContext.getScope() .setLocal(definition.getArguments().get(i), values.size() > i ? values.get(i) : NullValue.NULL_INSTANCE)); //execute function body DefinitionContext.pushScope(definition.getDefinitionScope()); try { classStatement.execute(); } finally { DefinitionContext.endScope(); } return classValue; } finally { MemoryContext.endScope(); ClassInstanceContext.popValue(); } } }


2.2 オペレーター

数式を評価するために使用しているすべての演算子は、対応する優先順位、文字、および各演算の結果を計算するために参照するOperatorExpression型と共にOperator列挙型に格納されます。

 ... public enum Operator { Not("!", NotOperator.class, 7), ClassInstance("new", ClassInstanceOperator.class, 7), ... private final String character; private final Class<? extends OperatorExpression> type; private final Integer precedence; Operator(String character, Class<? extends OperatorExpression> type, Integer precedence) { this.character = character; this.type = type; this.precedence = precedence; } public static Operator getType(String character) { return Arrays.stream(values()) .filter(t -> Objects.equals(t.getCharacter(), character)) .findAny().orElse(null); } ... }


通常のクラスの初期化のためのClassInstance値はすでにあります。ネストされたクラス インスタンスを管理する新しい値を追加しましょう。


新しいNestedClassInstance値は、以前にTokenTypeで定義したのと同じ文字式を持ち、通常のクラスのインスタンスと同じ優先順位を持ちます。


OperatorExpression タイプについては、以前に定義されたNestedClassInstanceOperatorを使用します。

 ... public enum Operator { Not("!", NotOperator.class, 7), ClassInstance("new", ClassInstanceOperator.class, 7), NestedClassInstance(":{2}\\s+new", NestedClassInstanceOperator.class, 7), ... }


この新しい演算子を除いて、文字プロパティに正規表現がないことに気付くかもしれません。正規表現を使用してNestedClassInstanceオペレーターを読み取るには、 Operator#getType()メソッドを更新して、オペレーターを正規表現と一致させる必要があります。

 public enum Operator { ... public static Operator getType(String character) { return Arrays.stream(values()) .filter(t -> character.matches(t.getCharacter())) .findAny().orElse(null); } ... }


最後に、次の記号を含む操作の文字の前に 2 つのバックスラッシュ\\を追加する必要があります: +, *, (, )これらの文字が正規表現検索記号として扱われないようにします。

 Multiplication("\\*", MultiplicationOperator.class, 6), Addition("\\+", AdditionOperator.class, 5), LeftParen("\\(", 3), RightParen("\\)", 3),


NestedClassInstance演算子を導入した後、実際に数式をオペランドと演算子に解析するExpressionReaderクラスに挿入する必要があります。クラスインスタンスを読み取る行を見つけるだけです。

 if (!operators.isEmpty() && operators.peek() == Operator.ClassInstance) { operand = readClassInstance(token); }


NestedClassInstanceオペレーターの読み取りをサポートするために、オペレーターのスタック内の現在のオペレーターに対応する条件を追加します。

 if (!operators.isEmpty() && (operators.peek() == Operator.ClassInstance || operators.peek() == Operator.NestedClassInstance)) { operand = readClassInstance(token); }


readClassInstance()メソッドは、通常のクラス宣言を読み取るのと同じ方法で、プロパティを持つネストされたクラスの宣言を読み取ります。このメソッドは、オペランド式全体としてClassExpressionインスタンスを返します。

3. まとめ

すべての準備が整いました。このパートでは、完全なプログラミング言語を作成するためのもう 1 つのステップとして、ネストされたクラスを実装しました。