view core/src/luan/impl/ThemeParser.java @ 595:8370c4009cce

remove theme attributes
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 14 Sep 2015 14:20:52 -0600
parents e91e476186c7
children b926e53910dd
line wrap: on
line source

package luan.impl;

import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import luan.LuanSource;
import luan.LuanTable;
import luan.LuanElement;
import luan.LuanState;
import luan.LuanFunction;
import luan.LuanException;
import luan.modules.PackageLuan;


public final class ThemeParser {

	public static LuanFunction compile(LuanState luan,LuanSource source) throws LuanException {
		try {
			FnDef fnDef = new ThemeParser(source).parse();
			final LuanStateImpl luanImpl = (LuanStateImpl)luan;
			return new Closure(luanImpl,fnDef);
		} catch(ParseException e) {
//e.printStackTrace();
			throw new LuanException(luan, e.getFancyMessage() );
		}
	}

	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(LuanSource source) {
		this.source = source;
		this.parser = new Parser(this.source);
	}

	private LuanElement se(int start) {
		return se(start,null);
	}

	private LuanElement se(int start,String text) {
		return new LuanElement(source,start,parser.currentIndex(),text);
	}

	private int symbolsSize() {
		return frame.symbols.size();
	}

	private void addSymbol(String name) {
		frame.symbols.add(name);
		if( frame.stackSize < symbolsSize() )
			frame.stackSize = symbolsSize();
	}

	private FnDef newFnDef(int start,Stmt stmt) {
		return new FnDef( se(start), stmt, frame.stackSize, symbolsSize(), frame.isVarArg, frame.upValueGetters.toArray(NO_UP_VALUE_GETTERS) );
	}

	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(null,stackIndex(ENV));
	}

	private FnDef parse() throws ParseException {
		List<Stmt> stmts = new ArrayList<Stmt>();
		int stackStart = symbolsSize();
		{
			addSymbol(IO);
			LuanElement se = se(0,"require 'luan:Io'");
			FnCall requireCall = new FnCall( se, new ConstExpr(se,PackageLuan.requireFn), new ConstExpr(se,"luan:Io") );
			SetStmt setStmt = new SetStmt( new SetLocalVar(stackIndex(IO)), new ExpressionsExpr(requireCall) );
			stmts.add(setStmt);
		}
		{
			addSymbol(MOD);
			LuanElement se = se(0,"local M = {}");
			TableExpr.Field indent = new TableExpr.Field(new ConstExpr(null,INDENT),new ConstExpr(null,""));
			TableExpr tableExpr = new TableExpr( se, 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(null,new GetLocalVar(null,stackIndex(MOD))) );
		Stmt block = new Block( stmts.toArray(new Stmt[0]), stackStart, symbolsSize() );
		FnDef fnDef = newFnDef(0,block);
		return fnDef;
	}

	private Stmt 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");
		String spaces = "";
		if( BlankLine() ) {
			while( BlankLine() );
			int startSpaces = parser.currentIndex();
			InlineSpaces();
			spaces = parser.textFrom(startSpaces);
		}
		if( !parser.match("}") )
			return null;
		Expr table = new GetLocalVar(null,stackIndex(MOD));
		Settable fnName = new SetTableEntry(se(start),table,new ConstExpr(null,name));
		frame = new Frame(frame);
		addSymbol(ENV);
		Stmt block = parseBody("define:"+name,spaces,EndOfLine());
		FnDef fnDef = newFnDef(start,block);
		frame = frame.parent;
		Stmt rtn = new SetStmt(fnName,fnDef);
		return parser.success(rtn);
	}

	private Stmt parseBody(String tagName,String spaces,boolean initIndent) 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(se(start,"indent")) {
				@Override public Object eval(LuanStateImpl luan) throws LuanException {
					LuanTable tbl = (LuanTable)env.eval(luan);
					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);
		}
		boolean afterIndent = false;
		if( initIndent && parser.match(spaces) ) {
			addText(start,start,stmts,true);
			start = parser.currentIndex();
			afterIndent = true;
		}
		int end = start;
		while( !matchEndTag(tagName) ) {
			if( parser.endOfInput() )
				throw exception("unclosed block");
			Stmt block = parseBlock(spaces);
			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( !parser.match('{') )
			return parser.failure();
		Spaces();
		if( !(parser.match('/') && parser.match(tagName)) )
			return parser.failure();
		Spaces();
		if( !parser.match('}') )
			return parser.failure();
		return parser.success();
	}

	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(null,text) );
		}
		if( indent ) {
			args.add( new GetLocalVar(null,stackIndex(INDENT)) );
		}
		if( !args.isEmpty() ) {
			Expr io = new GetUpVar(null,upValueIndex(IO));
			Expr stdoutExp = new IndexExpr( se(start,"stdout"), io, new ConstExpr(null,"stdout") );
			Expr writeExp = new IndexExpr( se(start,"write"), stdoutExp, new ConstExpr(null,"write") );
			FnCall writeCall = new FnCall( se(start), writeExp, ExpList.build(args) );
			stmts.add( new ExpressionsStmt(writeCall) );
		}
	}

	private Stmt parseBlock(String spaces) throws ParseException {
		int start = parser.begin();
		if( !parser.match("{block:") )
			return parser.failure(null);
		String name = parseName();
		if( name==null )
			throw exception("invalid block name");
		if( !parser.match("}") )
			return null;
		frame = new Frame(frame);
		addSymbol(ENV);
		Stmt block = parseBody("block:"+name,spaces,false);
		FnDef fnDef = newFnDef(start,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( se(start,"block:"+name), env, new ConstExpr(null,name) );
		List<Expressions> args = new ArrayList<Expressions>();
		args.add( env );
		args.add( fnDef );
		FnCall fnCall = new FnCall( se(start), fn, ExpList.build(args) );
		Stmt rtn = new ExpressionsStmt(fnCall);
		return parser.success(rtn);
	}

	private Stmt parseSimpleTag(String spaces) throws ParseException {
		int start = parser.begin();
		if( !parser.match("{") )
			return parser.failure(null);
		String name = parseName();
		if( name==null )
			return parser.failure(null);
		Spaces();
		if( !parser.match("}") )
			return parser.failure(null);
//		rtn = "<% env." + name + (attrs.isEmpty() ? "()" : table(attrs)) + " %>";
		Expr env = env();
		Expr fn = new IndexExpr( se(start,name), env, new ConstExpr(null,name) );
		if( spaces!=null && spaces.length() > 0 ) {
			final Expr oldEnv = env;
			final Expr oldIndentExpr = new GetLocalVar(null,stackIndex(INDENT));
			final String addSpaces = spaces;
			env = new ExprImpl(se(start,"indent_env")) {
				@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( se(start), fn, env );
		Stmt rtn = new ExpressionsStmt(fnCall);
		return parser.success(rtn);
	}

	private void Spaces() {
		while( parser.anyOf(" \t\r\n") );
	}

	private void InlineSpaces() {
		while( parser.anyOf(" \t") );
	}

	private void BlankLines() {
		while( BlankLine() );
	}

	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() {
		int start = parser.begin();
		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("-_");
	}

}