changeset 687:fc08c3b42010

add theme_to_luan
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 18 Apr 2016 01:08:35 -0600
parents 33f1b4ad2c9d
children f99f51bc5bea
files core/src/luan/impl/LuanParser.java core/src/luan/impl/ThemeParser.java core/src/luan/modules/BasicLuan.java core/src/luan/modules/JavaLuan.java core/src/luan/modules/Luan.luan core/src/luan/modules/Parsers.luan core/src/luan/modules/parsers/Theme.java
diffstat 7 files changed, 193 insertions(+), 439 deletions(-) [+]
line wrap: on
line diff
--- a/core/src/luan/impl/LuanParser.java	Sun Apr 17 16:39:28 2016 -0600
+++ b/core/src/luan/impl/LuanParser.java	Mon Apr 18 01:08:35 2016 -0600
@@ -179,14 +179,11 @@
 		}
 	}
 
-//	final LuanSource source;
 	private Frame frame;
 	private final Parser parser;
 	private final Stmts top;
-	private boolean hasTemplateWrite = false;
 
 	LuanParser(String sourceName,String sourceText) {
-//		this.source = source;
 		this.frame = new Frame();
 		this.parser = new Parser(sourceName,sourceText);
 		this.top = new Stmts();
@@ -194,11 +191,9 @@
 
 	void addVar(String name) {
 		UpSym upSym = frame.addUpSym( "-ADDED-" ,"new Pointer()");
-		if( name != null ) {
-			LocalSym sym = frame.addLocalSym( name );
-			sym.isPointer = true;
-			top.add( "final Pointer " + sym.javaName + " = upValues[" + upSym.i + "];  " );
-		}
+		LocalSym sym = frame.addLocalSym( name );
+		sym.isPointer = true;
+		top.add( "final Pointer " + sym.javaName + " = upValues[" + upSym.i + "];  " );
 	}
 
 	private int symbolsSize() {
@@ -377,12 +372,8 @@
 		Expr exprs = TemplateExpressions(In.NOTHING);
 		if( exprs == null )
 			return null;
-		if( !hasTemplateWrite ) {
-			top.add(0,"final LuanFunction template_write = Luan.checkFunction(luan.index(PackageLuan.require(luan,\"luan:Io\"),\"template_write\"));  ");
-			hasTemplateWrite = true;
-		}
 		Stmts stmt = new Stmts();
-		stmt.add( "template_write.call(luan," );
+		stmt.add( "Luan.checkFunction(luan.index(PackageLuan.require(luan,\"luan:Io\"),\"template_write\")).call(luan," );
 		stmt.addAll( exprs.array() );
 		stmt.add( ");  " );
 		return stmt;
@@ -1275,7 +1266,8 @@
 				Expr envExpr = env();
 				if( envExpr != null )
 					return indexVar( envExpr, constExpStr(name) ).set(val);
-				throw new RuntimeException();
+				parser.failure(null);
+				throw parser.exception("name '"+name+"' not defined");
 			}
 		};
 	}
