prompto.expression.MemberSelector Maven / Gradle / Ivy
The newest version!
package prompto.expression;
import java.lang.reflect.Type;
import prompto.compiler.ClassConstant;
import prompto.compiler.CompilerUtils;
import prompto.compiler.FieldConstant;
import prompto.compiler.Flags;
import prompto.compiler.IOperand;
import prompto.compiler.IVerifierEntry.VerifierType;
import prompto.compiler.InterfaceConstant;
import prompto.compiler.MethodConstant;
import prompto.compiler.MethodInfo;
import prompto.compiler.Opcode;
import prompto.compiler.ResultInfo;
import prompto.compiler.StackLocal;
import prompto.compiler.StackState;
import prompto.compiler.StringConstant;
import prompto.declaration.CategoryDeclaration;
import prompto.declaration.ConcreteMethodDeclaration;
import prompto.declaration.IDeclaration;
import prompto.declaration.IMethodDeclaration;
import prompto.declaration.NativeCategoryDeclaration;
import prompto.declaration.NativeGetterMethodDeclaration;
import prompto.error.NullReferenceError;
import prompto.error.PromptoError;
import prompto.error.SyntaxError;
import prompto.grammar.Identifier;
import prompto.intrinsic.PromptoAny;
import prompto.intrinsic.PromptoConverter;
import prompto.intrinsic.PromptoDict;
import prompto.intrinsic.PromptoDocument;
import prompto.intrinsic.PromptoRoot;
import prompto.parser.Dialect;
import prompto.runtime.Context;
import prompto.runtime.Context.ClosureContext;
import prompto.runtime.Context.InstanceContext;
import prompto.runtime.Context.MethodDeclarationMap;
import prompto.statement.UnresolvedCall;
import prompto.transpiler.Transpiler;
import prompto.type.CategoryType;
import prompto.type.IType;
import prompto.type.MethodType;
import prompto.type.NullType;
import prompto.type.VoidType;
import prompto.utils.CodeWriter;
import prompto.value.ClosureValue;
import prompto.value.IInstance;
import prompto.value.IValue;
import prompto.value.NullValue;
public class MemberSelector extends SelectorBase {
Identifier id;
public MemberSelector(Identifier id) {
this.id = id;
}
public MemberSelector(IExpression parent, Identifier id) {
super(parent);
this.id = id;
}
public Identifier getId() {
return id;
}
public String getName() {
return id.toString();
}
@Override
public void toDialect(CodeWriter writer) {
if(writer.getDialect()==Dialect.E)
toEDialect(writer);
else
toOMDialect(writer);
}
void toEDialect(CodeWriter writer) {
try {
IType type = check(writer.getContext());
if(type instanceof MethodType)
writer.append("Method: ");
} catch(SyntaxError e) {
// gracefully skip exceptions
}
parentAndMemberToDialect(writer);
}
void toOMDialect(CodeWriter writer) {
parentAndMemberToDialect(writer);
}
protected void parentAndMemberToDialect(CodeWriter writer) {
try {
resolveParent(writer.getContext());
} catch(SyntaxError e) {
// ignore
}
if(writer.getDialect()==Dialect.E)
parentToEDialect(writer);
else
parentToOMDialect(writer);
writer.append(".");
writer.append(id);
}
protected void parentToEDialect(CodeWriter writer) {
if(parent instanceof UnresolvedCall) {
writer.append('(');
parent.toDialect(writer);
writer.append(')');
} else
parent.parentToDialect(writer);
}
protected void parentToOMDialect(CodeWriter writer) {
// if parent is: (method()), then supposedly this emanates from a translation from E dialect
if(parent instanceof ParenthesisExpression && ((ParenthesisExpression)parent).getExpression() instanceof UnresolvedCall)
((ParenthesisExpression)parent).getExpression().toDialect(writer);
else
parent.toDialect(writer);
}
@Override
public String toString() {
return parent.toString() + "." + id;
}
@Override
public IType check(Context context) {
if("this$0".equals(id.toString())) {
InstanceContext instance = context.getClosestInstanceContext();
return instance.getInstanceType();
} else {
IType parentType = checkParent(context);
if(parentType != null && parentType != NullType.instance())
return parentType.checkMember(context, id);
else
// parent would have reported the root problem
return VoidType.instance();
}
}
@Override
public IValue interpret(Context context) throws PromptoError {
// resolve parent to keep clarity
IExpression parent = resolveParent(context);
IValue instance = parent.interpret(context);
if (instance == null || instance == NullValue.instance())
throw new NullReferenceError();
else
return instance.getMember(context, id, false);
}
@Override
public IValue interpretReference(Context context) {
// resolve parent to keep clarity
IExpression parent = resolveParent(context);
IValue instance = parent.interpret(context);
if (instance == null || instance == NullValue.instance())
throw new NullReferenceError();
else if(instance instanceof IInstance) {
CategoryDeclaration category = ((IInstance)instance).getDeclaration();
MethodDeclarationMap methods = category.getMemberMethods(context, id, false);
IMethodDeclaration method = methods.getFirst(); // TODO check prototype
return new ClosureValue(context.newInstanceContext((IInstance)instance, true), new MethodType(method));
} else
throw new SyntaxError("Should never get here!");
}
@Override
public ResultInfo compile(Context context, MethodInfo method, Flags flags, boolean asParent) {
// resolve parent to keep clarity
IExpression parent = resolveParent(context);
ResultInfo info = parent.compileParent(context, method, flags);
if(info.isStatic())
return compileStaticMember(context, method, flags, parent);
else
return compileInstanceMember(context, method, flags, info, asParent);
}
@Override
public ResultInfo compileReference(Context context, MethodInfo method, Flags flags) {
IExpression parent = this.resolveParent(context);
IType parentType = parent.check(context);
if(parentType instanceof CategoryType) {
CategoryDeclaration category = (CategoryDeclaration)((CategoryType)parentType).getDeclaration(context);
MethodDeclarationMap methods = category.getMemberMethods(context, id, false);
ConcreteMethodDeclaration proto = (ConcreteMethodDeclaration)methods.getFirst(); // TODO check prototype
return proto.compileMethodInstance(context, method, flags, parent::compileParent);
} else
throw new SyntaxError("Should never get here!");
}
private ResultInfo compileStaticMember(Context context, MethodInfo method, Flags flags, IExpression parent) {
IType type = parent.check(context);
return type.compileGetStaticMember(context, method, flags, id);
}
private ResultInfo compileInstanceMember(Context context, MethodInfo method, Flags flags, ResultInfo info, boolean asParent) {
IType type = check(context);
Type resultType = type.toJavaType(context);
if(shouldCompileCharacterCodePoint(info))
return compileCharacterCodePoint(method, flags);
else if (shouldCompileStringLength(info))
return compileStringLength(method, flags);
// special case for o.json which translates to toJson
else if(shouldCompileToConvertToJson(context, parent))
return compileConvertToJson(method, flags);
else if(shouldCompileGetMember(info))
return compileGetMember(context, method, flags, info, resultType);
else if(shouldCompileToGetOrCreate(info.getType()))
return compileGetOrCreate(context, method, flags, info, resultType, asParent);
// special case for o.text which translates to toString
else if(shouldCompileToObjectToString(context, parent))
return compileObjectToString(method, flags);
else {
String getterName = CompilerUtils.getterName(getName());
if("getCategory".contentEquals(getterName))
compileGetCategory(context, method, flags);
else if(isCompilingGetter(context, method, info, getterName))
compileGetField(context, method, flags, info, resultType);
else if(context instanceof ClosureContext)
compileGetField(context, method, flags, info, resultType);
else if(PromptoDict.Entry.class==info.getType()) // TODO manage all generics
compileGenericGetter(context, method, flags, getterName, info, resultType);
else if(info.isNativeCategory())
compileNativeGetter(context, method, flags, getterName, info, resultType);
else if(info.isInterface())
compileInterfaceGetter(context, method, flags, getterName, info, resultType);
else
compileBeanGetter(context, method, flags, getterName, info, resultType);
return new ResultInfo(resultType);
}
}
private boolean shouldCompileToConvertToJson(Context context, IExpression parent) {
return "json".equals(getName());
}
private boolean shouldCompileGetMember(ResultInfo info) {
if("text".equals(getName()))
return PromptoAny.class==info.getType() || Object.class==info.getType();
else if(info.isNativeCategory())
return false;
else
return PromptoAny.class==info.getType();
}
private boolean shouldCompileStringLength(ResultInfo info) {
// special case for String.length() to avoid wrapping String.class for just one member
return String.class==info.getType() && "count".equals(getName());
}
private boolean shouldCompileCharacterCodePoint(ResultInfo info) {
// special case for char.codePoint() to avoid wrapping char.class for just one member
return Character.class==info.getType() && "codePoint".equals(getName());
}
private boolean shouldCompileToGetOrCreate(Type type) {
if(PromptoDocument.class!=type && Object.class!=type)
return false;
else switch(getName()) {
case "count":
case "keys":
case "values":
return false;
default:
return true;
}
}
private boolean shouldCompileToObjectToString(Context context, IExpression parent) {
if(!"text".equals(getName()))
return false;
IType parentType = parent.check(context);
if(parentType instanceof CategoryType) {
IDeclaration decl = ((CategoryType)parentType).getDeclaration(context);
if(decl instanceof CategoryDeclaration) {
if(((CategoryDeclaration)decl).hasAttribute(context, new Identifier("text")))
return false;
}
}
return true;
}
private void compileNativeGetter(Context context, MethodInfo method, Flags flags, String getterName, ResultInfo info, Type resultType) {
NativeGetterMethodDeclaration getter = getNativeGetter(context);
if(getter!=null) {
StackState state = method.captureStackState();
// can't use 'this' since it could refer to another object than the native parent
StackLocal local = method.registerLocal("$this$", VerifierType.ITEM_Object, new ClassConstant(info.getType()));
CompilerUtils.compileASTORE(method, local);
context = context.newInstanceContext(getter.getMemberOf().getType(context), false).newChildContext(); // mimic method call
getter.compile(context, method, new Flags());
method.unregisterLocal(local);
method.restoreStackLocals(state);
state = method.captureStackState();
method.placeLabel(state);
return;
} else
compileBeanGetter(context, method, flags, getterName, info, resultType);
}
private NativeGetterMethodDeclaration getNativeGetter(Context context) {
IType type = parent.check(context);
IDeclaration declaration = type instanceof CategoryType ? ((CategoryType)type).getDeclaration(context) : null;
if(declaration instanceof NativeCategoryDeclaration)
return (NativeGetterMethodDeclaration)((NativeCategoryDeclaration)declaration).findGetter(context, id);
else
return null;
}
private void compileGetCategory(Context context, MethodInfo method, Flags flags) {
IOperand oper = new MethodConstant(PromptoRoot.class, "getCategory", CategoryDeclaration.class);
method.addInstruction(Opcode.INVOKEVIRTUAL, oper);
}
private void compileBeanGetter(Context context, MethodInfo method, Flags flags, String getterName, ResultInfo info, Type resultType) {
IOperand oper = new MethodConstant(info.getType(), getterName, resultType);
method.addInstruction(Opcode.INVOKEVIRTUAL, oper);
}
private void compileInterfaceGetter(Context context, MethodInfo method, Flags flags, String getterName, ResultInfo info, Type resultType) {
IOperand oper = new InterfaceConstant(info.getType(), getterName, resultType);
method.addInstruction(Opcode.INVOKEINTERFACE, oper);
}
private void compileGenericGetter(Context context, MethodInfo method, Flags flags, String getterName, ResultInfo info, Type resultType) {
IOperand oper = new MethodConstant(info.getType(), getterName, Object.class);
method.addInstruction(Opcode.INVOKEVIRTUAL, oper);
method.addInstruction(Opcode.CHECKCAST, new ClassConstant(resultType));
}
private ResultInfo compileGetOrCreate(Context context, MethodInfo method, Flags flags, ResultInfo info, Type resultType, boolean asParent) {
if(info.getType()!=PromptoDocument.class)
method.addInstruction(Opcode.CHECKCAST, new ClassConstant(PromptoDocument.class));
IOperand oper = new StringConstant(getName());
method.addInstruction(Opcode.LDC_W, oper);
if(asParent) {
oper = new ClassConstant(PromptoDocument.class);
method.addInstruction(Opcode.LDC_W, oper);
} else
method.addInstruction(Opcode.ACONST_NULL);
oper = new MethodConstant(PromptoDocument.class, "getOrCreate", Object.class,
Class.class, Object.class);
method.addInstruction(Opcode.INVOKEVIRTUAL, oper);
if(resultType!=Object.class)
method.addInstruction(Opcode.CHECKCAST, new ClassConstant(resultType));
return new ResultInfo(resultType);
}
private ResultInfo compileGetMember(Context context, MethodInfo method, Flags flags, ResultInfo info, Type resultType) {
IOperand oper = new StringConstant(getName());
method.addInstruction(Opcode.LDC_W, oper);
oper = new MethodConstant(PromptoAny.class, "getMember", Object.class,
Object.class, Object.class);
method.addInstruction(Opcode.INVOKESTATIC, oper);
if(resultType!=Object.class)
method.addInstruction(Opcode.CHECKCAST, new ClassConstant(resultType));
return new ResultInfo(resultType);
}
private void compileGetField(Context context, MethodInfo method, Flags flags, ResultInfo info, Type resultType) {
Type classType = CompilerUtils.categoryConcreteTypeFrom(info.getType().getTypeName());
if("this$0".equals(id.toString()))
resultType = CompilerUtils.categoryConcreteTypeFrom(resultType.getTypeName());
FieldConstant f = new FieldConstant(classType, id.toString(), resultType);
method.addInstruction(Opcode.GETFIELD, f);
}
private boolean isCompilingGetter(Context context, MethodInfo method, ResultInfo parent, String getterName) {
return this.parent instanceof ThisExpression && getterName.equals(method.getName().getValue());
}
private ResultInfo compileCharacterCodePoint(MethodInfo method, Flags flags) {
IOperand oper = new MethodConstant(Character.class, "charValue", char.class);
method.addInstruction(Opcode.INVOKEVIRTUAL, oper);
if(flags.toPrimitive())
return CompilerUtils.intTolong(method);
else
return CompilerUtils.intToLong(method);
}
private ResultInfo compileObjectToString(MethodInfo method, Flags flags) {
IOperand oper = new MethodConstant(Object.class, "toString", String.class);
method.addInstruction(Opcode.INVOKEVIRTUAL, oper);
return new ResultInfo(String.class);
}
private ResultInfo compileConvertToJson(MethodInfo method, Flags flags) {
IOperand oper = new MethodConstant(PromptoConverter.class, "toJson", Object.class, String.class);
method.addInstruction(Opcode.INVOKESTATIC, oper);
return new ResultInfo(String.class);
}
private ResultInfo compileStringLength(MethodInfo method, Flags flags) {
IOperand oper = new MethodConstant(String.class, "length", int.class);
method.addInstruction(Opcode.INVOKEVIRTUAL, oper);
if(flags.toPrimitive())
return CompilerUtils.intTolong(method);
else
return CompilerUtils.intToLong(method);
}
@Override
public void declare(Transpiler transpiler) {
IExpression parent = this.resolveParent(transpiler.getContext());
if(parent==null)
transpiler.getContext().getProblemListener().reportError(this, "Cannot transpile " + this.toString());
else {
parent.declareParent(transpiler);
IType parentType = this.checkParent(transpiler.getContext());
if(parentType==null)
transpiler.getContext().getProblemListener().reportError(this, "Cannot transpile " + this.toString());
else
parentType.declareMember(transpiler, this.getId());
}
}
@Override
public boolean transpile(Transpiler transpiler) {
IExpression parent = this.resolveParent(transpiler.getContext());
if(parent==null)
transpiler.getContext().getProblemListener().reportError(this, "Cannot transpile " + this.toString());
else {
parent.transpileParent(transpiler);
transpiler.append(".");
IType parentType = this.checkParent(transpiler.getContext());
if(parentType==null)
transpiler.getContext().getProblemListener().reportError(this, "Cannot transpile " + this.toString());
else
parentType.transpileMember(transpiler, this.getId());
}
return false;
}
@Override
public boolean transpileMethodReference(Transpiler transpiler, MethodType method) {
IExpression parent = this.resolveParent(transpiler.getContext());
parent.transpileParent(transpiler);
transpiler.append(".")
.append(method.getMethod().getTranspiledName(transpiler.getContext(), this.getName()))
.append(".bind(");
parent.transpileParent(transpiler);
transpiler.append(")");
return false;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy