package terse.vm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import terse.vm.Expr.Block;

public class Parser extends Static {
	Cls cls;
	Terp terp;
	TLex lex;
	HashMap<String, Integer> localVars; // maps to Local Var index.
	HashMap<String, String> localVarSpelling;
	int temp = 0;

	public static boolean isAlphaMessage(String msg) {
		char m0 = msg.charAt(0);
		return Character.isLetter(m0);
	}

	// This is the public function to parse a string.
	public static Expr.Top parseMethod(Cls cls, String methName, String code) {
		Terp terp = cls.terp;
		Parser p = new Parser(cls, methName, code);
		int numArgs = 0;
		if (isAlphaMessage(methName)) {
			for (int i = 0; i < methName.length(); i++) {
				if (methName.charAt(i) == ':') {
					String paramName = Character
							.toString((char) ((int) 'a' + numArgs));
					p.localVars.put(paramName, numArgs);
					p.localVarSpelling.put(paramName, paramName);
					++numArgs;
				}
			}
		} else {
			p.localVars.put("a", 0);
			p.localVarSpelling.put("a", "a");
			numArgs = 1;
		}
		p.parseExpr(); // Once to learn names of variables.
		if (p.lex.w.trim().length() > 0) {
			terp.toss("Parser:  Leftover word after parsing: <%s>",
					p.lex.w.trim());
		}
		if (p.lex.sub.trim().length() > 0) {
			terp.toss("Parser:  Leftover junk after parsing: <%s>",
					p.lex.sub.trim());
		}
		p.lex = new TLex(terp, code); // Ugly.
		Expr expr = p.parseExpr(); // Again for code generation.
		Expr.Top top = new Expr.Top(p.localVars.size(), numArgs, p.localVars,
				p.localVarSpelling, cls, methName, expr);
		// say("parseMethod <<< %s.%s <<< %s", cls, methName, code);
		// say("parseMethod >>> %s", top);
		return top;
	}

	// These are keyboard character substitutions.
	static String[] TRIGRAPH_DATA = new String[] { "eq", "==", "ne", "!=",
			"lt", "<", "le", "<=", "gt", ">", "ge", ">=", "pl", "+", "ti", "*",
			"pc", "%", "an", "&", "or", "|", "xo", "^", "sl", "<<", "sr", ">>",
			"lr", ">>>", "co", ":", "as", "=", "cm", ",", "sc", ";", "dr", "$" };
	static HashMap<String, String> TRIGRAPH_MAP = new HashMap<String, String>();
	static {
		for (int i = 0; i < TRIGRAPH_DATA.length; i += 2) {
			TRIGRAPH_MAP.put(TRIGRAPH_DATA[i], TRIGRAPH_DATA[i + 1]);
		}
	}
	static Pattern TRIGRAPH = Pattern.compile("/[a-z][a-z]",
			Pattern.CASE_INSENSITIVE);

	static public String charSubsts(String s) {
		return bigraphSubst(trigraphSubst(s));
	}

	static public String trigraphSubst(String s) {
		StringBuilder sb = new StringBuilder();
		int i = 0;
		while (true) {
			Matcher m = TRIGRAPH.matcher(s);
			boolean found = m.find(i);
			if (!found) {
				sb.append(s.substring(i));
				return sb.toString();
			}
			int j = m.start();
			sb.append(s.substring(i, j));
			String abbrev = TRIGRAPH_MAP.get(s.substring(j + 1, j + 3));
			if (abbrev == null) {
				sb.append(s.substring(j, j + 3));
			} else {
				sb.append(abbrev);
			}
			i = j + 3;
		}
	}

	static Pattern COLON = Pattern.compile("\\.\\.");
	static Pattern DOLLAR = Pattern.compile(",,");
	static Pattern EQUALS = Pattern.compile("--");
	static Pattern SEMICOLON = Pattern.compile("\\.,|,\\.");
	static Pattern BANG = Pattern.compile("!");
	static Pattern HUH = Pattern.compile("\\?");
	static Pattern BANG_EQ = Pattern.compile("\\(=");

	static public String bigraphSubst(String s) {
		String z = replace(COLON, ":", s);
		z = replace(DOLLAR, "\\$", z);
		z = replace(EQUALS, "=", z);
		z = replace(SEMICOLON, ";", z);
		z = replace(BANG, "(", z);
		z = replace(HUH, ")", z);
		z = replace(BANG_EQ, "!=", z);
		return z;
	}

