asmtools/src/org/openjdk/asmtools/jcoder/Jcoder.java

769 lines
25 KiB
Java

/*
* Copyright (c) 2009, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.openjdk.asmtools.jcoder;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Stack;
import static org.openjdk.asmtools.jcoder.JcodTokens.ConstType;
import static org.openjdk.asmtools.jcoder.JcodTokens.Token;
/**
* Compiles just 1 source file
*/
class Jcoder {
/*-------------------------------------------------------- */
/* Jcoder Fields */
private ArrayList<ByteBuffer> Classes = new ArrayList<>();
private ByteBuffer buf;
private DataOutputStream bufstream;
private int depth = 0;
private String tabStr = "";
private Context context = null;
protected SourceFile env;
protected Scanner scanner;
/*-------------------------------------------------------- */
/* Jcoder inner classes */
/*-------------------------------------------------------- */
/* ContextTag (marker) - describes the type of token */
/* this is rather cosmetic, no function currently. */
private enum ContextTag {
NULL ( ""),
CLASS ( "Class"),
CONSTANTPOOL ( "Constant-Pool"),
INTERFACES ( "Interfaces"),
INTERFACE ( "Interface"),
METHODS ( "Methods"),
METHOD ( "Method"),
FIELDS ( "Fields"),
FIELD ( "Field"),
ATTRIBUTE ( "Attribute");
private final String printValue;
ContextTag(String value) {
printValue = value;
}
public String printval() {
return printValue;
}
}
/*-------------------------------------------------------- */
/* ContextVal (marker) - Specific value on a context stack */
private class ContextVal {
public ContextTag tag;
int compCount;
ContextVal owner;
ContextVal(ContextTag tg) {
tag = tg;
compCount = 0;
owner = null;
}
ContextVal(ContextTag tg, ContextVal ownr) {
tag = tg;
compCount = 0;
owner = ownr;
}
}
/*-------------------------------------------------------- */
/* Context - Context stack */
public class Context {
Stack<ContextVal> stack;
private boolean hasCP;
private boolean hasMethods;
private boolean hasInterfaces;
private boolean hasFields;
Context() {
stack = new Stack<>();
init();
}
boolean isConstantPool() {
return !stack.empty() && (stack.peek().tag == ContextTag.CONSTANTPOOL);
}
public void init() {
stack.removeAllElements();
hasCP = false;
hasMethods = false;
hasInterfaces = false;
hasFields = false;
}
void update() {
if (stack.empty()) {
stack.push(new ContextVal(ContextTag.CLASS));
return;
}
ContextVal currentCtx = stack.peek();
switch (currentCtx.tag) {
case CLASS:
if (!hasCP) {
stack.push(new ContextVal(ContextTag.CONSTANTPOOL));
hasCP = true;
} else if (!hasInterfaces) {
stack.push(new ContextVal(ContextTag.INTERFACES));
hasInterfaces = true;
} else if (!hasFields) {
stack.push(new ContextVal(ContextTag.FIELDS));
hasFields = true;
} else if (!hasMethods) {
stack.push(new ContextVal(ContextTag.METHODS));
hasMethods = true;
} else {
// must be class attributes
currentCtx.compCount += 1;
stack.push(new ContextVal(ContextTag.ATTRIBUTE, currentCtx));
}
break;
case INTERFACES:
currentCtx.compCount += 1;
stack.push(new ContextVal(ContextTag.INTERFACE, currentCtx));
break;
case FIELDS:
currentCtx.compCount += 1;
stack.push(new ContextVal(ContextTag.FIELD, currentCtx));
break;
case METHODS:
currentCtx.compCount += 1;
stack.push(new ContextVal(ContextTag.METHOD, currentCtx));
break;
case FIELD:
case METHOD:
case ATTRIBUTE:
currentCtx.compCount += 1;
stack.push(new ContextVal(ContextTag.ATTRIBUTE, currentCtx));
break;
default:
break;
}
}
void exit() {
if (!stack.isEmpty()) {
stack.pop();
}
}
public String toString() {
if (stack.isEmpty()) {
return "";
}
ContextVal currentCtx = stack.peek();
String retval = currentCtx.tag.printval();
switch (currentCtx.tag) {
case INTERFACE:
case METHOD:
case FIELD:
case ATTRIBUTE:
if (currentCtx.owner != null) {
retval += "[" + currentCtx.owner.compCount + "]";
}
}
return retval;
}
}
/*-------------------------------------------------------- */
/* Jcoder */
/**
* Create a parser
*/
Jcoder(SourceFile sf, HashMap<String, String> macros) throws IOException {
scanner = new Scanner(sf, macros);
env = sf;
context = new Context();
}
/*-------------------------------------------------------- */
/**
* Expect a token, return its value, scan the next token or throw an exception.
*/
private void expect(Token t) throws SyntaxError, IOException {
if (scanner.token != t) {
env.traceln("expect:" + t + " instead of " + scanner.token);
switch (t) {
case IDENT:
env.error(scanner.pos, "identifier.expected");
break;
default:
env.error(scanner.pos, "token.expected", t.toString());
break;
}
throw new SyntaxError();
}
scanner.scan();
}
private void recoverField() throws SyntaxError, IOException {
while (true) {
switch (scanner.token) {
case LBRACE:
scanner.match(Token.LBRACE, Token.RBRACE);
scanner.scan();
break;
case LPAREN:
scanner.match(Token.LPAREN, Token.RPAREN);
scanner.scan();
break;
case LSQBRACKET:
scanner.match(Token.LSQBRACKET, Token.RSQBRACKET);
scanner.scan();
break;
case RBRACE:
case EOF:
case INTERFACE:
case CLASS:
// begin of something outside a class, panic more
throw new SyntaxError();
default:
// don't know what to do, skip
scanner.scan();
break;
}
}
}
/**
* Parse an array of struct.
*/
private void parseArray() throws IOException {
scanner.scan();
int length0 = buf.length, pos0 = scanner.pos;
int num_expected;
if (scanner.token == Token.INTVAL) {
num_expected = scanner.intValue;
scanner.scan();
} else {
num_expected = -1;
}
expect(Token.RSQBRACKET);
int numSize;
switch (scanner.token) {
case BYTEINDEX:
scanner.scan();
numSize = 1;
break;
case SHORTINDEX:
scanner.scan();
numSize = 2;
break;
case ZEROINDEX:
scanner.scan();
numSize = 0;
break;
default:
numSize = 2;
}
// skip array size
if (numSize > 0) {
buf.append(num_expected, numSize);
}
int num_present = parseStruct();
if (num_expected == -1) {
env.trace(" buf.writeAt(" + length0 + ", " + num_present + ", " + numSize + "); ");
// skip array size
if (numSize > 0) {
buf.writeAt(length0, num_present, numSize);
}
} else if ( num_expected != num_present) {
if (context.isConstantPool() && num_expected == num_present +1) return;
env.error(pos0, "warn.array.wronglength", num_expected, num_present);
}
}
/**
* Parse a byte array.
*/
private void parseByteArray() throws IOException {
scanner.scan();
expect(Token.LSQBRACKET);
int length0 = buf.length, pos0 = scanner.pos;
int len_expected;
if (scanner.token == Token.INTVAL) {
len_expected = scanner.intValue;
scanner.scan();
} else {
len_expected = -1;
}
expect(Token.RSQBRACKET);
int lenSize;
switch (scanner.token) {
case BYTEINDEX:
scanner.scan();
lenSize = 1;
break;
case SHORTINDEX:
scanner.scan();
lenSize = 2;
break;
case ZEROINDEX:
scanner.scan();
lenSize = 0;
break;
default:
lenSize = 4;
}
// skip array size
if (lenSize > 0) {
buf.append(len_expected, lenSize);
}
int length1 = buf.length;
parseStruct();
int len_present = buf.length - length1;
if (len_expected == -1) {
env.trace(" buf.writeAt(" + length0 + ", " + len_present + ", " + lenSize + "); ");
// skip array size
if (lenSize > 0) {
buf.writeAt(length0, len_present, lenSize);
}
} else if (len_expected != len_present) {
env.error(pos0, "warn.array.wronglength", len_expected, len_present);
}
}
/**
* Parse an Attribute.
*/
private void parseAttr() throws IOException {
scanner.scan();
expect(Token.LPAREN);
int cpx; // index int const. pool
if (scanner.token == Token.INTVAL) {
cpx = scanner.intValue;
scanner.scan();
/* } else if (token==STRINGVAL) {
Integer Val=(Integer)(CP_Strings.get(stringValue));
if (Val == null) {
env.error(pos, "attrname.notfound", stringValue);
throw new SyntaxError();
}
cpx=Val.intValue();
*/ } else {
env.error(scanner.pos, "attrname.expected");
throw new SyntaxError();
}
buf.append(cpx, 2);
int pos0 = scanner.pos, length0 = buf.length;
int len_expected;
if (scanner.token == Token.COMMA) {
scanner.scan();
len_expected = scanner.intValue;
expect(Token.INTVAL);
} else {
len_expected = -1;
}
buf.append(len_expected, 4);
expect(Token.RPAREN);
parseStruct();
int len_present = buf.length - (length0 + 4);
if (len_expected == -1) {
buf.writeAt(length0, len_present, 4);
} else if (len_expected != len_present) {
env.error(pos0, "warn.attr.wronglength", len_expected, len_present);
}
} // end parseAttr
/**
* Parse a Component of JavaCard .cap file.
*/
private void parseComp() throws IOException {
scanner.scan();
expect(Token.LPAREN);
int tag = scanner.intValue; // index int const. pool
expect(Token.INTVAL);
buf.append(tag, 1);
int pos0 = scanner.pos, length0 = buf.length;
int len_expected;
if (scanner.token == Token.COMMA) {
scanner.scan();
len_expected = scanner.intValue;
expect(Token.INTVAL);
} else {
len_expected = -1;
}
buf.append(len_expected, 2);
expect(Token.RPAREN);
parseStruct();
int len_present = buf.length - (length0 + 2);
if (len_expected == -1) {
buf.writeAt(length0, len_present, 2);
} else if (len_expected != len_present) {
env.error(pos0, "warn.attr.wronglength", len_expected, len_present);
}
} // end parseComp
private void adjustDepth(boolean up) {
if (up) {
depth += 1;
context.update();
scanner.setDebugCP(context.isConstantPool());
} else {
depth -= 1;
context.exit();
}
StringBuilder bldr = new StringBuilder();
int tabAmt = 4;
int len = depth * tabAmt;
for (int i = 0; i < len; i++) {
bldr.append(" ");
}
tabStr = bldr.toString();
}
/**
* Parse a structure.
*/
private int parseStruct() throws IOException {
adjustDepth(true);
env.traceln(" ");
env.traceln(tabStr + "MapStruct { <" + context + "> ");
expect(Token.LBRACE);
int num = 0;
int addElem = 0;
while (true) {
try {
switch (scanner.token) {
case COMMA: // ignored
scanner.scan();
break;
case SEMICOLON:
num++;
addElem = 0;
scanner.scan();
break;
case CLASS:
scanner.addConstDebug(ConstType.CONSTANT_CLASS);
env.trace("class ");
scanner.longValue = ConstType.CONSTANT_CLASS.value();
scanner.intSize = 1;
case INTVAL:
env.trace("int [" + scanner.longValue + "] ");
buf.append(scanner.longValue, scanner.intSize);
scanner.scan();
addElem = 1;
break;
case STRINGVAL:
scanner.scan();
scanner.addConstDebug(ConstType.CONSTANT_UTF8);
env.trace("UTF8 [\"" + scanner.stringValue + "\"] ");
bufstream.writeUTF(scanner.stringValue);
addElem = 1;
break;
case LONGSTRINGVAL:
scanner.scan();
env.traceln("LongString [\"" + Arrays.toString(scanner.longStringValue.data) + "\"] ");
buf.write(scanner.longStringValue.data, 0, scanner.longStringValue.length);
addElem = 1;
break;
case LBRACE:
parseStruct();
addElem = 1;
break;
case LSQBRACKET:
parseArray();
addElem = 1;
break;
case BYTES:
env.trace("bytes ");
parseByteArray();
addElem = 1;
break;
case ATTR:
env.trace("attr ");
parseAttr();
addElem = 1;
break;
case COMP:
env.trace("comp ");
parseComp();
addElem = 1;
break;
case RBRACE:
scanner.scan();
env.traceln(" ");
env.traceln(tabStr + "} // MapStruct <" + context + "> [");
adjustDepth(false);
return num + addElem;
default:
env.traceln("unexp token=" + scanner.token);
env.traceln(" scanner.stringval = \"" + scanner.stringValue + "\"");
env.error(scanner.pos, "element.expected");
throw new SyntaxError();
}
} catch (SyntaxError e) {
recoverField();
}
}
} // end parseStruct
/**
* Recover after a syntax error in the file. This involves discarding tokens until an
* EOF or a possible legal continuation is encountered.
*/
private void recoverFile() throws IOException {
while (true) {
switch (scanner.token) {
case CLASS:
case INTERFACE:
// Start of a new source file statement, continue
return;
case LBRACE:
scanner.match(Token.LBRACE, Token.RBRACE);
scanner.scan();
break;
case LPAREN:
scanner.match(Token.LPAREN, Token.RPAREN);
scanner.scan();
break;
case LSQBRACKET:
scanner.match(Token.LSQBRACKET, Token.RSQBRACKET);
scanner.scan();
break;
case EOF:
return;
default:
// Don't know what to do, skip
scanner.scan();
break;
}
}
}
/**
* Parse module declaration
*/
private void parseModule() throws IOException {
// skip module name as a redundant element
scanner.skipTill(Scanner.LBRACE);
buf = new ByteBuffer();
bufstream = new DataOutputStream(buf);
buf.myname = "module-info.class";
scanner.scan();
env.traceln("starting " + buf.myname);
// Parse the clause
parseClause();
env.traceln("ending " + buf.myname);
}
/**
* Parse a class or interface declaration.
*/
private void parseClass(Token prev) throws IOException {
scanner.scan();
buf = new ByteBuffer();
bufstream = new DataOutputStream(buf);
// Parse the class name
switch (scanner.token) {
case STRINGVAL:
buf.myname = scanner.stringValue;
break;
case BYTEINDEX:
case SHORTINDEX:
case ATTR:
case BYTES:
case MACRO:
case COMP:
case FILE:
case IDENT:
if (prev == Token.FILE) {
buf.myname = scanner.stringValue;
} else {
buf.myname = scanner.stringValue + ".class";
}
break;
default:
env.error(scanner.prevPos, "name.expected");
throw new SyntaxError();
}
scanner.scan();
env.traceln("starting class " + buf.myname);
// Parse the clause
parseClause();
env.traceln("ending class " + buf.myname);
} // end parseClass
private void parseClause() throws IOException {
switch (scanner.token) {
case LBRACE:
parseStruct();
break;
case LSQBRACKET:
parseArray();
break;
case BYTES:
parseByteArray();
break;
case ATTR:
parseAttr();
break;
case COMP:
parseComp();
break;
default:
env.error(scanner.pos, "struct.expected");
}
}
/**
* Parse an Jcoder file.
*/
ArrayList<ByteBuffer> parseFile() {
env.traceln("PARSER");
context.init();
try {
while (scanner.token != Token.EOF) {
try {
switch (scanner.token) {
case CLASS:
case MODULE:
case INTERFACE:
case FILE:
Token t = scanner.token;
if ( t == Token.MODULE) {
parseModule();
} else {
parseClass(t);
}
// End of the class,interface or module
env.flushErrors();
Classes.add(buf);
break;
case SEMICOLON:
// Bogus semi colon
scanner.scan();
break;
case EOF:
// The end
return Classes;
default:
env.traceln("unexpected token=" + scanner.token.toString());
env.error(scanner.pos, "toplevel.expected");
throw new SyntaxError();
}
} catch (SyntaxError e) {
String msg = e.getMessage();
env.traceln("SyntaxError " + (msg == null ? "" : msg));
if( env.debugInfoFlag ) {
e.printStackTrace();
}
recoverFile();
}
}
} catch (IOException e) {
env.error(scanner.pos, "io.exception", env.getSource());
return Classes;
}
return Classes;
} //end parseFile
/*---------------------------------------------*/
private static char fileSeparator; //=System.getProperty("file.separator");
/**
* write to the directory passed with -d option
*/
public void write(ByteBuffer cls, File destdir) throws IOException {
String myname = cls.myname;
if (myname == null) {
env.error("cannot.write", null);
return;
}
env.traceln("writing " + myname);
File outfile;
if (destdir == null) {
int startofname = myname.lastIndexOf('/');
if (startofname != -1) {
myname = myname.substring(startofname + 1);
}
outfile = new File(myname);
} else {
env.traceln("writing -d " + destdir.getPath());
if (fileSeparator == 0) {
fileSeparator = System.getProperty("file.separator").charAt(0);
}
if (fileSeparator != '/') {
myname = myname.replace('/', fileSeparator);
}
outfile = new File(destdir, myname);
File outdir = new File(outfile.getParent());
if (!outdir.exists() && !outdir.mkdirs()) {
env.error("cannot.write", outdir.getPath());
return;
}
}
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(outfile));
out.write(cls.data, 0, cls.length);
try {
out.close();
} catch (IOException ignored) { }
}
/**
* Writes the classes
*/
public void write(File destdir) throws IOException {
for (ByteBuffer cls : Classes) {
write(cls, destdir);
}
} // end write()
} // end Jcoder