package scriptingLanguage.variables;
import java.util.ArrayList;
import lipstone.joshua.customStructures.tuples.Pair;
import scriptingLanguage.Interpreter;
import scriptingLanguage.Token;
import scriptingLanguage.Type;
import scriptingLanguage.errors.InterpreterException;
import scriptingLanguage.errors.NullAccessException;
import scriptingLanguage.errors.UnendedLineException;
import scriptingLanguage.errors.UnexpectedTokenException;
import scriptingLanguage.frames.AbstractFrame;
import scriptingLanguage.frames.Frame;
import scriptingLanguage.frames.ObjectFrame;
public class InterpreterClass extends AbstractClass<Token> {
private final AbstractClass<?> superclass;
private final Frame[] frames;
private final ArrayList<Pair<Access, Token>> objects, methods, variables;
/**
* @param name
* the name of the class
* @param source
* the source for the object described by this class
* @param frame
* the frame in which this was created
* @throws ArrayIndexOutOfBoundsException
* @throws IllegalArgumentException
* @throws InterpreterException
*/
public InterpreterClass(String name, Token source, AbstractFrame frame) throws ArrayIndexOutOfBoundsException, IllegalArgumentException, InterpreterException {
this(name, source, frame, new Type<InterpreterClass>(name));
}
public InterpreterClass(String name, Token source, AbstractFrame frame, Type<?> tokenType) throws ArrayIndexOutOfBoundsException, IllegalArgumentException, InterpreterException {
super(name, source, tokenType);
Interpreter interpreter = frame.getInterpreter();
frames = new Frame[4];
frames[0] = new Frame(frame, interpreter);
frames[1] = new Frame(frames[0], interpreter);
frames[2] = new Frame(frames[1], interpreter);
frames[3] = new Frame(frames[2], interpreter);
objects = new ArrayList<>();
methods = new ArrayList<>();
variables = new ArrayList<>();
if (source.getCarType().equals(Interpreter.extendsType)) {
if (!source.getCarType().equals(Interpreter.identifierType))
throw new UnexpectedTokenException("Expected an identifier, got a " + source.getCarType().getName() + ". " + source.toString());
String extend = "";
while (!(source = source.getNextToken()).getCarType().equals(Interpreter.curlyBracesType))
extend = extend + source.getCar();
superclass = frame.getType(extend);
}
else
superclass = Interpreter.ObjectClass;
if (!source.getCarType().equals(Interpreter.curlyBracesType))
throw new UnendedLineException("An object declaration must have a body.");
source = (Token) source.getCar();
ArrayList<Token> lines = Interpreter.splitLines(source);
ArrayList<Pair<Access, Token>> staticObjects = new ArrayList<>(), staticMethods = new ArrayList<>(), staticVariables = new ArrayList<>();
while (lines.size() > 0) {
Token line = lines.remove(0);
Access access = Access.DEFAULT;
boolean use = false;
while (!line.isNull()) {
if (line.getCarType().equals(interpreter.importType)) {
use = true;
access = Access.PUBLIC;
break;
}
else if (line.getCarType().equals(Interpreter.scopeType)) {
access = Access.getAccess((String) line.getCar());
}
else if (line.getCarType().equals(Interpreter.staticType))
use = true;
else
break;
line = line.getNextToken();
}
//object [name] (extends <type>)? {body}
if (line.getCarType().equals(Interpreter.classType)) {
if (use) {
staticObjects.add(new Pair<>(access, line));
String nme;
frames[access.intValue()].addType((nme = (String) (line = line.getNextToken()).getCar()),
new InterpreterClass(nme, (line = line.getNextToken()).getNextToken(), frames[3], new Type<InterpreterClass>(nme)));
}
else
objects.add(new Pair<>(access, line));
}
else {
//Constructor
if (!use && line.getCar().equals(getName()) && line.getNextToken().getCarType().equals(Interpreter.parenthesesType))
methods.add(new Pair<>(access, line));
//Function
else if (line.getCarType().equals(Interpreter.identifierType) && line.getNextToken().getCarType().equals(Interpreter.identifierType) &&
line.getNextToken().getNextToken().getCarType().equals(Interpreter.parenthesesType))
if (use)
staticMethods.add(new Pair<>(access, line));
else
methods.add(new Pair<>(access, line));
//Variables
else if (use)
staticVariables.add(new Pair<>(access, line));
else
variables.add(new Pair<>(access, line));
}
}
construct(staticObjects, staticMethods, staticVariables, frames);
frames[0].writeVariable("class", new Token(this, getTokenType()), true); //Adds .class
}
//For cloning
private InterpreterClass(String name, Token source, Type<?> tokenType, AbstractClass<?> superclass, Frame[] frames,
ArrayList<Pair<Access, Token>> objects, ArrayList<Pair<Access, Token>> methods, ArrayList<Pair<Access, Token>> variables) {
super(name, source, tokenType);
this.superclass = superclass;
this.frames = frames;
this.objects = objects;
this.methods = methods;
this.variables = variables;
}
protected void construct(ArrayList<Pair<Access, Token>> objects, ArrayList<Pair<Access, Token>> methods,
ArrayList<Pair<Access, Token>> variables, Frame[] frames) throws InterpreterException {
for (Pair<Access, Token> p : objects) {
Token line = p.getY();
String nme;
frames[p.getX().intValue()].addType((nme = (String) (line = line.getNextToken()).getCar()),
new InterpreterClass(nme, (line = line.getNextToken()).getNextToken(), frames[3], new Type<InterpreterClass>(nme)));;
}
for (Pair<Access, Token> p : methods) {
Pair<String, InterpreterMethod> method = InterpreterMethod.initMethod(this, p.getY(), frames[3]);
if (method.getX().indexOf(':') == 0) //This is a constructor
frames[p.getX().intValue()].writeVariable(getName() + method.getX(), new Token(InterpreterConstructor.init(method.getY()),
method.getY().getType().getTokenType()), true);
frames[p.getX().intValue()].writeVariable(method.getX(), new Token(method.getY(), method.getY().getType().getTokenType()), true);
}
for (Pair<Access, Token> p : variables)
Interpreter.evalVariableDeclaration(this, p.getY(), frames[3], frames[p.getX().intValue()]);
}
@Override
public Token eval(AbstractClass<?> caller, Token parameters, AbstractFrame frame) throws InterpreterException, IllegalArgumentException {
Access access = Access.getAccess(caller, getType());
if (parameters.getCarType().equals(Interpreter.callType)) { //This is a static method call
if (!(parameters = parameters.getNextToken()).getNextToken().getCarType().equals(Interpreter.parenthesesType)) {
if (!frames[access.intValue()].hasVariable((String) parameters.getCar()))
return superclass.eval(caller, parameters, frame);
return frames[access.intValue()].readVariable((String) parameters.getCar());
}
String name = (String) parameters.getCar();
ArrayList<Token> arguments = Interpreter.evalFunctionArguments(caller, (Token) parameters.getNextToken().getCar(), frame);
name = Interpreter.evalFunctionName(name, arguments);
if (!frames[access.intValue()].hasVariable(name))
return superclass.eval(caller, parameters, frame);
return ((AbstractMethod<?>) frames[access.intValue()].readVariable(name).getCar()).eval(arguments);
}
ArrayList<Pair<Access, Token>> objects = new ArrayList<>(), methods = new ArrayList<>(), variables = new ArrayList<>();
for (Pair<Access, Token> pair : this.objects)
objects.add(new Pair<>(pair.getX(), pair.getY().clone()));
for (Pair<Access, Token> pair : this.methods)
methods.add(new Pair<>(pair.getX(), pair.getY().clone()));
for (Pair<Access, Token> pair : this.variables)
variables.add(new Pair<>(pair.getX(), pair.getY().clone()));
ObjectFrame[] frames = new ObjectFrame[4];
frames[0] = new ObjectFrame(this.frames[0].getPrevious(), this.frames[0].getInterpreter(), this.frames[0]);
frames[1] = new ObjectFrame(frames[0], this.frames[1].getInterpreter(), this.frames[1]);
frames[2] = new ObjectFrame(frames[1], this.frames[2].getInterpreter(), this.frames[2]);
frames[3] = new ObjectFrame(frames[2], this.frames[3].getInterpreter(), this.frames[3]);
construct(objects, methods, variables, frames);
String name = getName().substring(getName().lastIndexOf('.') + 1); //This works because it returns -1 if there is no match, and -1 + 1 = 0 = complete String
ArrayList<Token> arguments = Interpreter.evalFunctionArguments(caller, parameters, frame);
name = Interpreter.evalFunctionName(name, arguments);
InterpreterConstructor constructor = (InterpreterConstructor) frames[access.intValue()].readVariable(name).getCar();
AbstractObject<?> superObject = constructor.initSuperclass(caller, superclass, parameters, frame);
Token result = new Token(new InterpreterObject(this, superObject, frames), getTokenType());
//Initialize keywords
frames[3].writeVariable("this", result, true);
frames[3].writeVariable("super", new Token(superObject, superclass.getTokenType()), true);
constructor.eval(arguments);
return result;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof InterpreterClass))
return false;
return getType().equals(((InterpreterClass) o).getType());
}
@Override
public int extend(AbstractClass<?> type) throws NullAccessException {
if (type.equals(this))
return 0;
int result = superclass.extend(type);
return result == -1 ? result : result + 1;
}
@Override
public Variable<Token> clone() {
return new InterpreterClass(getName(), getSource(), getTokenType(), superclass, frames, objects, methods, variables);
}
@Override
public Token makePlaceholder() {
return new Token(new InterpreterObject(this, null, null), getTokenType());
}
}