在创建您自己的编程语言的这一部分中,我们将通过实现嵌套类并稍微升级上一部分介绍的类来继续改进我们的语言。请查看前面的部分:
GitHub 上提供了完整的源代码。
在第一部分中,我们将介绍词法分析。简而言之,就是将源代码分解成语言词素的过程,如关键字、变量、运算符等。
您可能还记得前面的部分,我使用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
词素中:
当我们创建一个嵌套类的实例时,我们使用以下结构,两个表达式和它们之间的一个运算符:
左边的表达式class_instance
是我们要从中获取嵌套类的类的实例。正确的表达式NestedClass [args]
是一个嵌套类,具有创建实例的属性。
最后,作为创建嵌套类的运算符,我将使用以下表达式:: new
,这意味着我们使用两个冒号::
运算符引用类实例属性,然后我们使用new
创建一个实例操作员。
使用当前的词素集,我们只需要为:: new
运算符添加一个正则表达式。可以通过以下正则表达式验证此运算符:
:{2}\\s+new
让我们在Operator
词素中将此表达式作为 OR 表达式添加到代表访问类属性的:{2}
部分之前:
public enum TokenType { ... Operator("(\\+|-|\\*|/{1,2}|%|>=|>|<=|<{1,2}|={1,2}|!=|!|:{2}\\s+new|:{2}|\\(|\\)|(new|and|or)(?=\\s|$))"), ... }
在第二部分中,我们将从词法分析器接收到的词位转换为遵循我们的语言规则的最终语句。
为了评估数学表达式,我们使用Dijkstra 的 Two-Stack 算法。该算法中的每个操作都可以由具有一个操作数的一元运算符或分别具有两个操作数的二元运算符表示:
嵌套类的实例化是一个二元操作,其中左边的操作数是我们用来引用定义嵌套类的类的类实例,第二个操作数是我们创建实例的嵌套类:
让我们通过扩展来创建NestedClassInstanceOperator
实现
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()
正确的操作数。在这种情况下,我们不能直接调用Expression#evaluate()
因为嵌套类的定义是在父类的 DefinitionScope 中声明的(在左操作数中)。
要访问嵌套类的定义,我们应该创建一个辅助ClassExpression#evaluate(ClassValue)
方法,该方法将采用左操作数并使用其 DefinitionScope 来访问嵌套类定义并创建以下实例:
@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(); } } }
我们用来评估数学表达式的所有运算符都存储在Operator枚举中,具有相应的优先级、字符和OperatorExpression
类型,我们参考计算每个运算的结果:
... 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); } ... }
最后,我们应该在包含以下符号的操作字符前添加两个反斜杠\\
: +, *, (, )
以确保这些字符不被视为正则表达式搜索符号:
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
实例作为整个操作数表达式返回。
现在一切都准备好了。在这一部分中,我们实现了嵌套类,作为构建完整编程语言的又一步。