	// static Pattern BIGRAPH = Pattern.compile("--|[.,][.,]|[!?]");
	// String bigraphSubst(String s) {
	// StringBuilder sb = new StringBuilder();
	// int i = 0;
	// while(true) {
	// Matcher m = BIGRAPH.matcher(s);
	// boolean found = m.find(i);
	// if (!found) {
	// sb.append(s.substring(i);
	// return sb.toString();
	// }
	// int j = m.start();
	// sb.append(s.substring(i, j));
	// String topic =
	// if ()
	// if (abbrev == null) {
	// sb.append(s.substring(j, j+3));
	// } else {
	// sb.append(abbrev);
	// }
	// i = j+3;
	// }
	// }

	private Parser(Cls cls, String methName, String code) {
		this.cls = cls;
		this.terp = cls.terp;
		this.lex = new TLex(terp, code);
		this.localVars = new HashMap<String, Integer>();
		this.localVarSpelling = new HashMap<String, String>();
	}

	private static String replace(Pattern p, String replacement, String a) {
		Matcher m = p.matcher(a);
		StringBuffer sb = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(sb, replacement);
		}
		m.appendTail(sb);
		return sb.toString();
	}

	private void skipStmtEnders() { // i.e. dots & semicolons
		while (lex.endsStmt()) {
			lex.advance();
		}
	}

	private Expr parseExpr() {
		Expr[] v = emptyExprs;
		skipStmtEnders();
		while (lex.t != null) {
			if (lex.closesBlock() || lex.closesParen())
				break;

			Expr s = parseStmt();
			v = append(v, s);
			skipStmtEnders();
		}
		if (v.length == 0) {
			return new Expr.Lit(terp.instNil); // Trivial case.
		} else if (v.length == 1) {
			return v[0]; // Trivial case.
		} else {
			return new Expr.Seq(terp, v);
		}
	}

	private Expr parseStmt() {
		lex.storeState();
		Expr.LValue lvalue = parseLValueOrNull();
		if (lvalue != null && lex.w.equals("=")) {
			lvalue.fixIndices(this);
			lex.advance();
			Expr rhs = parseStmt();
			return new Expr.PutLValue(lvalue, rhs);
		}
		lex.recallState();
		return parseList();
	}

	// When a variable is assigned, it must be either Inst or Local.
	// If it is in the class's allVars, then it is Inst.
	// Otherwise if we don't have it as a local yet, add it.
	// Returns -1 if inst var, or Local Var index if local.
	int indexForLocalVariable(String varName, String varKey) {
		// Determine Inst or Local. Globals are never assigned.
		if (cls.allVars.containsKey(varKey)) {
			return -1;
		}
		int index;
		if (localVars.containsKey(varKey)) {
			index = localVars.get(varKey);
		} else {
			// Does not exist yet, so make it.
			index = localVars.size();
			localVars.put(varKey, index);
			localVarSpelling.put(varKey, varName);
		}
		return index;
	}

	private Expr parseList() {
		boolean itsAList = false;
		Expr[] v = exprs(parseTuple());
		while (lex.isLister()) {
			itsAList = true;
			lex.advance();
			if (lex.endsStmt() || lex.closesBlock() || lex.closesParen()
					|| lex.isEOF()) {
				break;
			}
			v = append(v, parseTuple());
		}
		if (itsAList) {
			return new Expr.MakeVec(terp, v, ';');
		} else {
			// It's not a list at all.
			return v[0];
		}
	}

	private Expr parseTuple() {
		boolean itsATuple = false;
		Expr[] v = exprs(parseChain());
		while (lex.isTupler()) {
			itsATuple = true;
			lex.advance();
			if (lex.isLister() || lex.endsStmt() || lex.closesBlock()
					|| lex.closesParen() || lex.isEOF()) {
				break;
			}
			v = append(v, parseChain());
		}
		if (itsATuple) {
			return new Expr.MakeVec(terp, v, ',');
		} else {
			// It's not a tuple at all.
			return v[0];
		}
	}

	private Expr parseChain() {
		// Normal SmallTalk "cascades" using ";", but we don't.
		// Instead we "chain" with ",," or '�'.
		// Whereas cascade sends subsequent messages to the same receiver,
		// this chaining sends subsequent messages to the previous result.
		Expr unary = parseUnary();
		Expr expr = parseKeyed(unary, 999);
		while (lex.isChain()) {
			lex.advance();
			while (lex.t == Pat.NAME && !lex.isKeyword()) { // parse Unary
															// messages at front
															// of
				// chain.
				expr = new Expr.Send(expr, lex.w.toLowerCase(), emptyExprs);
				lex.advance();
			}
			if (lex.isKeyword() || lex.isTupler() || lex.isBinop()) {
				expr = parseKeyed(expr, 999);
			}
		}
		return expr;
	}

	private Expr parseKeyed(Expr receiver, int cap) {
		receiver = parseBinary3(receiver);
		if (lex.isKeyword() && lex.keywordStrength() < cap) {
			while (lex.isKeyword() && lex.keywordStrength() < cap) {
				String keywords = "";
				Expr[] args = emptyExprs;
				int strength = lex.keywordStrength();
				while (lex.isKeyword() && lex.keywordStrength() == strength) {
					keywords += lex.keywordName().toLowerCase() + ":";
					lex.advance();
					assert lex.w.charAt(0) == ':';
					lex.advance();
					Expr unary = parseUnary();
					Expr tuple = parseBinary3(unary);
					Expr arg = parseKeyed(tuple, strength); // Do keywords of
					// smaller strength.
					args = append(args, arg);
				}
				receiver = new Expr.Send(receiver, keywords, args);
			}
			return receiver;
		} else {
			return receiver; // No keyword with strength less than cap.
		}
	}

	private Expr parseBinary3(Expr receiver) {
		receiver = parseBinary2(receiver);
		Expr e = receiver;
		while (lex.isBinop3()) {
			String op = lex.w;
			lex.advance();
			Expr unary = parseUnary();
			Expr a = parseBinary2(unary);
			e = new Expr.Send(e, op, exprs(a));
		}
		return e;
	}

	private Expr parseBinary2(Expr receiver) {
		receiver = parseBinary1(receiver);
		Expr e = receiver;
		while (lex.isBinop2()) {
			String op = lex.w;
			lex.advance();
			Expr unary = parseUnary();
			Expr a = parseBinary1(unary);
			e = new Expr.Send(e, op, exprs(a));
		}
		return e;
	}

	private Expr parseBinary1(Expr receiver) {
		Expr e = receiver;
		while (lex.isBinop1()) {
			String op = lex.w;
			lex.advance();
			Expr a = parseUnary();
			e = new Expr.Send(e, op, exprs(a));
		}
		return e;
	}

	private Expr parseUnary() {
		Expr expr = parsePrim();
		while (lex.t == Pat.NAME && !lex.isKeyword()) {
			expr = new Expr.Send(expr, lex.w.toLowerCase(), emptyExprs);
			lex.advance();
		}
		return expr;
	}

	private Expr parsePrim() {
		Expr z = null;
		switch (lex.t) {
		case RESERVED:
			String lower = lex.w.toLowerCase();
			if (lower.equals("nil"))
				z = new Expr.Lit(terp.instNil);
			else if (lower.equals("se"))
				z = new Expr.GetSelf(terp);
			else if (lower.equals("self"))
				z = new Expr.GetSelf(terp);
			else if (lower.equals("sup"))
				z = new Expr.Lit(terp.instSuper);
			else if (lower.equals("super"))
				z = new Expr.Lit(terp.instSuper);
			break;
		case NUMBER:
			z = new Expr.Lit(new Pro.Num(terp, Double.parseDouble(lex.w)));
			break;
		case STRING:
			// For old regexp that included quotes:
			// z = new Expr.Lit(new Pro.Str(terp,
			// reduceDoubleSingleQuotes(lex.w.substring(1, lex.w.length() -
			// 1))));
			// For new one that leaves them out:
			z = new Expr.Lit(new Pro.Str(terp, lex.w));
			break;
		case OTHER:
			z = lex.opensBlock() ? parseBlock()
					: lex.opensParen() ? parseParen() : null;
			break;
		case NAME:
			z = lex.isMacro() ? parseMacro() : getVar(lex.w,
					lex.w.toLowerCase());
			break;
		}
		if (z == null) {
			terp.toss("Syntax Error in parsePrim: <%s>", lex.w);
		}
		// NOTA BENE: All of the above cases do not advance. We do it here.
		lex.advance();
		return z;
	}

	private Expr getVar(String varName, String varKey) {
		// NOTICE this is using "allVars" rather than JustAtThisClass vars.
		// TODO: factor this out.
		// Determine Inst or Local. Globals are never assigned.
		if (cls.allVars.containsKey(varKey)) {
			return new Expr.GetInstVar(terp, varName, cls.allVars.get(varKey));
		}
		int index;
		if (localVars.containsKey(varKey)) {
			index = localVars.get(varKey);
			return new Expr.GetLocalVar(terp, varName, index);
		} else {
			return new Expr.GetGlobalVar(terp, varKey);
		}
	}

	private Expr parseBlock() {
		assert lex.opensBlock();
		lex.advance();
		Expr[] params = exprs();

		while (true) {
			lex.storeState();
			Expr.LValue lvalue = parseLValueOrNull();
			if (lvalue != null && lex.w.equals(":")) {
				lvalue.fixIndices(this);
				params = append(params, lvalue);
				lex.advance();
			} else {
				lex.recallState();
				break;
			}
		}
		Expr body = parseExpr();
		assert lex.closesBlock() : fmt("lex=%s<%s> body=<%s>", lex.t, lex.w,
				body);// Do not advance; parsePrim() does that after switch().
		return new Expr.Block(body, params);
	}

	private Expr parseParen() {
		assert lex.opensParen();
		lex.advance();
		if (lex.closesParen()) {
			// The unit tuple (). It's an empty but mutable vector, not a
			// literal.
			return new Expr.MakeVec(terp, emptyExprs, ',');
		}
		Expr body = parseExpr();
		assert lex.closesParen();
		// Do not advance; parsePrim() does that after switch().
		return body;
//		return new Expr.Send(new Expr.Block(body, emptyExprs), "run",
//				emptyExprs);
	}

	private Expr parseMacro() {
		ArrayList<String> names = new ArrayList<String>();
		ArrayList<Expr.Block> blocks = new ArrayList<Expr.Block>();
		while (lex.t == Pat.NAME && lex.isMacro()) {
			names.add(lex.w);
			lex.advance();
			Expr.Block b = (Expr.Block) parseBlock();
			blocks.add(b);
		}
		final int sz = names.size();
		StringBuilder message = new StringBuilder("macro:");
		Expr[] bb = exprs(new Expr.GetFrame(terp));
		for (int i = 0; i < sz; i++) {
			message.append(names.get(i).toLowerCase());
			message.append(':');
			bb = append(bb, blocks.get(i));
		}
		return new Expr.Send(new Expr.GetSelf(terp), message.toString(), bb);
	}

	private Expr.LValue parseLValueOrNull() {
		Expr.LValue a = parseLvNameOrNull();
		if (a == null)
			return null;
		if (lex.w.equals(",")) {
			a = parseLvTupleOrNull(a);
			if (a == null)
				return null;
		}
		if (lex.w.equals(";")) {
			a = parseLvListOrNull(a);
		}
		return a;
	}

	private Expr.LvName parseLvNameOrNull() {
		if (lex.t == Pat.NAME) {
			// local or inst?
			boolean isInstVar = cls.allVars.containsKey(lex.w.toLowerCase());
			Expr.LvName z;
			if (isInstVar) {
				z = new Expr.LvInstName(terp, lex.w);
			} else {
				z = new Expr.LvLocalName(terp, lex.w);
			}
			lex.advance();
			return z;
		}
		return null;
	}

	private Expr.LValue parseLvTupleOrNull(Expr a) {
		Expr.LvTuple z = new Expr.LvTuple(terp, pros(a));
		while (lex.w.equals(",")) {
			lex.advance();
			Expr.LvName x = parseLvNameOrNull();
			if (x == null)
				return null;
			z.arr = append(z.arr, x);
		}
		return z;
	}

	private Expr.LValue parseLvListOrNull(Expr a) {
		Expr.LvList z = new Expr.LvList(terp, pros(a));
		while (lex.w.equals(";")) {
			lex.advance();
			Expr.LValue x = parseLvNameOrNull();
			if (lex.w.equals(";")) {
				x = parseLvTupleOrNull(x);
				if (a == null)
					return null;
			}
			if (x == null)
				return null;
			z.arr = append(z.arr, x);
		}
		return z;
	}

	enum Pat {
		WHITE("\\s\\s*"), COMMENT("\"[^\"]*\""), STRING("'[^']*'"), NUMBER(
				"-?[0-9][0-9]*(\\.[0-9][0-9]*)?([Ee][+-]?[0-9][0-9]*)?"), NAME(
				"[A-Za-z][A-Za-z0-9]*"), OTHER(
				"/[A-Za-z][A-Za-z]|\\.\\.|\\.,|,\\.|,,|--|==|!=|<=|>=|:+|."), RESERVED(
				"(self|se|super|sup|nil)\\b", Pattern.CASE_INSENSITIVE);

		Pattern p;

		Pat(String s) {
			this.p = Pattern.compile(s, 0);
		}

		Pat(String s, int flags) {
			this.p = Pattern.compile(s, flags);
		}

		Matcher matcher(String x) {
			// say("Attempting %s pattern %s ON %s", this, p, x);
			return p.matcher(x);
		}
	}

	static final class TLex extends Static {
		static Pattern ASSIGNISH = Pattern
				.compile("[A-Za-z][A-Za-z0-9,\\s]*(--|[=] )");
		// TODO: need to say "not followed by =, instead of space hack.

		Terp terp;

		// The parser will use these fields:
		public String white; // preceeding white space & comments.
		public String w; // current word
		public Pat t; // type that matched
		public Matcher m;

		String putback_white;
		String putback_w;
		Pat putback_t;
		Matcher putback_m;
		String putback_sub;

		// These will be used for finding errors.
		String prog; // program to parse
		String sub; // Remaining substring of p to parse
		TLex storage = null;

		TLex(Terp t, String prog) {
			this.terp = t;
			this.prog = prog;
			this.white = "";
			this.w = "";
			this.t = null;
			this.m = null;
			this.putback_white = null;
			this.putback_w = null;
			this.putback_t = null;
			this.putback_m = null;
			this.putback_sub = null;
			this.sub = prog;
			advance();
			if (prog.length() > 0) {
				// prog length stops infinite recursion.
				storage = new TLex(terp, "");
			}
		}

		// Copy constructor.
		void copyStateFrom(TLex that) {
			this.terp = that.terp;
			this.prog = that.prog;
			this.white = that.white;
			this.w = that.w;
			this.t = that.t;
			this.m = that.m;
			this.putback_white = that.putback_white;
			this.putback_w = that.putback_w;
			this.putback_t = that.putback_t;
			this.putback_m = that.putback_m;
			this.putback_sub = that.putback_sub;
			this.sub = that.sub;
		}

		void storeState() {
			storage.copyStateFrom(this);
		}

		void recallState() {
			this.copyStateFrom(storage);
		}

		// You can back up one token.
		void back() {
			white = putback_white;
			w = putback_w;
			t = putback_t;
			m = putback_m;
			sub = putback_sub;
		}

		private boolean attempt(Pat pat) {
			m = pat.matcher(sub);
			if (m.lookingAt()) {
				w = sub.substring(m.start(), m.end()); // Detected word.
				sub = sub.substring(m.end() - m.start()); // New substring of p
				// to match.
				t = pat; // Which pattern matched.

				return true;
			}
			return false;
		}

		void advance() {
			// Save state before advancing, so we can go back.
			putback_white = white;
			putback_w = w;
			putback_t = t;
			putback_m = m;
			putback_sub = sub;
			white = "";

			while (true) {
				if (sub.length() == 0) {
					// EOF condition.
					w = "";
					t = null;
					return;
				}
				// Skip white space & comments.
				if (attempt(Pat.WHITE)) {
					// TODO: keep only the newlines.
					white += w;
				} else if (attempt(Pat.COMMENT)) {
					white += w;
				} else {
					break;
				}
			}

			if (sub.length() == 0) {
				// EOF condition.
				w = "";
				t = null;
				return;
			}

			// Check for reserved words first.
			if (attempt(Pat.RESERVED) || attempt(Pat.NAME)
					|| attempt(Pat.NUMBER)) {
				return;
			} else if (attempt(Pat.STRING)) {
				StringBuffer sb = new StringBuffer();
				while (true) {
					sb.append(w.substring(1, w.length() - 1)); // leave off
					// initial &
					// final <'>
					// If another string starts immediately, it was a doubled
					// <''>.
					if (Pat.STRING.matcher(sub).lookingAt()) {
						sb.append("'"); // Add one <'> for the doubled <''>
						// input.
						attempt(Pat.STRING);
					} else {
						break;
					}
				}
				w = sb.toString();
				return;
			} else if (attempt(Pat.OTHER)) {
				return;
			} else {
				StringBuffer sb = new StringBuffer();
				for (int i = 0; i < prog.length(); i++) {
					sb.append(fmt("%d ", (int) prog.charAt(i)));
				}
				terp.say("PROG = %s", sb);
				terp.say("Entire Program (%d chars): <%s>", prog.length(), prog);
				terp.say("Previous Token: %s <%s>", putback_t, putback_w);
				terp.toss(
						"Parser Error at char <%d>, remaining unparsed code: <%s>",
						prog.length() - sub.length(), sub);
			}
		}

		boolean isAssignish() {
			return ASSIGNISH.matcher(w + sub).lookingAt();
		}

		boolean isEOF() {
			return t == null;
		}

		boolean endsStmt() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == '.';
		}

		boolean marksBlockParam() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == ':' || c == '.' && w.equals("..");
		}

		boolean isAssign() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == '=' || c == '-' && w.equals("--");
		}

		boolean isTupler() {
			if (t != Pat.OTHER)
				return false;
			return w.equals(",");
		}

		boolean isLister() {
			if (t != Pat.OTHER)
				return false;
			return w.equals(";") || w.equals(".,") || w.equals(",.");
		}

		boolean isChain() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == '$' || c == ',' && w.equals(",,");
		}

		boolean opensBlock() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == '[' || c == '{';
		}

		boolean closesBlock() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == ']' || c == '}';
		}

		boolean opensParen() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == '(' || c == '!';
		}

		boolean closesParen() {
			if (t != Pat.OTHER)
				return false;
			char c = w.charAt(0);
			return c == ')' || c == '?';
		}

		boolean isBinop3() {
			if (t != Pat.OTHER)
				return false;
			return w.equals("==") || w.equals("!=") || w.equals("<")
					|| w.equals("<=") || w.equals(">") || w.equals(">=")
					|| w.equals("/eq") || w.equals("/ne") || w.equals("/lt")
					|| w.equals("/le") || w.equals("/gt") || w.equals("/ge");
		}

		boolean isBinop2() {
			if (t != Pat.OTHER)
				return false;
			return w.equals("+") || w.equals("-") || w.equals("|")
					|| w.equals("^") || w.equals("/pl") || w.equals("/mi")
					|| w.equals("/or") || w.equals("/xo");
		}

		boolean isBinop1() {
			if (t != Pat.OTHER)
				return false;
			return w.equals("*") || w.equals("/") || w.equals("%")
					|| w.equals("&") || w.equals("/ti") || w.equals("/di")
					|| w.equals("/mo") || w.equals("/an");
		}

		boolean isBinop() {
			return isBinop1() || isBinop2() || isBinop3();
		}

		boolean isKeyword() {
			if (t == Pat.NAME) {
				storeState();
				advance();
				if (w.length() > 0 && w.charAt(0) == ':') {
					recallState();
					return true;
				}
				recallState();
			}
			return false;
		}

		/** Name without the trailing colons or slashes. */
		String keywordName() {
			assert isKeyword();
			return w;
		}

		/** Number of trailing colons or slashes */
		int keywordStrength() {
			if (t == Pat.NAME) {
				storeState();
				if (w != null && w.charAt(0) == ':') {
					recallState();
					return w.length();
				}
				recallState();
			}
			assert false : w + " " + this.sub;
			return 0;
		}

		/** Look ahead at the next token, to distinguish MACRO. */
		boolean isMacro() {
			assert (t == Pat.NAME);
			advance();
			boolean z = opensParen() || opensBlock();
			back();
			return z;
		}
	}
}
