package ch.wlkl.shell;

import static ch.wlkl.shell.Parser.findEndStart;
import static ch.wlkl.shell.Parser.notDo;
import static ch.wlkl.shell.Parser.shWord;
import static ch.wlkl.shell.Parser.shName;
import static ch.wlkl.shell.Parser.space;
import static ch.wlkl.shell.Parser.string;
import static ch.wlkl.shell.Strings.unquote;
import static ch.wlkl.shell.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.
 * 
 * <h4>The main feature of this shell language</h4>
 * <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>
 * 
 * <h4>Shell Syntax</h4>
 * <code><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.
 * <p>
 * <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.
 * <p>
 * <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
 * <li id=syInput>input  = '$<' ioOpts exprS | hereData
 * <li id=syHereData>hereData  = '$&lt;&lt;' ioOpts shWord sp* (nl notNl*)* nl shWord
 * <li> output = ('$&gt;' | '$&gt;&gt;' | '$&') ioOpts exprS  
 * <li> 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*)+
 * <p>
 * <li id=syStmt>stmt =  ass | '$@{' shell '$}' | '$[' data '$]'| '$$' exprS  | '$£' lang  |'$.' exprW stmt | '$@run' primary
 * <li>ass = '$=' exprW colCla ( '=' exprS | '£' lang)
 * </ul></code> 
 * <h4>Lexicals</h4>
 * <ul>
 * <li>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 *)
 * <li>shWord = (* 1 or more characters not sp, nl, comma, ", ',$, : ,}, = or £ *)
 * <p>
 * <li> spNlCom = nl | sp | comment
 * <li> spCom = sp | comment
 * <li> comment = '$**' notNl* | '$*+' notNl* nl | '$*(' (comment | hereData | string | char)* '$*)'
 * <p>
 * <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 *)
 * </B></code></ul>
 * <h4>Semantics</h4>
 * 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>
 * <h4 id=Typing>Typing</h4>
 * <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>
 * <h4>Tutorial</h4>
 * <h4>Plans, toDo</h4>
 * <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) {
			EnvMan.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 = "\nUt.write(" + env() + ".out(), 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", "\nEnvMan.envMan().push(new Env<String, String>(\">£\", new Buf<String>()));" + stmts + "\nreturn Strings.cat(((Buf<String>) (EnvMan.envMan().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>", "\nEnvMan.envMan().push(new Env<String, String>(\">£\", new Buf<String>()));" + stmts + "\nreturn (Buf<String>) EnvMan.envMan().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("EnvMan.envMan.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() + ".isDefined(" + 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 ? "\nEnvMan.envMan().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("\nEnvMan.envMan().barBegin();").append(stmts.get(0));
			for (int ix=1; ix < stmts.size()-1; ix++) 
				ios.append("\nEnvMan.envMan().bar();").append(stmts.get(ix));
			ios.append("\nEnvMan.envMan().barLast();").append(stmts.get(stmts.size()-1)).append("\nEnvMan.envMan().barEnd();");
		}
		if (hasIos) 
			ios.append("\nEnvMan.envMan.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 (parser.lit("$£")) {
			} 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 $. 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 "EnvMan.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);
	}
}