--- a/core/src/luan/impl/ThemeParser.java	Sun Apr 17 16:39:28 2016 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,414 +0,0 @@
-package luan.impl;
-
-import java.util.Map;
-import java.util.HashMap;
-import java.util.List;
-import java.util.ArrayList;
-import luan.Luan;
-import luan.LuanTable;
-import luan.LuanState;
-import luan.LuanFunction;
-import luan.LuanException;
-import luan.modules.PackageLuan;
-
-
-public final class ThemeParser {
-
-	public static LuanFunction compile(LuanState luan,String sourceName,String sourceText) throws LuanException {
-/*
-		try {
-			FnDef fnDef = new ThemeParser(sourceName,sourceText).parse();
-			final LuanStateImpl luanImpl = (LuanStateImpl)luan;
-			return new Closure(luanImpl,fnDef);
-		} catch(ParseException e) {
-//e.printStackTrace();
-			throw new LuanException( e.getFancyMessage() );
-		}
-*/
-		return null;
-	}
-/*
-	private static final class Frame {
-		final Frame parent;
-		final List<String> symbols = new ArrayList<String>();
-		int stackSize = 0;
-		final boolean isVarArg;
-		final List<String> upValueSymbols = new ArrayList<String>();
-		final List<UpValue.Getter> upValueGetters = new ArrayList<UpValue.Getter>();
-
-		Frame() {
-			this.parent = null;
-			isVarArg = true;
-		}
-
-		Frame(Frame parent) {
-			this.parent = parent;
-			isVarArg = false;
-			if( upValueIndex(MOD) != 0 )
-				throw new RuntimeException();
-		}
-
-		int stackIndex(String name) {
-			int i = symbols.size();
-			while( --i >= 0 ) {
-				if( symbols.get(i).equals(name) )
-					return i;
-			}
-			return -1;
-		}
-
-		int upValueIndex(String name) {
-			int i = upValueSymbols.size();
-			while( --i >= 0 ) {
-				if( upValueSymbols.get(i).equals(name) )
-					return i;
-			}
-			if( parent==null )
-				return -1;
-			i = parent.stackIndex(name);
-			if( i != -1 ) {
-				upValueGetters.add(new UpValue.StackGetter(i));
-			} else {
-				i = parent.upValueIndex(name);
-				if( i == -1 )
-					return -1;
-				upValueGetters.add(new UpValue.NestedGetter(i));
-			}
-			upValueSymbols.add(name);
-			return upValueSymbols.size() - 1;
-		}
-
-		void addUpValueGetter(String name,UpValue.Getter upValueGetter) {
-			upValueSymbols.add(name);
-			upValueGetters.add(upValueGetter);
-		}
-	}
-
-	private static final String IO = "-IO-";
-	private static final String MOD = "-MOD-";
-	private static final String ENV = "-ENV-";
-	private static final String INDENT = "-INDENT-";
-	private static final UpValue.Getter[] NO_UP_VALUE_GETTERS = new UpValue.Getter[0];
-
-//	private final LuanSource source;
-	private final Parser parser;
-	private Frame frame = new Frame();
-
-	private ThemeParser(String sourceName,String sourceText) {
-//		this.source = source;
-		this.parser = new Parser(sourceName,sourceText);
-	}
-
-	private int symbolsSize() {
-		return frame.symbols.size();
-	}
-
-	private void addSymbol(String name) {
-		frame.symbols.add(name);
-		if( frame.stackSize < symbolsSize() )
-			frame.stackSize = symbolsSize();
-	}
-
-	private Class newFnDef(Stmt stmt) {
-//		return new FnDef( stmt, frame.stackSize, symbolsSize(), frame.isVarArg, frame.upValueGetters.toArray(NO_UP_VALUE_GETTERS) );
-		return null;
-	}
-
-	private int stackIndex(String name) {
-		return frame.stackIndex(name);
-	}
-
-	private void popSymbols(int n) {
-		List<String> symbols = frame.symbols;
-		while( n-- > 0 ) {
-			symbols.remove(symbols.size()-1);
-		}
-	}
-
-	private int upValueIndex(String name) {
-		return frame.upValueIndex(name);
-	}
-
-	private ParseException exception(String msg) {
-		parser.failure();
-		return parser.exception(msg);
-	}
-
-	private Expr env() {
-		return new GetLocalVar(stackIndex(ENV));
-	}
-
-	private Class parse() throws ParseException {
-		List<Stmt> stmts = new ArrayList<Stmt>();
-		int stackStart = symbolsSize();
-		{
-			addSymbol(IO);
-			FnCall requireCall = new FnCall( new ConstExpr(PackageLuan.requireFn), new ConstExpr("luan:Io") );
-			SetStmt setStmt = new SetStmt( new SetLocalVar(stackIndex(IO)), new ExpressionsExpr(requireCall) );
-			stmts.add(setStmt);
-		}
-		{
-			addSymbol(MOD);
-			TableExpr.Field indent = new TableExpr.Field(new ConstExpr(INDENT),new ConstExpr(""));
-			TableExpr tableExpr = new TableExpr( new TableExpr.Field[]{indent}, ExpList.emptyExpList );
-			SetStmt setStmt = new SetStmt( new SetLocalVar(stackIndex(MOD)), tableExpr );
-			stmts.add(setStmt);
-		}
-		while( !parser.endOfInput() ) {
-			Stmt def = parseDef();
-			if( def != null ) {
-				stmts.add(def);
-			} else {
-				parser.anyChar();
-			}
-		}
-		stmts.add( new ReturnStmt(new GetLocalVar(stackIndex(MOD))) );
-		Stmt block = new Block( stmts.toArray(new Stmt[0]), stackStart, symbolsSize() );
-		Class fnDef = newFnDef(block);
-		return fnDef;
-	}
-
-	private Stmt parseDef() throws ParseException {
-		parser.begin();
-		if( !parser.match("{define:") )
-			return parser.failure(null);
-		String name = parseName();
-		if( name==null )
-			throw parser.exception("invalid block name");
-		if( !parser.match('}') )
-			throw parser.exception("unclosed define tag");
-		String spaces = "";
-		boolean indent = BlankLine();
-		if( indent ) {
-			int startSpaces = parser.currentIndex();
-			InlineSpaces();
-			spaces = parser.textFrom(startSpaces);
-		}
-		Expr table = new GetLocalVar(stackIndex(MOD));
-		Settable fnName = new SetTableEntry(table,new ConstExpr(name));
-		frame = new Frame(frame);
-		addSymbol(ENV);
-		Stmt block = parseBody("define:"+name,spaces,indent);
-		Class fnDef = newFnDef(block);
-		frame = frame.parent;
-		Stmt rtn = new SetStmt(fnName,fnDef);
-		return parser.success(rtn);
-	}
-
-	private Stmt parseBody(String tagName,final String spaces,boolean afterIndent) throws ParseException {
-		List<Stmt> stmts = new ArrayList<Stmt>();
-		int stackStart = symbolsSize();
-		int start = parser.currentIndex();
-		{
-			addSymbol(INDENT);
-			final Expr env = env();
-			Expr exp = new ExprImpl() {
-				@Override public Object eval(LuanStateImpl luan) throws LuanException {
-					Object obj = env.eval(luan);
-					if( !(obj instanceof LuanTable) )
-						throw new LuanException("bad argument (table expected, got "+Luan.type(obj)+")");
-					LuanTable tbl = (LuanTable)obj;
-					String indent = (String)tbl.get(luan,INDENT);
-					if( indent==null )  throw new NullPointerException();
-					return indent;
-				}
-			};
-//			Expr exp = new IndexExpr( se(start,"indent"), env(), new ConstExpr(null,INDENT) );
-			SetStmt setStmt = new SetStmt( new SetLocalVar(stackIndex(INDENT)), exp );
-			stmts.add(setStmt);
-		}
-		int end = start;
-		while( !matchEndTag(tagName) ) {
-			if( parser.endOfInput() )
-				throw exception("unclosed block");
-			Stmt block = parseBlock(spaces,afterIndent);
-			if( block != null ) {
-				addText(start,end,stmts,false);
-				start = parser.currentIndex();
-				stmts.add(block);
-				afterIndent = false;
-				continue;
-			}
-			{
-				String extraSpaces = null;
-				if( afterIndent ) {
-					int startSpaces = parser.currentIndex();
-					InlineSpaces();
-					extraSpaces = parser.textFrom(startSpaces);
-					end = parser.currentIndex();
-				}
-				Stmt simpleTag = parseSimpleTag(extraSpaces);
-				if( simpleTag != null ) {
-					addText(start,end,stmts,false);
-					start = parser.currentIndex();
-					stmts.add(simpleTag);
-					afterIndent = false;
-					continue;
-				}
-				if( extraSpaces!=null && extraSpaces.length() > 0 )
-					continue;
-			}
-			if( EndOfLine() ) {
-				end = parser.currentIndex();
-				afterIndent = false;
-				if( parser.match(spaces) ) {
-					addText(start,end,stmts,true);
-					start = parser.currentIndex();
-					afterIndent = true;
-				}
-				continue;
-			}
-			parser.anyChar();
-			end = parser.currentIndex();
-			afterIndent = false;
-		}
-		addText(start,end,stmts,false);
-		Stmt block = new Block( stmts.toArray(new Stmt[0]), stackStart, symbolsSize() );
-		popSymbols(1);
-		return block;
-	}
-
-	private boolean matchEndTag(String tagName) {
-		parser.begin();
-/*
-		if( tagName.startsWith("define:") )
-			EndOfLine();
-* /
-		if( EndOfLine() )
-			InlineSpaces();
-		return parser.match("{/") && parser.match(tagName) && parser.match('}') ? parser.success() : parser.failure();
-	}
-
-	private void addText(int start,int end,List<Stmt> stmts,boolean indent) {
-		List<Expressions> args = new ArrayList<Expressions>();
-		if( start < end ) {
-			String text = parser.text.substring(start,end);
-			args.add( new ConstExpr(text) );
-		}
-		if( indent ) {
-			args.add( new GetLocalVar(stackIndex(INDENT)) );
-		}
-		if( !args.isEmpty() ) {
-			Expr io = new GetUpVar(upValueIndex(IO));
-			Expr stdoutExp = new IndexExpr( io, new ConstExpr("stdout") );
-			Expr writeExp = new IndexExpr( stdoutExp, new ConstExpr("write") );
-			FnCall writeCall = new FnCall( writeExp, ExpList.build(args) );
-			stmts.add( new ExpressionsStmt(writeCall) );
-		}
-	}
-
-	private Stmt parseBlock(String spaces,boolean afterIndent) throws ParseException {
-		parser.begin();
-		String tagSpaces = null;
-		if( afterIndent ) {
-			tagSpaces = spaces;
-		} else if( EndOfLine() ) {
-			int startSpaces = parser.currentIndex();
-			InlineSpaces();
-			tagSpaces = parser.textFrom(startSpaces);
-		}
-		if( !parser.match("{block:") )
-			return parser.failure(null);
-		String name = parseName();
-		if( name==null )
-			throw exception("invalid block name");
-		if( !parser.match('}') )
-			return null;
-		if( tagSpaces != null ) {
-			parser.begin();
-			InlineSpaces();
-			if( EndOfLine() ) {
-				int startSpaces = parser.currentIndex();
-				InlineSpaces();
-				String line1Spaces = parser.textFrom(startSpaces);
-				if( line1Spaces.startsWith(tagSpaces) ) {
-					String newSpaces = spaces + line1Spaces.substring(tagSpaces.length());
-					if( line1Spaces.startsWith(newSpaces) )
-						spaces = newSpaces;
-				}
-			}
-			parser.failure();  // rollback
-		}
-		frame = new Frame(frame);
-		addSymbol(ENV);
-		Stmt block = parseBody("block:"+name,spaces,false);
-		Class fnDef = newFnDef(block);
-		frame = frame.parent;
-//		String rtn = "<% env." + tag.name + "(" + (tag.attrs.isEmpty() ? "nil" : table(tag.attrs)) + ",env,function(env) %>" + block + "<% end) %>";
-		Expr env = env();
-		Expr fn = new IndexExpr( env, new ConstExpr(name) );
-		List<Expressions> args = new ArrayList<Expressions>();
-		args.add( env );
-		args.add( fnDef );
-		FnCall fnCall = new FnCall( fn, ExpList.build(args) );
-		Stmt rtn = new ExpressionsStmt(fnCall);
-		return parser.success(rtn);
-	}
-
-	private Stmt parseSimpleTag(String spaces) throws ParseException {
-		parser.begin();
-		if( !parser.match('{') )
-			return parser.failure(null);
-		String name = parseName();
-		if( name==null )
-			return parser.failure(null);
-		if( !parser.match('}') )
-			return parser.failure(null);
-//		rtn = "<% env." + name + (attrs.isEmpty() ? "()" : table(attrs)) + " %>";
-		Expr env = env();
-		Expr fn = new IndexExpr( env, new ConstExpr(name) );
-		if( spaces!=null && spaces.length() > 0 ) {
-			final Expr oldEnv = env;
-			final Expr oldIndentExpr = new GetLocalVar(stackIndex(INDENT));
-			final String addSpaces = spaces;
-			env = new ExprImpl() {
-				@Override public Object eval(LuanStateImpl luan) throws LuanException {
-					LuanTable mt = new LuanTable();
-					mt.rawPut("__index",oldEnv.eval(luan));
-					LuanTable tbl = new LuanTable();
-					tbl.setMetatable(mt);
-					String oldIndent = (String)oldIndentExpr.eval(luan);
-					String newIndent = oldIndent + addSpaces;
-					tbl.rawPut(INDENT,newIndent);
-					return tbl;
-				}
-			};
-		}
-		FnCall fnCall = new FnCall( fn, env );
-		Stmt rtn = new ExpressionsStmt(fnCall);
-		return parser.success(rtn);
-	}
-
-	private void InlineSpaces() {
-		while( parser.anyOf(" \t") );
-	}
-
-	private boolean BlankLine() {
-		parser.begin();
-		while( parser.anyOf(" \t") );
-		return EndOfLine() ? parser.success() : parser.failure();
-	}
-
-	private boolean EndOfLine() {
-		return parser.match( "\r\n" ) || parser.match( '\r' ) || parser.match( '\n' );
-	}
-
-	private String parseName() throws ParseException {
-		int start = parser.begin();
-		if( parser.match('/') )
-			throw exception("bad closing tag");
-		if( parser.match("define:") )
-			throw exception("unexpected definition");
-		if( !NameChar() )
-			return parser.failure(null);
-		while( NameChar() );
-		String match = parser.textFrom(start);
-		return parser.success(match);
-	}
-
-	private boolean NameChar() {
-		return parser.inCharRange('a', 'z') || parser.inCharRange('A', 'Z')
-			|| parser.inCharRange('0', '9') || parser.anyOf("-_.");
-	}
-*/
-}
--- a/core/src/luan/modules/BasicLuan.java	Sun Apr 17 16:39:28 2016 -0600
+++ b/core/src/luan/modules/BasicLuan.java	Mon Apr 18 01:08:35 2016 -0600
@@ -16,7 +16,6 @@
 import luan.LuanMethod;
 import luan.LuanMeta;
 import luan.impl.LuanCompiler;
