// ex: se sts=4 sw=4 expandtab: /* * Partial Java source code parser. * * This file is part of Yeti language compiler. * * Copyright (c) 2010 Madis Janson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package yeti.lang.compiler; import yeti.renamed.asmx.Opcodes; import java.util.*; // Java source tree node, can be class or method or field. class JavaNode { int modifier; String type; // extends for classes String name; // full name for classes JavaNode field; // field/method list String[] argv; // implements for classes JavaSource source; } /* * Represents Java source file. * * It is also partial Java parser that parses class fields and * method signatures, and skips method bodies. */ class JavaSource implements Opcodes { private static final String[] CHAR_SPOOL = new String[128]; private static final Map MODS = new HashMap(); private int p, e; private char[] s; private String lookahead; private String fn; private Map classes; private int line = 1; private final String packageName; private List imports = new ArrayList(); private String[] importPackages; // list of packages to search on resolve private String get(int level) { if (lookahead != null) { String id = lookahead; lookahead = null; return id; } char c, s[] = this.s; int p = this.p - 1, e = this.e; boolean annotation = false; for (;;) { // skip whitespace and comments while (++p < e && (c = s[p]) >= '\000' && c <= ' ') if (c == '\n' || c == '\r' && p + 1 < e && s[p + 1] != '\n') ++line; if (p + 1 < e && s[p] == '/') { if ((c = s[++p]) == '/') { while (++p < e && (c = s[p]) != '\r' && c != '\n'); --p; continue; } if (c == '*') { for (++p; ++p < e && ((c = s[p-1]) != '*' || s[p] != '/');) if (c == '\n' || c == '\r' && s[p] != '\n') ++line; continue; } --p; } if (p >= e) { this.p = p; return null; } // string, we really don't care about it's contents if ((c = s[p]) == '"' || c == '\'') { while (++p < e && s[p] != c && s[p] != '\n') if (s[p] == '\\') ++p; } // skipping block if (level > 0 && (c != '}' || --level > 0)) { if (c == '{') ++level; continue; } if (level == 0 && (!annotation || c != '(')) { if (c != '@') break; while (++p < e && s[p] >= '0'); // skip name --p; annotation = true; } else if (c == '(') { --level; } else if (c == ')') { ++level; annotation = false; } } int f = p, l; // get token while (p < e && ((c = s[p]) == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c > '~')) ++p; if (f == p) for (c = s[p]; ++p < e && c == '.' && s[p] == c;); this.p = p; // faster and ensures all operators to be interned if ((l = p - f) == 1 && (c = s[f]) >= '\000' && c < CHAR_SPOOL.length) return CHAR_SPOOL[c]; return new String(s, f, p - f); } void expect(String expect, String id, String at, String name) { if (!expect.equals(id)) { CompileException e = new CompileException(line, 0, "Expected `" + expect + (id == null ? "EOF" : "', not `" + id + '\'') + at + (name == null ? "" : " (" + name + ")")); e.fn = fn; throw e; } } private int modifiers() { String id; Object mod; int result = 0; while ((mod = MODS.get(id = get(0))) != null) { result |= ((Integer) mod).intValue(); } lookahead = id; return result; } // mode 0 - classname (dotted identifier) // mode 1 - variable name (identifier with []) // mode 2 - parametric classname (dotted identifier, <>) // mode 3 - full type (dotted identifier <> []) private String type(int mode) { StringBuffer result = null; String id = get(0), sep = null; if (id == "{") return id; while (id != null) { sep = get(0); if (sep == "<" && mode > 1) { int level = 1; String x; while ((x = get(0)) != null && (x != ">" || --level > 0)) if (x == "<") ++level; sep = get(0); } if (sep != "." || mode == 1) break; if (result == null) result = new StringBuffer(id); result.append('/'); if ((id = get(0)) != null) result.append(id); } String type = result == null ? id : result.toString(); if (mode != 0) while (sep == "[" && mode != 2) { expect("]", get(0), " after type name", type); type = "[".concat(type); sep = get(0); } lookahead = sep; return type; } private String field(int modifiers, String type, JavaNode target) { JavaNode n = null; String id = type(1); if (id != null && ((modifiers & ACC_PRIVATE) == 0 || id == "(")) { if ("...".equals(id)) { type = "[".concat(type); if ((id = type(1)) == null) return null; } while (id.startsWith("[")) { type = "[".concat(type); id = id.substring(1); } type = type.intern(); if (target == null) return type; n = new JavaNode(); n.modifier = modifiers; if (id == "(") { type = "void"; n.name = "<init>"; } else { n.name = id.intern(); } n.type = type; n.field = target.field; target.field = n; } int method = 0; if (id == "(" || (id = get(0)) == "(") { List l = new ArrayList(); do { modifiers(); if ((id = type(3)) == ")") break; type = field(0, id, null); if ((id = get(0)) != null) l.add(type); } while (id == ","); expect(")", id, " after method argument list", n == null ? null : n.name); if (n != null) n.argv = (String[]) l.toArray(new String[l.size()]); method = 1; } else if (id != "=") { return id; } int level = method; while ((id = get(0)) != null && id != ";" && (level > 0 || id != ",")) if (id == "{") { get(1); if (method != 0) return ";"; } else if (id == "(") { ++level; } else if (id == ")") { --level; } return id; } private String readClass(String outer, int modifiers) { String id = type(3); if ("interface".equals(id)) modifiers |= ACC_INTERFACE | ACC_ABSTRACT; else if (!"class".equals(id)) return id; JavaNode cl = new JavaNode(); cl.source = this; cl.modifier = modifiers; id = type(2); cl.name = outer != null ? outer + '$' + id : packageName.length() != 0 ? packageName + '/' + id : id; id = get(0); boolean iface_extends = false; if ("extends".equals(id)) { if ((modifiers & ACC_INTERFACE) == 0) { cl.type = type(2); id = get(0); } else { iface_extends = true; } } if (iface_extends || "implements".equals(id)) { List impl = new ArrayList(); do { impl.add(type(2)); } while ((id = get(0)) == ","); cl.argv = (String[]) impl.toArray(new String[impl.size()]); } expect("{", id, " for the class definition body", cl.name); while ((id = readClass(cl.name, modifiers = modifiers())) != "}") { if (id == null) return null; if (id == "{") get(1); else while (id != "" && field(modifiers, id, cl) == ","); } classes.put(cl.name, cl); return ""; } JavaSource(String sourceName, char[] source, Map classes) { fn = sourceName; s = source; e = source.length; this.classes = classes; String id = get(0); if ("package".equals(id)) { packageName = type(0); id = get(0); } else { packageName = ""; } for (; id != null; id = get(0)) { if (id == ";") continue; // skip toplevel ; if (!"import".equals(id)) break; if ("static".equals(id = type(0))) type(0); // ignore import static blaah else if (id != null) imports.add(id); } lookahead = id; while (readClass(null, modifiers()) != null); s = null; classes = null; fn = null; } /* * Java weird inner class implementation means that * import w.x.y.z; doesn't tell whether it means really * class x.y.z or x.y$z or x$y$z. * Only way to find out is to try, which class exists... */ private static String resolveFull(ClassFinder finder, String t, Map to) { String name = null, full = t; char[] cs = t.toCharArray(); for (int i = cs.length; --i >= 0;) if (cs[i] == '/') { if (name == null) name = t.substring(i + 1, cs.length); else full = new String(cs); if (finder.exists(full)) { full = 'L' + full + ';'; if (to != null) { Object o = to.put(name, full); if (o != null) // don't override primitives! to.put(name, o); } return full; } cs[i] = '$'; } return null; } private synchronized void prepareResolve(ClassFinder finder) { if (importPackages != null) return; // already done // reuse classes for full-name import map classes = new HashMap(JavaType.JAVA_PRIM); classes.remove("number"); List pkgs = new ArrayList(); pkgs.add(packageName.concat("/")); pkgs.add("java/lang/"); for (int i = 0; i < imports.size(); ++i) { String s = (String) imports.get(i); if (s.endsWith("/*")) pkgs.add(s.substring(0, s.length() - 1)); else resolveFull(finder, s, classes); } classes.put("void", "V"); importPackages = (String[]) pkgs.toArray(new String[pkgs.size()]); imports = null; } // Java package imports and inner class naming means that it is impossible // to resolve Java type name without knowing what classes really exists // (so all source classes must be parsed before attempting any resolving). private YType resolve(ClassFinder finder, String type, String[] to, int n) { int array = 0, l = type.length(); while (array < l && type.charAt(array) == '[') ++array; int dot = (type = type.substring(array)).indexOf('/'); String subclass = type, cname = type; if (dot > 0) { subclass = type.replace('/', '$'); cname = type.substring(0, dot); } String res = (String) classes.get(cname); if (res == null && (dot <= 0 || (res = resolveFull(finder, type, null)) == null)) { // try to resolve from package imports for (int i = 0; res == null && i < importPackages.length; ++i) if (finder.exists(importPackages[i].concat(cname))) res = importPackages[i].concat(subclass); if (res == null) // couldn't resolve res = dot > 0 ? type : importPackages[0].concat(type); res = 'L' + res + ';'; } if (to != null) { to[n] = array != 0 || res.length() <= 1 ? type : res.substring(1, res.length() - 1); return null; } YType t = new YType(res); while (--array >= 0) t = new YType(YetiType.JAVA_ARRAY, new YType[] { t }); return t; } static void loadClass(ClassFinder finder, JavaTypeReader tr, JavaNode n) { JavaSource src = n.source; src.prepareResolve(finder); String cname = n.name; String[] superType = { "java/lang/Object" }; if (n.type != null) src.resolve(finder, n.type, superType, 0); String[] interfaces = new String[n.argv == null ? 0 : n.argv.length]; for (int i = 0; i < interfaces.length; ++i) src.resolve(finder, n.argv[i], interfaces, i); tr.visit(0, n.modifier, cname, null, superType[0], interfaces); for (JavaNode i = n.field; i != null; i = i.field) { int access = i.modifier; if ((n.modifier & ACC_INTERFACE) != 0) access |= i.argv == null ? ACC_PUBLIC | ACC_FINAL | ACC_STATIC : ACC_PUBLIC | ACC_ABSTRACT; String name = i.name; YType t = src.resolve(finder, i.type, null, 0); if (i.argv == null) { // field ((access & ACC_STATIC) == 0 ? tr.fields : tr.staticFields) .put(name, new JavaType.Field(name, access, cname, t)); continue; } YType[] av = new YType[i.argv.length]; for (int j = 0; j < av.length; ++j) av[j] = src.resolve(finder, i.argv[j], null, 0); JavaType.Method m = new JavaType.Method(); m.name = name; m.access = access; m.returnType = t; m.arguments = av; m.className = cname; m.sig = name + m.descr(null); if (name == "<init>") tr.constructors.add(m); else ((access & ACC_STATIC) == 0 ? tr.methods : tr.staticMethods) .add(m); } if (tr.constructors.size() == 0) // default constructor tr.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); } private static void mod(String name, int value) { MODS.put(name, new Integer(value)); } static { char[] x = { ' ' }; for (short i = 0; i < CHAR_SPOOL.length; ++i) { x[0] = (char) i; CHAR_SPOOL[i] = new String(x).intern(); } mod("abstract", ACC_ABSTRACT); mod("final", ACC_FINAL); mod("native", ACC_NATIVE); mod("private", ACC_PRIVATE); mod("protected", ACC_PROTECTED); mod("public", ACC_PUBLIC); mod("static", ACC_STATIC); mod("strictfp", ACC_STRICT); mod("synchronized", ACC_SYNCHRONIZED); mod("transient", ACC_TRANSIENT); mod("volatile", ACC_VOLATILE); } }