
lt.compiler.SemanticProcessor Maven / Gradle / Ivy
Show all versions of latte-compiler Show documentation
/*
* The MIT License (MIT)
*
* Copyright (c) 2016 KuiGang Wang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package lt.compiler;
import lt.compiler.semantic.*;
import lt.compiler.semantic.builtin.*;
import lt.compiler.semantic.helper.ASTGHolder;
import lt.compiler.semantic.helper.HalfAppliedTypes;
import lt.compiler.syntactic.*;
import lt.compiler.syntactic.def.*;
import lt.compiler.syntactic.literal.BoolLiteral;
import lt.compiler.syntactic.literal.NumberLiteral;
import lt.compiler.syntactic.literal.StringLiteral;
import lt.compiler.syntactic.operation.OneVariableOperation;
import lt.compiler.syntactic.operation.TwoVariableOperation;
import lt.compiler.syntactic.operation.UnaryOneVariableOperation;
import lt.compiler.syntactic.pre.Import;
import lt.compiler.syntactic.pre.Modifier;
import lt.compiler.syntactic.pre.PackageDeclare;
import lt.compiler.util.BindList;
import lt.compiler.util.Consts;
import lt.dependencies.asm.MethodVisitor;
import lt.generator.SourceGenerator;
import lt.lang.GenericTemplate;
import lt.lang.Unit;
import lt.lang.function.Function1;
import lt.lang.function.Function2;
import lt.runtime.*;
import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.*;
import java.net.URL;
import java.util.*;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* semantic processor
*/
public class SemanticProcessor {
public static final int PARSING_CLASS = 0;
public static final int PARSING_INTERFACE = 1;
public static final String DYNAMIC_CLASS_NAME = Dynamic.class.getName();
public static final int INDEX_invoke_targetClass = 0;
public static final int INDEX_invoke_o = 1;
public static final int INDEX_invoke_isStatic = 2;
public static final int INDEX_invoke_functionalObject = 3;
public static final int INDEX_invoke_invoker = 4;
public static final int INDEX_invoke_method = 5;
public static final int INDEX_invoke_primitives = 6;
public static final int INDEX_invoke_args = 7;
public static final int INDEX_invoke_canInvokeImport = 8;
/**
* Map<FileName, List of statements>
*/
public final Map> mapOfStatements;
/**
* maps full name to type
* one type should only exist once in a Processor
*/
public Map types = new HashMap();
/**
* full name to {@link ClassDef} from {@link Parser}
*/
public Map> originalClasses = new HashMap>();
/**
* full name to {@link InterfaceDef} from {@link Parser}
*/
public Map> originalInterfaces = new HashMap>();
/**
* full name to {@link FunDef} from {@link Parser}
*/
public Map> originalFunctions = new HashMap>();
/**
* full name to {@link ObjectDef} from {@link Parser}
*/
public Map> originalObjects = new HashMap>();
/**
* full name to {@link AnnotationDef} from {@link Parser}
*/
public Map> originalAnnotations = new HashMap>();
/**
* {@link SMethodDef} to it's containing statements
*/
public Map> methodToStatements = new HashMap>();
/**
* file name to Import info
*/
public Map> fileNameToImport = new HashMap>();
/**
* file name to package name
*/
public Map fileNameToPackageName = new HashMap();
/**
* a set of types that should be return value of {@link #parse()} method.
* these types are to be compiled into byte codes
*/
public final Set typeDefSet = new HashSet();
/**
* invokable => (the-invokable-to-invoke => the current default parameter).
*/
public Map> defaultParamInvokable = new HashMap>();
/**
* retrieve existing classes from this class loader
*/
public final ClassLoader classLoader;
/**
* error manager
*/
public final ErrorManager err;
/**
* access which represents a type can be converted into instantiation
*/
public boolean enableTypeAccess = true;
/**
* source files
*/
private static List sourceClasses = new LinkedList();
private boolean alreadyWarnJar = false;
/**
* initialize the Processor
*
* @param mapOfStatements a map of fileName to statements
* @param classLoader retrieve loaded classes from this class loader
* @param err error manager. the fast fail would be set to `true`
*/
public SemanticProcessor(Map> mapOfStatements, ClassLoader classLoader, ErrorManager err) {
this.mapOfStatements = mapOfStatements;
this.classLoader = classLoader;
this.err = err;
err.setFastFail(true);
// initiate types map
// primitive and void
types.put("int", IntTypeDef.get());
types.put("long", LongTypeDef.get());
types.put("double", DoubleTypeDef.get());
types.put("float", FloatTypeDef.get());
types.put("boolean", BoolTypeDef.get());
types.put("bool", BoolTypeDef.get());
types.put("char", CharTypeDef.get());
types.put("short", ShortTypeDef.get());
types.put("byte", ByteTypeDef.get());
}
public static String byte2hex(byte[] b) {
StringBuilder sb = new StringBuilder();
String tmp;
for (byte aB : b) {
tmp = Integer.toHexString(aB & 0XFF);
if (tmp.length() == 1) {
sb.append("0").append(tmp);
} else {
sb.append(tmp);
}
}
return sb.toString();
}
@SuppressWarnings("unused")
public static byte[] hex2byte(String str) {
str = str.trim();
int len = str.length();
if (len == 0 || len % 2 == 1) {
return null;
}
byte[] b = new byte[len / 2];
for (int i = 0; i < str.length(); i += 2) {
b[i / 2] = (byte) Integer.decode("0X" + str.substring(i, i + 2)).intValue();
}
return b;
}
/**
* get the map of defined types.
*
* @return a map of name => type
*/
public Map getTypes() {
return types;
}
/**
* parse the input AST into STypeDef objects.
* the parsing process are divided into 4 steps.
*
* - recording : scan all classes and record them
* - signatures : parse parents/superInterfaces, members, annotations, but don't parse statements or annotation values
* - validate :
* check circular inheritance and override,
* check overload with super classes/interfaces and check whether the class overrides all methods from super interfaces/super abstract classes,
* check @Override @FunctionalInterface @FunctionalAbstractClass,
* check whether the class is a `data class` and do some modifications
* - parse : parse annotation values and statements
*
*
* @return parsed types, including all inside members and statements
* @throws SyntaxException compile error
*/
public Set parse() throws SyntaxException {
Map> fileNameToClassDef = new HashMap>();
Map> fileNameToInterfaceDef = new HashMap>();
Map> fileNameToFunctions = new HashMap>();
Map> fileNameToObjectDef = new HashMap>();
Map> fileNameToAnnotationDef = new HashMap>();
// record types and packages
for (String fileName : mapOfStatements.keySet()) {
List statements = mapOfStatements.get(fileName);
Iterator statementIterator = statements.iterator();
// import
List imports = new ArrayList();
// class definition
List classDefs = new ArrayList();
// interface definition
List interfaceDefs = new ArrayList();
// fun definition
List funDefs = new ArrayList();
// object definition
List objectDefs = new ArrayList();
// annotation definition
List annotationDefs = new ArrayList();
// put into map
fileNameToImport.put(fileName, imports);
fileNameToClassDef.put(fileName, classDefs);
fileNameToInterfaceDef.put(fileName, interfaceDefs);
fileNameToFunctions.put(fileName, funDefs);
fileNameToObjectDef.put(fileName, objectDefs);
fileNameToAnnotationDef.put(fileName, annotationDefs);
// package
String pkg; // if no package, then it's an empty string, otherwise, it's 'packageName.' with dot at the end
if (statementIterator.hasNext()) {
Statement statement = statementIterator.next();
if (statement instanceof PackageDeclare) {
PackageDeclare p = (PackageDeclare) statement;
pkg = p.pkg.pkg.replace("::", ".") + ".";
} else {
pkg = "";
select_import_class_interface_fun_object(
statement, imports, classDefs, interfaceDefs, funDefs, objectDefs, annotationDefs);
}
while (statementIterator.hasNext()) {
Statement stmt = statementIterator.next();
select_import_class_interface_fun_object(
stmt, imports, classDefs, interfaceDefs, funDefs, objectDefs, annotationDefs);
}
} else continue;
// add package into import list at index 0
imports.add(0, new Import(new AST.PackageRef(
pkg.endsWith(".")
? pkg.substring(0, pkg.length() - 1).replace(".", "::")
: pkg
, LineCol.SYNTHETIC), null, true, false, LineCol.SYNTHETIC));
// add java.lang into import list
// java::lang::_
// lt::lang::_
// lt::util::Utils._
imports.add(new Import(new AST.PackageRef("lt::lang", LineCol.SYNTHETIC), null, true, false, LineCol.SYNTHETIC));
imports.add(new Import(new AST.PackageRef("java::lang", LineCol.SYNTHETIC), null, true, false, LineCol.SYNTHETIC));
imports.add(new Import(null, new AST.Access(new AST.PackageRef("lt::util", LineCol.SYNTHETIC), "Utils", LineCol.SYNTHETIC), true, false, LineCol.SYNTHETIC));
// import implicit built in casts
imports.add(new Import(null, new AST.Access(new AST.PackageRef("lt::lang::implicit", LineCol.SYNTHETIC), "PrimitivesImplicit", LineCol.SYNTHETIC), false, true, LineCol.SYNTHETIC));
imports.add(new Import(null, new AST.Access(new AST.PackageRef("lt::lang::implicit", LineCol.SYNTHETIC), "StringImplicit", LineCol.SYNTHETIC), false, true, LineCol.SYNTHETIC));
imports.add(new Import(null, new AST.Access(new AST.PackageRef("lt::lang::implicit", LineCol.SYNTHETIC), "CollectionImplicit", LineCol.SYNTHETIC), false, true, LineCol.SYNTHETIC));
imports.add(new Import(null, new AST.Access(new AST.PackageRef("lt::lang::implicit", LineCol.SYNTHETIC), "ObjectImplicit", LineCol.SYNTHETIC), false, true, LineCol.SYNTHETIC));
fileNameToPackageName.put(fileName, pkg);
}
for (String fileName : mapOfStatements.keySet()) {
// import
List imports = fileNameToImport.get(fileName);
// class definition
List classDefs = fileNameToClassDef.get(fileName);
// interface definition
List interfaceDefs = fileNameToInterfaceDef.get(fileName);
// fun definition
List funDefs = fileNameToFunctions.get(fileName);
// package name
String pkg = fileNameToPackageName.get(fileName);
// object definition
List objectDefs = fileNameToObjectDef.get(fileName);
// annotation definition
List annotationDefs = fileNameToAnnotationDef.get(fileName);
// check importing same simple name
Set importSimpleNames = new HashSet();
for (Import i : imports) {
if (i.pkg == null && !i.importAll) {
// class name are the same
if (importSimpleNames.contains(i.access.name)) {
err.SyntaxException("duplicate imports", i.line_col());
return null;
}
importSimpleNames.add(i.access.name);
}
}
importSimpleNames.clear(); // release
// ======= step 1 =======
// record all classes and interfaces to compile
// classes and interfaces should be filled with
// package|fullName|modifiers
// in this step
for (ClassDef c : classDefs) {
recordClass(c, pkg, Collections.emptyList());
}
for (InterfaceDef i : interfaceDefs) {
recordInterface(i, pkg, Collections.emptyList());
}
for (FunDef f : funDefs) {
recordFun(f, pkg, Collections.emptyList());
}
for (ObjectDef o : objectDefs) {
recordObject(o, pkg, Collections.emptyList());
}
for (AnnotationDef a : annotationDefs) {
recordAnnotation(a, pkg, Collections.emptyList());
}
}
// templateName to halfAppliedTypes
// templateName means the type without generic parameters
// halfAppliedType means the type is not fully applied, e.g. class A<:T,U:> and A<:int, U:>
final Map templateNameToHalfAppliedTypes = new HashMap();
for (String file : mapOfStatements.keySet()) {
List stmts = mapOfStatements.get(file);
final List imports = fileNameToImport.get(file);
final String pkg = fileNameToPackageName.get(file);
// traverse func
Function2 traverseFunc = new Function2() {
@Override
public Boolean apply(Statement statement, HalfAppliedTypes halfAppliedTypes) throws Exception {
if (statement instanceof AST.Access) {
AST.Access access = (AST.Access) statement;
handleGenericAST(access, imports, halfAppliedTypes, templateNameToHalfAppliedTypes);
return true;
}
if (statement instanceof ClassDef || statement instanceof InterfaceDef || statement instanceof ObjectDef) {
assert halfAppliedTypes == null;
List generics;
String simpleName;
if (statement instanceof ClassDef) {
ClassDef c = (ClassDef) statement;
generics = c.generics;
simpleName = c.name;
} else if (statement instanceof InterfaceDef) {
InterfaceDef i = (InterfaceDef) statement;
generics = i.generics;
simpleName = i.name;
} else /*if (statement instanceof ObjectDef)*/ {
ObjectDef o = (ObjectDef) statement;
generics = o.generics;
simpleName = o.name;
}
if (!generics.isEmpty()) {
// record the generics
if (templateNameToHalfAppliedTypes.containsKey(pkg + simpleName)) {
halfAppliedTypes = templateNameToHalfAppliedTypes.get(pkg + simpleName);
} else {
halfAppliedTypes = new HalfAppliedTypes();
templateNameToHalfAppliedTypes.put(pkg + simpleName, halfAppliedTypes);
}
statement.foreachInnerStatements(this, halfAppliedTypes);
return false;
}
}
return true;
}
};
for (Statement stmt : stmts) {
try {
if (traverseFunc.apply(stmt, null)) {
stmt.foreachInnerStatements(traverseFunc, null);
}
} catch (SyntaxException e) {
throw e;
} catch (Exception e) {
throw new LtBug("should not have any exception other than SyntaxException", e);
}
}
}
// all classes occurred in the parsing process will be inside `types` map or is already defined
// check package and type exists
for (String fileName : mapOfStatements.keySet()) {
// import
List imports = fileNameToImport.get(fileName);
ListIterator ite = imports.listIterator();
while (ite.hasNext()) {
Import i = ite.next();
if (i.pkg == null) {
// try class
STypeDef type = getTypeWithAccess(i.access, Collections.emptyMap(), Collections.emptyList(), true);
if (null == type) {
boolean pass = false;
if (i.importAll) {
// test whether it's a package
AST.Access access = i.access;
AST.PackageRef pkgRef = checkAndGetPackage(fileNameToPackageName, access);
if (pkgRef != null) {
pass = true;
ite.set(new Import(pkgRef, null, true, i.implicit, i.line_col()));
}
// else {
// do nothing
// should error }
} else {
AST.Access newAccessWithPkg = transformAccess(i.access);
if (i.access != newAccessWithPkg) {
pass = true;
// replace the original object with new one
ite.set(new Import(null, newAccessWithPkg, false, i.implicit, i.line_col()));
}
}
if (!pass) {
err.SyntaxException("Type " + i.access + " not found", i.access.line_col());
}
} // else: pass, for that a type is retrieved
} else {
// check package exists
String pkg = i.pkg.pkg.replace("::", ".");
if (!isPackage(fileNameToPackageName, pkg)) {
err.SyntaxException("Package " + i.pkg.pkg + " not found", i.pkg.line_col());
}
}
}
}
step2(fileNameToPackageName);
step3();
// ensures that @ImplicitImports and @StaticImports are loaded
getTypeWithName("lt.runtime.ImplicitImports", LineCol.SYNTHETIC);
getTypeWithName("lt.runtime.StaticImports", LineCol.SYNTHETIC);
getTypeWithName("java.lang.annotation.Retention", LineCol.SYNTHETIC);
step4();
addImportImplicit();
addImportStatic();
addRetention();
return typeDefSet;
}
private void recordClass(ClassDef c, String pkg, List generics) throws SyntaxException {
String className = buildTemplateAppliedName(pkg + c.name, generics);
// check occurrence
if (typeExists(className)) {
err.SyntaxException("duplicate type names " + className, c.line_col());
return;
}
SClassDef sClassDef = new SClassDef(SClassDef.NORMAL, c.line_col());
sClassDef.setFullName(className);
sClassDef.setPkg(pkg.endsWith(".") ? pkg.substring(0, pkg.length() - 1) : pkg);
sClassDef.modifiers().add(SModifier.PUBLIC);
for (Modifier m : c.modifiers) {
switch (m.modifier) {
case ABSTRACT:
sClassDef.modifiers().add(SModifier.ABSTRACT);
break;
case VAL:
sClassDef.modifiers().add(SModifier.FINAL);
break;
case PUBLIC:
case PRIVATE:
case PROTECTED:
case PKG:
// pub|pri|pro|pkg are for constructors
break;
case DATA:
sClassDef.setIsDataClass(true);
break;
default:
err.UnexpectedTokenException("valid modifier for class (val|abstract|public|private|protected|internal)", m.toString(), m.line_col());
return;
}
}
types.put(className, sClassDef); // record the class
originalClasses.put(className, new ASTGHolder(c, generics));
typeDefSet.add(sClassDef);
}
private void recordInterface(InterfaceDef i, String pkg, List generics) throws SyntaxException {
String interfaceName = buildTemplateAppliedName(pkg + i.name, generics);
// check occurrence
if (typeExists(interfaceName)) {
err.SyntaxException("duplicate type names " + interfaceName, i.line_col());
return;
}
SInterfaceDef sInterfaceDef = new SInterfaceDef(i.line_col());
sInterfaceDef.setFullName(interfaceName);
sInterfaceDef.setPkg(pkg.endsWith(".") ? pkg.substring(0, pkg.length() - 1) : pkg);
sInterfaceDef.modifiers().add(SModifier.PUBLIC);
sInterfaceDef.modifiers().add(SModifier.ABSTRACT);
for (Modifier m : i.modifiers) {
switch (m.modifier) {
case PUBLIC:
case ABSTRACT:
// can only be abstract or public
break;
default:
err.UnexpectedTokenException("valid modifier for interface (abs)", m.toString(), m.line_col());
return;
}
}
types.put(interfaceName, sInterfaceDef); // record the interface
originalInterfaces.put(interfaceName, new ASTGHolder(i, generics));
typeDefSet.add(sInterfaceDef);
}
private void recordFun(FunDef f, String pkg, List generics) throws SyntaxException {
String className = buildTemplateAppliedName(pkg + f.name, generics);
// check occurrence
if (typeExists(className)) {
err.SyntaxException("duplicate type names " + className, f.line_col());
return;
}
SClassDef sClassDef = new SClassDef(SClassDef.FUN, f.line_col());
sClassDef.setFullName(className);
sClassDef.setPkg(pkg.endsWith(".") ? pkg.substring(0, pkg.length() - 1) : pkg);
sClassDef.modifiers().add(SModifier.PUBLIC);
sClassDef.modifiers().add(SModifier.FINAL);
types.put(className, sClassDef); // record the class
originalFunctions.put(className, new ASTGHolder(f, generics));
typeDefSet.add(sClassDef);
}
private void recordObject(ObjectDef o, String pkg, List generics) throws SyntaxException {
String className = buildTemplateAppliedName(pkg + o.name, generics);
// check occurrence
if (typeExists(className)) {
err.SyntaxException("duplicate type names " + className, o.line_col());
return;
}
SClassDef sClassDef = new SClassDef(SClassDef.OBJECT, o.line_col());
sClassDef.setFullName(className);
sClassDef.setPkg(pkg.endsWith(".") ? pkg.substring(0, pkg.length() - 1) : pkg);
sClassDef.modifiers().add(SModifier.PUBLIC);
sClassDef.modifiers().add(SModifier.FINAL);
types.put(className, sClassDef);
originalObjects.put(className, new ASTGHolder(o, generics));
typeDefSet.add(sClassDef);
}
private void recordAnnotation(AnnotationDef a, String pkg, List generics) throws SyntaxException {
String annoName = buildTemplateAppliedName(pkg + a.name, generics);
// check occurrence
if (typeExists(annoName)) {
err.SyntaxException("duplicate type names " + annoName, a.line_col());
return;
}
SAnnoDef sAnnoDef = new SAnnoDef(a.line_col());
sAnnoDef.setPkg(pkg);
sAnnoDef.setFullName(annoName);
types.put(annoName, sAnnoDef);
originalAnnotations.put(annoName, new ASTGHolder(a, generics));
typeDefSet.add(sAnnoDef);
}
/**
* handle the {@link lt.compiler.syntactic.AST.Access} found in ast.
*
* for those generics are fully applied, check the map and apply all
* corresponding half applied types.
* for those half applied types, record in list
*
* @param accessType the access instance
* @param imports current file imports
* @param currentHalfAppliedTypes list (as the variable name says)
* @param templateNameToHalfAppliedTypes map (as the variable name says)
* @return true if fully applied, false otherwise
* @throws SyntaxException compile exception
*/
private boolean handleGenericAST(AST.Access accessType,
List imports,
/*@Nullable*/ HalfAppliedTypes currentHalfAppliedTypes,
Map templateNameToHalfAppliedTypes) throws SyntaxException {
if (accessType.generics.isEmpty()) {
return true;
}
boolean isFullyApplied = true; // any one sub generic type not fully applied then it's not applied
for (AST.Access g : accessType.generics) {
boolean b = handleGenericAST(g, imports, currentHalfAppliedTypes, templateNameToHalfAppliedTypes);
if (!b) {
isFullyApplied = false;
}
}
if (!isFullyApplied) {
// record in the list
assert null != currentHalfAppliedTypes;
currentHalfAppliedTypes.add(accessType);
return false;
}
AST.Access accessWithoutGeneric = new AST.Access(accessType.exp, accessType.name, accessType.line_col());
// templateName means
// the generic type name without type parameters
// e.g. class A<:T:>, the templateName would be A
//
// the templateName might not be able to find when
// higher kind is supportted
String templateName = accessToClassName(accessWithoutGeneric, Collections.emptyMap(), imports, true);
if (templateName == null) {
currentHalfAppliedTypes.add(accessType);
return false;
}
if (tryToApplyGenericType(accessType, imports, templateName, Collections.emptyMap(), true)) {
List genericTypes = new ArrayList();
for (AST.Access a : accessType.generics) {
STypeDef t = getTypeWithAccess(a, Collections.emptyMap(), imports);
genericTypes.add(t);
}
applyToHalfAppliedTypes(templateNameToHalfAppliedTypes, templateName, genericTypes);
return true;
} else {
assert null != currentHalfAppliedTypes;
currentHalfAppliedTypes.add(accessType);
return false;
}
}
private boolean tryToApplyGenericType(AST.Access access,
List imports,
String templateName,
Map genericMap,
boolean allowException) throws SyntaxException {
// clsName means
// the generic type full name
// Latte generates a long name for the applied generic type
// e.g. class A<:T:> and A<:int:> would be something looks like 'A_xxx_int'
String clsName = accessToClassName(access, genericMap, imports, allowException);
if (null == clsName) {
return false;
}
if (types.containsKey(clsName)) {
// already defined
// ignore and return
return true;
}
List genericTypes = new ArrayList();
for (AST.Access a : access.generics) {
STypeDef t = getTypeWithAccess(a, genericMap, imports);
genericTypes.add(t);
}
int genericParamSize;
if (originalClasses.containsKey(templateName)) {
ClassDef ast = originalClasses.get(templateName).s;
String file = ast.line_col().fileName;
recordClass(ast, fileNameToPackageName.get(file), genericTypes);
genericParamSize = ast.generics.size();
} else if (originalInterfaces.containsKey(templateName)) {
InterfaceDef ast = originalInterfaces.get(templateName).s;
String file = ast.line_col().fileName;
recordInterface(ast, fileNameToPackageName.get(file), genericTypes);
genericParamSize = ast.generics.size();
} else if (originalObjects.containsKey(templateName)) {
ObjectDef ast = originalObjects.get(templateName).s;
String file = ast.line_col().fileName;
recordObject(ast, fileNameToPackageName.get(file), genericTypes);
genericParamSize = ast.generics.size();
} else if (originalFunctions.containsKey(templateName)) {
err.SyntaxException("function definitions are never generic types", access.line_col());
return true; // would not reach here
} else if (originalAnnotations.containsKey(templateName)) {
err.SyntaxException("annotations are never generic types", access.line_col());
return true; // would not reach here
} else {
err.SyntaxException("type " + templateName + " not found", access.line_col());
return true; // would not reach here
}
if (genericParamSize != access.generics.size()) {
err.SyntaxException("generic params size is " + genericParamSize
+ ", but generic args size is " + access.generics.size(),
access.line_col());
}
return true;
}
private void applyToHalfAppliedTypes(Map templateNameToHalfAppliedTypes,
String templateName,
List genericTypes) throws SyntaxException {
HalfAppliedTypes types;
if (templateNameToHalfAppliedTypes.containsKey(templateName)) {
types = templateNameToHalfAppliedTypes.get(templateName);
} else {
types = new HalfAppliedTypes();
templateNameToHalfAppliedTypes.put(templateName, types);
}
final Map genericMap = new HashMap();
List typeGenerics;
if (originalClasses.containsKey(templateName)) {
typeGenerics = originalClasses.get(templateName).s.generics;
} else if (originalObjects.containsKey(templateName)) {
typeGenerics = originalObjects.get(templateName).s.generics;
} else if (originalInterfaces.containsKey(templateName)) {
typeGenerics = originalInterfaces.get(templateName).s.generics;
} else {
throw new LtBug("trying to get generic parameters from " + templateName);
}
for (int i = 0; i < typeGenerics.size(); ++i) {
AST.Access g = typeGenerics.get(i);
STypeDef t = genericTypes.get(i);
genericMap.put(g.name, t);
}
types.setApply(new Function1() {
@Override
public Void apply(AST.Access type) throws Exception {
String filename = type.line_col().fileName;
List imports = fileNameToImport.get(filename);
// build non generic access
AST.Access nonGenericType = new AST.Access(type.exp, type.name, type.line_col());
String clsName = accessToClassName(nonGenericType, genericMap, imports, false);
tryToApplyGenericType(type, imports, clsName, genericMap, false);
return null;
}
});
}
private AST.PackageRef checkAndGetPackage(Map fileNameToPackageName, AST.Access access) {
if (access.exp == null) {
if (isPackage(fileNameToPackageName, access.name)) {
return new AST.PackageRef(access.name, access.line_col());
} else {
return null;
}
}
if (!(access.exp instanceof AST.Access)) {
return null;
}
List pkgSplitList = new ArrayList();
pkgSplitList.add(access.name);
AST.Access tmp = (AST.Access) access.exp;
while (true) {
pkgSplitList.add(tmp.name);
Expression exp = tmp.exp;
if (null == exp) break;
if (!(exp instanceof AST.Access)) return null;
tmp = (AST.Access) exp;
}
Collections.reverse(pkgSplitList);
StringBuilder pkgBuilder = new StringBuilder();
boolean isFirst = true;
for (String pkgSplitName : pkgSplitList) {
if (isFirst) {
isFirst = false;
} else {
pkgBuilder.append(".");
}
pkgBuilder.append(pkgSplitName);
}
String pkg = pkgBuilder.toString();
if (isPackage(fileNameToPackageName, pkg)) {
return new AST.PackageRef(pkg.replace(".", "::"), access.line_col());
} else {
return null;
}
}
private boolean isPackage(Map fileNameToPackageName, String javaPkg) {
return fileNameToPackageName.containsValue(javaPkg + ".")
|| packageExistsInClassPath(javaPkg, classLoader)
|| packageExistInJRE(javaPkg);
}
private void addImportImplicit() throws SyntaxException {
// validate imports
for (List imports : fileNameToImport.values()) {
for (Import im : imports) {
if (im.implicit) {
STypeDef type = getTypeWithAccess(im.access, Collections.emptyMap(), Collections.emptyList());
if (!(type instanceof SClassDef)) {
err.SyntaxException("import implicit should be an implicit class", im.line_col());
return;
}
SClassDef cType = (SClassDef) type;
int count = 0;
for (SAnno a : cType.annos()) {
if (a.type().fullName().equals("lt.runtime.Implicit")) {
++count;
}
}
if (count != 1) {
err.SyntaxException("import implicit should be an implicit class", im.line_col());
return;
}
}
}
}
SAnnoDef ImplicitImports = (SAnnoDef) getTypeWithName("lt.runtime.ImplicitImports", LineCol.SYNTHETIC);
SArrayTypeDef classArrayTypeDef = (SArrayTypeDef) getTypeWithName("[Ljava.lang.Class;", LineCol.SYNTHETIC);
SClassDef classTypeDef = (SClassDef) getTypeWithName("java.lang.Class", LineCol.SYNTHETIC);
SAnnoField annoField = null;
for (SAnnoField f : ImplicitImports.annoFields()) {
if (f.name().equals("implicitImports")) {
annoField = f;
break;
}
}
if (annoField == null) throw new LtBug("lt.runtime.ImplicitImports#implicitImports should exist");
for (STypeDef sTypeDef : typeDefSet) {
if (sTypeDef instanceof SAnnoDef) continue;
// filter
if (sTypeDef.line_col().fileName == null || !fileNameToImport.containsKey(sTypeDef.line_col().fileName)) {
continue;
}
int count = 0;
for (SAnno anno : sTypeDef.annos()) {
if (anno.type().fullName().equals("lt.runtime.ImplicitImports")) {
++count;
}
}
boolean alreadyExists = count > 0;
if (alreadyExists) continue;
List imports = fileNameToImport.get(sTypeDef.line_col().fileName);
List valueList = new ArrayList();
Set tmpSet = new HashSet(); // distinct
for (Import i : imports) {
if (i.implicit) {
Ins.GetClass getC = new Ins.GetClass(getTypeWithAccess(i.access, Collections.emptyMap(), Collections.emptyList()), classTypeDef);
if (!getC.targetType().equals(sTypeDef)) {
if (tmpSet.add(getC)) {
valueList.add(getC);
}
}
}
}
if (valueList.isEmpty()) continue;
// build the annotation instance
SAnno importImplicit = new SAnno();
importImplicit.setAnnoDef(ImplicitImports);
importImplicit.setPresent(sTypeDef);
sTypeDef.annos().add(importImplicit);
// build the array instance
SArrayValue arrayValue = new SArrayValue();
arrayValue.setDimension(1);
arrayValue.setType(classArrayTypeDef);
Value[] valueArray = new Value[valueList.size()];
arrayValue.setValues(valueList.toArray(valueArray));
// add value into anno
importImplicit.values().put(annoField, arrayValue);
}
}
private void addImportStatic() throws SyntaxException {
SAnnoDef StaticImports = (SAnnoDef) getTypeWithName("lt.runtime.StaticImports", LineCol.SYNTHETIC);
SArrayTypeDef classArrayTypeDef = (SArrayTypeDef) getTypeWithName("[Ljava.lang.Class;", LineCol.SYNTHETIC);
SClassDef classTypeDef = (SClassDef) getTypeWithName("java.lang.Class", LineCol.SYNTHETIC);
SAnnoField annoField = null;
for (SAnnoField f : StaticImports.annoFields()) {
if (f.name().equals("staticImports")) {
annoField = f;
break;
}
}
if (annoField == null) throw new LtBug("lt.runtime.StaticImports#staticImports should exist");
for (STypeDef sTypeDef : typeDefSet) {
if (sTypeDef instanceof SAnnoDef) continue;
// filter
if (sTypeDef.line_col().fileName == null || !fileNameToImport.containsKey(sTypeDef.line_col().fileName)) {
continue;
}
int count = 0;
for (SAnno anno : sTypeDef.annos()) {
if (anno.type().fullName().equals("lt.runtime.StaticImports")) {
++count;
}
}
boolean alreadyExists = count > 0;
if (alreadyExists) continue;
List imports = fileNameToImport.get(sTypeDef.line_col().fileName);
List valueList = new ArrayList();
Set tmpSet = new HashSet(); // distinct
for (Import i : imports) {
if (i.importAll && i.pkg == null) {
Ins.GetClass getC = new Ins.GetClass(getTypeWithAccess(i.access, Collections.emptyMap(), Collections.emptyList()), classTypeDef);
if (!getC.targetType().equals(sTypeDef)) {
if (tmpSet.add(getC)) {
valueList.add(getC);
}
}
}
}
if (valueList.isEmpty()) continue;
// build the annotation instance
SAnno importStatic = new SAnno();
importStatic.setAnnoDef(StaticImports);
importStatic.setPresent(sTypeDef);
sTypeDef.annos().add(importStatic);
// build the array instance
SArrayValue arrayValue = new SArrayValue();
arrayValue.setDimension(1);
arrayValue.setType(classArrayTypeDef);
Value[] valueArray = new Value[valueList.size()];
arrayValue.setValues(valueList.toArray(valueArray));
// add value into anno
importStatic.values().put(annoField, arrayValue);
}
}
private void addRetention() throws SyntaxException {
SAnnoDef RetentionType = (SAnnoDef) getTypeWithName("java.lang.annotation.Retention", LineCol.SYNTHETIC);
SAnnoField value = null;
EnumValue enumValue = new EnumValue();
enumValue.setType(getTypeWithName("java.lang.annotation.RetentionPolicy", LineCol.SYNTHETIC));
enumValue.setEnumStr("RUNTIME");
for (SAnnoField af : RetentionType.annoFields()) {
if (af.name().equals("value")) {
value = af;
break;
}
}
assert value != null;
out:
for (STypeDef sTypeDef : typeDefSet) {
if (!(sTypeDef instanceof SAnnoDef)) continue;
SAnnoDef sAnnoDef = (SAnnoDef) sTypeDef;
for (SAnno anno : sAnnoDef.annos()) {
if (anno.type().equals(RetentionType)) {
continue out;
}
}
SAnno anno = new SAnno();
anno.setAnnoDef(RetentionType);
anno.setPresent(sAnnoDef);
anno.values().put(value, enumValue);
sAnnoDef.annos().add(anno);
}
}
public static boolean packageExistsInClassPath(String pkg, ClassLoader classLoader) {
if (classLoader == null) return false;
String path = pkg.replace(".", "/");
URL url = classLoader.getResource(path);
return url != null || packageExistsInClassPath(pkg, classLoader.getParent());
}
public boolean packageExistInJRE(String pkg) {
if (alreadyWarnJar) return true;
if (sourceClasses.isEmpty()) {
String homePath = System.getProperty("java.home");
if (homePath == null) {
err.warning("Cannot find java home via System.getProperty('java.home')");
alreadyWarnJar = true;
return true; // assume it's a valid import
}
File homePathFile = new File(homePath);
File[] rtFileA = null;
boolean found = false;
// the file may be in $JAVA_HOME/../Contents/Classes/classes.jar
// instead of $JAVA_HOME/lib/rt.jar
// check for classes.jar
File[] classesFileA = homePathFile.getParentFile().listFiles(new FileFilter() {
@Override
public boolean accept(File f) {
return f.getName().equals("Classes");
}
});
if (classesFileA != null && classesFileA.length != 0) {
File classesFile = classesFileA[0];
rtFileA = classesFile.listFiles(
new FileFilter() {
@Override
public boolean accept(File f) {
return f.getName().equals("classes.jar");
}
});
found = true;
}
// not found
// check rt.jar
if (!found) {
File[] libFileA = homePathFile.listFiles(
new FileFilter() {
@Override
public boolean accept(File f) {
return f.getName().equals("lib");
}
});
if (libFileA == null || libFileA.length == 0) {
err.warning(homePath + "/lib not exist");
alreadyWarnJar = true;
return true;
}
File libFile = libFileA[0];
rtFileA = libFile.listFiles(
new FileFilter() {
@Override
public boolean accept(File f) {
return f.getName().equals("rt.jar");
}
});
if (rtFileA != null && rtFileA.length > 0 && rtFileA[0].exists()) {
found = true;
}
}
if (found) {
assert rtFileA != null;
File rtFile = rtFileA[0];
if (!rtFile.exists()) {
err.warning(homePath + "/lib/rt.jar not exist");
alreadyWarnJar = true;
return true;
}
try {
sourceClasses.add(new JarFile(rtFile));
} catch (IOException e) {
err.warning("Occurred exception " + e + " when opening rt.jar");
alreadyWarnJar = true;
return true;
}
} else {
// check java 9 mods
if (!findJava9(homePathFile)) {
err.warning(homePath + "/lib/rt.jar not exist");
alreadyWarnJar = true;
return true;
}
}
}
return findPackage(pkg, sourceClasses);
}
private boolean findJava9(File home) {
return findJava9JMods(home);
}
private boolean findJava9JMods(File home) {
File[] jmodsDirA = home.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.getName().equals("jmods") && file.isDirectory();
}
});
if (jmodsDirA == null || jmodsDirA.length == 0) {
return false;
}
File jmodsDir = jmodsDirA[0];
File[] mods = jmodsDir.listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.getName().endsWith(".jmod") && file.isFile();
}
});
if (mods == null || mods.length == 0) {
return false;
}
for (File f : mods) {
try {
sourceClasses.add(new ZipFile(f));
} catch (IOException e) {
return false;
}
}
return true;
}
private static boolean findPackage(String pkg, List files) {
for (ZipFile f : files) {
if (findPackage(pkg, f.entries())) {
return true;
}
}
return false;
}
private static boolean findPackage(String pkg, Enumeration extends ZipEntry> entries) {
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().startsWith(pkg.replace(".", "/"))) {
return true;
}
if (entry.getName().startsWith("classes/" + pkg.replace(".", "/"))) {
return true;
}
}
return false;
}
/**
* ======= step 2 =======
* build fields,methods,constructors,parameters,parent-classes,super-interfaces,annotations.
* but no details (annotation's values|method statements|constructor statements won't be parsed)
*
* @param fileNameToPackageName file name to package name
* @throws SyntaxException exception
*/
public void step2(Map fileNameToPackageName) throws SyntaxException {
for (STypeDef sTypeDef : typeDefSet) {
if (isGenericTemplateType(sTypeDef)) {
continue; // ignore the template types
}
String fileName = sTypeDef.line_col().fileName;
List imports = fileNameToImport.get(fileName);
String pkg = fileNameToPackageName.get(fileName);
if (sTypeDef instanceof SClassDef) {
SClassDef sClassDef = (SClassDef) sTypeDef;
if (sClassDef.classType() == SClassDef.FUN) {
SAnno latteFun = new SAnno();
latteFun.setAnnoDef((SAnnoDef) getTypeWithName("lt.runtime.LatteFun", LineCol.SYNTHETIC));
latteFun.setPresent(sClassDef);
sClassDef.annos().add(latteFun);
} else if (sClassDef.classType() == SClassDef.OBJECT) {
ASTGHolder objectHolder = originalObjects.get(sClassDef.fullName());
ObjectDef objectDef = objectHolder.s;
// present annotation
SAnno objectAnno = new SAnno();
objectAnno.setAnnoDef((SAnnoDef) getTypeWithName("lt.runtime.LatteObject", LineCol.SYNTHETIC));
objectAnno.setPresent(sClassDef);
sClassDef.annos().add(objectAnno);
parseClassDefInfo(sClassDef, objectDef.superWithInvocation,
objectDef.superWithoutInvocation, imports, objectDef.line_col());
// modifiers
boolean isImplicit = false;
for (Modifier m : objectDef.modifiers) {
switch (m.modifier) {
case IMPLICIT:
isImplicit = true;
break;
default:
err.UnexpectedTokenException("valid modifier (implicit)", m.toString(), m.line_col());
return;
}
}
// annos
parseAnnos(objectDef.annos, sClassDef, imports, ElementType.TYPE,
Collections.singletonList(ElementType.CONSTRUCTOR));
if (isImplicit) {
checkAndAddImplicitAnno(sClassDef);
}
// build constructor
SConstructorDef constructor = new SConstructorDef(LineCol.SYNTHETIC);
constructor.setDeclaringType(sClassDef);
// annotation
parseAnnos(objectDef.annos, constructor, imports, ElementType.CONSTRUCTOR,
Collections.singletonList(ElementType.TYPE));
// modifier
constructor.modifiers().add(SModifier.PRIVATE);
// declaring
constructor.setDeclaringType(sClassDef);
sClassDef.constructors().add(constructor);
// only add once, for that all types under one template share the same ast structure
if (objectDef.statements.isEmpty() || !(objectDef.statements.get(0) instanceof AST.StaticScope)) {
// static public val singletonInstance = XXX
VariableDef v = new VariableDef(CompileUtil.SingletonFieldName,
new HashSet(Arrays.asList(
new Modifier(Modifier.Available.VAL, LineCol.SYNTHETIC),
new Modifier(Modifier.Available.PUBLIC, LineCol.SYNTHETIC)
)), Collections.emptySet(), LineCol.SYNTHETIC);
AST.Access vType = new AST.Access(pkg.isEmpty() ? null : new AST.PackageRef(pkg, LineCol.SYNTHETIC),
objectDef.name, LineCol.SYNTHETIC);
vType.generics.addAll(objectDef.generics);
v.setType(vType);
objectDef.statements.add(0, new AST.StaticScope(Collections.singletonList(v),
LineCol.SYNTHETIC));
}
// fields methods
parseFieldsAndMethodsForClass(sClassDef, objectDef.statements, imports);
} else if (sClassDef.classType() == SClassDef.NORMAL) {
ASTGHolder classHolder = originalClasses.get(sClassDef.fullName());
ClassDef classDef = classHolder.s;
parseClassDefInfo(sClassDef, classDef.superWithInvocation,
classDef.superWithoutInvocation, imports, classDef.line_col());
// annos
parseAnnos(classDef.annos, sClassDef, imports, ElementType.TYPE,
Collections.singletonList(ElementType.CONSTRUCTOR));
// parse constructor
int generateIndex = -1;
for (VariableDef v : classDef.params) {
if (v.getInit() == null) {
++generateIndex;
} else break;
}
SConstructorDef lastConstructor = null;
for (int i = classDef.params.size(); i > generateIndex; --i) {
// generate constructors
// these constructors will call the constructor that has length+1 parameter length constructor
// e.g.
// class Cls(a,b=1)
// will be parsed into
// public class Cls{
// public Cls(a){
// this(a,1);
// }
// public Cls(a,b){
// ...
// }
// }
// however the statements won't be filled in this step
SConstructorDef constructor = new SConstructorDef(classDef.line_col());
constructor.setDeclaringType(sClassDef);
sClassDef.constructors().add(constructor);
// constructor should be filled with
// parameters|declaringType(class)|modifiers
// in this step
// annotation
parseAnnos(classDef.annos, constructor, imports, ElementType.CONSTRUCTOR,
Collections.singletonList(ElementType.TYPE));
// modifier
boolean hasAccessModifier = false;
for (Modifier m : classDef.modifiers) {
if (m.modifier.equals(Modifier.Available.PUBLIC)
|| m.modifier.equals(Modifier.Available.PRIVATE)
|| m.modifier.equals(Modifier.Available.PROTECTED)
|| m.modifier.equals(Modifier.Available.PKG)) {
hasAccessModifier = true;
}
}
if (!hasAccessModifier) {
constructor.modifiers().add(SModifier.PUBLIC);
}
for (Modifier m : classDef.modifiers) {
switch (m.modifier) {
case PUBLIC:
constructor.modifiers().add(SModifier.PUBLIC);
break;
case PRIVATE:
constructor.modifiers().add(SModifier.PRIVATE);
break;
case PROTECTED:
constructor.modifiers().add(SModifier.PROTECTED);
break;
case VAL:
case PKG:
case DATA:
case ABSTRACT:
// data, val and abs are presented on class
break; // pkg don't need to sign modifier
default:
err.UnexpectedTokenException("valid constructor modifier (public|private|protected|internal)", m.toString(), m.line_col());
return;
}
}
// parameters
parseParameters(classDef.params, i, constructor, imports, true);
if (lastConstructor != null) {
// record the constructor and expressions
Map invoke = new HashMap();
invoke.put(lastConstructor, classDef.params.get(i).getInit());
defaultParamInvokable.put(constructor, invoke);
}
lastConstructor = constructor;
}
// constructor finished
// fields and methods
// parse field from constructor parameters
for (VariableDef v : classDef.params) {
parseField(v, sClassDef, imports, PARSING_CLASS, false, true);
}
parseFieldsAndMethodsForClass(sClassDef, classDef.statements, imports);
} else {
throw new LtBug("unknown sClassDef.classType(): " + sClassDef.classType());
}
} else if (sTypeDef instanceof SInterfaceDef) {
SInterfaceDef sInterfaceDef = (SInterfaceDef) sTypeDef;
ASTGHolder interfaceHolder = originalInterfaces.get(sInterfaceDef.fullName());
InterfaceDef interfaceDef = interfaceHolder.s;
// parse super interfaces
for (AST.Access access : interfaceDef.superInterfaces) {
SInterfaceDef superInterface = (SInterfaceDef) getTypeWithAccess(access, getGenericMap(sInterfaceDef), imports);
sInterfaceDef.superInterfaces().add(superInterface);
}
// parse annos
parseAnnos(interfaceDef.annos, sInterfaceDef, imports, ElementType.TYPE, Collections.emptyList());
// parse fields and methods
List staticScopes = new ArrayList();
for (Statement stmt : interfaceDef.statements) {
if (stmt instanceof VariableDef) {
parseField((VariableDef) stmt, sInterfaceDef, imports, PARSING_INTERFACE, false, false);
} else if (stmt instanceof MethodDef) {
MethodDef m = (MethodDef) stmt;
int generateIndex = -1;
for (VariableDef param : m.params) {
if (param.getInit() == null) {
++generateIndex;
} else break;
}
SMethodDef lastMethod = null;
for (int i = m.params.size(); i > generateIndex; --i) {
parseMethod(m, i, sInterfaceDef, lastMethod, imports, PARSING_INTERFACE, false);
lastMethod = sInterfaceDef.methods().get(sInterfaceDef.methods().size() - 1);
// record the method
methodToStatements.put(lastMethod, m.body);
}
} else if (stmt instanceof AST.StaticScope) {
staticScopes.add((AST.StaticScope) stmt);
} else if (stmt instanceof AST.Destruct) {
parseFieldsFromDestruct((AST.Destruct) stmt, sInterfaceDef, true);
} else {
err.SyntaxException("interfaces don't have initiators", stmt.line_col());
return;
}
}
for (AST.StaticScope staticScope : staticScopes) {
for (Statement stmt : staticScope.statements) {
if (stmt instanceof VariableDef) {
parseField((VariableDef) stmt, sInterfaceDef, imports, PARSING_INTERFACE, true, false);
} else if (stmt instanceof MethodDef) {
MethodDef m = (MethodDef) stmt;
int generateIndex = -1;
for (VariableDef param : m.params) {
if (param.getInit() == null) {
++generateIndex;
} else break;
}
SMethodDef lastMethod = null;
for (int i = m.params.size(); i > generateIndex; --i) {
parseMethod(m, i, sInterfaceDef, lastMethod, imports, PARSING_INTERFACE, true);
lastMethod = sInterfaceDef.methods().get(sInterfaceDef.methods().size() - 1);
// record the method
methodToStatements.put(lastMethod, m.body);
}
} else if (stmt instanceof AST.Destruct) {
parseFieldsFromDestruct((AST.Destruct) stmt, sInterfaceDef, true);
} else {
err.SyntaxException("interfaces don't have initiators", stmt.line_col());
return;
}
}
}
} else if (sTypeDef instanceof SAnnoDef) {
SAnnoDef sAnnoDef = (SAnnoDef) sTypeDef;
ASTGHolder annoHolder = originalAnnotations.get(sAnnoDef.fullName());
AnnotationDef annotationDef = annoHolder.s;
// annos
parseAnnos(annotationDef.annos, sAnnoDef, imports, ElementType.ANNOTATION_TYPE,
Collections.emptyList());
// annotation fields
parseAnnotationFields(sAnnoDef, annotationDef.stmts, imports);
} else {
throw new LtBug("unknown STypeDef " + sTypeDef);
}
}
}
private void assertToBeAnnotationField(STypeDef type) throws SyntaxException {
if (type instanceof PrimitiveTypeDef) return;
if (type.fullName().equals("java.lang.String")) return;
if (type.fullName().equals("java.lang.Class")) return;
if (getTypeWithName(Enum.class.getName(), LineCol.SYNTHETIC).isAssignableFrom(type)) return;
if (type instanceof SArrayTypeDef && ((SArrayTypeDef) type).dimension() == 1) {
assertToBeAnnotationField(((SArrayTypeDef) type).type());
return;
}
if (type instanceof SAnnoDef) return;
err.SyntaxException(type + " cannot be type of annotation fields", type.line_col());
}
/**
* fill annotation fields into the annotation, but values are not parsed
*
* @param sAnnoDef the annotation def
* @param stmts statements in annotation
* @param imports imports
* @throws SyntaxException compiling error
*/
public void parseAnnotationFields(SAnnoDef sAnnoDef, List stmts, List imports) throws SyntaxException {
for (Statement stmt : stmts) {
if (stmt instanceof VariableDef) {
VariableDef v = (VariableDef) stmt;
if (!v.getModifiers().isEmpty()) {
err.SyntaxException("modifiers cannot present on annotation fields", v.line_col());
}
if (!v.getAnnos().isEmpty()) {
err.SyntaxException("annotations cannot present on annotation fields", v.line_col());
}
if (v.getType() == null) {
err.SyntaxException("annotation fields should have a type", v.line_col());
}
STypeDef type = getTypeWithAccess(v.getType(), getGenericMap(sAnnoDef), imports);
assertToBeAnnotationField(type);
SAnnoField f = new SAnnoField();
f.setName(v.getName());
f.setType(type);
f.setDeclaringType(sAnnoDef);
sAnnoDef.annoFields().add(f);
} else {
err.SyntaxException("only variable definition can exist in annotation", stmt.line_col());
}
}
}
private void checkAndAddImplicitAnno(SAnnotationPresentable annoPresentable) throws SyntaxException {
// check implicit annotation
boolean hasImplicitAnno = false;
for (SAnno sAnno : annoPresentable.annos()) {
if (sAnno.type().fullName().equals("lt.runtime.Implicit")) {
hasImplicitAnno = true;
break;
}
}
if (!hasImplicitAnno) {
SAnno ImplicitAnno = new SAnno();
ImplicitAnno.setAnnoDef((SAnnoDef) getTypeWithName("lt.runtime.Implicit", LineCol.SYNTHETIC));
ImplicitAnno.setPresent(annoPresentable);
annoPresentable.annos().add(ImplicitAnno);
}
}
/**
* parse class info
*
* @param sClassDef SClassDef object
* @param superWithInvocation :???(...)
* @param superWithoutInvocation :???
* @param imports imports
* @param lineCol lineCol
* @throws SyntaxException compiling error
*/
private void parseClassDefInfo(SClassDef sClassDef,
AST.Invocation superWithInvocation,
List superWithoutInvocation,
List imports,
LineCol lineCol) throws SyntaxException {
// generic type map
Map genericTypeMap = getGenericMap(sClassDef);
// parse parent
Iterator superWithoutInvocationAccess;
if (superWithInvocation == null) {
if (superWithoutInvocation.isEmpty()) {
// no interfaces, no parent class
sClassDef.setParent((SClassDef) getTypeWithName("java.lang.Object", lineCol));
superWithoutInvocationAccess = null;
} else {
superWithoutInvocationAccess = superWithoutInvocation.iterator();
AST.Access mightBeClassAccess = superWithoutInvocationAccess.next();
STypeDef tmp = getTypeWithAccess(mightBeClassAccess, genericTypeMap, imports);
if (tmp instanceof SClassDef) {
// constructor without constructor invocation
sClassDef.setParent((SClassDef) tmp);
} else if (tmp instanceof SInterfaceDef) {
// interface
sClassDef.superInterfaces().add((SInterfaceDef) tmp);
// set java.lang.Object as super class
sClassDef.setParent((SClassDef) getTypeWithName("java.lang.Object", lineCol));
} else {
err.SyntaxException(mightBeClassAccess.toString() + " is not class or interface",
mightBeClassAccess.line_col());
return;
}
}
} else {
// super class
if (!(superWithInvocation.exp instanceof AST.Access)) {
throw new LtBug("classDef.superWithInvocation.exp should always be AST.Access");
}
AST.Access access = (AST.Access) superWithInvocation.exp;
STypeDef tmp = getTypeWithAccess(access, genericTypeMap, imports);
if (tmp instanceof SClassDef) {
sClassDef.setParent((SClassDef) tmp);
} else {
err.SyntaxException(access.toString() + " is not class or interface",
access.line_col());
return;
}
superWithoutInvocationAccess = superWithoutInvocation.iterator();
}
// interfaces to be parsed
while (superWithoutInvocationAccess != null && superWithoutInvocationAccess.hasNext()) {
AST.Access interfaceAccess = superWithoutInvocationAccess.next();
STypeDef tmp = getTypeWithAccess(interfaceAccess, getGenericMap(sClassDef), imports);
if (tmp instanceof SInterfaceDef) {
sClassDef.superInterfaces().add((SInterfaceDef) tmp);
} else {
err.SyntaxException(interfaceAccess.toString() + " is not interface",
interfaceAccess.line_col());
return;
}
}
}
/**
* parse fields and methods for class
*
* @param sClassDef SClassDef object
* @param statements statements
* @param imports imports
* @throws SyntaxException compiling error
*/
private void parseFieldsAndMethodsForClass(SClassDef sClassDef,
List statements,
List imports) throws SyntaxException {
// get static scope and parse non-static fields/methods
List staticScopes = new ArrayList();
for (Statement stmt : statements) {
if (stmt instanceof AST.StaticScope) {
staticScopes.add((AST.StaticScope) stmt);
} else if (stmt instanceof VariableDef) {
// define a non-static field
parseField((VariableDef) stmt, sClassDef, imports, PARSING_CLASS, false, false);
} else if (stmt instanceof MethodDef) {
// define a non-static method
MethodDef methodDef = (MethodDef) stmt;
int generateIndex = -1;
for (VariableDef v : methodDef.params) {
if (v.getInit() == null) {
++generateIndex;
} else break;
}
SMethodDef lastMethod = null;
for (int i = methodDef.params.size(); i > generateIndex; --i) {
parseMethod((MethodDef) stmt, i, sClassDef, lastMethod, imports, PARSING_CLASS, false);
lastMethod = sClassDef.methods().get(sClassDef.methods().size() - 1);
// record the method
methodToStatements.put(lastMethod, methodDef.body);
}
} else if (stmt instanceof AST.Destruct) {
parseFieldsFromDestruct((AST.Destruct) stmt, sClassDef, false);
}
}
// get static field and methods
for (AST.StaticScope scope : staticScopes) {
for (Statement stmt : scope.statements) {
if (stmt instanceof VariableDef) {
// define a static field
parseField((VariableDef) stmt, sClassDef, imports, PARSING_CLASS, true, false);
} else if (stmt instanceof MethodDef) {
// define a static method
MethodDef methodDef = (MethodDef) stmt;
int generateIndex = -1;
for (VariableDef v : methodDef.params) {
if (v.getInit() == null) {
++generateIndex;
} else break;
}
SMethodDef lastMethod = null;
for (int i = methodDef.params.size(); i > generateIndex; --i) {
parseMethod((MethodDef) stmt, i, sClassDef, lastMethod, imports, PARSING_CLASS, true);
lastMethod = sClassDef.methods().get(sClassDef.methods().size() - 1);
// record the method
methodToStatements.put(lastMethod, methodDef.body);
}
} else if (stmt instanceof AST.Destruct) {
parseFieldsFromDestruct((AST.Destruct) stmt, sClassDef, true);
}
}
}
}
/**
* ========step 3========
* validation
*
* check circular inheritance
* check method override
* check method signature
* check annotations
*
* @throws SyntaxException exception
*/
public void step3() throws SyntaxException {
for (STypeDef sTypeDef : typeDefSet) {
if (isGenericTemplateType(sTypeDef)) continue;
if (sTypeDef instanceof SClassDef) {
List circularRecorder = new ArrayList();
SClassDef parent = ((SClassDef) sTypeDef).parent();
while (parent != null) {
circularRecorder.add(parent);
if (parent.equals(sTypeDef)) {
err.SyntaxException("circular inheritance " + circularRecorder, LineCol.SYNTHETIC);
return;
}
parent = parent.parent();
}
circularRecorder.clear();
} else if (sTypeDef instanceof SInterfaceDef) {
SInterfaceDef i = (SInterfaceDef) sTypeDef;
checkInterfaceCircularInheritance(i, i.superInterfaces(), new ArrayList());
} else if (!(sTypeDef instanceof SAnnoDef)) {
throw new LtBug("wrong STypeDefType " + sTypeDef.getClass());
}
}
// check override and overload with super methods
for (STypeDef sTypeDef : typeDefSet) {
if (isGenericTemplateType(sTypeDef)) continue;
if (sTypeDef instanceof SAnnoDef) continue;
checkOverrideAllMethods(sTypeDef);
}
// after the override check are done, try to get signatures of functions.
for (STypeDef sTypeDef : typeDefSet) {
if (isGenericTemplateType(sTypeDef)) continue;
if (sTypeDef instanceof SClassDef) {
SClassDef sClassDef = (SClassDef) sTypeDef;
if (sClassDef.classType() != SClassDef.FUN) {
continue;
}
ASTGHolder funHolder = originalFunctions.get(sClassDef.fullName());
FunDef fun = funHolder.s;
List imports = fileNameToImport.get(fun.line_col().fileName);
// get super class/interface
STypeDef type = getTypeWithAccess(fun.superType, getGenericMap(sTypeDef), imports);
if (!(type instanceof SClassDef || type instanceof SInterfaceDef)) {
err.SyntaxException("function super type should be functional interfaces or functional abstract classes", fun.superType.line_col());
return;
}
SConstructorDef[] zeroParamConstructor = new SConstructorDef[1];
SMethodDef[] methodToOverride = new SMethodDef[1];
if (!getMethodForLambda(type, zeroParamConstructor, methodToOverride)) {
err.SyntaxException("function super type should be functional interfaces or functional abstract classes", fun.superType.line_col());
return;
}
// class and the annos, super class
parseAnnos(fun.annos, sClassDef, imports, ElementType.TYPE, Arrays.asList(ElementType.METHOD, ElementType.CONSTRUCTOR));
if (zeroParamConstructor[0] == null) {
sClassDef.setParent(getObject_Class());
assert type instanceof SInterfaceDef;
sClassDef.superInterfaces().add((SInterfaceDef) type);
} else {
sClassDef.setParent((SClassDef) zeroParamConstructor[0].declaringType());
}
// constructors (fill statements directly)
SConstructorDef cons = new SConstructorDef(LineCol.SYNTHETIC);
parseAnnos(fun.annos, cons, imports, ElementType.CONSTRUCTOR, Arrays.asList(ElementType.TYPE, ElementType.METHOD));
cons.setDeclaringType(sClassDef);
sClassDef.constructors().add(cons);
if (zeroParamConstructor[0] == null) {
zeroParamConstructor[0] = getObject_Class().constructors().get(0);
}
cons.statements().add(new Ins.InvokeSpecial(new Ins.This(sClassDef), zeroParamConstructor[0], LineCol.SYNTHETIC));
cons.modifiers().add(SModifier.PUBLIC);
// method name, declaringType, return type, params
SMethodDef method = new SMethodDef(LineCol.SYNTHETIC);
method.setDeclaringType(sClassDef);
method.setReturnType(
getRealReturnType(
methodToOverride[0].getReturnType(), true));
method.setName(methodToOverride[0].name());
sClassDef.methods().add(method);
parseAnnos(fun.annos, method, imports, ElementType.METHOD, Arrays.asList(ElementType.TYPE, ElementType.CONSTRUCTOR));
method.modifiers().add(SModifier.PUBLIC);
// fill parameters
parseParameters(fun.params, fun.params.size(), method, imports, false);
methodToStatements.put(method, fun.statements);
// check signature
checkOverrideAllMethods(sClassDef);
}
}
// check annotation (@FunctionalInterface @FunctionalAbstractClass @Override @Implicit)
for (STypeDef typeDef : typeDefSet) {
// annotation would never be generic
// no need to check
// if (isGenericTemplateType(typeDef)) continue;
if (typeDef instanceof SAnnoDef) continue;
for (SAnno anno : typeDef.annos()) {
if (anno.type().fullName().equals("java.lang.FunctionalInterface")
|| anno.type().fullName().equals("lt.lang.FunctionalInterface")) {
final String msg = typeDef + " is not a functional interface";
if (typeDef instanceof SInterfaceDef) {
if (!getMethodForLambda(typeDef, new SConstructorDef[1], new SMethodDef[1])) {
err.SyntaxException(msg, typeDef.line_col());
return;
}
} else {
err.SyntaxException(msg, typeDef.line_col());
return;
}
} else if (anno.type().fullName().equals("lt.lang.FunctionalAbstractClass")) {
final String msg = typeDef + " is not a functional abstract class";
if (typeDef instanceof SClassDef) {
if (!getMethodForLambda(typeDef, new SConstructorDef[1], new SMethodDef[1])) {
err.SyntaxException(msg, typeDef.line_col());
return;
}
} else {
err.SyntaxException(msg, typeDef.line_col());
return;
}
} else if (anno.type().fullName().equals("lt.runtime.Implicit")) {
if (typeDef instanceof SClassDef) {
boolean isObject = false;
for (SAnno a : typeDef.annos()) {
if (a.type().fullName().equals("lt.runtime.LatteObject")) {
isObject = true;
break;
}
}
if (isObject) {
// check methods
for (SMethodDef m : ((SClassDef) typeDef).methods()) {
for (SAnno a : m.annos()) {
if (a.type().fullName().equals("lt.runtime.Implicit")) {
if (m.getParameters().size() != 1) {
err.SyntaxException("implicit methods should contain only one param", m.line_col());
}
if (m.getReturnType().equals(VoidType.get())) {
err.SyntaxException("implicit method's return type should not be Unit", m.line_col());
}
}
}
}
} else {
err.SyntaxException("implicit should only exist on object classes", typeDef.line_col());
}
} else {
err.SyntaxException("implicit should only exist on object classes", typeDef.line_col());
}
}
}
List methods;
if (typeDef instanceof SClassDef) methods = ((SClassDef) typeDef).methods();
else methods = ((SInterfaceDef) typeDef).methods();
for (SMethodDef method : methods) {
for (SAnno anno : method.annos()) {
if (anno.type().fullName().equals("java.lang.Override")) {
if (method.overRide().isEmpty()) {
err.SyntaxException(method + " doesn't override any method", method.line_col());
return;
}
}
}
}
}
// data class
for (STypeDef typeDef : typeDefSet) {
if (isGenericTemplateType(typeDef)) continue;
if (typeDef instanceof SClassDef) {
SClassDef cls = (SClassDef) typeDef;
if (cls.isDataClass()) {
fillMethodsIntoDataClass(cls);
}
}
}
}
private void checkAndFillAnnotations() throws SyntaxException {
// not compiled annotations
for (STypeDef typeDef : typeDefSet) {
if (typeDef instanceof SAnnoDef) {
SAnnoDef annoDef = (SAnnoDef) typeDef;
ASTGHolder holder = originalAnnotations.get(annoDef.fullName());
AnnotationDef astAnnoDef = holder.s;
for (SAnnoField f : annoDef.annoFields()) {
for (Statement stmt : astAnnoDef.stmts) {
if (stmt instanceof VariableDef
&&
((VariableDef) stmt).getName().equals(f.name())) {
Expression exp = ((VariableDef) stmt).getInit();
if (exp != null) {
// do fill
f.doesHaveDefaultValue();
}
}
}
}
}
}
// compiled annotations
// fill the values directly
for (STypeDef typeDef : types.values()) {
if (typeDef instanceof SAnnoDef) {
boolean isCompiledAnnotation = true;
SAnnoDef annoDef = (SAnnoDef) typeDef;
Class> cls = null;
try {
cls = loadClass(annoDef.fullName());
} catch (ClassNotFoundException e) {
isCompiledAnnotation = false;
}
// parse field default values
if (isCompiledAnnotation) {
for (SAnnoField f : annoDef.annoFields()) {
try {
Method annoM = cls.getDeclaredMethod(f.name());
try {
Object o = annoM.getDefaultValue();
if (null != o) {
Value value = parseValueFromObject(o);
f.setDefaultValue(value);
}
} catch (TypeNotPresentException ignore) {
}
} catch (NoSuchMethodException e) {
throw new LtBug(e);
}
}
}
}
}
// fill true value to not compiled annotations
for (STypeDef typeDef : typeDefSet) {
if (typeDef instanceof SAnnoDef) {
SAnnoDef annoDef = (SAnnoDef) typeDef;
ASTGHolder holder = originalAnnotations.get(annoDef.fullName());
AnnotationDef astAnnoDef = holder.s;
for (SAnnoField f : annoDef.annoFields()) {
for (Statement stmt : astAnnoDef.stmts) {
if (stmt instanceof VariableDef
&&
((VariableDef) stmt).getName().equals(f.name())) {
Expression exp = ((VariableDef) stmt).getInit();
if (exp != null) {
Value value = transformIntoAnnoValidValue(
parseValueFromExpression(exp, f.type(), null),
exp.line_col());
f.setDefaultValue(value);
}
}
}
}
}
}
// parse annotations presented on this type
for (STypeDef typeDef : types.values()) {
if (typeDef instanceof SAnnoDef) {
SAnnoDef annoDef = (SAnnoDef) typeDef;
parseAnnoValues(annoDef.annos());
}
}
}
private Value transformIntoAnnoValidValue(Value value, LineCol lineCol) throws SyntaxException {
if (value.type() instanceof PrimitiveTypeDef) return value;
if (value instanceof StringConstantValue) return value;
if (value instanceof Ins.GetClass) return value;
if (value instanceof SAnno) return value;
if (value instanceof Ins.GetStatic) {
// enum
EnumValue enumValue = new EnumValue();
enumValue.setType(((Ins.GetStatic) value).field().declaringType());
enumValue.setEnumStr(((Ins.GetStatic) value).field().name());
return enumValue;
}
if (value instanceof Ins.NewArray) {
// array
SArrayValue sArrayValue = new SArrayValue();
sArrayValue.setDimension(1);
List values = ((Ins.NewArray) value).initValues();
sArrayValue.setValues(values.toArray(new Value[values.size()]));
sArrayValue.setType((SArrayTypeDef) value.type());
return sArrayValue;
}
if (value instanceof Ins.ANewArray) {
// ref array
SArrayValue sArrayValue = new SArrayValue();
sArrayValue.setDimension(1);
List values = ((Ins.ANewArray) value).initValues();
sArrayValue.setValues(values.toArray(new Value[values.size()]));
sArrayValue.setType((SArrayTypeDef) value.type());
return sArrayValue;
}
err.SyntaxException("cannot resolve valid value for annotation field", lineCol);
return null;
}
/**
* ========step 4========
* first parse anno types
* the annotations presented on these anno types will also be parsed
*
* @throws SyntaxException exception
*/
public void step4() throws SyntaxException {
checkAndFillAnnotations();
// then
// foreach typeDefSet, parse their statements
List typeDefList = new ArrayList(typeDefSet);
for (STypeDef sTypeDef : typeDefList) {
if (isGenericTemplateType(sTypeDef)) {
typeDefSet.remove(sTypeDef);
generateGenericTemplateClass(sTypeDef);
continue;
}
if (sTypeDef instanceof SClassDef) {
SClassDef sClassDef = (SClassDef) sTypeDef;
ASTGHolder classHolder = originalClasses.get(sClassDef.fullName());
ASTGHolder objectHolder = originalObjects.get(sClassDef.fullName());
ClassDef astClass = (null == classHolder) ? null : classHolder.s;
ObjectDef astObject = (null == objectHolder) ? null : objectHolder.s;
parseAnnoValues(sClassDef.annos());
// initiate the type scope
SemanticScope scope = new SemanticScope(sTypeDef, null);
// parse constructors
for (SConstructorDef constructorToFillStatements : sClassDef.constructors()) {
// if is not empty then continue
if (!constructorToFillStatements.statements().isEmpty())
continue;
// initiate constructor scope
SemanticScope constructorScope = new SemanticScope(scope, constructorToFillStatements.meta());
constructorScope.setThis(new Ins.This(sTypeDef)); // set `this`
for (SParameter param : constructorToFillStatements.getParameters()) {
constructorScope.putLeftValue(param.name(), param);
}
if (defaultParamInvokable.containsKey(constructorToFillStatements)) {
fillDefaultParamMethod(constructorToFillStatements, constructorScope);
} else {
// parse invoke super constructor statement
SClassDef parent = sClassDef.parent();
Ins.InvokeSpecial invokeConstructor = null;
assert (astClass == null && astObject != null) || (astClass != null && astObject == null);
AST.Invocation superWithInvocation = (
astClass == null) ? astObject.superWithInvocation
: astClass.superWithInvocation;
if (null == superWithInvocation) {
// invoke super();
for (SConstructorDef cons : parent.constructors()) {
if (cons.getParameters().size() == 0) {
invokeConstructor = new Ins.InvokeSpecial(new Ins.This(sClassDef), cons,
sClassDef.line_col());
break;
}
}
} else {
// invoke super with args
for (SConstructorDef cons : parent.constructors()) {
if (cons.getParameters().size() == superWithInvocation.args.size()) {
invokeConstructor = new Ins.InvokeSpecial(new Ins.This(sClassDef), cons,
superWithInvocation.line_col());
List parameters = cons.getParameters();
List args = superWithInvocation.args;
for (int i = 0; i < parameters.size(); ++i) {
Value v = parseValueFromExpression(args.get(i), parameters.get(i).type(), constructorScope);
invokeConstructor.arguments().add(v);
}
break;
}
}
}
if (null == invokeConstructor) {
err.SyntaxException("no suitable super constructor to invoke in " + sClassDef, sClassDef.line_col());
return;
}
constructorToFillStatements.statements().add(invokeConstructor);
// put field
for (SParameter param : constructorToFillStatements.getParameters()) {
SFieldDef f = null;
for (SFieldDef field : sClassDef.fields()) {
if (field.name().equals(param.name())) {
f = field;
break;
}
}
if (f == null) throw new LtBug("f should not be null");
Ins.PutField putField = new Ins.PutField(f, constructorScope.getThis(),
new Ins.TLoad(param, constructorScope, LineCol.SYNTHETIC), LineCol.SYNTHETIC, err);
constructorToFillStatements.statements().add(putField);
}
// a new constructor scope
// the parameters are ignored and all variables are fields
constructorScope = new SemanticScope(scope, constructorToFillStatements.meta());
constructorScope.setThis(new Ins.This(sTypeDef)); // set `this`
for (SParameter param : constructorToFillStatements.getParameters()) {
constructorScope.putLeftValue(constructorScope.generateTempName(), param);
}
paramValueAvaliable(constructorToFillStatements.getParameters(),
constructorToFillStatements.statements(), constructorScope,
constructorToFillStatements.line_col());
// parse this constructor
List statements = (
astClass == null) ? astObject.statements
: astClass.statements;
for (Statement stmt : statements) {
parseStatement(
stmt,
VoidType.get(),
constructorScope,
constructorToFillStatements.statements(),
constructorToFillStatements.exceptionTables(),
null, null,
true);
}
}
}
// parse method
// use traditional for loop because the method list might be modified
int methodSize = sClassDef.methods().size();
List methods = sClassDef.methods();
for (int i = 0; i < methodSize; i++) {
SMethodDef method = methods.get(i);
parseAnnoValues(method.annos());
parseMethod(method, methodToStatements.get(method), scope);
}
// if not function
if (sClassDef.classType() != SClassDef.FUN) {
assert (astClass == null && astObject != null) || (astClass != null && astObject == null);
List statements = (
astClass == null) ? astObject.statements
: astClass.statements;
// parse static
SemanticScope staticScope = new SemanticScope(scope, sClassDef.staticMeta());
if (sClassDef.classType() == SClassDef.OBJECT) {
SFieldDef singletonInstanceField = null;
for (SFieldDef f : sClassDef.fields()) {
if (f.name().equals(CompileUtil.SingletonFieldName)) {
singletonInstanceField = f;
break;
}
}
if (singletonInstanceField == null)
throw new LtBug("object class should have field " + CompileUtil.SingletonFieldName);
Ins.New aNew = new Ins.New(
sClassDef.constructors().get(0), LineCol.SYNTHETIC
);
Ins.PutStatic ps = new Ins.PutStatic(singletonInstanceField,
aNew, LineCol.SYNTHETIC, err);
sClassDef.staticStatements().add(ps);
}
for (Statement statement : statements) {
if (statement instanceof AST.StaticScope) {
AST.StaticScope sta = (AST.StaticScope) statement;
for (Statement stmt : sta.statements) {
parseStatement(
stmt,
VoidType.get(),
staticScope,
sClassDef.staticStatements(),
sClassDef.staticExceptionTable(),
null, null,
true);
}
}
}
}
} else if (sTypeDef instanceof SInterfaceDef) {
SInterfaceDef sInterfaceDef = (SInterfaceDef) sTypeDef;
ASTGHolder holder = originalInterfaces.get(sInterfaceDef.fullName());
InterfaceDef astInterface = holder.s;
parseAnnoValues(sInterfaceDef.annos());
SemanticScope scope = new SemanticScope(sInterfaceDef, null);
// parse method
// use traditional for loop because the method list might be modified
int methodSize = sInterfaceDef.methods().size();
List methods = sInterfaceDef.methods();
for (int i = 0; i < methodSize; ++i) {
SMethodDef method = methods.get(i);
parseMethod(method, methodToStatements.get(method), scope);
}
// parse static
SemanticScope staticScope = new SemanticScope(scope, sInterfaceDef.staticMeta());
for (Statement statement : astInterface.statements) {
if (statement instanceof AST.StaticScope) {
for (Statement statementInStatic : ((AST.StaticScope) statement).statements) {
parseStatement(
statementInStatic,
VoidType.get(),
staticScope,
sInterfaceDef.staticStatements(),
sInterfaceDef.staticExceptionTable(),
null, null,
true);
}
} else {
parseStatement(
statement,
VoidType.get(),
staticScope,
sInterfaceDef.staticStatements(),
sInterfaceDef.staticExceptionTable(),
null, null,
true);
}
}
} else if (!(sTypeDef instanceof SAnnoDef)) {
throw new LtBug("wrong STypeDefType " + sTypeDef.getClass());
}
}
}
private void generateGenericTemplateClass(STypeDef sTypeDef) throws SyntaxException {
Definition defi;
if (sTypeDef instanceof SClassDef) {
switch (((SClassDef) sTypeDef).classType()) {
case SClassDef.NORMAL:
defi = originalClasses.get(sTypeDef.fullName()).s;
break;
case SClassDef.OBJECT:
defi = originalObjects.get(sTypeDef.fullName()).s;
break;
case SClassDef.FUN:
defi = originalFunctions.get(sTypeDef.fullName()).s;
break;
default:
throw new LtBug("unknown class type: " + ((SClassDef) sTypeDef).classType());
}
} else if (sTypeDef instanceof SInterfaceDef) {
defi = originalInterfaces.get(sTypeDef.fullName()).s;
} else if (sTypeDef instanceof SAnnoDef) {
defi = originalInterfaces.get(sTypeDef.fullName()).s;
} else {
throw new LtBug("unknown sTypeDef " + sTypeDef);
}
String str = serializeObjectToString(defi);
// class
SClassDef c = new SClassDef(SClassDef.NORMAL, LineCol.SYNTHETIC);
c.setFullName(sTypeDef.fullName());
c.modifiers().add(SModifier.PUBLIC);
// field
SFieldDef f = new SFieldDef(LineCol.SYNTHETIC);
f.setName(Consts.AST_FIELD);
f.setType(getTypeWithName("java.lang.String", LineCol.SYNTHETIC));
f.modifiers().add(SModifier.PUBLIC);
f.modifiers().add(SModifier.STATIC);
c.fields().add(f);
f.setDeclaringType(c);
c.staticStatements().add(new Ins.PutStatic(f, new StringConstantValue(str), LineCol.SYNTHETIC, err));
f.alreadyAssigned();
// anno
SAnnoDef aDef = (SAnnoDef) getTypeWithName("lt.lang.GenericTemplate", LineCol.SYNTHETIC);
SAnno a = new SAnno();
a.setAnnoDef(aDef);
c.annos().add(a);
a.setPresent(c);
typeDefSet.add(c);
}
/**
* check whether overrides all methods from super
*
* @param sTypeDef sTypeDef
* @throws SyntaxException exception
*/
public void checkOverrideAllMethods(STypeDef sTypeDef) throws SyntaxException {
checkOverride(sTypeDef);
// check whether overrides all methods from super
if (sTypeDef instanceof SClassDef) {
SClassDef c = (SClassDef) sTypeDef;
if (c.modifiers().contains(SModifier.ABSTRACT)) return;
// check all abstract methods
// record them
List abstractMethods = new ArrayList();
recordAbstractMethodsForOverrideCheck(c, abstractMethods);
// do check
for (SMethodDef m : abstractMethods) {
boolean found = false;
for (SMethodDef overridden : m.overridden()) {
if (overridden.declaringType().equals(c)) {
found = true;
break;
}
}
if (!found) {
err.SyntaxException(m + " is not overridden in " + c, c.line_col());
return;
}
}
}
}
/**
* fill statements into default param invokable.
*
* @param invokable the invokable to fill
* @param scope the invokable scope
* @throws SyntaxException exception
*/
public void fillDefaultParamMethod(SInvokable invokable, SemanticScope scope) throws SyntaxException {
Map invokePair = defaultParamInvokable.get(invokable);
SInvokable methodToInvoke = invokePair.keySet().iterator().next();
Expression arg = invokePair.get(methodToInvoke);
if (invokable instanceof SConstructorDef) {
// invoke another constructor
Ins.InvokeSpecial invoke = new Ins.InvokeSpecial(scope.getThis(), methodToInvoke, LineCol.SYNTHETIC);
for (SParameter p : invokable.getParameters()) {
invoke.arguments().add(new Ins.TLoad(p, scope, LineCol.SYNTHETIC));
}
List paramsOfLast = methodToInvoke.getParameters();
invoke.arguments().add(parseValueFromExpression(arg, paramsOfLast.get(paramsOfLast.size() - 1).type(),
scope));
invokable.statements().add(invoke);
} else {
assert invokable instanceof SMethodDef;
SMethodDef methodDef = (SMethodDef) invokable;
SMethodDef lastMethod = (SMethodDef) methodToInvoke;
boolean isStatic = lastMethod.modifiers().contains(SModifier.STATIC);
Ins.Invoke invoke;
if (lastMethod.modifiers().contains(SModifier.PRIVATE)) {
invoke = new Ins.InvokeSpecial(new Ins.This(methodDef.declaringType()), lastMethod, LineCol.SYNTHETIC);
} else if (isStatic) {
invoke = new Ins.InvokeStatic(lastMethod, LineCol.SYNTHETIC);
} else {
invoke = new Ins.InvokeVirtual(new Ins.This(methodDef.declaringType()), lastMethod, LineCol.SYNTHETIC);
}
for (int ii = 0; ii < methodDef.getParameters().size(); ++ii) {
// load arguments
invoke.arguments().add(new Ins.TLoad(methodDef.getParameters().get(ii), scope, LineCol.SYNTHETIC));
}
List lastParams = lastMethod.getParameters();
invoke.arguments().add(parseValueFromExpression(arg, lastParams.get(lastParams.size() - 1).type(), null));
if (methodDef.getReturnType().equals(VoidType.get())) {
methodDef.statements().add(invoke);
} else {
methodDef.statements().add(new Ins.TReturn(invoke, LineCol.SYNTHETIC));
}
}
}
/**
* override {@link Object#toString()} {@link Object#hashCode()} {@link Object#equals(Object)}
* and generate g/setters for the data class
* and generate unapply
*
* @param cls the class should be data class
* @throws SyntaxException compiling error
*/
public void fillMethodsIntoDataClass(SClassDef cls) throws SyntaxException {
// check parameter modifiers
// cannot be `val`
for (SParameter p : cls.constructors().get(0).getParameters()) {
if (!p.canChange()) {
err.SyntaxException("data class cannot have `val` parameters", cls.line_col());
return;
}
}
// implement Cloneable and Serializable
cls.superInterfaces().add((SInterfaceDef) getTypeWithName(Cloneable.class.getName(), LineCol.SYNTHETIC));
cls.superInterfaces().add((SInterfaceDef) getTypeWithName(Serializable.class.getName(), LineCol.SYNTHETIC));
// add clone()=super.clone()
SMethodDef methodClone = new SMethodDef(LineCol.SYNTHETIC);
methodClone.setName("clone");
methodClone.setDeclaringType(cls);
cls.methods().add(methodClone);
methodClone.setReturnType(getObject_Class());
methodClone.modifiers().add(SModifier.PUBLIC);
SMethodDef Object_clone = null;
for (SMethodDef m : getObject_Class().methods()) {
if (m.name().equals("clone")) {
Object_clone = m;
break;
}
}
assert Object_clone != null;
Ins.InvokeSpecial invokeObjectClone = new Ins.InvokeSpecial(new Ins.This(cls), Object_clone, LineCol.SYNTHETIC);
methodClone.statements().add(new Ins.TReturn(invokeObjectClone, LineCol.SYNTHETIC));
Map setters = new HashMap();
Map getters = new HashMap();
SMethodDef toStringOverride = null;
SMethodDef equalsOverride = null;
SMethodDef hashCodeOverride = null;
SMethodDef unapply = null;
SConstructorDef zeroParamCons = null;
// get existing setters
for (SFieldDef f : cls.fields()) {
if (f.modifiers().contains(SModifier.STATIC)) continue;
String name = f.name();
String setterName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
String getterName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
for (SMethodDef m : cls.methods()) {
if (m.name().equals(setterName)
&&
m.getParameters().size() == 1
&&
m.getParameters().get(0).type().equals(f.type())) {
setters.put(f, m);
}
if (m.name().equals(getterName)
&&
m.getParameters().size() == 0) {
getters.put(f, m);
}
}
}
// get existing equals(o)/toString()/hashCode()/unapply(?)
for (SMethodDef m : cls.methods()) {
if (m.name().equals("toString") && m.getParameters().size() == 0) toStringOverride = m;
if (m.name().equals("equals")
&& m.getParameters().size() == 1
&& m.getParameters().get(0).type().fullName().equals("java.lang.Object"))
equalsOverride = m;
if (m.name().equals("hashCode") && m.getParameters().size() == 0) hashCodeOverride = m;
if (m.name().equals("unapply") && m.getParameters().size() == 1
&& m.getParameters().get(0).type().isAssignableFrom(cls)) unapply = m;
}
// get existing zero param constructor
for (SConstructorDef con : cls.constructors()) {
if (con.getParameters().isEmpty()) {
zeroParamCons = con;
break;
}
}
SemanticScope scope = new SemanticScope(cls, null);
scope.setThis(new Ins.This(cls));
String className = cls.fullName();
String simpleName = className.contains(".") ? className.substring(className.lastIndexOf(".") + 1) : className;
LineCol lineCol = new LineCol(cls.line_col().fileName, 0, 0);
if (toStringOverride == null) {
// toString():String
// return "SimpleName("+
// "field="+field+
// ", field2="+field2+
// ...+
// ")"
toStringOverride = new SMethodDef(lineCol);
toStringOverride.setName("toString");
toStringOverride.setDeclaringType(cls);
cls.methods().add(toStringOverride);
toStringOverride.setReturnType(getTypeWithName("java.lang.String", lineCol));
toStringOverride.modifiers().add(SModifier.PUBLIC);
// SimpleName(
Expression lastExp = new StringLiteral("\"" + simpleName + "(\"", lineCol);
// fields
boolean isFirst = true;
for (SFieldDef f : cls.fields()) {
if (f.modifiers().contains(SModifier.STATIC)) continue;
String literal = "";
if (isFirst) {
isFirst = false;
} else {
literal = ", ";
}
literal += (f.name() + "=");
StringLiteral s = new StringLiteral("\"" + literal + "\"", lineCol);
lastExp = new TwoVariableOperation(
"+", lastExp, s, lineCol
);
lastExp = new TwoVariableOperation(
"+", lastExp, new AST.Access(
new AST.Access(null, "this", lineCol),
f.name(), lineCol),
lineCol
);
}
// )
StringLiteral s2 = new StringLiteral("\")\"", lineCol);
lastExp = new TwoVariableOperation(
"+", lastExp, s2, lineCol
);
Statement stmt = new AST.Return(lastExp, lineCol);
parseStatement(stmt, toStringOverride.getReturnType(),
new SemanticScope(scope, toStringOverride.meta()),
toStringOverride.statements(), toStringOverride.exceptionTables(),
null, null, false);
}
if (hashCodeOverride == null) {
// hashCode():int
// return field1 as int +
// field2 as int +
// ...
hashCodeOverride = new SMethodDef(lineCol);
hashCodeOverride.setName("hashCode");
hashCodeOverride.setDeclaringType(cls);
cls.methods().add(hashCodeOverride);
hashCodeOverride.setReturnType(IntTypeDef.get());
hashCodeOverride.modifiers().add(SModifier.PUBLIC);
Expression lastExp;
if (cls.fields().isEmpty()) {
lastExp = new NumberLiteral("0", lineCol);
} else {
Iterator it = cls.fields().iterator();
// LtRuntime.getHashCode(this.field)
lastExp = null;
while (it.hasNext()) {
SFieldDef f = it.next();
if (f.modifiers().contains(SModifier.STATIC)) continue;
Expression exp = new AST.Invocation(
new AST.Access(
new AST.Access(
new AST.PackageRef("lt::runtime", lineCol),
"LtRuntime",
lineCol
),
"getHashCode",
lineCol
),
Collections.singletonList(
new AST.Access(
new AST.Access(
null, "this", lineCol
),
f.name(),
lineCol
)
),
false,
lineCol
);
if (lastExp == null) {
lastExp = exp;
} else {
lastExp =
new TwoVariableOperation(
"+",
lastExp,
exp, lineCol
);
}
}
}
if (lastExp == null) {
lastExp = new NumberLiteral("0", lineCol);
}
Statement stmt = new AST.Return(lastExp, lineCol);
parseStatement(stmt, hashCodeOverride.getReturnType(),
new SemanticScope(scope, hashCodeOverride.meta()),
hashCodeOverride.statements(), hashCodeOverride.exceptionTables(),
null, null, false);
}
if (equalsOverride == null) {
// equals(o):bool
// return o is type CurrentType and
// o.field1 is this.field1 and
// o.field2 is this.field2
// ...
equalsOverride = new SMethodDef(lineCol);
equalsOverride.setName("equals");
equalsOverride.setDeclaringType(cls);
cls.methods().add(equalsOverride);
equalsOverride.setReturnType(BoolTypeDef.get());
SParameter o = new SParameter();
o.setTarget(equalsOverride);
equalsOverride.getParameters().add(o);
o.setName("o");
o.setType(getTypeWithName("java.lang.Object", lineCol));
equalsOverride.modifiers().add(SModifier.PUBLIC);
// o is type CurrentType
Expression lastExp = new TwoVariableOperation(
"is",
new AST.Access(null, "o", lineCol),
new AST.TypeOf(
new AST.Access(null, simpleName, lineCol),
lineCol
),
lineCol
);
for (SFieldDef f : cls.fields()) {
if (f.modifiers().contains(SModifier.STATIC)) continue;
lastExp = new TwoVariableOperation(
"and",
lastExp,
// o.field is this.field
new TwoVariableOperation(
"is",
new AST.Access(
new AST.Access(
null, "o", lineCol
),
f.name(),
lineCol
),
new AST.Access(
new AST.Access(
null, "this", lineCol
),
f.name(),
lineCol
),
lineCol
),
lineCol
);
}
Statement stmt = new AST.Return(lastExp, lineCol);
SemanticScope equalsScope = new SemanticScope(scope, equalsOverride.meta());
equalsScope.putLeftValue("o", o);
parseStatement(stmt, equalsOverride.getReturnType(),
equalsScope,
equalsOverride.statements(), equalsOverride.exceptionTables(),
null, null, false);
}
if (zeroParamCons == null) {
zeroParamCons = new SConstructorDef(lineCol);
zeroParamCons.setDeclaringType(cls);
cls.constructors().add(zeroParamCons);
zeroParamCons.modifiers().add(SModifier.PUBLIC);
SConstructorDef con = cls.constructors().get(cls.constructors().size() - 2);
List initValues = new ArrayList();
for (SParameter p : con.getParameters()) {
if (p.type().equals(IntTypeDef.get())) {
initValues.add(new IntValue(0));
} else if (p.type().equals(ShortTypeDef.get())) {
initValues.add(new ShortValue((short) 0));
} else if (p.type().equals(ByteTypeDef.get())) {
initValues.add(new ByteValue((byte) 0));
} else if (p.type().equals(BoolTypeDef.get())) {
initValues.add(new BoolValue(false));
} else if (p.type().equals(CharTypeDef.get())) {
initValues.add(new CharValue((char) 0));
} else if (p.type().equals(LongTypeDef.get())) {
initValues.add(new LongValue(0));
} else if (p.type().equals(FloatTypeDef.get())) {
initValues.add(new FloatValue(0));
} else if (p.type().equals(DoubleTypeDef.get())) {
initValues.add(new DoubleValue(0));
} else {
initValues.add(NullValue.get());
}
}
Ins.InvokeSpecial is = new Ins.InvokeSpecial(
new Ins.This(cls),
con,
LineCol.SYNTHETIC
);
is.arguments().addAll(initValues);
zeroParamCons.statements().add(is);
// annotations
for (SAnno anno : con.annos()) {
SAnno newAnno = new SAnno();
newAnno.setAnnoDef(anno.type());
newAnno.setPresent(zeroParamCons);
newAnno.values().putAll(anno.values());
zeroParamCons.annos().add(newAnno);
}
}
for (SFieldDef f : cls.fields()) {
if (f.modifiers().contains(SModifier.STATIC)) continue;
SMethodDef getter = getters.get(f);
SMethodDef setter = setters.get(f);
String name = f.name();
name = name.substring(0, 1).toUpperCase() + name.substring(1);
if (getter == null) {
// getField():Type
// return this.field
getter = new SMethodDef(lineCol);
getter.setName("get" + name);
getter.setDeclaringType(cls);
cls.methods().add(getter);
getter.setReturnType(f.type());
getter.modifiers().add(SModifier.PUBLIC);
Statement stmt = new AST.Return(
new AST.Access(
new AST.Access(null, "this", lineCol),
f.name(),
lineCol
),
lineCol
);
parseStatement(stmt, getter.getReturnType(), new SemanticScope(scope, getter.meta()), getter.statements(),
getter.exceptionTables(), null, null, false);
}
if (setter == null) {
// setField(field:Type)
// this.field=field
setter = new SMethodDef(lineCol);
setter.setName("set" + name);
setter.setDeclaringType(cls);
cls.methods().add(setter);
setter.setReturnType(VoidType.get());
SParameter p = new SParameter();
p.setName(f.name());
setter.getParameters().add(p);
p.setTarget(setter);
p.setType(f.type());
setter.modifiers().add(SModifier.PUBLIC);
SemanticScope setterScope = new SemanticScope(scope, setter.meta());
setterScope.putLeftValue(f.name(), p);
Statement stmt = new AST.Assignment(
new AST.Access(
new AST.Access(null, "this", lineCol),
f.name(),
lineCol
),
"=",
new AST.Access(null, f.name(), lineCol),
lineCol
);
parseStatement(stmt, setter.getReturnType(), setterScope, setter.statements(),
setter.exceptionTables(), null, null, false);
}
}
if (unapply == null) {
unapply = new SMethodDef(LineCol.SYNTHETIC);
unapply.setName("unapply");
unapply.setDeclaringType(cls);
unapply.setReturnType(getTypeWithName("java.util.List", LineCol.SYNTHETIC));
unapply.modifiers().add(SModifier.PUBLIC);
unapply.modifiers().add(SModifier.STATIC);
cls.methods().add(unapply);
SParameter p = new SParameter();
p.setTarget(unapply);
p.setName("o");
p.setType(cls);
unapply.getParameters().add(p);
// static { unapply }
SemanticScope unapplyScope = new SemanticScope(new SemanticScope(cls, null), unapply.meta());
unapplyScope.putLeftValue("o", p);
List tmpList = new ArrayList();
for (SFieldDef f : cls.fields()) {
tmpList.add(new AST.Access(
// o.f
new AST.Access(null, "o", LineCol.SYNTHETIC),
f.name(), LineCol.SYNTHETIC
));
}
AST.Return ret = new AST.Return(
new AST.ArrayExp(tmpList, LineCol.SYNTHETIC),
LineCol.SYNTHETIC
);
parseStatement(ret, unapply.getReturnType(), unapplyScope, unapply.statements(),
unapply.exceptionTables(), null, null, false);
}
}
/**
* in step 4
* fills the method with instructions
*
* @param methodDef the method to be filled with instructions
* @param statements statements to be parsed into instructions
* @param superScope the class scope
* @throws SyntaxException compile error
*/
public void parseMethod(SMethodDef methodDef, List statements, SemanticScope superScope) throws SyntaxException {
if (!methodDef.statements().isEmpty()) return;
if (methodDef.modifiers().contains(SModifier.ABSTRACT)) {
if (!statements.isEmpty()) {
err.SyntaxException("abstract method cannot contain statements", statements.get(0).line_col());
return;
}
return;
}
if (methodDef.declaringType() instanceof SInterfaceDef) {
if (!statements.isEmpty()) {
err.SyntaxException("default methods are not supported in interfaces", statements.get(0).line_col());
return;
}
}
if (methodDef.modifiers().contains(SModifier.STATIC) && methodDef.declaringType() instanceof SInterfaceDef) {
err.SyntaxException("static methods are not allowed in interfaces", methodDef.line_col());
return;
}
// fill in return
if (!methodDef.getReturnType().equals(VoidType.get())) {
transformLastExpToReturn(statements);
}
SemanticScope scope = new SemanticScope(superScope, methodDef.meta());
if (!methodDef.modifiers().contains(SModifier.STATIC)) {
scope.setThis(new Ins.This(scope.type()));
}
for (SParameter p : methodDef.getParameters()) {
if (p.canChange() && !isPointerType(p.type()) && CompileUtil.isValidName(p.name())) {
scope.putLeftValue(scope.generateTempName(), p);
} else {
scope.putLeftValue(p.name(), p);
}
}
if (defaultParamInvokable.containsKey(methodDef)) {
fillDefaultParamMethod(methodDef, scope);
} else {
paramValueAvaliable(methodDef.getParameters(), methodDef.statements(), scope, methodDef.line_col());
for (SParameter p : methodDef.getParameters()) {
if (p.canChange() && !isPointerType(p.type()) && CompileUtil.isValidName(p.name())) {
// get the value and put into container
PointerType t = new PointerType(p.type());
if (types.containsKey(t.toString())) {
t = (PointerType) types.get(t.toString());
} else {
types.put(t.toString(), t);
}
LocalVariable local = new LocalVariable(t, p.canChange());
scope.putLeftValue(p.name(), local);
local.setWrappingParam(p);
// local = new Pointer(p)
Ins.TStore tStore = new Ins.TStore(
local, invokePointerSet(
constructPointer(p.isNotNull(), p.isNotEmpty()),
new Ins.TLoad(p, scope, LineCol.SYNTHETIC),
LineCol.SYNTHETIC),
scope, LineCol.SYNTHETIC, err);
tStore.flag |= Consts.IS_POINTER_NEW;
methodDef.statements().add(tStore);
}
}
// fill statements
if (statements.isEmpty()) {
methodDef.statements().add(new Ins.Nop());
} else {
for (Statement stmt : statements) {
parseStatement(
stmt,
methodDef.getReturnType(),
scope,
methodDef.statements(),
methodDef.exceptionTables(),
null, null,
false);
}
}
}
}
private SConstructorDef java_lang_NullPointerException_cons;
private SConstructorDef getJava_lang_NullPointerException_cons() throws SyntaxException {
if (java_lang_NullPointerException_cons == null) {
SClassDef NPE = (SClassDef) getTypeWithName("java.lang.NullPointerException", LineCol.SYNTHETIC);
for (SConstructorDef cons : NPE.constructors()) {
if (cons.getParameters().isEmpty()) {
java_lang_NullPointerException_cons = cons;
break;
}
}
}
return java_lang_NullPointerException_cons;
}
private SConstructorDef java_lang_IllegalArgumentException_cons;
private SConstructorDef getJava_lang_IllegalArgumentException_cons() throws SyntaxException {
if (java_lang_IllegalArgumentException_cons == null) {
SClassDef IAE = (SClassDef) getTypeWithName("java.lang.IllegalArgumentException", LineCol.SYNTHETIC);
for (SConstructorDef cons : IAE.constructors()) {
if (cons.getParameters().isEmpty()) {
java_lang_IllegalArgumentException_cons = cons;
break;
}
}
}
return java_lang_IllegalArgumentException_cons;
}
/**
* handle nonnull/nonempty modifiers on parameters
*
* @param params parameters
* @param instructions instructions
* @param scope current scope
* @param lineCol line and column
* @throws SyntaxException compiling error
*/
public void paramValueAvaliable(List params, List instructions, SemanticScope scope, LineCol lineCol) throws SyntaxException {
for (SParameter param : params) {
if (param.isNotEmpty()) {
Ins.Nop nop = new Ins.Nop();
Ins.IfNe ifNe = new Ins.IfNe(
cast(BoolTypeDef.get(), new Ins.TLoad(param, scope, lineCol), scope.type(), lineCol),
nop, lineCol);
instructions.add(ifNe);
instructions.add(new Ins.AThrow(
new Ins.New(
getJava_lang_IllegalArgumentException_cons(), lineCol
), lineCol));
instructions.add(nop);
} else if (param.isNotNull()) {
if (param.type() instanceof PrimitiveTypeDef) {
continue;
}
// null
Ins.Nop nop = new Ins.Nop();
Ins.IfNonNull ifNonNull = new Ins.IfNonNull(
new Ins.TLoad(param, scope, lineCol),
nop, lineCol);
instructions.add(ifNonNull);
instructions.add(new Ins.AThrow(
new Ins.New(
getJava_lang_NullPointerException_cons(), lineCol
), lineCol));
instructions.add(nop);
// unit
Ins.Nop nop2 = new Ins.Nop();
Ins.IfACmpNe ifACmpNe = new Ins.IfACmpNe(
new Ins.TLoad(param, scope, lineCol),
invoke_Unit_get(lineCol),
nop2, lineCol
);
instructions.add(ifACmpNe);
instructions.add(new Ins.AThrow(
new Ins.New(
getJava_lang_IllegalArgumentException_cons(), lineCol
), lineCol));
instructions.add(nop2);
}
}
}
private void transformLastExpToReturn(List statements) {
if (statements.isEmpty()) return;
int lastIndex = statements.size() - 1;
Statement lastStmt = statements.get(lastIndex);
if (lastStmt instanceof Expression) {
AST.Return ret = new AST.Return((Expression) lastStmt, lastStmt.line_col());
statements.set(lastIndex, ret);
} else if (lastStmt instanceof AST.If) {
for (AST.If.IfPair pair : ((AST.If) lastStmt).ifs) {
transformLastExpToReturn(pair.body);
}
} else if (lastStmt instanceof AST.Synchronized) {
transformLastExpToReturn(((AST.Synchronized) lastStmt).statements);
} else if (lastStmt instanceof AST.Try) {
AST.Try aTry = (AST.Try) lastStmt;
transformLastExpToReturn(aTry.statements);
transformLastExpToReturn(aTry.catchStatements);
}
}
/**
* in step 4
* fill the given annotation with parsed values
*
* @param annos annotations to fill values
* @throws SyntaxException exception
*/
public void parseAnnoValues(Collection annos) throws SyntaxException {
// check annotation
for (SAnno sAnno : annos) {
AST.Anno anno = annotationRecorder.get(sAnno); // get original anno object
Map map = new HashMap();
out:
for (SAnnoField f : sAnno.type().annoFields()) {
if (anno == null) {
for (Map.Entry entry : sAnno.alreadyCompiledAnnotationValueMap().entrySet()) {
if (entry.getKey().equals(f.name())) {
// find annotation field
Value v = parseValueFromObject(entry.getValue());
map.put(f, v);
continue out;
}
}
} else {
for (AST.Assignment a : anno.args) {
if (a.assignTo.name.equals(f.name())) {
// find annotation field
Value v = parseValueFromExpression(a.assignFrom, f.type(), null);
v = checkAndCastAnnotationValues(v, a.assignTo.line_col());
map.put(f, v);
continue out;
}
}
}
// not found, check defaultValue
if (!f.hasDefaultValue()) {
err.SyntaxException(f.name() + " is missing",
anno == null ? LineCol.SYNTHETIC : anno.line_col());
return;
}
}
sAnno.values().putAll(map);
}
}
/**
* check and cast annotation values
*
* @param value value
* @param lineCol line column
* @return the cast value
* @throws SyntaxException compiling error
*/
public Value checkAndCastAnnotationValues(Value value, LineCol lineCol) throws SyntaxException {
if (value instanceof IntValue
|| value instanceof ShortValue
|| value instanceof ByteValue
|| value instanceof CharValue
|| value instanceof BoolValue
|| value instanceof LongValue
|| value instanceof DoubleValue
|| value instanceof FloatValue
|| value instanceof SArrayValue
|| value instanceof StringConstantValue
|| value instanceof SAnno
|| value instanceof Ins.GetClass
|| value instanceof EnumValue) {
return value;
} else if (value instanceof Ins.GetStatic) {
Ins.GetStatic gs = (Ins.GetStatic) value;
EnumValue enumValue = new EnumValue();
enumValue.setType(gs.field().declaringType());
enumValue.setEnumStr(gs.field().name());
return enumValue;
} else if (value instanceof Ins.NewArray || value instanceof Ins.ANewArray) {
List theValues =
value instanceof Ins.NewArray
? ((Ins.NewArray) value).initValues()
: ((Ins.ANewArray) value).initValues();
SArrayValue arr = new SArrayValue();
arr.setDimension(1);
arr.setType((SArrayTypeDef) value.type());
List values = new ArrayList();
for (Value v : theValues) {
values.add(checkAndCastAnnotationValues(v, lineCol));
}
arr.setValues(values.toArray(new Value[values.size()]));
return arr;
} else if (value instanceof DummyValue) {
return value;
} else {
err.SyntaxException("invalid annotation field " + value, lineCol);
return null;
}
}
/**
* check whether the literal is int type
* also, the requiredType should be int or superclass or Integer or null
*
* @param requiredType required type(should be int or superclass of Integer)
* or null
* @param literal int literal
* @param lineCol file_line_col
* @return true if it's int(boxed or primitive)
* @throws SyntaxException exception
*/
public boolean isInt(STypeDef requiredType, NumberLiteral literal, LineCol lineCol) throws SyntaxException {
return (requiredType == null ||
requiredType instanceof IntTypeDef ||
(requiredType instanceof SClassDef && requiredType.isAssignableFrom(
getTypeWithName("java.lang.Integer", lineCol))
)) &&
// &&
!literal.literal().contains(".");
}
/**
* check whether the literal is long type
*
* @param requiredType required type(should be long or superclass of Long)
* or null
* @param literal long literal
* @param lineCol file_line_col
* @return true if it's long(boxed or primitive)
* @throws SyntaxException exception
*/
public boolean isLong(STypeDef requiredType, NumberLiteral literal, LineCol lineCol) throws SyntaxException {
return (requiredType == null ||
requiredType instanceof LongTypeDef ||
(requiredType instanceof SClassDef) && requiredType.isAssignableFrom(
getTypeWithName("java.lang.Long", lineCol)
)) &&
// &&
!literal.literal().contains(".");
}
/**
* check whether the literal is short type
*
* @param requiredType required type(should be short or superclass of Short)
* or null
* @param literal short literal
* @param lineCol file_line_col
* @return true if it's short(boxed or primitive)
* @throws SyntaxException exception
*/
public boolean isShort(STypeDef requiredType, NumberLiteral literal, LineCol lineCol) throws SyntaxException {
return (requiredType == null ||
requiredType instanceof ShortTypeDef ||
requiredType instanceof SClassDef && requiredType.isAssignableFrom(
getTypeWithName("java.lang.Short", lineCol)
)) &&
// &&
!literal.literal().contains(".");
}
/**
* check whether the literal is byte type
*
* @param requiredType required type(should be byte or superclass of Byte)
* or null
* @param literal byte literal
* @param lineCol file_line_col
* @return true if it's byte(boxed or primitive)
* @throws SyntaxException exception
*/
public boolean isByte(STypeDef requiredType, NumberLiteral literal, LineCol lineCol) throws SyntaxException {
return (requiredType == null ||
requiredType instanceof ByteTypeDef ||
requiredType instanceof SClassDef && requiredType.isAssignableFrom(
getTypeWithName("java.lang.Byte", lineCol)
)) &&
// &&
!literal.literal().contains(".");
}
/**
* check whether the required type is float type
* number literal always will match float value
*
* @param requiredType required type(should be float or superclass of Float)
* or null
* @param lineCol file_line_col
* @return true if it's float(boxed or primitive)
* @throws SyntaxException exception
*/
public boolean isFloat(STypeDef requiredType, LineCol lineCol) throws SyntaxException {
return (requiredType == null ||
requiredType instanceof FloatTypeDef ||
requiredType instanceof SClassDef && requiredType.isAssignableFrom(
getTypeWithName("java.lang.Float", lineCol)
));
}
/**
* check whether the required type is double type
* number literal always will match double value
*
* @param requiredType required type(should be double or superclass of Double)
* or null
* @param lineCol file_line_col
* @return true if it's double(boxed or primitive)
* @throws SyntaxException exception
*/
public boolean isDouble(STypeDef requiredType, LineCol lineCol) throws SyntaxException {
return (requiredType == null ||
requiredType instanceof DoubleTypeDef ||
requiredType instanceof SClassDef && requiredType.isAssignableFrom(
getTypeWithName("java.lang.Double", lineCol)
));
}
/**
* check whetehr the required type is bool type
*
* @param requiredType required type(should be boolean or superclass of Boolean)
* or null
* @param lineCol file_line_col
* @return true if it's boolean(boxed or primitve)
* @throws SyntaxException exception
*/
public boolean isBool(STypeDef requiredType, LineCol lineCol) throws SyntaxException {
return (requiredType == null ||
requiredType instanceof BoolTypeDef ||
requiredType instanceof SClassDef && requiredType.isAssignableFrom(
getTypeWithName("java.lang.Boolean", lineCol)
));
}
/**
* check whether the literal is char type
* the literal should be string literal with only one character and required type should match char or Character
* if the requiredType is Character or char, the literal can be surrounded with either \" or \'.
* if the requiredType is not Character but assignable from Character and not assignable from String, the literal can be surrounded with either \" or \'
* if the requiredType is assignable from both Character and String, the literal should be surrounded with \' and length should == 1
*
* @param requiredType required type
* @param literal literal
* @param lineCol file_line_col
* @return true if it's char
* @throws SyntaxException exception
*/
public boolean isChar(STypeDef requiredType, StringLiteral literal, LineCol lineCol) throws SyntaxException {
if (requiredType == null)
return isChar(literal, lineCol, true);
if (requiredType instanceof CharTypeDef)
return isChar(literal, lineCol, false);
if (requiredType instanceof SClassDef) {
SClassDef characterClass = (SClassDef) getTypeWithName("java.lang.Character", lineCol);
if (requiredType.equals(characterClass)) return true; // Character
if (requiredType.isAssignableFrom(characterClass)) {
SClassDef stringClass = (SClassDef) getTypeWithName("java.lang.String", lineCol);
if (requiredType.isAssignableFrom(stringClass)) {
// assignable from Character and String
// check start with \' and length == 1
return isChar(literal, lineCol, true);
} else // assignable from only Character
return isChar(literal, lineCol, false);
}
}
return false;
}
/**
* check whether the literal is char
* the method only checks literal start symbol and it's length
*
* @param literal literal
* @param lineCol file_line_col
* @param testSymbol true if the symbol is taken into consideration
* @return true if the literal is char
* @throws SyntaxException exception
*/
public boolean isChar(StringLiteral literal, LineCol lineCol, boolean testSymbol) throws SyntaxException {
String str = literal.literal();
str = str.substring(1);
str = str.substring(0, str.length() - 1);
// testSymbol==true and not start with \' then return false
if (testSymbol && !literal.literal().startsWith("\'")) return false;
// check whether the string length is 1
String s = unescape(str, lineCol);
assert s != null;
return s.length() == 1;
}
private SClassDef Throwable_Class;
public SClassDef getThrowable_Class() throws SyntaxException {
if (Throwable_Class == null) {
Throwable_Class = (SClassDef) getTypeWithName("java.lang.Throwable", LineCol.SYNTHETIC);
}
return Throwable_Class;
}
/**
* in step 4
* parse instructions
*
* - {@link Statement} => {@link Instruction}
* - {@link Expression} => {@link Value}
* - {@link lt.compiler.syntactic.AST.Return} => {@link lt.compiler.semantic.Ins.TReturn}
* - {@link lt.compiler.syntactic.AST.If} => a list of Instructions containing {@link lt.compiler.semantic.Ins.IfEq} {@link lt.compiler.semantic.Ins.IfNe} {@link lt.compiler.semantic.Ins.Goto}
* - {@link lt.compiler.syntactic.AST.While} => a list of Instructions (same as above)
* - {@link lt.compiler.syntactic.AST.For} => a list of Instructions (same as above)
* - {@link lt.compiler.syntactic.AST.Throw} => {@link lt.compiler.semantic.Ins.AThrow}
* - {@link lt.compiler.syntactic.AST.Try} => a list of Instructions and some ExceptionTable
* - {@link lt.compiler.syntactic.AST.Synchronized} => {@link lt.compiler.semantic.Ins.MonitorEnter} and {@link lt.compiler.semantic.Ins.MonitorExit}
* - {@link MethodDef} => {@link #parseInnerMethod(MethodDef, SemanticScope, boolean)}
* - {@link lt.compiler.syntactic.AST.Break => goto instruction}
* - {@link lt.compiler.syntactic.AST.Continue => goto instruction}
*
*
* @param statement instructions
* @param methodReturnType the method's return type
* @param scope scope that contains local variables and local methods
* @param instructions currently parsing {@link SInvokable} object instructions
* @param exceptionTable the exception table (start,end,handle,type)
* @param breakIns jump to this position when meets a break (or null if it's not inside any loop)
* @param continueIns jump to this position when meets a continue (or null if it's not inside any loop)
* @param doNotParseMethodDef the methodDef should not be parsed( in this case, they should be outer methods instead of inner methods)
* @throws SyntaxException compile error
*/
public void parseStatement(Statement statement,
STypeDef methodReturnType,
SemanticScope scope,
List instructions,
List exceptionTable,
Ins.Nop breakIns,
Ins.Nop continueIns,
boolean doNotParseMethodDef) throws SyntaxException {
if (statement instanceof Expression) {
// expression
// required type is null , which means no required type
Value v = parseValueFromExpression((Expression) statement, null, scope);
if (v instanceof Instruction) {
instructions.add((Instruction) v);
} // else ignore the result
} else if (statement instanceof AST.Return) {
parseInstructionFromReturn((AST.Return) statement, methodReturnType, scope, instructions);
} else if (statement instanceof AST.If) {
parseInstructionFromIf((AST.If) statement, methodReturnType, scope,
instructions, exceptionTable, breakIns, continueIns);
} else if (statement instanceof AST.While) {
// while or do while
parseInstructionFromWhile((AST.While) statement, methodReturnType, scope, instructions, exceptionTable);
} else if (statement instanceof AST.For) {
// for i in xxx
parseInstructionFromFor((AST.For) statement, methodReturnType, scope, instructions, exceptionTable);
} else if (statement instanceof AST.Throw) {
// throw xxx
Value throwable = parseValueFromExpression(
((AST.Throw) statement).exp,
null,
scope);
assert throwable != null;
if (!getThrowable_Class().isAssignableFrom(throwable.type())) {
// cast to throwable
Ins.InvokeStatic invokeStatic = new Ins.InvokeStatic(getLang_castToThrowable(), LineCol.SYNTHETIC);
invokeStatic.arguments().add(throwable);
throwable = invokeStatic;
}
Ins.AThrow aThrow = new Ins.AThrow(throwable, statement.line_col());
instructions.add(aThrow);
} else if (statement instanceof AST.Try) {
parseInstructionFromTry((AST.Try) statement, methodReturnType, scope, instructions, exceptionTable, breakIns, continueIns);
} else if (statement instanceof AST.Synchronized) {
parseInstructionFromSynchronized((AST.Synchronized) statement,
methodReturnType,
scope,
instructions,
exceptionTable,
breakIns, continueIns);
} else if (statement instanceof MethodDef) {
if (!doNotParseMethodDef)
parseInnerMethod((MethodDef) statement, scope, false);
} else if (statement instanceof AST.Break) {
if (breakIns == null) {
err.SyntaxException("break should be inside a loop", statement.line_col());
return;
}
instructions.add(new Ins.Goto(breakIns));
} else if (statement instanceof AST.Continue) {
if (continueIns == null) {
err.SyntaxException("continue should be inside a loop", statement.line_col());
return;
}
instructions.add(new Ins.Goto(continueIns));
} else if (!(statement instanceof AST.StaticScope || statement instanceof AST.Pass)) {
throw new LtBug("unknown statement " + statement);
}
}
/**
* {@link LtRuntime#castToBool(Object)}
*/
private SMethodDef Lang_castToBool;
/**
* @return {@link LtRuntime#castToThrowable(Object)}
* @throws SyntaxException exception
*/
public SMethodDef getLang_castToBool() throws SyntaxException {
if (Lang_castToBool == null) {
SClassDef Lang = (SClassDef) getTypeWithName("lt.runtime.LtRuntime", LineCol.SYNTHETIC);
assert Lang != null;
for (SMethodDef m : Lang.methods()) {
if (m.name().equals("castToBool")) {
Lang_castToBool = m;
break;
}
}
}
return Lang_castToBool;
}
/**
* {@link LtRuntime#castToThrowable(Object)}
*/
private SMethodDef Lang_castToThrowable;
/**
* @return {@link LtRuntime#castToThrowable(Object)}
* @throws SyntaxException exception
*/
public SMethodDef getLang_castToThrowable() throws SyntaxException {
if (Lang_castToThrowable == null) {
SClassDef Lang = (SClassDef) getTypeWithName("lt.runtime.LtRuntime", LineCol.SYNTHETIC);
assert Lang != null;
for (SMethodDef m : Lang.methods()) {
if (m.name().equals("castToThrowable")) {
Lang_castToThrowable = m;
break;
}
}
}
return Lang_castToThrowable;
}
/**
* parse inner method
* the inner method name is automatically generated
* method parameters would capture all existing local variables. they are positioned ahead of params that the inner method requires
* e.g.
*
* outer()
* i=1
* j=2
* inner(x)=1+i+j+x
*
* the inner method would be parsed into inner$SomeGeneratedName$(i,j,x)
* when invoking, the captured local variables would be passed in as parameters
* both final and not-final variables can be captured,
* but the variable inside the inner method would NOT have effect on the outer one
*
* @param methodDef method def object, defines the inner method
* @param scope current scope
* @param lambdaParam add one param for lambda
* @return the new method (the inner method)
* @throws SyntaxException compile error
*/
public SMethodDef parseInnerMethod(MethodDef methodDef, SemanticScope scope, boolean lambdaParam) throws SyntaxException {
if (scope.parent == null) throw new LtBug("scope.parent should not be null");
SemanticScope theTopScope = scope.parent;
while (theTopScope.parent != null) theTopScope = theTopScope.parent;
// check method name
if (scope.containsInnerMethod(methodDef.name)) {
err.SyntaxException("duplicate inner method name", methodDef.line_col());
return null;
}
// inner method cannot have modifiers or annotations
if (!methodDef.modifiers.isEmpty() &&
(methodDef.modifiers.size() != 1 || !methodDef.modifiers.iterator().next().modifier.equals(Modifier.Available.DEF))
) {
err.SyntaxException("inner method cannot have modifiers", methodDef.line_col());
return null;
}
if (!methodDef.annos.isEmpty()) {
err.SyntaxException("inner method cannot have annotations", methodDef.line_col());
return null;
}
// check param names, see if it's already used
// also, init values are not allowed
for (VariableDef v : methodDef.params) {
if (null != scope.getLeftValue(v.getName())) {
err.SyntaxException(v.getName() + " is already used", v.line_col());
return null;
}
if (v.getInit() != null) {
err.SyntaxException("parameters of inner methods cannot have default value", v.line_col());
return null;
}
}
// get current type methods
List methods;
if (scope.type() instanceof SClassDef) {
methods = ((SClassDef) scope.type()).methods();
} else {
methods = ((SInterfaceDef) scope.type()).methods();
}
// generate method name
String generatedMethodName = methodDef.name + "$Latte$InnerMethod$";
int i = 0;
out:
while (true) {
String tmpName = generatedMethodName + i;
for (SMethodDef m : methods) {
if (m.name().equals(tmpName)) {
++i;
continue out;
}
}
break;
}
generatedMethodName += i;
String name = methodDef.name;
int paramCount = methodDef.params.size();
// fill in local variable as parameters
// the params are added to front positions
LinkedHashMap localVariables = scope.getLocalVariables();
List param4Locals = new ArrayList();
List realPointerTypes = new ArrayList();
for (Map.Entry entry : localVariables.entrySet()) {
String k = entry.getKey();
STypeDef v = entry.getValue();
if (!CompileUtil.isValidName(k)) continue;
if (k.equals("$")) continue;
// construct a synthetic VariableDef as param
VariableDef variable = new VariableDef(k, Collections.emptySet(), Collections.emptySet(), LineCol.SYNTHETIC);
if (v instanceof SArrayTypeDef) {
STypeDef x = ((SArrayTypeDef) v).type();
AST.Access theType = new AST.Access(
x.pkg() == null
? null
: new AST.PackageRef(x.pkg(), LineCol.SYNTHETIC),
x.fullName().contains(".")
? x.fullName().substring(x.fullName().lastIndexOf('.') + 1)
: x.fullName(),
LineCol.SYNTHETIC
);
for (int ii = 0; ii < ((SArrayTypeDef) v).dimension(); ++ii) {
theType = new AST.Access(theType, "[]", LineCol.SYNTHETIC);
}
variable.setType(theType);
} else {
variable.setType(
new AST.Access(
v.pkg() == null
? null
: new AST.PackageRef(v.pkg(), LineCol.SYNTHETIC),
v.fullName().contains(".")
? v.fullName().substring(v.fullName().lastIndexOf('.') + 1)
: v.fullName(),
LineCol.SYNTHETIC
)
);
if (v instanceof PointerType) {
realPointerTypes.add((PointerType) v);
}
}
param4Locals.add(variable);
}
MethodDef newMethodDef = new MethodDef(
generatedMethodName,
Collections.emptySet(),
methodDef.returnType,
new ArrayList(methodDef.params),
Collections.emptySet(),
methodDef.body,
methodDef.line_col()
);
newMethodDef.params.addAll(0, param4Locals);
if (lambdaParam) {
newMethodDef.params.add(
new VariableDef("$",
Collections.emptySet(), Collections.emptySet(), LineCol.SYNTHETIC));
}
// parse the method
parseMethod(newMethodDef, newMethodDef.params.size(), scope.type(), null, fileNameToImport.get(newMethodDef.line_col().fileName),
(scope.type() instanceof SClassDef) ? PARSING_CLASS : PARSING_INTERFACE, scope.getThis() == null);
SMethodDef m = methods.get(methods.size() - 1);
// set captured values
for (int x = 0; x < param4Locals.size(); ++x) {
m.meta().pointerLocalVar.add(m.getParameters().get(x));
}
// change the modifier
m.modifiers().remove(SModifier.PUBLIC);
m.modifiers().remove(SModifier.PROTECTED);
m.modifiers().add(0, SModifier.PRIVATE);
// change to real pointer type
// and mark capture parameters
int cursor = 0;
int currentIndex = 0;
int capturedParamSize = param4Locals.size();
for (SParameter p : m.getParameters()) {
if (isPointerType(p.type())) {
p.setType(realPointerTypes.get(cursor++));
}
if (currentIndex < capturedParamSize) {
p.setCapture(true);
}
++currentIndex;
}
assert cursor == realPointerTypes.size();
// add into scope
SemanticScope.MethodRecorder rec = new SemanticScope.MethodRecorder(m, paramCount);
scope.addMethodDef(name, rec);
// generate a scope for the inner method
// the scope contains the inner method itself
SemanticScope innerMethodScope = new SemanticScope(theTopScope, m.meta());
for (Map.Entry srec : scope.getInnerMethods().entrySet()) {
innerMethodScope.addMethodDef(srec.getKey(), srec.getValue());
}
parseMethod(m, newMethodDef.body, innerMethodScope);
return m;
}
/**
* parse synchronized
* every monitor must have an exit
*
* sync(a,b,c)
* ...
* ==>
* a
* astoreX
* monitor enter
* b
* astoreY
* monitor enter
* c
* astoreZ
* monitor enter
* ...
* astore
* Z
* monitor exit
* Y
* monitor exit
* X
* monitor exit
* goto `nop`
* [end normally]
* --------------------
* [end with exception]
* Z
* monitor exit
* Y
* monitor exit
* X
* monitor exit
* athrow
* nop
*
* and every position of return should insert after monitor exit instructions
*
* @param aSynchronized synchronized object
* @param methodReturnType method return type
* @param scope current scope
* @param instructions instructions
* @param exceptionTable exception table
* @param breakIns jump to this position when meets a break
* @param continueIns jump to this position when meets a continue
* @throws SyntaxException compile error
*/
public void parseInstructionFromSynchronized(AST.Synchronized aSynchronized,
STypeDef methodReturnType,
SemanticScope scope,
List instructions,
List exceptionTable,
Ins.Nop breakIns,
Ins.Nop continueIns) throws SyntaxException {
SemanticScope subScope = new SemanticScope(scope, scope.getMeta());
Stack stack = new Stack();
for (Expression exp : aSynchronized.toSync) {
Value v = parseValueFromExpression(exp, null, subScope);
Ins.MonitorEnter enter = new Ins.MonitorEnter(v, subScope, exp.line_col());
stack.push(enter);
instructions.add(enter); // monitor enter
}
// parse statements
List instructionList = new ArrayList();
for (Statement stmt : aSynchronized.statements) {
parseStatement(stmt, methodReturnType, subScope, instructionList, exceptionTable, breakIns, continueIns, false);
}
if (instructionList.size() == 0) instructionList.add(new Ins.Nop());
// build exit for return, continue and break
int returnCount = 0;
int continueCount = 0;
int breakCount = 0;
for (Instruction ins : instructionList) {
if (ins instanceof Ins.TReturn) ++returnCount;
else if (breakIns != null) {
assert continueIns != null;
if (ins instanceof Ins.Goto) {
if (((Ins.Goto) ins).gotoIns() == breakIns) ++breakCount;
else if (((Ins.Goto) ins).gotoIns() == continueIns) ++continueCount;
}
}
}
List exitNormal = new ArrayList(stack.size());
List exitForExceptions = new ArrayList(stack.size());
List> exitForReturn = new ArrayList>();
List> exitForBreak = new ArrayList>();
List> exitForContinue = new ArrayList>();
for (int i = 0; i < returnCount; ++i) exitForReturn.add(new ArrayList());
for (int i = 0; i < continueCount; ++i) exitForContinue.add(new ArrayList());
for (int i = 0; i < breakCount; ++i) exitForBreak.add(new ArrayList());
while (!stack.empty()) {
Ins.MonitorEnter monitorEnter = stack.pop();
exitNormal.add(new Ins.MonitorExit(monitorEnter));
exitForExceptions.add(new Ins.MonitorExit(monitorEnter));
for (List list : exitForReturn) list.add(new Ins.MonitorExit(monitorEnter));
for (List list : exitForContinue) list.add(new Ins.MonitorExit(monitorEnter));
for (List list : exitForBreak) list.add(new Ins.MonitorExit(monitorEnter));
}
// insert exit before return
returnCount = 0;
continueCount = 0;
breakCount = 0;
for (int i = 0; i < instructionList.size(); ++i) {
Instruction ins = instructionList.get(i);
if (ins instanceof Ins.TReturn) {
i += insertInstructionsBeforeReturn(instructionList, i, exitForReturn.get(returnCount++), subScope);
} else if (breakIns != null) {
if (ins instanceof Ins.Goto) {
List exitList = null;
if (((Ins.Goto) ins).gotoIns() == breakIns) {
exitList = exitForBreak.get(breakCount++);
} else if (((Ins.Goto) ins).gotoIns() == continueIns) {
exitList = exitForContinue.get(continueCount++);
}
if (exitList != null) {
instructionList.addAll(i, exitList);
i += exitList.size();
}
}
}
}
instructions.addAll(instructionList);
instructions.addAll(exitNormal);
// might occur an exception
LocalVariable localVariable = new LocalVariable(
getTypeWithName("java.lang.Throwable", aSynchronized.line_col()),
false);
subScope.putLeftValue(subScope.generateTempName(), localVariable);
Ins.AThrow aThrow = new Ins.AThrow(
new Ins.TLoad(localVariable, subScope, aSynchronized.line_col()),
aSynchronized.line_col());
Ins.ExStore exStore = new Ins.ExStore(localVariable, subScope);
Ins.Nop nop = new Ins.Nop();
Ins.Goto aGoto = new Ins.Goto(nop);
instructions.add(aGoto); // goto athrow
instructions.add(exStore); // astore
instructions.addAll(exitForExceptions);
instructions.add(aThrow); // athrow
instructions.add(nop); // nop
ExceptionTable table = new ExceptionTable(instructionList.get(0), exitNormal.get(0), exStore, null);
exceptionTable.add(table);
}
/**
* separate TReturn with it's return value, and insert instructions
* TheValueToReturn, TStore, instructionToInsert, TLoad, TReturn
*
* @param instructions the original instruction list
* @param returnIndex TReturn position
* @param toInsert the instructions to insert
* @param scope scope
* @return the cursor should += this return value
* @throws SyntaxException compiling error
*/
public int insertInstructionsBeforeReturn(List instructions,
int returnIndex,
List extends Instruction> toInsert,
SemanticScope scope) throws SyntaxException {
Ins.TReturn tReturn = (Ins.TReturn) instructions.remove(returnIndex); // get return
Value returnValue = tReturn.value();
if (returnValue != null) {
LocalVariable tmp = new LocalVariable(returnValue.type(), false);
scope.putLeftValue(scope.generateTempName(), tmp);
Ins.TStore TStore = new Ins.TStore(tmp, returnValue, scope, LineCol.SYNTHETIC, err); // store the value
Ins.TLoad tLoad = new Ins.TLoad(tmp, scope, LineCol.SYNTHETIC); // retrieve the value
tReturn.setReturnValue(tLoad); // return the value
instructions.add(returnIndex++, TStore);
}
instructions.addAll(returnIndex, toInsert);
returnIndex += toInsert.size();
instructions.add(returnIndex, tReturn);
return toInsert.size() + 2;
}
/**
* {@link LtRuntime#throwableWrapperObject(Throwable)}
*/
private SMethodDef Lang_throwableWrapperObject;
/**
* @return {@link LtRuntime#throwableWrapperObject(Throwable)}
* @throws SyntaxException exception
*/
public SMethodDef getLang_throwableWrapperObject() throws SyntaxException {
if (Lang_throwableWrapperObject == null) {
SClassDef Lang = (SClassDef) getTypeWithName("lt.runtime.LtRuntime", LineCol.SYNTHETIC);
assert Lang != null;
for (SMethodDef m : Lang.methods()) {
if (m.name().equals("throwableWrapperObject")) {
Lang_throwableWrapperObject = m;
break;
}
}
}
return Lang_throwableWrapperObject;
}
/**
* parse try
*
* try
* A
* catch Ex
* B
* finally
* D
* E
*
* ==>
*
* A------------------┐
* ( │
* DoSomething │
* D │
* return goto D2
* or │
* DoSomething │
* ) │
* goto D1------------┘
* B-----------------goto D2
* goto D1------------┘
* D2 (exception)
* D1 (normal)
* E
*
*
* when meets a return value, separate value to return
and the return instruction itself using {@link #insertInstructionsBeforeReturn(List, int, List, SemanticScope)}
* the finally part would be inserted and they shouldn't be caught
*
* something
* TReturn(Value)
*
* ==>
*
* something-------------┐
* Value caught
* TStore----------------┘
* [the finally part]----shoudn't be caught
* TLoad
* TReturn
*
*
* @param aTry try
* @param methodReturnType method return type
* @param scope scope
* @param instructions instruction list
* @param exceptionTable exception table
* @param breakIns jump to this position when meets a break
* @param continueIns jump to this position when meets a continue
* @throws SyntaxException compile error
*/
public void parseInstructionFromTry(AST.Try aTry,
STypeDef methodReturnType,
SemanticScope scope,
List instructions,
List exceptionTable,
Ins.Nop breakIns,
Ins.Nop continueIns) throws SyntaxException {
// try ...
SemanticScope scopeA = new SemanticScope(scope, scope.getMeta());
List insA = new ArrayList(); // instructions in scope A
for (Statement stmt : aTry.statements) {
parseStatement(stmt, methodReturnType, scopeA, insA, exceptionTable, breakIns, continueIns, false);
}
// record the start and end for exception table
// end is inclusive in this map
// and should be converted to an exclusive one when added into exception table
LinkedHashMap startToEnd = new LinkedHashMap();
Instruction start = null;
for (int i1 = 0; i1 < insA.size(); i1++) {
Instruction i = insA.get(i1);
if (start == null) { // start is not set
// if no instructions before return
// then the block would not be added into exception table
// else
if (!(i instanceof Ins.TReturn)) {
start = i;
}
} else {
// start is already set
// if it's return
// put into map and set `start` to null
if (i instanceof Ins.TReturn
||
(breakIns != null && i instanceof Ins.Goto && (((Ins.Goto) i).gotoIns() == breakIns
|| ((Ins.Goto) i).gotoIns() == continueIns))
) {
startToEnd.put(start, insA.get(i1 - 1));
start = null;
}
}
}
// put last pair
if (start != null) startToEnd.put(start, insA.get(insA.size() - 1));
if (startToEnd.isEmpty() && insA.size() == 1) {
// insA[0] is return / break / continue
insA.add(0, new Ins.Nop());
startToEnd.put(insA.get(0), insA.get(0));
}
// the map preparation is done
// build normal finally (D1)
List normalFinally = new ArrayList();
for (Statement stmt : aTry.fin) {
parseStatement(stmt, methodReturnType, new SemanticScope(scope, scope.getMeta()), normalFinally, exceptionTable, breakIns, continueIns, false);
}
if (normalFinally.isEmpty()) {
normalFinally.add(new Ins.Nop());
}
Instruction D1start = normalFinally.get(0);
// build exception finally (D2)
SemanticScope exceptionFinallyScope = new SemanticScope(scope, scope.getMeta());
// add D2start to exception finally at 0
// add throw to exception finally at end
LocalVariable tmpForExStore = new LocalVariable(
getTypeWithName("java.lang.Throwable", aTry.line_col()), false);
exceptionFinallyScope.putLeftValue(exceptionFinallyScope.generateTempName(), tmpForExStore);
// store
Ins.ExStore D2start = new Ins.ExStore(tmpForExStore, exceptionFinallyScope);
// throw
Ins.AThrow aThrow = new Ins.AThrow(
new Ins.TLoad(tmpForExStore, exceptionFinallyScope,
aTry.line_col()),
aTry.line_col());
List exceptionFinally = new ArrayList();
// fill the list
exceptionFinally.add(D2start);
for (Statement stmt : aTry.fin) {
parseStatement(stmt, methodReturnType, exceptionFinallyScope,
exceptionFinally, exceptionTable, breakIns, continueIns, false);
}
exceptionFinally.add(aThrow);
// add D into every position before
// return, break, continue in insA
for (int i = 0; i < insA.size(); ++i) {
Instruction ins = insA.get(i);
if (ins instanceof Ins.TReturn) {
List