// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz) // All rights reserved. // // This software may be modified and distributed under the terms // of the BSD license. See the LICENSE file for details. package wyil.io; import java.io.*; import java.math.BigInteger; import java.util.*; import wybs.lang.NameID; import wyfs.io.BinaryOutputStream; import wyfs.lang.Path; import wyil.lang.*; import wyil.lang.SyntaxTree.Location; import wyil.util.AbstractBytecode; /** * <p> * Responsible for writing a WyilFile to an output stream in binary form. The * binary format is structured to given maximum flexibility and to avoid * built-in limitations in terms of e.g. maximum sizes, etc. * </p> * * <p> * The primitive component of a WyilFile is a <i>block</i>. Each block has a * kind, a given and then the payload. Blocks which are not recognised can be * ignored by skipping over their payload. Blocks are always byte aligned, but * their contents may not be. * </p> * * <p> * A binary Wyil file begins with a header and a resource pool stratified into * sections containing the string constants, general constants, types and more. * Following the header are zero or more module blocks. Additional top-level * block kinds (e.g. for licenses) may be specified in the future. Each module * block consists of zero or more declaration blocks, which include method and * function declarations, type declarations and constant declarations. * </p> * * @author David J. Pearce * */ public final class WyilFileWriter { private static final int MAJOR_VERSION = 0; private static final int MINOR_VERSION = 1; private final BinaryOutputStream out; private final ArrayList<String> stringPool = new ArrayList<>(); private final HashMap<String, Integer> stringCache = new HashMap<>(); private final ArrayList<PATH_Item> pathPool = new ArrayList<>(); private final HashMap<Path.ID, Integer> pathCache = new HashMap<>(); private final ArrayList<NAME_Item> namePool = new ArrayList<>(); private final HashMap<NameID, Integer> nameCache = new HashMap<>(); private final ArrayList<Constant> constantPool = new ArrayList<>(); private final HashMap<Constant, Integer> constantCache = new HashMap<>(); private final ArrayList<Type> typePool = new ArrayList<>(); private final HashMap<Type, Integer> typeCache = new HashMap<>(); public WyilFileWriter(OutputStream output) { this.out = new BinaryOutputStream(output); } public void close() throws IOException { out.close(); } public void write(WyilFile module) throws IOException { // first, write magic number out.write_u8(0x57); // W out.write_u8(0x59); // Y out.write_u8(0x49); // I out.write_u8(0x4C); // L out.write_u8(0x46); // F out.write_u8(0x49); // I out.write_u8(0x4C); // L out.write_u8(0x45); // E // second, build pools buildPools(module); // third, write head block writeBlock(BLOCK_Header, module, out); // fourth, write module block(s) writeBlock(BLOCK_Module, module, out); out.flush(); } private void writeBlock(int kind, Object data, BinaryOutputStream output) throws IOException { // determine bytes for block byte[] bytes = null; switch (kind) { case BLOCK_Header: bytes = generateHeaderBlock((WyilFile) data); break; case BLOCK_Module: bytes = generateModuleBlock((WyilFile) data); break; case BLOCK_Type: bytes = generateTypeBlock((WyilFile.Type) data); break; case BLOCK_Constant: bytes = generateConstantBlock((WyilFile.Constant) data); break; case BLOCK_Function: case BLOCK_Method: bytes = generateFunctionOrMethodBlock((WyilFile.FunctionOrMethod) data); break; case BLOCK_Property: bytes = generatePropertyBlock((WyilFile.Property) data); break; } output.pad_u8(); // pad out to next byte boundary output.write_uv(kind); output.write_uv(bytes.length); output.pad_u8(); // pad out to next byte boundary output.write(bytes); } /** * Write the header information for this WYIL file, including the stratified * resource pool. * * @param module * * @throws IOException */ private byte[] generateHeaderBlock(WyilFile module) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); BinaryOutputStream output = new BinaryOutputStream(bytes); // second, write the file version number output.write_uv(MAJOR_VERSION); output.write_uv(MINOR_VERSION); // third, write the various pool sizes output.write_uv(stringPool.size()); output.write_uv(pathPool.size()); output.write_uv(namePool.size()); output.write_uv(typePool.size()); output.write_uv(constantPool.size()); // finally, write the number of remaining blocks output.write_uv(module.blocks().size()); writeStringPool(output); writePathPool(output); writeNamePool(output); writeTypePool(output); writeConstantPool(output); output.close(); return bytes.toByteArray(); } /** * Write the list of strings making up the string pool. Each entry is * formated like so: * * <pre> * +-----------------+ * | uv : len | * +-----------------+ * | u8[len] : bytes | * +-----------------+ * </pre> * * The encoding for each string item is UTF8. * * @throws IOException */ private void writeStringPool(BinaryOutputStream output) throws IOException { for (String s : stringPool) { try { byte[] bytes = s.getBytes("UTF-8"); output.write_uv(bytes.length); output.write(bytes, 0, bytes.length); } catch (UnsupportedEncodingException e) { // hmmm, this aint pretty ;) } } } /** * Write the list of paths which constitute the path pool. Each entry is * formated like so: * * <pre> * +-----------------+ * | uv : parent | * +-----------------+ * | uv : stringIdx | * +-----------------+ * </pre> * * Each entry is a child of some parent entry, with index zero being * automatically designated the "root". The <code>stringIdx</code> * corresponds to an index in the string pool. */ private void writePathPool(BinaryOutputStream output) throws IOException { for (int i = 1; i < pathPool.size(); ++i) { PATH_Item p = pathPool.get(i); output.write_uv(p.parentIndex); output.write_uv(p.stringIndex); } } /** * Read the list of names which constitute the name pool. Each entry is * formated like so: * * <pre> * +-----------------+ * | uv : pathIdx | * +-----------------+ * | uv : stringIdx | * +-----------------+ * </pre> * * Each entry consists of a path component and a name component, both of * which index the path and string pools (respectively). * * @param count * @throws IOException */ private void writeNamePool(BinaryOutputStream output) throws IOException { for (NAME_Item p : namePool) { output.write_uv(p.pathIndex); output.write_uv(p.nameIndex); } } /** * Write the list of constants which constitute the constant pool. Each entry * is formated like so: * * <pre> * +-----------------+ * | uv : code | * +-----------------+ * | ... payload ... | * +-----------------+ * </pre> * * Here, the size of the payload is determined by the constant code. In some * cases, there is no payload (e.g. for the constant NULL). In other case, * there can be numerous bytes contained in the payload (e.g. for an Integer * constant). * * @param count * @throws IOException */ private void writeConstantPool(BinaryOutputStream output) throws IOException { for (Constant val : constantPool) { if (val instanceof Constant.Null) { output.write_uv(CONSTANT_Null); } else if (val instanceof Constant.Bool) { Constant.Bool b = (Constant.Bool) val; output.write_uv(b.value() ? CONSTANT_True : CONSTANT_False); } else if (val instanceof Constant.Byte) { Constant.Byte b = (Constant.Byte) val; output.write_uv(CONSTANT_Byte); output.write_u8(b.value()); } else if (val instanceof Constant.Integer) { Constant.Integer i = (Constant.Integer) val; BigInteger num = i.value(); byte[] numbytes = num.toByteArray(); output.write_uv(CONSTANT_Int); output.write_uv(numbytes.length); output.write(numbytes); } else if (val instanceof Constant.Array) { Constant.Array s = (Constant.Array) val; output.write_uv(CONSTANT_Array); output.write_uv(s.values().size()); for (Constant v : s.values()) { int index = constantCache.get(v); output.write_uv(index); } } else if (val instanceof Constant.Record) { Constant.Record r = (Constant.Record) val; output.write_uv(CONSTANT_Record); output.write_uv(r.values().size()); for (java.util.Map.Entry<String, Constant> v : r.values().entrySet()) { output.write_uv(stringCache.get(v.getKey())); int index = constantCache.get(v.getValue()); output.write_uv(index); } } else if (val instanceof Constant.FunctionOrMethod) { Constant.FunctionOrMethod fm = (Constant.FunctionOrMethod) val; Type.FunctionOrMethod t = fm.type(); output.write_uv(t instanceof Type.Function ? CONSTANT_Function : CONSTANT_Method); output.write_uv(typeCache.get(t)); output.write_uv(nameCache.get(fm.name())); } else if (val instanceof Constant.Type) { Constant.Type ct = (Constant.Type) val; output.write_uv(CONSTANT_Type); output.write_uv(typeCache.get(ct.value())); } else { throw new RuntimeException("Unknown value encountered - " + val); } } } private void writeTypePool(BinaryOutputStream output) throws IOException { for (Type type : typePool) { if (type == Type.T_ANY) { output.write_uv(TYPE_Any); } else if (type == Type.T_VOID) { output.write_uv(TYPE_Void); } else if (type == Type.T_NULL) { output.write_uv(TYPE_Null); } else if (type == Type.T_BOOL) { output.write_uv(TYPE_Bool); } else if (type == Type.T_BYTE) { output.write_uv(TYPE_Byte); } else if (type == Type.T_INT) { output.write_uv(TYPE_Int); } else if (type == Type.T_META) { output.write_uv(TYPE_Type); } else if (type instanceof Type.Nominal) { Type.Nominal t = (Type.Nominal) type; output.write_uv(TYPE_Nominal); output.write_uv(nameCache.get(t.name())); } else if (type instanceof Type.Reference) { Type.Reference t = (Type.Reference) type; output.write_uv(TYPE_Reference); output.write_uv(typeCache.get(t.element())); output.write_uv(stringCache.get(t.lifetime())); } else if (type instanceof Type.Nominal) { Type.Nominal t = (Type.Nominal) type; output.write_uv(TYPE_Nominal); output.write_uv(nameCache.get(t.name())); } else if (type instanceof Type.Array) { Type.Array t = (Type.Array) type; output.write_uv(TYPE_Array); output.write_uv(typeCache.get(t.element())); } else if (type instanceof Type.Record) { Type.Record t = (Type.Record) type; String[] t_fields = t.getFieldNames(); output.write_uv(TYPE_Record); output.write_uv(t_fields.length); output.write_bit(t.isOpen()); for(int i=0;i!=t_fields.length;++i) { String field = t_fields[i]; Type fieldType = t.getField(field); output.write_uv(stringCache.get(field)); output.write_uv(typeCache.get(fieldType)); } } else if (type instanceof Type.Function) { Type.Function t = (Type.Function) type; output.write_uv(TYPE_Function); writeTypes(t.params(),output); writeTypes(t.returns(),output); } else if (type instanceof Type.Property) { Type.Property t = (Type.Property) type; output.write_uv(TYPE_Property); writeTypes(t.params(),output); } else if (type instanceof Type.Method) { Type.Method t = (Type.Method) type; output.write_uv(TYPE_Method); writeStrings(t.lifetimeParams(),output); writeStrings(t.contextLifetimes(),output); writeTypes(t.params(),output); writeTypes(t.returns(),output); } else if (type instanceof Type.Union) { Type.Union t = (Type.Union) type; output.write_uv(TYPE_Union); writeTypes(t.bounds(),output); } else if (type instanceof Type.Intersection) { Type.Intersection t = (Type.Intersection) type; output.write_uv(TYPE_Intersection); writeTypes(t.bounds(),output); } else if (type instanceof Type.Negation) { Type.Negation t = (Type.Negation) type; output.write_uv(TYPE_Negation); output.write_uv(typeCache.get(t.element())); } else { throw new RuntimeException("Unknown type encountered - " + type); } } } private void writeTypes(Type[] types, BinaryOutputStream output) throws IOException { output.write_uv(types.length); for(Type b: types) { output.write_uv(typeCache.get(b)); } } private void writeStrings(String[] strings, BinaryOutputStream output) throws IOException { output.write_uv(strings.length); for(String str : strings) { output.write_uv(stringCache.get(str)); } } private byte[] generateModuleBlock(WyilFile module) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); BinaryOutputStream output = new BinaryOutputStream(bytes); output.write_uv(pathCache.get(module.getEntry().id())); output.write_uv(MODIFIER_Public); // for now output.write_uv(module.blocks().size()); for (WyilFile.Block d : module.blocks()) { writeModuleBlock(d, output); } output.close(); return bytes.toByteArray(); } private void writeModuleBlock(WyilFile.Block d, BinaryOutputStream output) throws IOException { if (d instanceof WyilFile.Constant) { writeBlock(BLOCK_Constant, d, output); } else if (d instanceof WyilFile.Type) { writeBlock(BLOCK_Type, d, output); } else if(d instanceof WyilFile.Property) { writeBlock(BLOCK_Property, d, output); } else if (d instanceof WyilFile.FunctionOrMethod) { WyilFile.FunctionOrMethod md = (WyilFile.FunctionOrMethod) d; if (md.type() instanceof Type.Function) { writeBlock(BLOCK_Function, d, output); } else { writeBlock(BLOCK_Method, d, output); } } } /** * Construct a BLOCK_Constant, that is a WyIL module block representing a * constant declaration. The format is: * * <pre> * +-----------------+ * | uv : nameIdx | * +-----------------+ * | uv : Modifiers | * +-----------------+ * | uv : constIdx | * +-----------------+ * ~~~~~~~ u8 ~~~~~~~~ * </pre> * * The <code>nameIdx</code> is an index into the <code>stringPool</code> * representing the declaration's name, whilst <code>constIdx</code> is an * index into the <code>constantPool</code> representing the constant value * itself. * * @throws IOException */ private byte[] generateConstantBlock(WyilFile.Constant cd) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); BinaryOutputStream output = new BinaryOutputStream(bytes); output.write_uv(stringCache.get(cd.name())); output.write_uv(generateModifiers(cd.modifiers())); output.write_uv(constantCache.get(cd.constant())); // TODO: write attributes output.close(); return bytes.toByteArray(); } /** * Construct a BLOCK_Type, that is a WyIL module block representing a type * declaration. The format is: * * <pre> * +------------------------+ * | uv : nameIdx | * +------------------------+ * | uv : Modifiers | * +------------------------+ * | uv : typeIdx | * +------------------------+ * | uv : nInvariants | * +------------------------+ * | uv[nInvariants] | * +------------------------+ * | SyntaxTree | * +------------------------+ * ~~~~~~~~~~ u8 ~~~~~~~~~~~~ * </pre> * * The <code>nameIdx</code> is an index into the <code>stringPool</code> * representing the declaration's name, whilst <code>typeIdx</code> is an * index into the <code>typePool</code> representing the type itself. * * @throws IOException */ private byte[] generateTypeBlock(WyilFile.Type td) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); BinaryOutputStream output = new BinaryOutputStream(bytes); // int nameIdx = stringCache.get(td.name()); int modifiers = generateModifiers(td.modifiers()); int typeIdx = typeCache.get(td.type()); List<Location<Bytecode.Expr>> invariant = td.getInvariant(); // output.write_uv(nameIdx); output.write_uv(modifiers); output.write_uv(typeIdx); output.write_uv(invariant.size()); for(Location<?> clause : invariant) { output.write_uv(clause.getIndex()); } writeSyntaxTree(td.getTree(),output); output.close(); return bytes.toByteArray(); } /** * Construct a BLOCK_Function or BLOCK_Method, that is a WyIL module block * representing a function or method declaration. The format is: * * <pre> * +------------------------+ * | uv : nameIdx | * +------------------------+ * | uv : Modifiers | * +------------------------+ * | uv : typeIdx | * +------------------------+ * | uv : nPreconditions | * +------------------------+ * | uv : nPostconditions | * +------------------------+ * | uv[nPreconditions] | * +------------------------+ * | uv[nPostconditions] | * +------------------------+ * | uv : body | * +------------------------+ * | SyntaTree | * +------------------------+ * ~~~~~~~~~~ u8 ~~~~~~~~~~~~ * </pre> * * The <code>nameIdx</code> is an index into the <code>stringPool</code> * representing the declaration's name, whilst <code>typeIdx</code> is an * index into the <code>typePool</code> representing the function or method * type itself. * * @throws IOException */ private byte[] generateFunctionOrMethodBlock(WyilFile.FunctionOrMethod md) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); BinaryOutputStream output = new BinaryOutputStream(bytes); // int nameIdx = stringCache.get(md.name()); int modifiers = generateModifiers(md.modifiers()); int typeIdx = typeCache.get(md.type()); List<Location<Bytecode.Expr>> precondition = md.getPrecondition(); List<Location<Bytecode.Expr>> postcondition = md.getPostcondition(); // output.write_uv(nameIdx); output.write_uv(modifiers); output.write_uv(typeIdx); output.write_uv(precondition.size()); output.write_uv(postcondition.size()); // for(Location<Bytecode.Expr> clause : precondition) { output.write_uv(clause.getIndex()); } for(Location<Bytecode.Expr> clause : postcondition) { output.write_uv(clause.getIndex()); } output.write_uv(md.getBody().getIndex()); writeSyntaxTree(md.getTree(),output); output.close(); return bytes.toByteArray(); } private byte[] generatePropertyBlock(WyilFile.Property md) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); BinaryOutputStream output = new BinaryOutputStream(bytes); // int nameIdx = stringCache.get(md.name()); int modifiers = generateModifiers(md.modifiers()); int typeIdx = typeCache.get(md.type()); List<Location<Bytecode.Expr>> precondition = md.getPrecondition(); // output.write_uv(nameIdx); output.write_uv(modifiers); output.write_uv(typeIdx); output.write_uv(precondition.size()); // for(Location<Bytecode.Expr> clause : precondition) { output.write_uv(clause.getIndex()); } writeSyntaxTree(md.getTree(),output); output.close(); return bytes.toByteArray(); } /** * Write a syntax tree to the output stream. The format * of a syntax tree is one of the following: * * <pre> * +-------------------+ * | uv : nLocs | * +-------------------+ * | Locations[nLocs] | * +-------------------+ * </pre> * * * * @param register * Register to be written out * @param output * @throws */ private void writeSyntaxTree(SyntaxTree tree, BinaryOutputStream output) throws IOException { List<Location<?>> locations = tree.getLocations(); output.write_uv(locations.size()); for(int i=0;i!=locations.size();++i) { Location<?> location = locations.get(i); writeLocation(location,output); } } /** * Write details of a Location to the output stream. The format * of a location is: * * <pre> * +-------------------+ * | uv : nTypes | * +-------------------+ * | uv[] : typeIdxs | * +-------------------+ * | uv : nAttrs | * +-------------------+ * | Bytecode | * +-------------------+ * | Attribute[nAttrs] | * +-------------------+ * </pre> * */ private void writeLocation(SyntaxTree.Location<?> location, BinaryOutputStream output) throws IOException { output.write_uv(location.numberOfTypes()); for(int i=0;i!=location.numberOfTypes();++i) { output.write_uv(typeCache.get(location.getType(i))); } output.write_uv(0); // no attributes for now writeBytecode(location.getBytecode(), output); } /** * <p> * Write out a given bytecode whose format is currently given as follows: * </p> * * <pre> * +-------------------+ * | u8 : opcode | * +-------------------+ * | uv : nAttrs | * +-------------------+ * | Attribute[nAttrs] | * +-------------------+ * ... * * </pre> * * <p> * <b>NOTE:</b> The intention is to support a range of different bytecode * formats in order to optimise the common cases. For example, when there * are no targets, no operands, no types, etc. Furthermore, when the size of * items can be reduced from uv to u4, etc. * </p> */ private void writeBytecode(Bytecode code, BinaryOutputStream output) throws IOException { output.write_u8(code.getOpcode()); output.write_uv(0); // attributes writeOperands(code,output); writeOperandGroups(code,output); if (code instanceof Bytecode.Stmt) { writeBlocks((Bytecode.Stmt) code, output); } writeExtras(code, output); } private void writeOperands(Bytecode code, BinaryOutputStream output) throws IOException { Bytecode.Schema schema = AbstractBytecode.schemas[code.getOpcode()]; switch(schema.getOperands()) { case ZERO: // do nout break; case ONE: output.write_uv(code.getOperand(0)); break; case TWO: output.write_uv(code.getOperand(0)); output.write_uv(code.getOperand(1)); break; case MANY: writeUnboundArray(code.getOperands(),output); } } private void writeOperandGroups(Bytecode code, BinaryOutputStream output) throws IOException { Bytecode.Schema schema = AbstractBytecode.schemas[code.getOpcode()]; switch(schema.getOperandGroups()) { case ZERO: // do nout break; case ONE: writeUnboundArray(code.getOperandGroup(0),output); break; case TWO: writeUnboundArray(code.getOperandGroup(0),output); writeUnboundArray(code.getOperandGroup(1),output); break; case MANY: output.write_uv(code.numberOfOperandGroups()); for(int i=0;i!=code.numberOfOperandGroups();++i) { writeUnboundArray(code.getOperandGroup(i),output); } } } private void writeBlocks(Bytecode.Stmt code, BinaryOutputStream output) throws IOException { Bytecode.Schema schema = AbstractBytecode.schemas[code.getOpcode()]; switch(schema.getBlocks()) { case ZERO: // do nout break; case ONE: output.write_uv(code.getBlock(0)); break; case TWO: output.write_uv(code.getBlock(0)); output.write_uv(code.getBlock(1)); break; case MANY: writeUnboundArray(code.getBlocks(),output); } } private void writeUnboundArray(int[] values, BinaryOutputStream output) throws IOException { output.write_uv(values.length); // Write operand locations for (int i = 0; i != values.length; ++i) { output.write_uv(values[i]); } } /** * Write the "rest" of a bytecode instruction. This includes additional * information as specified for the given opcode. For compound bytecodes, * this includes the block identifier(s) of the nested block(s) in question. * For branching bytecodes, it will include the branch destination as a * relative offset. Other bytecodes include indices which identify constants * in one of the pools. * * @param code * --- The bytecode to be written. * @param offset * --- The current offset of this bytecode in the bytecode array * being generated. This offset is measured in complete * bytecodes, not in e.g. bytes. Therefore, the first bytecode * has offset zero, the second bytecode has offset 1, etc. The * offset is required for calculating jump targets for branching * instructions (e.g. goto). Since jump targets (in short form) * are encoded as a relative offset, we need to know our current * offset to compute the relative target. * @param output * --- The binary output stream to which this bytecode is being * written. * @throws IOException */ private void writeExtras(Bytecode code, BinaryOutputStream output) throws IOException { // switch (code.getOpcode()) { case Bytecode.OPCODE_const: { Bytecode.Const c = (Bytecode.Const) code; output.write_uv(constantCache.get(c.constant())); break; } case Bytecode.OPCODE_fieldload: { Bytecode.FieldLoad c = (Bytecode.FieldLoad) code; output.write_uv(stringCache.get(c.fieldName())); break; } case Bytecode.OPCODE_indirectinvoke: { Bytecode.IndirectInvoke c = (Bytecode.IndirectInvoke) code; output.write_uv(typeCache.get(c.type())); break; } case Bytecode.OPCODE_namedblock: { Bytecode.NamedBlock c = (Bytecode.NamedBlock) code; output.write_uv(stringCache.get(c.getName())); break; } case Bytecode.OPCODE_invoke: { Bytecode.Invoke c = (Bytecode.Invoke) code; output.write_uv(typeCache.get(c.type())); output.write_uv(nameCache.get(c.name())); break; } case Bytecode.OPCODE_lambda: { Bytecode.Lambda c = (Bytecode.Lambda) code; output.write_uv(typeCache.get(c.type())); break; } case Bytecode.OPCODE_switch: { Bytecode.Switch c = (Bytecode.Switch) code; Bytecode.Case[] cases = c.cases(); output.write_uv(cases.length); for (int i = 0; i != cases.length; ++i) { Bytecode.Case cAse = cases[i]; Constant[] values = cAse.values(); output.write_uv(cAse.block()); output.write_uv(values.length); for (int j = 0; j != values.length; ++j) { output.write_uv(constantCache.get(values[j])); } } break; } case Bytecode.OPCODE_vardecl: case Bytecode.OPCODE_vardeclinit: { Bytecode.VariableDeclaration d = (Bytecode.VariableDeclaration) code; output.write_uv(stringCache.get(d.getName())); break; } } } private int generateModifiers(Collection<Modifier> modifiers) { int mods = 0; for (Modifier m : modifiers) { if (m == Modifier.PUBLIC) { mods |= MODIFIER_Public; } else if (m == Modifier.PRIVATE) { mods |= MODIFIER_Private; } else if (m == Modifier.NATIVE) { mods |= MODIFIER_Native; } else if (m == Modifier.EXPORT) { mods |= MODIFIER_Export; } } return mods; } private void buildPools(WyilFile module) { stringPool.clear(); stringCache.clear(); pathPool.clear(); pathCache.clear(); // preload the path root pathPool.add(null); pathCache.put(wyfs.util.Trie.ROOT, 0); namePool.clear(); nameCache.clear(); constantPool.clear(); constantCache.clear(); typePool.clear(); typeCache.clear(); addPathItem(module.getEntry().id()); for (WyilFile.Block d : module.blocks()) { buildPools(d); } } private void buildPools(WyilFile.Block declaration) { if (declaration instanceof WyilFile.Type) { buildPools((WyilFile.Type) declaration); } else if (declaration instanceof WyilFile.Constant) { buildPools((WyilFile.Constant) declaration); } else if (declaration instanceof WyilFile.FunctionOrMethodOrProperty) { buildPools((WyilFile.FunctionOrMethodOrProperty) declaration); } } private void buildPools(WyilFile.Constant declaration) { addStringItem(declaration.name()); addConstantItem(declaration.constant()); } private void buildPools(WyilFile.Type declaration) { addStringItem(declaration.name()); addTypeItem(declaration.type()); buildPools(declaration.getTree()); } private void buildPools(WyilFile.FunctionOrMethodOrProperty declaration) { addStringItem(declaration.name()); addTypeItem(declaration.type()); buildPools(declaration.getTree()); } private void buildPools(SyntaxTree tree) { for(Location<?> e : tree.getLocations()) { buildPools(e); } } private void buildPools(SyntaxTree.Location<?> loc) { for(int i=0;i!=loc.numberOfTypes();++i) { addTypeItem(loc.getType(i)); } buildPools(loc.getBytecode()); } private void buildPools(Bytecode code) { if (code instanceof Bytecode.Const) { Bytecode.Const c = (Bytecode.Const) code; addConstantItem(c.constant()); } else if (code instanceof Bytecode.FieldLoad) { Bytecode.FieldLoad c = (Bytecode.FieldLoad) code; addStringItem(c.fieldName()); } else if (code instanceof Bytecode.Invoke) { Bytecode.Invoke c = (Bytecode.Invoke) code; addNameItem(c.name()); addTypeItem(c.type()); } else if(code instanceof Bytecode.NamedBlock) { Bytecode.NamedBlock nb = (Bytecode.NamedBlock) code; addStringItem(nb.getName()); } else if (code instanceof Bytecode.Switch) { Bytecode.Switch s = (Bytecode.Switch) code; for (Bytecode.Case cs : s.cases()) { for (Constant c : cs.values()) { addConstantItem(c); } } } else if (code instanceof Bytecode.IndirectInvoke) { Bytecode.IndirectInvoke b = (Bytecode.IndirectInvoke) code; addTypeItem(b.type()); } else if (code instanceof Bytecode.Invoke) { Bytecode.Invoke b = (Bytecode.Invoke) code; addTypeItem(b.type()); } else if (code instanceof Bytecode.VariableDeclaration) { Bytecode.VariableDeclaration vd = (Bytecode.VariableDeclaration) code; addStringItem(vd.getName()); } } private int addNameItem(NameID name) { Integer index = nameCache.get(name); if (index == null) { int i = namePool.size(); nameCache.put(name, i); namePool.add(new NAME_Item(addPathItem(name.module()), addStringItem(name.name()))); return i; } else { return index; } } private int addStringItem(String string) { Integer index = stringCache.get(string); if (index == null) { int i = stringPool.size(); stringCache.put(string, i); stringPool.add(string); return i; } else { return index; } } private void addStringItems(String[] strings) { for(String string : strings) { addStringItem(string); } } private int addPathItem(Path.ID pid) { Integer index = pathCache.get(pid); if (index == null) { int parent = addPathItem(pid.parent()); int i = pathPool.size(); pathPool.add(new PATH_Item(parent, addStringItem(pid.last()))); pathCache.put(pid, i); return i; } else { return index; } } private int addTypeItem(Type t) { Integer index = typeCache.get(t); if (index == null) { // All subitems must have lower indices than the containing item. // So, we must add subitems first before attempting to allocate a // place for this value. addTypeSubitems(t); // finally allocate space for this type int i = typePool.size(); typeCache.put(t, i); typePool.add(t); return i; } else { return index; } } private void addTypeItems(Type[] types) { for(Type type : types) { addTypeItem(type); } } private void addTypeSubitems(Type type) { if(type instanceof Type.Nominal) { Type.Nominal t = (Type.Nominal) type; addNameItem(t.name()); } else if(type instanceof Type.Array) { Type.Array t = (Type.Array) type; addTypeItem(t.element()); } else if(type instanceof Type.Reference) { Type.Reference t = (Type.Reference) type; addTypeItem(t.element()); addStringItem(t.lifetime()); } else if(type instanceof Type.Record) { Type.Record t = (Type.Record) type; String[] fields = t.getFieldNames(); for(int i=0;i!=fields.length;++i) { String field = fields[i]; Type fieldType = t.getField(field); addStringItem(field); addTypeItem(fieldType); } } else if(type instanceof Type.Function) { Type.FunctionOrMethod t = (Type.Function) type; addTypeItems(t.params()); addTypeItems(t.returns()); } else if(type instanceof Type.Method) { Type.Method t = (Type.Method) type; addStringItems(t.contextLifetimes()); addStringItems(t.lifetimeParams()); addTypeItems(t.params()); addTypeItems(t.returns()); } else if(type instanceof Type.Union) { Type.Union t = (Type.Union) type; addTypeItems(t.bounds()); } else if(type instanceof Type.Intersection) { Type.Intersection t = (Type.Intersection) type; addTypeItems(t.bounds()); } else if(type instanceof Type.Negation) { Type.Negation t = (Type.Negation) type; addTypeItem(t.element()); } } private int addConstantItem(Constant v) { Integer index = constantCache.get(v); if (index == null) { // All subitems must have lower indices than the containing item. // So, we must add subitems first before attempting to allocate a // place for this value. addConstantSubitems(v); // finally allocate space for this constant. int i = constantPool.size(); constantCache.put(v, i); constantPool.add(v); return i; } return index; } private void addConstantSubitems(Constant v) { if (v instanceof Constant.Array) { Constant.Array l = (Constant.Array) v; for (Constant e : l.values()) { addConstantItem(e); } } else if (v instanceof Constant.Record) { Constant.Record r = (Constant.Record) v; for (Map.Entry<String, Constant> e : r.values().entrySet()) { addStringItem(e.getKey()); addConstantItem(e.getValue()); } } else if (v instanceof Constant.FunctionOrMethod) { Constant.FunctionOrMethod fm = (Constant.FunctionOrMethod) v; addTypeItem(fm.type()); addNameItem(fm.name()); } else if (v instanceof Constant.Type) { Constant.Type ct = (Constant.Type) v; addTypeItem(ct.value()); } } /** * An PATH_Item represents a path item. * * @author David J. Pearce * */ private class PATH_Item { /** * The index in the path pool of the parent for this item, or -1 if it * has not parent. */ public final int parentIndex; /** * The index of this path component in the string pool */ public final int stringIndex; public PATH_Item(int parentIndex, int stringIndex) { this.parentIndex = parentIndex; this.stringIndex = stringIndex; } } private enum NAME_Kind { PACKAGE(0), MODULE(1), CONSTANT(2), TYPE(3), FUNCTION(4), METHOD(5); private final int kind; private NAME_Kind(int kind) { this.kind = kind; } public int kind() { return kind; } } /** * A NAME_Item represents a named path item, such as a package, module or * something within a module (e.g. a function or method declaration). * * @author David J. Pearce * */ private class NAME_Item { /** * The kind of name item this represents. */ // public final NAME_Kind kind; /** * Index of path for this item in path pool */ public final int pathIndex; /** * Index of string for this named item */ public final int nameIndex; public NAME_Item(/* NAME_Kind kind, */int pathIndex, int nameIndex) { // this.kind = kind; this.pathIndex = pathIndex; this.nameIndex = nameIndex; } } // ========================================================================= // BLOCK identifiers // ========================================================================= public final static int BLOCK_Header = 0; public final static int BLOCK_Module = 1; public final static int BLOCK_Documentation = 2; public final static int BLOCK_License = 3; // ... (anticipating some others here) public final static int BLOCK_Type = 10; public final static int BLOCK_Constant = 11; public final static int BLOCK_Function = 12; public final static int BLOCK_Method = 13; public final static int BLOCK_Property = 14; // ... (anticipating some others here) // ========================================================================= // CONSTANT identifiers // ========================================================================= public final static int CONSTANT_Null = 0; public final static int CONSTANT_True = 1; public final static int CONSTANT_False = 2; public final static int CONSTANT_Byte = 3; public final static int CONSTANT_Int = 5; public final static int CONSTANT_Array = 9; public final static int CONSTANT_Record = 10; public final static int CONSTANT_Type = 12; public final static int CONSTANT_Function = 13; public final static int CONSTANT_Method = 14; // ========================================================================= // TYHPE identifiers // ========================================================================= public final static int TYPE_Any = 0; public final static int TYPE_Void = 1; public final static int TYPE_Null = 2; public final static int TYPE_Bool = 3; public final static int TYPE_Byte = 4; public final static int TYPE_Int = 5; public final static int TYPE_Type = 6; public final static int TYPE_Nominal = 7; public final static int TYPE_Reference = 8; public final static int TYPE_Array = 9; public final static int TYPE_Record = 10; public final static int TYPE_Function = 11; public final static int TYPE_Method = 12; public final static int TYPE_Union = 13; public final static int TYPE_Intersection = 14; public final static int TYPE_Negation = 15; public final static int TYPE_Property = 16; // ========================================================================= // MODIFIER identifiers // ========================================================================= public final static int MODIFIER_PROTECTION_MASK = 3; public final static int MODIFIER_Private = 0; public final static int MODIFIER_Public = 1; // public final static int MODIFIER_Protected = 2; // for later // public final static int MODIFIER_Package = 3; // public final static int MODIFIER_Module = 4; public final static int MODIFIER_MANGLE_MASK = 3 << 4; public final static int MODIFIER_Native = 1 << 4; public final static int MODIFIER_Export = 2 << 4; // public final static int MODIFIER_Total = 3 << 4; }