Two weeks of self-made scripting language - day 9 Design of object-oriented language

Keywords: Programming Java

Day 9 design object oriented language

Objective: to add support for classes and objects for the Stone language. Single inheritance only

9.1 syntax designed to operate on classes and objects

After the processing function of the added classes and objects, the following Stone language can be executed correctly

class Position {
    x = y = 0
    def move(nx,ny) {
        x = nx; y = ny
    }
}
p = Position.new
p.move(3, 4)
p.x = 10
print p.x + p.y

First, a Position class is defined, and the method is defined by the def statement. The fields in the class are represented by variables and given initial values. The above example defines the move method and fields x and y.

The code consisting of. new follows the class name to create an object. To simplify the implementation, it is specified here that the Stone language cannot define a constructor with parameters.

If you want to inherit other classes, just write extends after the class name. For example, the following code can define a subclass Pos3D of the program Position class

class Pos3D extends Position {
    z = 0
    def set(nx,ny,nz) {
        x = nx;y = ny;z = nz
    }
}
p = Pos3D.new
p.move(3,4)
print p.x
p.set(5,6,7)
print p.z

Stone does not support method overloading. A method with the same name cannot be defined in the same class with different number of parameters or different types

9.2 syntax rules for implementing classes

Listing 9.1 is a class related syntax rule modification. Only the differences between listing 7.1 and listing 7.13 are shown here. Among them, the definition of non terminal postfix and program has changed, and some other non terminal characters have been added to the syntax rules.

The nonterminal class ﹣ body represents several member s separated by semicolons or line breaks surrounded by braces {}. The non terminal postfix has been modified to support period based method calls and field access.

Listing 9.2 is a parser program updated according to the syntax rules in listing 9.1. Code listing 9.3, code listing 9.4, and code listing 9.5 are the class definitions used.

postfix and program add new or branch options through the insertChoice method.

Listing 9.1 syntax rules related to classes

member  : def | simple
class_body  :  "{" [ member ] {(";" | EOL) [ member ]} "}"
defclass  :  "class" IDENTIFIER [ "extends" IDENTIFIER ] class_body
postfix  : "." IDENTIFIER | "(" [ args ] ")"
program  : [ defclass | def | statement ] (";" | EOL)

Listing 9.2 syntax analyzer ClassPraser.java supporting classes

package Stone;
import static Stone.Parser.rule;
import Stone.ast.ClassBody;
import Stone.ast.ClassStmnt;
import Stone.ast.Dot;

public class ClassParser extends ClosureParser {
    Parser member = rule().or(def, simple);
    Parser class_body = rule(ClassBody.class).sep("{").option(member)
                            .repeat(rule().sep(";", Token.EOL).option(member))
                            .sep("}");
    Parser defclass = rule(ClassStmnt.class).sep("class").identifier(reserved)
                          .option(rule().sep("extends").identifier(reserved))
                          .ast(class_body);
    public ClassParser() {
        postfix.insertChoice(rule(Dot.class).sep(".").identifier(reserved));
        program.insertChoice(defclass);
    }
}

9.3 implementation of eval

Next, you need to add an eval method to the class of the new abstract syntax tree. Listing 9.6 is the required modifier.

First, the modifier adds the eval method to the class statement used for class definition. The class statement starts with the word class, and its corresponding non terminator is defclass, which is represented in the form of class stmnt (code listing 9.4) in the abstract syntax tree. The new Eval method of ClassStmnt class will create a ClassInfo object and add a name value pair consisting of the class name and the object to the environment. The name of the class defined by the class statement. After that, the interpreter gets the class information from the environment through. New. for example

class Position {omitted}

This statement can create a ClassInfo object that holds the definition information of the Position class in the Stone language. After the object is created, it is added to the environment with the class name Position.

The ClassInfo object holds the abstract syntax tree of the class statement. It is similar to the Function class that holds the abstract syntax tree for Function definitions (listing 7.8 in Chapter 7). Including the ClassInfo object added in this chapter, the current environment has been able to record various types of name value pairs. Table 9.1 summarizes all the values presented so far.

Code listing 9.3 ClassBody.java

package Stone.ast;
import java.util.List;

public class ClassBody extends ASTList {

	public ClassBody(List<ASTree> c) {
		super(c);
	}
}

Listing 9.4 ClassStmnt.java

package Stone.ast;
import java.util.List;

public class ClassStmnt extends ASTList {

	public ClassStmnt(List<ASTree> c) {
		super(c);
	}

	public String name() {
		return ((ASTLeaf) child(0)).token().getText();
	}

	public String superClass() {
		if (numChildren() < 3)
			return null;
		else
			return ((ASTLeaf) child(1)).token().getText();
	}

	public ClassBody body() {
		return (ClassBody) child(numChildren() - 1);
	}

	public String toStirng() {
		String parent = superClass();
		if (parent == null)
			parent = "*";
		return "(class " + name() + " " + parent + " " + body() + ")";
	}
}

Dot.java

package Stone.ast;
import java.util.List;

public class Dot extends Postfix {

