java/ch/wlkl/wsh/Compiler.java
package ch.wlkl.wsh;
import static ch.wlkl.wsh.Parser.findEndStart;
import static ch.wlkl.wsh.Parser.notDo;
import static ch.wlkl.wsh.Parser.shName;
import static ch.wlkl.wsh.Parser.shWord;
import static ch.wlkl.wsh.Parser.space;
import static ch.wlkl.wsh.Parser.string;
import static ch.wlkl.wsh.Strings.unquote;
import static ch.wlkl.wsh.Strings.xQuote;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* compiles shell or data syntax to javaCode and a temporary JavaClass, subclass of {@link Run} and loads it into the virtual machine.
*
* <br><br><b>Attention:</b> java implements an old version of wsh, <a href="https://www.wlkl.ch/index.php/Inf/WshSyn">the rexx version</a> contains much innovation since then.
*
* <h1>The main feature of this shell language</h1>
* <ul>
* <li>standard input and ouput with a unix-shell like syntax, to the file system, temporary buffers and heredata
* <li>piping of statemts (using the output of one statement as input for the next)
* <li>variable expansion in heredata or java
* <li>integration with java, but the design is as much language independent as possible, there is a version z/OS REXX under construction
* </ul>
*
* <h1>Shell Syntax</h1>
* <ul>
* <li id="syData"><B>data = (spCom* | exprD) (nl exprD)* (nl spCom*)? ((stmt | input) data)*</B>
* <br>data consists of literal data line interpersed with variable expansions, input from files or heredata and statements.
* Partial lines are ignored, if they consist only of spaces and comments.
* <li id="syShell"><B>shell = pipe ? ('$;' pipe?)*</B>
* <br>Pipes separated by $;. Caution: Semicolons without $ do not terminate a pipe but are part of the underlying language.
* <li id="syExpr"><B>exprD = (( notDoNl | primary ) comment* )+</B>
* <br>expressions consists of notDoNl interpreted litaral constants and primaries, comments are ignored.
* <li><B>exprS = spCom* exprD spCom*</B>
* <br>a flavour of expression ignoring leading and trailing spaces
* <li><B>exprW = spCom* (( shWord | primary ) comment* )+ spCom*</B>
* <br>a flavour of expression ignoring leading and trailing spaces and text only words
* <li id=syLang><B>lang = (( notDoNl | primary ) comment* )+</B>
* <br> syntactically the same as <B>exprD</B>, however <B>notDoNl</B> is interpreted as language source. This may yield a java expression or java statements. Comments are replaced by a single space.
* <li id=syPrimary><B>primary = vars | string | '$(' lang '$)' | '$-[' data '$]' | '$-{' shell '$}' | $-compShell | $-compData</B>
* <br>the $-comp... primaries compile standard input as shell, respectively data and return the resulting instance of {@link Run}
* <li><B>vars = '$' shName | '${' ('?' | '>')? exprW colCla? '}'</B>
* <br>retrieves the contents of a variable from the variable pool, tests its existence or reads from std input, see <A hRef=#seVars>semantics</A>
* <li><B>colCla = spCom ':' spCom shWord</B>S
* <li id=syInput><b>input = '$<' ioOpts exprS | hereData</b>
* <li id=syHereData><b>hereData = '$<<' ioOpts shWord sp* (nl notNl*)* nl shWord</b>
* <li> <b>output = ('$>' | '$>>' | '$&') ioOpts exprS</b>
* <li> <b>ioOpts = '+'? '-'? ( '?' | '#' | '&' | '[' | '{' )? </b>
* <br> option characters to specify the meaning of exprS fileName see <A href=#seIoOpts>semantics</A>
* <li id=syPipe><B>pipe = ((input | output) spNlCom*)* (stmts ( '$|' colCla? stmts)*)?</b>
* <br>this is execution order, syntax order is free
* <li id=syStmts><B>stmts = ((stmt | lang) spNlCom*)+</b>
* <li id=syStmt><b>stmt = ass | '$@{' shell '$}' | '$[' data '$]'| '$$' exprS | '$?' lang |'$@for' exprW colCla? stmt | '$@run' primary</b>
* <li><b>ass = '$=' exprW colCla? ( '=' exprS | '?' lang)</B>
* </ul>
* <h1>Lexicals</h1>
* <ul>
* <li><b>string = '$''' ((* notNl not '''' *) | '''''')* '''' | '$?' ((* notNl not '?' *) | '??')* '?''</b>
* <br>a string enclosed in single or double quotes, prefixed by a dollar. Inside, two quotes stand for a single one.
* <li><b>shName = (* starting with a letter followed by any number of digits or characters *)</b>
* <li><b>shWord = (* 1 or more characters not sp, nl, {, }, =, ? or : *)</b>
* <br>
* <li><b>spNlCom = nl | sp | comment</b>
* <li><b>spCom = sp | comment</b>
* <li><b> comment = '$**' notNl* | '$*+' notNl* nl | '$*(' (comment | hereData | string | char)* '$*)'</b>
* <br>
* <li> notDoNl = (* one character from notNl but not '$' *)
* <li> notNl = (* one character not nl *)
* <li> nl = (* newLine character(s) or record switch *)
* <li id="sySp"> sp = (* one space character ' ', tab etc., but not nl *)
* </ul>
* <h1>Semantics</h1>
* The run of a program fragment has the following effects
* <ul>
* <li>it reads from standard input
* <li>it writes to standard output
* <li>it accesses and updates the variable pool
* <li>runs java code either from <b>lang</b> constructs or by method calls from the accesses to the variable pool
* </ul>
* The semantics of the syntactical constructs
* <ul>
* <li><b>data</b> writes in the given sequence to standard output: expressions, the output of statements and the contents of inputs.
* <li><b>shell</b> writes the output of its pipes to standard output.
* <li><b>exprD, exprS, exprW</b> if it constists of a single primary returns this primary otherwise a String consisting of the concatenation of each primary and literal
* <li><B>primary</B> yields a String or an instance of another java class.
* <ul><li id=seVars><B>vars</B> retrieves the contents of a variable from the variable pool, tests its existence or reads from std input. Nesting is supported. The function depends on the options:
* <ul>
* <li> <B>?</B> returns a boolean, whether the variable named exprW is defined in the variable pool.
* <li> <B>></B> reads from standard input and puts the line read into the variable named exprW. returns false at end of file.
* <li> <B>neither</B> returns the contents of the variable named exprW or fails if it is undefined
* <li> <B>colCla</B> returns an object of the specified class, if not specified shell infers the type
* </ul>
* <li><B>string</B> a string constant
* <li><B>'$(' lang '$)'</B> lang must be a java expression. Yields the object (of any class) of this java expression.
* <li><B>'$-[' data '$]'</B> yields a string: the concatenation of all lines of data, separated by a single space.
* <li><B>$-{' shell '$}'</B> yields a string: shell is exececuted and the lines of its standard output concatenated, separated by a single space.
* <li><B>$-compShell, $-compData</B> compiles standard input as shell, respectively data yields the resulting instance of {@link Run}
* </ul>
* <li><b>colCla</b> designates the type (class) of an expression or declares the type of a variable. see <A hRef=#Typing>Typing</A>
* <li><b>input</b> in a statment copies the specified {@link Read}, File, etc. to std output. As a io redirection of a pipe defines (std) input.
* <li><b>hereData</b> the following source lines are used as input, depending on ioOpts literally or compiled.
* <li><b>output</b> In a io redirection of a pipe defines (std) output.
* <li id=seIoOpts><b>ioOpts</b> option characters to specify the meaning of the subsequent exprW. Which options are valid is contextdependent.
* <br>general
* <ul>
* <li> <B>+</B> for input: concatenenate this input with the next input, for an external FD reroute the following inputOutput to this external FD
* <li> <B>-</B> do not open and close an inputOutput, do not allocate and deallocate an external FD
* </ul>
* not for hereData see {@link Cat#make(String, Object)}
* <ul>
* <li> <B>?</B> exprS yields a languge Object that will be transformed to an {@link OpenClose} by {@link Cat#make(String, Object)}
* <li> <B>&</B> exprS is a String specification of an external FD
* <li> <B>#</B> exprS is a name in the variable pool. If it does not exist, a new Buf<String> is put there. If it exists the retrieved value is interpreted as with the ? option.
* <li> otherwise exprS is a filename of the external file system
* </ul>
* only for hereData
* <ul>
* <li> <B>[</B> interpret this hereData as <A hRef=#syData>data</A>
* <li> <B>{</B> interpret this hereData as <A hRef=#syShell>shell</A>
* <li> otherwise this hereData is interpreted literally
* </ul>
* <li><B>pipe</B> a pipe. input get std input (which may consist of a concatenation) of the whole pipe (, output std output. Additionally external FD may be allocated for the duration of the whole pipe. $| stands for the rerouting of std output of the previous stmts with std input of the following stmts.
* <li><B>stmts</B> java code interpreted as java statements possibly interpersed with stmt.
* <li><B>stmt</B> a shell statement is
* <ul><li><B>ass</B> assigns a variable in the variable pool.
* <li><B>'$@{' shell '$}'</B> is a block simply executing the shell.
* <li><B>'$@[' data '$]'</B> writes data to std output.
* <li><B>'$$', '$?'</B> write the subsequent shell respectively language expression to std output.
* <li><B>$@for</B> read std input until end of input. For each object execute the subsequent statement after assigning the object read to the variable name exprW.
* <li><B>$@run</B> the expression must yield an instance of {@link Run} which is run.
* </ul>
* </ul>
* <h1 id=Typing>Typing</h1>
* <ul>
* <li>The $| operator with the <B>colCla</B> defines the type of standard output within this pipe fragment.
* <li>The of a constant variable name of the variable pool is remember when the <B>colCla</B> option is used in the assignment, or the type of the expression can be deduced. The type is remembered in the following text.
* <li>the type of an expression of a single primary is the type of the primary, for all other expression it is String
* </ul>
* <h1>Tutorial</h1>
* <h1>Plans, toDo</h1>
* <ul>
* <li>simple include facility and general compileTime processing and generating permanent named classes
* <li> Env or EnvMan without typing?
* </ul>
* @author walter keller
*
*/
public class Compiler extends Top {
Parser parser = null;
final JavaCmpLoad<Run> loader;
final static String pckg = "temporary.shell";
String envIn;
String envOut;
final String envRoot;
final HashMap<String, String> envMap = new HashMap<String, String>();
final HashMap<String, String> varType = new HashMap<String, String>();
StringBuffer methods = new StringBuffer();
int methodCnt = 0;
public Compiler(JavaCmpLoad<?> ldr, Read<String> src) {
loader = new JavaCmpLoad<Run>(ldr, null, null, null, Run.class);
parser = new Parser(src);
envIn = envOut = envRoot = envNew();
envMap.put(envRoot, "String");
}
/**
* return an instance of a newly compiled subclass of {@link Run}
* @param type type for {@link #compileClass(char, String)}
* @return the new instance of the newly compiled subclass of {@link Run}
*/
public Run compileInstance(char type) {
Class<? extends Run> cl = compileClass(type, null);
try {
return cl.newInstance();
} catch (Exception e) {
parser.fail("instanciate error class " + cl + ": " + e);
return null;
}
}
/**
* return {@link #compileClass(char, String)} or null if an exception is catched
*/
public Class<? extends Run> compileClassCatch(char type, String name) {
try {
return compileClass(type, name);
} catch(AssertionError e) {
Env.env().write("compile failed: " + e);
return null;
}
}
/**
* compile a subclass of <tt>Run</tt>
* @param type type for {@link #compileSrc(char)}
* @param name the name of the class (may include package), if null a unique name is generated
* @return the compiled class
*/
public Class<? extends Run> compileClass(char type, String name) {
String run = compileSrc(type);
Object[] importing = {getClass().getPackage()};
Object[] implementing = {};
StringBuffer code = new StringBuffer();
int bx=0, ex;
methods.append("\n@SuppressWarnings(\"unchecked\")\npublic void run(String... arg) {");
methods.append(run == null ? "" : run).append("\n}");
while (bx < methods.length()&& 0 <= (ex = methods.indexOf(".env(?", bx))) {
final int sx = methods.indexOf(" ", ex);
final int cx = methods.indexOf(")", sx);
final String io = "(" + envResolve(methods.substring(ex+5, sx)) + ") null, (" + envResolve(methods.substring(sx+1, cx)) + ") null";
code.append(methods, bx, ex+5);
if (! io.equals("(String) null, (String) null"))
code.append(io);
code.append(')');
bx = cx + 1;
}
code.append(methods, bx, methods.length());
if (name == null)
name = loader.uniqueName("TempRun");
return loader.makeClass(name, loader.classSource(importing, name, implementing, code));
}
/**
* return the java code for shell or data interpretation of the complete current source
* @param type 's' for shell or 'd' for data
* @return java code
*/
private String compileSrc(char type) {
String src, what, expec;
final String oldEnvIn = envIn;
final String oldEnvOut = envOut;
if (type == 's') {
what = "shell";
expec = "pipe or $;";
parser.spaceNlComment();
src = shell();
} else if (type == 'd') {
what = "data";
expec = "sExpression or block";
src = data(false);
} else {
parser.fail("bad type " + type);
return null;
}
if (! parser.atEnd())
parser.fail(expec + " expected: compile " + what + " stopped before end of input");
if (! (oldEnvIn.equals(envIn) && oldEnvOut.equals(envOut)))
fail("after compile " + what + " environment = (" + envIn + ", " + envOut + ") not as before (" + oldEnvIn + ", " + oldEnvOut + ")");
return src;
}
/**
* compile a <A hRef=#syData>data</A> clause
* @param makeExpr if true return an expression otherwise statements
* @return the compiled code
*/
private String data(boolean makeExpr) {
ArrayList<String> exprs = new ArrayList<String>();
String text;
String one;
Compiler$Expr nd, io;
boolean isExpr = makeExpr;
while (true) {
boolean aftEol = false;
while (true) {
text = "";
do {
if (parser.next(notDo))
text += parser.tok;
} while (parser.comment());
nd = expr('d');
boolean befEol = parser.nl();
if (nd != null || (aftEol && befEol) || (text.length() > 0 && ! space.matcher(text).matches())) {
one = (text.length() <= 0 ? "" : xQuote(text) + (nd == null ? "" : " + " )) + (nd == null ? "" : nd.code);
exprs.add(isExpr ? null2EE(one) : writeExpr(one));
}
if (! befEol)
break;
aftEol = true;
}
if (null != (one = stmt())) {
} else if (null != (io = ioRedirection())) {
if (! io.constant.startsWith("<") || io.constant.indexOf('+') >= 0)
parser.fail("simple input");
/* create java to write the one inputReader to standard out */
if (io.constant.startsWith("<<"))
one = io.code;
else
one = "\n" + env() + ".writeAll(\"" + io.constant + "\", Cat.makeRead(\"" + io.constant + "\", " + io.code + "));";
} else {
return isExpr ? Strings.cat(exprs, " + \" \" + ") : makeExpr ? stmts2expr(Strings.cat(exprs, ""), " ") : Strings.cat(exprs, "");
}
if (isExpr) {
isExpr = false;
writeExpr(exprs);
}
exprs.add(one);
}
}
/**
* write the expression to std ouput
* @param ex the expression to write
* @return the java code writing the expression
*/
private String writeExpr(String ex) {
return "\n" + env() + ".write(" + null2EE(ex) + ");";
}
/**
* replace all each expression by a write of this expression to std output
* @param exprs
*/
private void writeExpr(List<String> exprs) {
for (int ix=0; ix < exprs.size(); ix ++)
exprs.set(ix, writeExpr(exprs.get(ix)));
}
/**
* return the java code that yields an expression which is the concatenation of the output of the passed statements
* We move the statements to a method, which captures the output to a buffer.
* @param stmts
* @param mid the string to concatenate between two output lines
* @return javacode calling the method which executes the stmts
*/
String stmts2expr(String stmts, String mid) {
return addMethod("String", "\nEnv.push(new Env<String, String>(\">?\", new Buf<String>()));" + stmts + "\nreturn Strings.cat(((Buf<String>) (Env.pop())).contents, \"" + mid + "\");");
}
/**
* return java code to yield a reader, containing the output of the statements.
* We move the statements to a method, which captures the output to a buffer.
* @param stmts
* @return the javacode to call the new method
*/
String stmts2read(String stmts) {
return addMethod("Read<String>", "\nEnv.push(new Env<String, String>(\">?\", new Buf<String>()));" + stmts + "\nreturn (Buf<String>) Env.pop();");
}
/**
* create a method executing the passed code and return a call to the method.
* @param cla the return type of the method
* @param code
* @return javacode calling the method
*/
String addMethod(String cla, String code) {
String name = "method" + ++methodCnt;
methods.append("\n").append(cla).append(" ").append(name).append("() {").append(code).append("\n}");
return name + "()";
}
/**
* compile an <A href=#syExpr>expr clause</A>.
* <p>
* Set isSpaceOnly to whether clause consists only of spaces and {@link Parser#comment()}.
* @param ty 'd' exprD, 's' exprS, 'w' exprW
* @return a Node containing the compiled javaCode
*/
private Compiler$Expr expr(char ty) {
boolean strip = ty == 's' || ty == 'w';
boolean first = true;
String text;
Compiler$Expr one;
Compiler$Expr single = null;
Compiler$Expr res = new Compiler$Expr("", "?c", "");
if (strip)
parser.spaceComment();
while (true) { // primary loop
res.constant = text = "";
while (true) { // constant loop
if (parser.next(ty == 'w' ? shWord : notDo)) {
text += parser.tok;
} else if (parser.comment()) {
} else if (null != (one = primary()) && "?c".equals(one.type)) {
res.constant += text + one.constant;
text = "";
} else {
break;
}
}
if (one == null && strip)
text = text.substring(0, findEndStart(text, space));
res.constant += text;
if (res.constant.length() > 0)
res.code += (first ? "" : " + ") + xQuote(res.constant);
if (one == null) {
if (strip)
parser.spaceComment();
if (first)
return res.code.length() <= 0 ? null : res;
if (single != null && res.constant.length() <= 0 )
return single;
res.constant = null;
return res;
}
res.type = "String";
if (single != null) {
res.code = "\"\" + " + res.code;
single = null;
}
if (res.code.length() <= 0)
res.code = (single = one).code;
else
res.code += " + " + one.code;
first = false;
}
}
/**
* compile a <A href=#syPrimary>primary clause</A>
*
* @return a Node containing the compiled javaCode
*/
private Compiler$Expr primary() {
String one = null;
Compiler$Expr nd = null;
if (! parser.lit("$")) {
return null;
} else if (parser.next(string)) {
return new Compiler$Expr(xQuote(one = unquote(parser.tok)), "String", one);
} else if (parser.lit("(")) {
one = checkNN(lang(), "jaString in $(...$)");
if (! parser.lit("$)"))
parser.fail("$) for end of $( jaExpression $) expected");
return new Compiler$Expr("(" + one + ")", "?e");
} else if (parser.lit("-[")) {
one = data(true);
if (! parser.lit("$]"))
parser.fail("$] for end of $-[ shExpression expected");
return new Compiler$Expr(one, "?e");
} else if (parser.lit("-{")) {
one = stmts2expr(shell(), " ");
if (! parser.lit("$}"))
parser.fail("$} for end of $-{ shell expected");
return new Compiler$Expr(one, "?e");
} else if (parser.lit("-cmpShell") || parser.lit("-cmpData")) {
return new Compiler$Expr("Env.compile('" + (parser.tok.charAt(4) == 'D' ? 'd': 's') + "')", "Run");
} else if (parser.lit("{")) {
nd = varCla(parser.lit("?") || parser.lit(">") ? parser.tok.charAt(0) : 'g');
if (! parser.lit("}"))
parser.fail("closing } after ${ missing");
return nd;
} else if ((nd = varCla('$')) != null) {
return nd;
} else {
parser.charIx--;
return null;
}
}
/**
* return a node for the variable access. Keep track of the variable name if it is a constant and infer the type
* @param fun
* @return a node for the compiled variable access
*/
Compiler$Expr varCla(char fun) {
Compiler$Expr exp = null;
Compiler$Expr res = new Compiler$Expr("");
if (fun == '$') {
if (! parser.next(shName))
return null;
exp = new Compiler$Expr(xQuote(parser.tok), "?c", parser.tok);
} else {
parser.spaceComment();
exp = expr('w');
if (exp == null || exp.code.length() < 1)
parser.fail("variable name expected");
res.type = colClass();
}
if (exp.type.equals("?c")) {
res.constant = exp.constant;
if (fun == '=' && res.type != null)
varType.put(res.constant, res.type);
else if (fun == '>')
varType.put(res.constant, res.type == null ? res.type=envResolve(envIn) : res.type);
else if (res.type == null && varType.containsKey(res.constant))
res.type = varType.get(res.constant);
} else {
res.type = null;
}
if (fun == 'g' || fun == '$') {
res.code = (res.type == null ? "" : "((" + res.type + ") ") + env() +".via(" + exp.code + (res.type == null ? ")" : "))");
} else if (fun == '>') {
res.code = env() + ".read(" + exp.code + ")";
res.type = "bool";
} else if (fun == '?') {
res.code = env() + ".hasKey(" + exp.code + (res.type == null ? ")" : ", " + res.type + ".class)");
res.type = "bool";
} else if (fun == '=') {
res.code = env() + ".put(" + exp.code + (res.type == null ? ", " : ", (" + res.type + ") null, ");
} else {
fail("bad fun " + fun);
}
return res;
}
/**
* compile a </A href=#syLang>lang clause</A>.
* <p>
* Text is interpreted as Java source. A comment is replaced by a space, if it the last generated character is not already a space character
* @return the compile JavaSrc
*/
private String lang() {
StringBuffer code = new StringBuffer();
Compiler$Expr nd;
while (true) {
if (parser.next(notDo))
code.append(parser.tok);
else if (null != (nd = primary()))
code.append(nd.code);
else if (parser.comment())
code.append(' ');
else
return code.length() <= 0 ? null : code.toString();
}
}
/**
* compile a ':' class clause
* @return <, <shWrd>.class>, <, def> or ""
*/
private String colClass() {
if (! parser.lit(":"))
return null;
parser.spaceComment();
if (! parser.next(shWord)|| space.matcher(parser.tok).matches())
parser.fail("class expected after :");
String cl = parser.tok;
parser.spaceComment();
return cl;
}
/**
*
* @return a Node, constant contains option, code specification of filename or object
*/
private Compiler$Expr ioRedirection() {
Compiler$Expr res = new Compiler$Expr("");
if (parser.lit("$" + Cat.eExtFD)) {
res.constant = Cat.eExtFDStr + parser.chars("+-");
res.code = expr('s').code;
} else if (parser.lit("$<") || parser.lit("$>")) {
res.constant = parser.tok.substring(1);
if (parser.lit(res.constant))
res.constant += res.constant;
res.constant += parser.chars("+-?" + Cat.eBufVar);
if (! res.constant.startsWith("<<")) {
res.code = checkNN(expr('s'), "io expr expected").code;
} else {
char compType = parser.lit("[") ? 'd' : parser.lit("{") ? 's' : ' ';
ArrayList<String> data = new ArrayList<String>();
if (! parser.next(shName))
parser.fail("after $<< delimiter word");
String stopper = parser.tok;
parser.spaceComment();
if (! parser.atEol())
parser.fail("spaceComment* nl expected after $<<" + stopper);
while (parser.switchLine() && ! parser.src.startsWith(stopper))
data.add(parser.src);
if (! parser.lit(stopper))
parser.fail("end word " + stopper + "expected after $<<");
if (compType != ' ') {
Parser old = parser;
parser = new Parser(new Buf<String>(data));
res.code= compileSrc(compType);
parser = old;
} else {
res.code = "";
for (String s : data)
res.code += "\n" + env() + ".write(" + xQuote(s) + ");";
}
}
} else {
return null;
}
return res;
}
/**
* compile a <A href=#syShell>shell syntax</A>
* @return java statements
*/
private String shell() {
String one;
StringBuffer code = new StringBuffer("");
while (true) {
if (null != (one = pipe()))
code.append(one);
if (! parser.lit("$;"))
return (code.length() > 0 ? code.toString() : null);
parser.spaceNlComment();
}
}
/**
* compile a <A href=#syPipe>pipe syntax</A>
* @return java statements
*/
private String pipe() {
Compiler$Expr io1;
ArrayList<String> stmts = new ArrayList<String>();
String one = null;
StringBuffer ios = new StringBuffer();
final String envOldIn = envIn;
final String envOldOut = envOut;
String envNxt = envNew();
boolean hasIos;
while (true) {
if (null != (io1 = ioRedirection())) {
if (io1.constant.startsWith("<<")) {
io1.code = stmts2read(io1.code);
io1.constant = io1.constant.substring(1) + '?';
}
ios.append(ios.length() < 1 ? "\nEnv.push(new Env<String, String>(" : ", ");
ios.append('"').append(io1.constant).append("\", ").append(io1.code);
} else if (stmts.isEmpty()) {
envOut = envNxt;
one = stmts();
envOut = envOldOut;
if (null == one)
break;
stmts.add(one);
} else if (parser.lit("$|")) {
parser.spaceNlComment();
envMap.put(envNxt, colClass());
envIn = envNxt;
envOut = envNxt = envNew();
stmts.add(checkNN(stmts(), "stmts expected after $| or $|: class"));
envIn = envOldIn;
envOut = envOldOut;
} else {
break;
}
parser.spaceNlComment();
}
if (io1 != null && io1.constant.indexOf('+') >= 0)
parser.fail("pipe ends with open concatenation " + io1.constant + ", " + io1.code);
if (! stmts.isEmpty())
envMap.put(envNxt, envOldOut);
if (! (hasIos = ios.length() > 0)) {
if (stmts.isEmpty())
return null;
else if (stmts.size() == 1)
return stmts.get(0);
} else {
if (stmts.isEmpty())
stmts.add("\n" + env() + ".readWrite();");
ios.append("));");
}
if (stmts.size() <= 1) {
ios.append(stmts.get(0));
} else {
ios.append("\nEnv.barBegin();").append(stmts.get(0));
for (int ix=1; ix < stmts.size()-1; ix++)
ios.append("\nEnv.bar();").append(stmts.get(ix));
ios.append("\nEnv.barLast();").append(stmts.get(stmts.size()-1)).append("\nEnv.barEnd();");
}
if (hasIos)
ios.append("\nEnv.pop();"); // wkTest eager???
return ios.toString();
}
/**
* Compile a <A href=#syStmts>stmts clause</A> containing java source and statemtens.
* @return java statements
*/
private String stmts () {
String one;
StringBuffer code = new StringBuffer("");
boolean found = false;
while(true) {
if ((one = stmt()) != null) {
if (one.length() > 0)
code.append(one);
} else if (null != (one = lang())) {
code.append('\n').append(one);
} else {
return found ? code.toString() : null;
}
parser.spaceNlComment();
found = true;
}
}
/**
* Compile a <A href=#syStmt>stmt</A>, which is a single shell statment.
* @return java statements
*/
private String stmt () {
String one = null;
if (parser.lit("$=")) {
Compiler$Expr nd = varCla('=');
if (parser.lit("=")){
Compiler$Expr xx = expr('s');
if (xx.type != null && xx.type.charAt(0) != '?' && nd.type == null && nd.constant != null) {
nd.code += "(" + xx.type + ")null, ";
varType.put(nd.constant, xx.type);
}
one = null2EE(xx == null || xx.code.length() < 1 ? null : xx.code);
} else if (parser.lit("?")) {
one = checkNN(lang(), "java Expression");
} else {
parser.fail("= or ? expected in assignement");
}
return "\n" + nd.code + one + ");";
} else if ( parser.lit("$@{")) {
parser.spaceNlComment();
one = shell();
if ( ! parser.lit("$}"))
parser.fail("closing $} for $@{ block missing");
return one == null ? "" : "\n{" + one + "\n}";
} else if ( parser.lit("$[")) {
one = data(false);
if ( ! parser.lit("$]"))
parser.fail("closing $] for $[ block missing");
return one == null ? "" : "\n{" + one + "\n}";
} else if (parser.lit("$$")) {
return "\n" + env() + ".write(" + expr('s').code + ");";
} else if (parser.lit("$?")) {
return "\n" + env() + ".write(" + lang() + ");";
} else if ( parser.lit("$@for")) {
return "\nwhile (" + varCla('>').code + ") {" + checkNN(stmt(), "statement after $@for var expected") + "\n}";
} else if (parser.lit("$@run")) {
return "\n(" + checkNN(expr('s'), "expression for $@run expected").code + ").run();";
} else {
return null;
}
}
/**
* if the first argument is null, parser.fail with the message in the second argument.
* @return the first argument
*/
private <T> T checkNN(T vl, String msg) {
if (vl == null)
parser.fail(msg + " expected here");
return vl;
}
/** return the argument if not null, otherwise the empty String */
private String null2EE(String vl) {
return vl == null || vl.length() < 1 ? "\"\"" : vl;
}
private String envNew() {
String n = "?" + envMap.size();
envMap.put(n, n);
return n;
}
private String env() {
return "Env.env(" + envIn + ' ' + envOut +")";
}
private String envResolve(String k) {
if (! envMap.containsKey(k))
fail("envResolve key " + k + " not in envMap");
String v = envMap.get(k);
if (k.equals(v))
fail("circular env " + k);
else if (v == null)
envMap.put(k, v = "String");
else if (v.length()> 0 && v.charAt(0) == '?')
envMap.put(k, v = envResolve(v));
return v;
}
}
class Compiler$Expr {
String code;
String type;
String constant;
Compiler$Expr(String code, String type, String constant) {
this.code = code;
this.type = type;
this.constant = constant;
}
Compiler$Expr(String code, String type) {
this(code, type, null);
}
Compiler$Expr(String code) {
this(code, null, null);
}
}