view core/src/luan/impl/ThemeParser.java @ 586:a140be489a72

minor
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 24 Aug 2015 02:17:46 -0600
parents 0742ac78fa69
children fa281ee942c8
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 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 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 tableExpr = new TableExpr( se, new TableExpr.Field[0], 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 static class Tag {
		final String name;
		final Map<String,String> attrs;

		Tag(String name,Map<String,String> attrs) {
			this.name = name;
			this.attrs = attrs;
		}
	}

	private Stmt parseDef() throws ParseException {
		int start = parser.begin();
		Tag tag = parseBlockTag();
		if( tag == null )
			return parser.failure(null);
		parser.match('\r');  parser.match('\n');  // ignore newline
		Stmt rtn = null;
		if( tag.name.equals("Set") ) {
			String name = tag.attrs.remove("name");
			if( name == null )
				throw exception("block:Set missing required attribute 'name'");
			if( !tag.attrs.isEmpty() )
				throw exception("block:Set has unrecognized attributes: "+tag.attrs);
			if( !validateName(name) )
				throw exception("invalid Set name: "+name);
			addSymbol( name );
			frame = new Frame(frame);
			addSymbol(ENV);
			Stmt block = parseBody(tag);
			FnDef fnDef = newFnDef(start,block);
			frame = frame.parent;
			rtn = new SetStmt( new SetLocalVar(symbolsSize()-1), fnDef );
		} else {
			if( !tag.attrs.isEmpty() )
				throw exception("this block should have no attributes");
			Expr table = new GetLocalVar(null,stackIndex(MOD));
			Settable fnName = new SetTableEntry(se(start),table,new ConstExpr(null,tag.name));
			frame = new Frame(frame);
			addSymbol(ENV);
			Stmt block = parseBody(tag);
			FnDef fnDef = newFnDef(start,block);
			frame = frame.parent;
			rtn = new SetStmt(fnName,fnDef);
		}
		return parser.success(rtn);
	}

	private Stmt parseBody(Tag tag) throws ParseException {
		String endTag = "{/block:" + tag.name + "}";
		List<Stmt> stmts = new ArrayList<Stmt>();
		int stackStart = symbolsSize();
		StringBuilder sb = new StringBuilder();
		int start = -1;
		while( !parser.match(endTag) ) {
			if( parser.endOfInput() )
				throw exception("unclosed block");
			Stmt block = parseBlock();
			if( block != null ) {
				addText(start,stmts,sb);
				stmts.add(block);
				continue;
			}
			Stmt simpleTag = parseSimpleTag();
			if( simpleTag != null ) {
				addText(start,stmts,sb);
				stmts.add(simpleTag);
				continue;
			}
			if( sb.length() == 0 )
				start = parser.currentIndex();
			sb.append( parser.currentChar() );
			parser.anyChar();
		}
		addText(start,stmts,sb);
		Stmt block = new Block( stmts.toArray(new Stmt[0]), 0, symbolsSize() );
		return block;
	}

	private void addText(int start,List<Stmt> stmts,StringBuilder sb) {
		if( sb.length() == 0 )
			return;
		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, new ConstExpr(null,sb.toString()) );
		stmts.add( new ExpressionsStmt(writeCall) );
		sb.setLength(0);
	}

	private Stmt parseBlock() throws ParseException {
		int start = parser.begin();
		Tag tag = parseBlockTag();
		if( tag == null )
			return parser.failure(null);
		if( tag.name.equals("Set") )
			throw exception("block:Set not allowed here");
		frame = new Frame(frame);
		addSymbol(ENV);
		Stmt block = parseBody(tag);
		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:"+tag.name), env, new ConstExpr(null,tag.name) );
		List<Expressions> args = new ArrayList<Expressions>();
		args.add( tag.attrs.isEmpty() ? new ConstExpr(null,null) : table(tag.attrs) );
		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 Tag parseBlockTag() throws ParseException {
		parser.begin();
		if( !parser.match("{block:") )
			return parser.failure(null);
		String name = parseName();
		if( name==null )
			throw exception("invalid block name");
		Map<String,String> attrs = parseAttrs();
		if( !parser.match("}") )
			return parser.failure(null);
		Tag tag = new Tag(name,attrs);
		return parser.success(tag);
	}

	private Stmt parseSimpleTag() throws ParseException {
		int start = parser.begin();
		if( !parser.match("{") )
			return parser.failure(null);
		String name = parseName();
		if( name==null )
			return parser.failure(null);
		Map<String,String> attrs = parseAttrs();
		if( !parser.match("}") )
			return parser.failure(null);
		FnCall fnCall;
		if( name.equals("Get") ) {
			name = attrs.remove("name");
			if( name == null )
				throw exception("Get missing required attribute 'name'");
			if( !attrs.isEmpty() )
				throw exception("Get has unrecognized attributes: "+attrs);
			if( !validateName(name) )
				throw exception("invalid Get name: "+name);
//			rtn = "<% " + name + "(env) %>";
			int index = upValueIndex(name);
			if( index == -1 )
				throw exception("name '"+name+"' not defined");
			Expr fn = new GetUpVar(se(start,name),index);
			fnCall = new FnCall( se(start), fn, env() );
		} else {
//			rtn = "<% env." + name + (attrs.isEmpty() ? "()" : table(attrs)) + " %>";
			Expr fn = new IndexExpr( se(start,name), env(), new ConstExpr(null,name) );
			Expressions args = attrs.isEmpty() ? ExpList.emptyExpList : table(attrs);
			fnCall = new FnCall( se(start), fn, args );
		}
		Stmt rtn = new ExpressionsStmt(fnCall);
		return parser.success(rtn);
	}

	private TableExpr table(Map<String,String> attrs) {
		List<TableExpr.Field> fields = new ArrayList<TableExpr.Field>();
		for( Map.Entry<String,String> entry : attrs.entrySet() ) {
			ConstExpr key = new ConstExpr(null,entry.getKey());
			ConstExpr value = new ConstExpr(null,entry.getValue());
			fields.add( new TableExpr.Field(key,value) );
		}
		return new TableExpr( null, fields.toArray(new TableExpr.Field[0]), ExpList.emptyExpList );
	}

	private Map<String,String> parseAttrs() {
		Map<String,String> attrs = new HashMap<String,String>();
		while( parseAttr(attrs) );
		Spaces();
		return attrs;
	}

	private boolean parseAttr(Map<String,String> attrs) {
		parser.begin();
		Spaces();
		String name = parseName();
		if( name==null )
			return parser.failure();
		Spaces();
		if( !parser.match('=') )
			return parser.failure();
		Spaces();
		if( !parser.match('"') )
			return parser.failure();
		int start = parser.currentIndex();
		while( parser.noneOf("\"}") );
		String val = parser.textFrom(start);
		if( !parser.match('"') )
			return parser.failure();
		attrs.put(name,val);
		return parser.success();
	}

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

	private String parseName() {
		return parseName(parser);
	}

	private static boolean validateName(String name) {
		return name.equals(parseName(new Parser(new LuanSource("NAME",name))));
	}

	private static String parseName(Parser parser) {
		int start = parser.begin();
		if( !NameFirstChar(parser) )
			return parser.failure(null);
		while( NameChar(parser) );
		String match = parser.textFrom(start);
		return parser.success(match);
	}

	private static boolean NameChar(Parser parser) {
		return NameFirstChar(parser) || parser.inCharRange('0', '9');
	}

	private static boolean NameFirstChar(Parser parser) {
		return parser.inCharRange('a', 'z') || parser.inCharRange('A', 'Z') || parser.match('_');
	}

}