	public Dot(List<ASTree> c) {
		super(c);
	}

	public String name() {
		return ((ASTLeaf) child(0)).token().getText();
	}

	public String toString() {
		return "." + name();
	}
}

Next, we need to add a new Eval method to enable the program to implement method call and field access through period. The corresponding abstract syntax tree is a Dot class (listing 9.5). The Dot class is a subclass of Postfix. The eval method of the Dot class is directly called by the evalsubexpr method of the PrimaryExpr class. The eval method of the PrimaryExpr class will get the call result through the evalsubexpr method

The eval method that the modifier added to the Dot class requires two parameters. One is the environment, the other is the calculation result to the left of the period.

If new is to the right of the period, the period expression is used to create an object. To the left of the period is the class to be created, which evaluates to a ClassInfo object. The eval method creates and returns an object based on the information provided by the ClassInfo object.

If the right side of the period is not new, the period expression is used for method calls or field access. To the left of the period is the object to be accessed, which evaluates to a StoneObject object. If this is a field, the interpreter will call the read method of to get the value of the field and return it.

The AssignEx modifier in listing 9.6 implements the field assignment function. The modifier inherits from BinaryEx, which is itself a modifier (Chapter 6, code listing 6.3). The AssignEx modifier modifies the BinaryExpr class. The AssignEx modifier overrides the computeAssign method added by the BinaryEx modifier, enabling the assignment of fields

The computeAssign method modified by the AssignEx modifier will call the write method of stoneobject to perform the assignment operation when a field is on the left side of the assignment operation. If not, it will call the original computeAssign method through super

When assigning values to fields, it must be noted that the left side of the assignment operation is not always a simple field name. For example, fields can be represented in the following ways

table.get().next.x = 3

The interpreter will first call the get method of the object indicated by the variable table, and then assign the field x contained in the object pointed to by the next field in the returned object to 3. Among them, only. X will evaluate the left value of the operator and assign a value, and table.get().next will still calculate the rightmost value in the usual way. The computeAssign method performs this calculation through the internal evalsubExpr method. The return value assigned to variable t is also the right value calculation result of table.get().next in the above example.

Listing 9.6 ClassEvaluator.java

package chap9;
import java.util.List;
import Stone.StoneException;
import Stone.ast.*;
import chap6.BasicEvaluator.ASTreeEx;
import chap6.BasicEvaluator;
import chap6.Environment;
import chap7.FuncEvaluator;
import chap7.FuncEvaluator.EnvEx;
import chap7.FuncEvaluator.PrimaryEx;
import chap7.NestedEnv;
import chap9.StoneObject.AccessException;
import javassist.gluonj.*;

@Require(FuncEvaluator.class)
@Reviser public class ClassEvaluator {
	@Reviser public static class ClassStmntEx extends ClassStmnt {
		public ClassStmntEx(List<ASTree> c) {
			super(c);
		}

		public Object eval(Environment env) {
			ClassInfo ci = new ClassInfo(this, env);
			((EnvEx) env).put(name(), ci);
			return name();
		}
	}

	@Reviser public static class ClassBodyEx extends ClassBody {
		public ClassBodyEx(List<ASTree> c) {
			super(c);
		}

		public Object eval(Environment env) {
			for (ASTree t : this)
				((ASTreeEx) t).eval(env);
			return null;
		}
		
		@Reviser public static class DotEx extends Dot {
			public DotEx(List<ASTree> c) {
				super(c);
			}
			
			public Object eval(Environment env,Object value) {
				String member = name();
				if (value instanceof ClassInfo) {
					if ("new".equals(member)) {
						ClassInfo ci = (ClassInfo)value;
						NestedEnv e = new NestedEnv(ci.environment);
						StoneObject so = new StoneObject(e);
						e.putNew("this", so);
						initObject(ci,e);
						return so;
					}
				} else if (value instanceof StoneObject) {
					try {
						return ((StoneObject)value).read(member);
					} catch (AccessException e) {}
				}
				throw new StoneException("bad member access: " + member,this);
			}
			
			protected void initObject(ClassInfo ci,Environment env) {
				if (ci.superClass() != null)
					initObject(ci.superClass(),env);
				((ClassBodyEx)ci.body()).eval(env);
			}
		}
		@Reviser public static class AssignEx extends BasicEvaluator.BinaryEx {
			public AssignEx(List<ASTree> c) {
				super(c);
			}
			
			protected Object computeAssign(Environment env,Object rvalue) {
				ASTree le = left();
				if (le instanceof PrimaryExpr) {
					PrimaryEx p = (PrimaryEx) le;
					if (p.hasPostfix(0) && p.postfix(0) instanceof Dot) {
						Object t = ((PrimaryEx)le).evalSubExpr(env, 1);
						if (t instanceof StoneObject)
							return setField((StoneObject)t,(Dot)p.postfix(0),rvalue);
					}
				}
				return super.computeAssign(env, rvalue);
			}
			
			protected Object setField(StoneObject obj,Dot expr,Object rvalue) {
				String name = expr.name();
				try {
					obj.write(name,rvalue);
					return rvalue;
				} catch (AccessException e) {
					throw new StoneException("bad member access " + location() + ": " + name);
				}
			}
		}
	}
}