-import luan.impl.ThemeParser;
 
 
 public final class BasicLuan {
@@ -33,14 +32,6 @@
 		return LuanCompiler.compile(sourceName,text,env,allowExpr);
 	}
 
-	public static LuanFunction load_theme(LuanState luan,String text,String sourceName)
-		throws LuanException
-	{
-		if( sourceName==null )
-			sourceName = "THEME";
-		return ThemeParser.compile(luan,sourceName,text);
-	}
-
 	public static LuanFunction load_file(LuanState luan,String fileName,Boolean addExtension) throws LuanException {
 		if( fileName == null )
 			fileName = "stdin:";
--- a/core/src/luan/modules/JavaLuan.java	Sun Apr 17 16:39:28 2016 -0600
+++ b/core/src/luan/modules/JavaLuan.java	Mon Apr 18 01:08:35 2016 -0600
@@ -109,7 +109,7 @@
 //System.out.println("invalid member '"+key+"' for java object: "+obj);
 		if( canReturnFail )
 			return FAIL;
-		throw new LuanException( "invalid index for java "+cls );
+		throw new LuanException( "invalid index '"+key+"' for java "+cls );
 	}
 
 	private static Object member(Object obj,List<Member> members) throws LuanException {
--- a/core/src/luan/modules/Luan.luan	Sun Apr 17 16:39:28 2016 -0600
+++ b/core/src/luan/modules/Luan.luan	Mon Apr 18 01:08:35 2016 -0600
@@ -15,7 +15,6 @@
 M.ipairs = BasicLuan.ipairs
 M.load = BasicLuan.load
 M.load_file = BasicLuan.load_file
-M.load_theme = BasicLuan.load_theme
 M.new_error = BasicLuan.new_error
 M.pairs = BasicLuan.pairs
 M.pcall = BasicLuan.pcall
--- a/core/src/luan/modules/Parsers.luan	Sun Apr 17 16:39:28 2016 -0600
+++ b/core/src/luan/modules/Parsers.luan	Mon Apr 18 01:08:35 2016 -0600
@@ -1,11 +1,13 @@
 java()
 local BBCode = require "java:luan.modules.parsers.BBCode"
 local Csv = require "java:luan.modules.parsers.Csv"
+local Theme = require "java:luan.modules.parsers.Theme"
 
 local M = {}
 
 M.bbcode_to_html = BBCode.toHtml
 M.bbcode_to_text = BBCode.toText
 M.csv_to_list = Csv.toList
+M.theme_to_luan = Theme.toLuan
 
 return M
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/src/luan/modules/parsers/Theme.java	Mon Apr 18 01:08:35 2016 -0600
@@ -0,0 +1,184 @@
+package luan.modules.parsers;
+
+import luan.LuanException;
+
+
+public final class Theme {
+
+	public static String toLuan(String source) throws LuanException {
+		try {
+			return new Theme(source).parse();
+		} catch(ParseException e) {
+			throw new LuanException(e.getMessage());
+		}
+	}
+
+	private final Parser parser;
+
+	private Theme(String source) {
+		this.parser = new Parser(source);
+	}
+
+	private ParseException exception(String msg) {
+//		parser.failure();
+		return new ParseException(parser,msg);
+	}
+
+	private String parse() throws ParseException {
+		StringBuilder stmts = new StringBuilder();
+		stmts.append( "local M = {};  " );
+		while( !parser.endOfInput() ) {
+			String def = parseDef();
+			if( def != null ) {
+				stmts.append(def);
+			} else {
+//				parser.anyChar();
+				stmts.append(parsePadding());
+			}
+		}
+		stmts.append( "\n\nreturn M\n" );
+		return stmts.toString();
+	}
+
+	private String parsePadding() throws ParseException {
+		int start = parser.currentIndex();
+		if( parser.match("--") ) {
+			while( parser.noneOf("\r\n") );
+		} else if( !parser.anyOf(" \t\r\n") ) {
+			throw exception("unexpected text");
+		}
+		return parser.textFrom(start);
+	}
+
+	private String parseDef() throws ParseException {
+		int start = parser.begin();
+		if( !parser.match("{define:") )
+			return parser.failure(null);
+		String name = parseName();
+		if( name==null )
+			throw exception("invalid block name");
+		if( !parser.match('}') )
+			throw exception("unclosed define tag");
+		String block = parseBody("define:"+name);
+		String rtn = "function M." + name + "(env) " + block + "end;  ";
+		return parser.success(rtn);
+	}
+
+	private String parseBody(String tagName) throws ParseException {
+		StringBuilder stmts = new StringBuilder();
+		int start = parser.currentIndex();
+		stmts.append( "%>" );
+		int end = start;
+		while( !matchEndTag(tagName) ) {
+			if( parser.endOfInput() ) {
+				parser.failure();
+				throw exception("unclosed block");
+			}
+			String block = parseBlock();
+			if( block != null ) {
+				addText(start,end,stmts);
+				start = parser.currentIndex();
+				stmts.append(block);
+				continue;
+			}
+			String simpleTag = parseSimpleTag();
+			if( simpleTag != null ) {
+				addText(start,end,stmts);
+				start = parser.currentIndex();
+				stmts.append(simpleTag);
+				continue;
+			}
+			parser.anyChar();
+			end = parser.currentIndex();
+		}
+		addText(start,end,stmts);
+		stmts.append( "<% ");
+		return stmts.toString();
+	}
+
+	private boolean matchEndTag(String tagName) {
+		parser.begin();
+/*
+		if( tagName.startsWith("define:") )
+			EndOfLine();
+*/
+		return parser.match("{/") && parser.match(tagName) && parser.match('}') ? parser.success() : parser.failure();
+	}
+
+	private void addText(int start,int end,StringBuilder stmts) {
+		if( start < end ) {
+			stmts.append( parser.text.substring(start,end) );
+		}
+	}
+
+	private String parseBlock() throws ParseException {
+		int start = parser.begin();
+		if( !parser.match("{block:") )
+			return parser.failure(null);
+		String name = parseName();
+		if( name==null ) {
+			parser.failure();
+			throw exception("invalid block name");
+		}
+		if( !parser.match('}') )
+			return parser.failure(null);
+		String block = parseBody("block:"+name);
+		String rtn = "<% env."+ name + "( env, function(env) " + block + "end) %>";
+//		String rtn = "<% env." + tag.name + "(" + (tag.attrs.isEmpty() ? "nil" : table(tag.attrs)) + ",env,function(env) %>" + block + "<% end) %>";
+		return parser.success(rtn);
+	}
+
+	private String parseSimpleTag() throws ParseException {
+		int start = parser.begin();
+		if( !parser.match('{') )
+			return parser.failure(null);
+		String name = parseName();
+		if( name==null )
+			return parser.failure(null);
+		if( !parser.match('}') )
+			return parser.failure(null);
+//		rtn = "<% env." + name + (attrs.isEmpty() ? "()" : table(attrs)) + " %>";
+		String rtn = "<% env." + name + "(env) %>";
+		return parser.success(rtn);
+	}
+
+	private void InlineSpaces() {
+		while( parser.anyOf(" \t") );
+	}
+
+	private boolean BlankLine() {
+		parser.begin();
+		while( parser.anyOf(" \t") );
+		return EndOfLine() ? parser.success() : parser.failure();
+	}
+
+	private boolean EndOfLine() {
+		return parser.match( "\r\n" ) || parser.match( '\r' ) || parser.match( '\n' );
+	}
+
+	private String parseName() throws ParseException {
+		int start = parser.begin();
+		if( parser.match('/') ) {
+			parser.failure();
+			throw exception("bad closing tag");
+		}
+		if( parser.match("define:") ) {
+			parser.failure();
+			throw exception("unexpected definition");
+		}
+		if( !FirstNameChar() )
+			return parser.failure(null);
+		while( NameChar() );
+		String match = parser.textFrom(start);
+		return parser.success(match);
+	}
+
+	private boolean FirstNameChar() {
+		return parser.inCharRange('a', 'z') || parser.inCharRange('A', 'Z') || parser.match('_');
+	}
+
+	private boolean NameChar() {
+		return FirstNameChar() || parser.inCharRange('0', '9');
+	}
+
+}