Listing 9.7 ClassInfo.java

package chap9;
import Stone.StoneException;
import Stone.ast.ClassBody;
import Stone.ast.ClassStmnt;
import chap6.Environment;

public class ClassInfo {
	protected ClassStmnt definition;
	protected Environment environment;
	protected ClassInfo superClass;

	public ClassInfo(ClassStmnt cs, Environment env) {
		definition = cs;
		environment = env;
		Object obj = env.get(cs.superClass());
		if (obj == null)
			superClass = null;
		else if (obj instanceof ClassInfo)
			superClass = (ClassInfo) obj;
		else
			throw new StoneException("unkonw super class: " + cs.superClass(), cs);
	}
	
	public String name() {
		return definition.name();
	}
	
	public ClassInfo superClass() {
		return superClass;
	}
	
	public ClassBody body() {
		return definition.body();
	}
	
	public Environment environment() {
		return environment;
	}
	
	public String toString() {
		return "<class " + name() + ">";
	}
}

Listing 9.8 StoneObject.java

package chap9;
import chap6.Environment;
import chap7.FuncEvaluator.EnvEx;

public class StoneObject {
	public static class AccessException extends Exception {
	}

	protected Environment env;

	public StoneObject(Environment e) {
		env = e;
	}

	public String toString() {
		return "<object:" + hashCode() + ">";
	}

	public Object read(String member) throws AccessException {
		return getEnv(member).get(member);
	}

	public void write(String member, Object value) throws AccessException {
		((EnvEx) getEnv(member)).putNew(member, value);
	}

	protected Environment getEnv(String member) throws AccessException {
		Environment e = ((EnvEx) env).where(member);
		if (e != null && e == env)
			return e;
		else
			throw new AccessException();
	}
}

9.4 representing objects through closures

From the implementation point of view, how to design the internal structure of the StoneObject object is the most important. That is to say, how to express Stone objects through Java objects. In fact, there are many ways to implement it. We will use the property that the environment can save the field value to represent the object.

The StoneObject object mainly stores the field values contained in the object in the Stone language, which can be said to be the corresponding relation table between the field name and the field value. From this point of view, environment, as the corresponding relation table of variable name and variable value, is very similar to object.

If the object is regarded as an environment, it is easy to implement method calls and field access to the object itself (that is, the object referred to by this in the Java language). Method calls and field access can be implemented through this.x, where this. Which refers to itself can be omitted. Here is an example.

class Positon {
    x = y = 0
    def move(nx,ny) {
        x = ny;y = ny
    }
}

At first glance, x in the move method is a local variable, in fact, it is the omitted form of this.x, representing the x field. This kind of x implementation is more cumbersome. If the definition of move method is regarded as function definition, x and y are free variables (free variables refer to functions other than function parameters and local variables). The parameters nx and ny are constraint variables.

If a free variable such as x exists inside a method, it must point (bind) to a field defined outside the method. This is similar to the mechanism of closures. For example, the following function position will return a closure.

def position () {
    x = y = 0
    fun (nx,ny) {
        x = ny;y = ny
    }
}

At this point, the local variable X of the position function is assigned to the variable x (bound to x) in the returned closure. Comparing the two, we can find that both closures and methods bind the internal variable names to the external variables (fields).

When you create a new StoneObject object through. New, the interpreter will first create a new environment. The StoneObject object saves the environment and adds a key value pair consisting of the name this and itself to the environment.

The interpreter then uses the environment to execute the body part of the class definition enclosed by braces {}. As with the execution of function bodies, this can be done by simply calling the eval method of the abstract syntax tree representing the body. This corresponds to constructor calls in languages such as Java. After the body part is executed, the field name, method name and corresponding value in the class definition will be recorded by the environment.

During execution, if you need to assign a value to a variable that appears for the first time, the interpreter will add a name value pair consisting of the name and value of the variable like the environment.

9.5 running programs containing classes

At this point, the Stone language can support the use of classes and objects. As before, the interpreter main program and the corresponding startup program will be introduced finally. See code listing 9.9 and code listing 9.10

Code listing 9.9 ClassInterperter.java

package chap9;
import Stone.ClassParser;
import Stone.ParseException;
import chap6.BasicInterpreter;
import chap7.NestedEnv;
import chap8.Natives;

public class ClassInterpreter extends BasicInterpreter {
	public static void main(String[] args) throws ParseException {
		run(new ClassParser(), new Natives().environment(new NestedEnv()));
	}
}

Code listing 9.10 ClassRunner.java

package chap9;
import chap7.ClosureEvaluator;
import chap8.NativeEvaluator;
import javassist.gluonj.util.Loader;

public class ClassRunner {
	public static void main(String[] args) throws Throwable {
		Loader.run(ClassInterpreter.class, args, ClassEvaluator.class,NativeEvaluator.class,ClosureEvaluator.class);
	}
}

Posted by aspguy on Tue, 21 Jan 2020 07:18:38 -0800