code:
package org.arkerthan.sanityCheck.element;

import org.arkerthan.sanityCheck.*;

import java.util.Arrays;
import java.util.List;

/**
 * @author Arkerthan
 */
public class AngleBracketElement extends Element {
	private static final List<String> logicTags = Arrays.asList("if", "elseif", "else", "switch", "case", "default");
	private int state = 0;
	/*
	 0 - initial: <
	 TWINE
	 1 - <<
	-1 - <</
	 2 - trying to complete twine tag: <<tag ???>>
	-2 - trying to complete twine tag: <</tag>>
	 3 - waiting for >>
	-3 - expecting > from 3
	 4 - waiting for >> with KnownElement
	-4 - expecting > from 4
	 5 - expecting >>
	-5 - expecting >
	 6 - expecting > with KnownElement opening; comparison?
	-6 - expecting > with KnownElement closing

	 HTML
	-9 - </
	10 - trying to complete HTML tag: <tag ???>
   -10 - trying to complete HTML tag: </tag>
	11 - waiting for >
   -11 - expecting >
	12 - waiting for > with KnownElement
	 */

	private TagSearchTree<Tag> tree;

	public AngleBracketElement(int line, int pos) {
		super(line, pos);
	}

	@Override
	public int handleChar(char c) throws SyntaxError {
		switch (state) {
			case 0:
				switch (c) {
					case '<':
						state = 1;
						return 1;
					case '/':
						state = -9;
						return 1;
					case '>':// empty <>
					case ' ':// assume comparison
					case '=':// "		"
					case '3':// a heart: <3
						return 2;
					default:
						try {
							state = 10;
							tree = Main.htmlTags;
							return handleOpeningHTML(c);
						} catch (SyntaxError e) {
							state = 1;
							throw new SyntaxError("Opening \"<\" missing, found " + c, 1);
						}
				}
			case 1:
				if (c == '<') {
					throw new SyntaxError("Too many \"<\".", 1);
				} else if (c == '>') {
					state = 3;
					throw new SyntaxError("Empty Statement?", 1);
				} else if (c == '/') {
					state = -1;
					return 1;
				}
				state = 2;
				tree = Main.twineTags;
				return handleOpeningTwine(c);
			case -1:
				if (c == '>') {
					throw new SyntaxError("Empty Statement?", 2, true);
				}
				state = -2;
				tree = Main.twineTags;
				return handleClosingTwine(c);

			case 2:
				return handleOpeningTwine(c);
			case -2:
				return handleClosingTwine(c);
			case 3:
				if (c == '>') {
					state = -3;
					return 1;
				}
				break;
			case -3:
				if (c == '>') {
					return 2;
				} else if (c == ' ' || c == '=') { // assuming comparison
					state = 3;
					return 1;
				} else {
					throw new SyntaxError("Closing \">\" missing, opened tag at [" + line + ":" + pos + "]", 2);
				}
			case 4:
				if (c == '>') {
					state = -4;
					return 1;
				}
				break;
			case -4:
				if (c == '>') {
					return 3;
				} else if (c == ' ' || c == '=') { // assuming comparison
					state = 4;
					return 1;
				} else {
					throw new SyntaxError("Closing \">\" missing, opened tag at[" + line + ":" + pos + "]", 2);
				}
			case 5:
				if (c == '>') {
					state = -5;
					return 1;
				} else {
					throw new SyntaxError("Closing \">\" missing, opened tag at [" + line + ":" + pos + "]", 2);
				}
			case -5:
				if (c == '>') {
					return 2;
				}
				throw new SyntaxError("Closing \">\" missing, opened tag at [" + line + ":" + pos + "]", 2);
			case 6:
				if (c == '>') {
					return 3;
				} else if (c == ' ' || c == '=') {
					state = 3;
					return 1;
				} else {
					throw new SyntaxError("Closing \">\" missing, opened tag at [" + line + ":" + pos + "]", 3);
				}
			case -6:
				if (c == '>') {
					return 3;
				}
				throw new SyntaxError("Closing \">\" missing, opened tag at [" + line + ":" + pos + "]", 3);

			case -9:
				if (c == '>') {
					throw new SyntaxError("Empty Statement?", 2, true);
				}
				state = -10;
				tree = Main.htmlTags;
				return handleClosingHTML(c);
			case 10:
				return handleOpeningHTML(c);
			case -10:
				return handleClosingHTML(c);
			case 11:
				if (c == '>')
					return 2;
				if (c == '@') //@ inside HTML tags is allowed
					return 1;
				break;
			case -11:
				if (c == '>')
					return 2;
				throw new SyntaxError("Closing \">\" missing [2]", 2);
			case 12:
				if (c == '>')
					return 3;
				if (c == '@') //@ inside HTML tags is allowed
					return 1;
				break;
			default:
				throw new UnknownStateException(state);
		}
		return 0;
	}

	private int handleOpeningHTML(char c) throws SyntaxError {
		if (c == ' ') {
			state = 11;
			if (tree.getElement() == null) {
				throw new SyntaxError("Unknown HTML tag", 1);
			}
			if (!tree.getElement().single) {
				k = new KnownHtmlElement(line, pos, true, tree.getElement().tag);
				state = 12;
				return 1;
			}
			return 1;
		}
		if (c == '>') {
			if (tree.getElement() == null) {
				throw new SyntaxError("Unknown HTML tag", 2);
			}
			if (!tree.getElement().single) {
				k = new KnownHtmlElement(line, pos, true, tree.getElement().tag);
				return 3;
			}
			return 2;
		}

		tree = tree.getBranch(c);
		if (tree == null) {
			state = 11;
			throw new SyntaxError("Unknown HTML tag or closing \">\" missing, found " + c, 1);
		}

		return 1;
	}

	private int handleClosingHTML(char c) throws SyntaxError {
		if (c == '>') {
			if (tree.getElement() == null) {
				throw new SyntaxError("Unknown HTML tag: " + tree.getPath(), 2);
			}
			if (tree.getElement().single) {
				throw new SyntaxError("Single HTML tag used as closing Tag: " + tree.getElement().tag, 2);
			}
			k = new KnownHtmlElement(line, pos, false, tree.getElement().tag);
			return 3;
		}

		tree = tree.getBranch(c);
		if (tree == null) {
			state = -11;
			throw new SyntaxError("Unknown HTML tag or closing \">\" missing, found " + c, 1);
		}

		return 1;
	}

	private int handleOpeningTwine(char c) throws SyntaxError {
		if (c == ' ') {
			state = 3;
			if (tree.getElement() == null) {
				//assuming not listed means widget until better solution
				return 1;
			}
			if (!tree.getElement().single) {
				if (logicTags.contains(tree.getElement().tag)) {
					k = new KnownLogicElement(line, pos, tree.getElement().tag, false);
				} else {
					k = new KnownTwineElement(line, pos, true, tree.getElement().tag);
				}
				state = 4;
				return 1;
			}
			return 1;
		}
		if (c == '>') {
			state = -5;
			if (tree.getElement() == null) {
				//assuming not listed means widget until better solution
				return 1;
			}
			if (!tree.getElement().single) {
				if (logicTags.contains(tree.getElement().tag)) {
					k = new KnownLogicElement(line, pos, tree.getElement().tag, false);
				} else {
					k = new KnownTwineElement(line, pos, true, tree.getElement().tag);
				}
				state = 6;
				return 1;
			}
			return 2;
		}

		tree = tree.getBranch(c);
		if (tree == null) {
			//assuming not listed means widget until better solution
			state = 3;
		}

		return 1;
	}

	private int handleClosingTwine(char c) throws SyntaxError {
		if (c == '>') {
			if (tree.getElement() == null) {
				throw new SyntaxError("Unknown Twine tag: " + tree.getPath(), 2);
			}
			if (tree.getElement().single) {
				throw new SyntaxError("Single Twine tag used as closing Tag: " + tree.getElement().tag, 2);
			}
			if (logicTags.contains(tree.getElement().tag)) {
				k = new KnownLogicElement(line, pos, tree.getElement().tag, true);
			} else {
				k = new KnownTwineElement(line, pos, false, tree.getElement().tag);
			}
			state = -6;
			return 1;
		}

		tree = tree.getBranch(c);
		if (tree == null) {
			state = 3;
			throw new SyntaxError("Unknown Twine closing tag or closing \">>\" missing, found " + c, 1);
		}

		return 1;
	}

	@Override
	public String getShortDescription() {
		StringBuilder builder = new StringBuilder();
		builder.append(getPositionAsString()).append(" ");
		switch (state) {
			case 0:
				builder.append("<");
				break;
			//TWINE
			case 1:
				builder.append("<<");
				break;
			case -1:
				builder.append("<</");
				break;
			case 2:
				builder.append("<<").append(tree.getPath());
				break;
			case -2:
				builder.append("<</").append(tree.getPath());
				break;
			case 3:
				builder.append("<<???");
				break;
			case -3:
				builder.append("<<???>");
				break;
			case 4:
				builder.append("<<").append(tree.getPath()).append(" ???");
				break;
			case -4:
				builder.append("<<").append(tree.getPath()).append(" ???>");
				break;
			case 5:
				builder.append("<<???");
				break;
			case -5:
				builder.append("<<").append(tree == null ? "???" : tree.getPath()).append(">");
				break;
			case 6:
				builder.append("<<").append(tree.getPath()).append(" ???>");
				break;
			case -6:
				builder.append("<</").append(tree.getPath()).append(">");
				break;
			//HTML
			case -9:
				builder.append("</");
				break;
			case 10:
				builder.append("<").append(tree.getPath());
				break;
			case -10:
				builder.append("</").append(tree.getPath());
				break;
			case 11:
				builder.append("<?").append(tree == null ? "???" : tree.getPath());
				break;
			case -11:
				builder.append("</").append(tree == null ? "???" : tree.getPath());
				break;
			case 12:
				builder.append("<").append(tree.getPath()).append(" ???");
			default:
				throw new UnknownStateException(state);
		}
		return builder.toString();
	}
}


code:
package org.arkerthan.sanityCheck.element;

import org.arkerthan.sanityCheck.SyntaxError;
import org.arkerthan.sanityCheck.UnknownStateException;

/**
 * @author Arkerthan
 */
public class AtElement extends Element {
	private int state = 0;
	// 0 = @
	// 1 = @@
	// 2 = @@.
	// 3 = @@.a -- @@.ab -- @@.abc
	// 4 = @@.abc;abc
	// 5 = @@.abc;abc@

	// example: @@.red;some text@@

	public AtElement(int line, int pos) {
		super(line, pos);
	}

	@Override
	public int handleChar(char c) throws SyntaxError {
		switch (state) {
			case 0:
				state = 1;
				if (c == '@') {
					return 1;
				} else {
					if (c == '.') {
						state = 2;
					}
					throw new SyntaxError("Opening \"@\" missing.", 1);
				}
			case 1:
				if (c == '.') {
					state = 2;
					return 1;
				} else {
					state = 4;
					throw new SyntaxError("\".\" missing, found \"" + c + "\". This might also indicate a " +
							"missing closure in the previous color code.", 0, true);
				}
			case 2:
				state = 3;
				if (Character.isAlphabetic(c)) {
					return 1;
				} else {
					throw new SyntaxError("Identifier might be wrong.", 1, true);
				}
			case 3:
				if (c == ';') {
					state = 4;
					return 1;
				} else if (c == ' ') {
					state = 4;
					throw new SyntaxError("\";\" missing or wrong space.", 1);
				}
				break;
			case 4:
				if (c == '@') {
					state = 5;
					return 1;
				}
				break;
			case 5:
				if (c == '@') {
					return 2;
				} else {
					throw new SyntaxError("Closing \"@\" missing.", 2);
				}
			default:
				throw new UnknownStateException(state);
		}
		return 0;
	}

	@Override
	public String getShortDescription() {
		StringBuilder builder = new StringBuilder();
		builder.append(getPositionAsString()).append(" ");
		switch (state) {
			case 0:
				builder.append("@");
				break;
			case 1:
				builder.append("@@");
				break;
			case 2:
				builder.append("@@.");
				break;
			case 3:
				builder.append("@@.???");
				break;
			case 4:
				builder.append("@@???");
				break;
			case 5:
				builder.append("@@???@");
				break;
			default:
				throw new UnknownStateException(state);
		}
		return builder.toString();
	}
}

repo_name: AmbrosiaCheesecake/fc


code:
package org.arkerthan.sanityCheck.element;

import org.arkerthan.sanityCheck.SyntaxError;

/**
 * @author Arkerthan
 */
public class BracketElement extends Element {
	//int state = 0;

	public BracketElement(int line, int pos) {
		super(line, pos);
	}

	@Override
	public int handleChar(char c) throws SyntaxError {
		if (c == ')') {
			return 2;
		} else {
			return 0;
		}
	}

	@Override
	public String getShortDescription() {
		return getPositionAsString() + " (???";
	}
}


code:
package org.arkerthan.sanityCheck.element;

import org.arkerthan.sanityCheck.SyntaxError;
import org.arkerthan.sanityCheck.UnknownStateException;

/**
 * @author Arkerthan
 */
public class CommentElement extends Element {
	private int state = 0;
	/*
	0 - /
	1 - /*???
	2 - /*???*
	3 - /%???
	4 - /%???%
	 */

	/**
	 * @param line line in which comment starts
	 * @param pos  position in line where comment starts
	 */
	public CommentElement(int line, int pos) {
		super(line, pos);
	}

	@Override
	public int handleChar(char c) throws SyntaxError {
		switch (state) {
			case 0:
				if (c == '*') {
					state = 1;
				} else if (c == '%') {
					state = 3;
				} else if (c == '>') {
					throw new SyntaxError("XHTML style closure", 4, true);
				} else {
					return 4;
				}
				break;
			case 1:
				if (c == '*') {
					state = 2;
				}
				break;
			case 2:
				if (c == '/') {
					return 2;
				} else if (c == '*') {
					return 1;
				}
				state = 1;
				break;
			case 3:
				if (c == '%') {
					state = 4;
				}
				break;
			case 4:
				if (c == '/') {
					return 2;
				}
				state = 3;
				break;
			default:
				throw new UnknownStateException(state);
		}
		return 1;
	}

	@Override
	public String getShortDescription() {
		return getPositionAsString() + "comment";
	}
}


code:
package org.arkerthan.sanityCheck.element;

import org.arkerthan.sanityCheck.SyntaxError;

/**
 * @author Arkerthan
 */
public abstract class Element {
	protected KnownElement k;
	protected int line, pos;

	/**
	 * @param line Line the instance was created
	 * @param pos  Position in line the instance was created
	 */
	protected Element(int line, int pos) {
		this.line = line;
		this.pos = pos;
	}

	/**
	 * Parses a Char and returns an int depending on the state of the element
	 * 0 - the Element did nothing
	 * 1 - the Element changed state
	 * 2 - the Element is finished
	 * 3 - the Element is finished and a KnownHtmlElement was generated
	 * 4 - the Element is finished and the char is still open for use
	 *
	 * @param c char to be parsed
	 * @return state change
	 * @throws SyntaxError thrown when an syntax error is detected
	 */
	public abstract int handleChar(char c) throws SyntaxError;

	/**
	 * @return the constructed KnownElement. null if none was constructed yet.
	 */
	public KnownElement getKnownElement() {
		return k;
	}

	/**
	 * Returns the line and position of the Element in the file it was created in.
	 *
	 * @return position of Element in file as String
	 */
	public String getPositionAsString() {
		return "[" + line + ":" + pos + "]";
	}

	/**
	 * @return a short description usually based on state and position of the Element
	 */
	public abstract String getShortDescription();
}


code:
package org.arkerthan.sanityCheck.element;

import org.arkerthan.sanityCheck.SyntaxError;

/**
 * @author Arkerthan
 */
public abstract class KnownElement extends Element {

	/**
	 * @param line at which it begins
	 * @param pos  at which it begins
	 */
	public KnownElement(int line, int pos) {
		super(line, pos);
	}

	/**
	 * @return true, if it needs another Known Element to close it.
	 */
	public abstract boolean isOpening();

	/**
	 * @return true if it closes another Element.
	 */
	public abstract boolean isClosing();

	/**
	 * @param k Element to be checked
	 * @return true if given Element closes Element
	 */
	public abstract boolean isMatchingElement(KnownElement k);

	@Override
	public int handleChar(char c) throws SyntaxError {
		return 0;
	}
}


code:
package org.arkerthan.sanityCheck.element;

/**
 * @author Arkerthan
 */
public class KnownHtmlElement extends KnownElement {

	private boolean opening;
	private String statement;

	/**
	 * @param line	  at which it begins
	 * @param pos	   at which it begins
	 * @param opening   if it opens a tag: <tag>  or closes it: </tag>
	 * @param statement statement inside the tag
	 */
	public KnownHtmlElement(int line, int pos, boolean opening, String statement) {
		super(line, pos);
		this.opening = opening;
		this.statement = statement;
	}

	@Override
	public String getShortDescription() {
		StringBuilder builder = new StringBuilder();
		builder.append(getPositionAsString()).append(" <");
		if (!opening) {
			builder.append("/");
		}
		return builder.append(statement).append(">").toString();
	}

	@Override
	public boolean isOpening() {
		return opening;
	}

	@Override
	public boolean isClosing() {
		return !opening;
	}

	@Override
	public boolean isMatchingElement(KnownElement k) {
		if (k instanceof KnownHtmlElement) {
			return ((KnownHtmlElement) k).statement.equals(this.statement);
		}
		return false;
	}
}


code:
package org.arkerthan.sanityCheck.element;

import org.arkerthan.sanityCheck.DisallowedTagException;
import org.arkerthan.sanityCheck.UnknownStateException;

import java.util.Arrays;
import java.util.List;

/**
 * @author Arkerthan
 */
public class KnownLogicElement extends KnownElement {
	private static final List<String> allowedTags = Arrays.asList("if", "elseif", "else");
	private final int state;
	private boolean last;
	/*
	0 - if
	1 - elseif
	2 - else
	3 - switch
	4 - case
	5 - default
	 */

	public KnownLogicElement(int line, int pos, String tag, boolean last) {
		this(line, pos, tag);
		this.last = last;
	}

	public KnownLogicElement(int line, int pos, String tag) {
		super(line, pos);
		switch (tag) {
			case "if":
				state = 0;
				break;
			case "elseif":
				state = 1;
				break;
			case "else":
				state = 2;
				break;
			case "switch":
				state = 3;
				break;
			case "case":
				state = 4;
				break;
			case "default":
				state = 5;
				break;
			default:
				throw new DisallowedTagException(tag);
		}
		last = false;
	}

	@Override
	public boolean isOpening() {
		return !last;
	}

	@Override
	public boolean isClosing() {
		return (state != 0 && state != 3) || last;
	}

	@Override
	public boolean isMatchingElement(KnownElement k) {
		if (!(k instanceof KnownLogicElement)) {
			return false;
		}
		KnownLogicElement l = (KnownLogicElement) k;
		switch (state) {
			case 0:
			case 1:
				return l.state == 1 || l.state == 2 || (l.state == 0 && l.last);
			case 2:
				return l.state == 0 && l.last;
			case 3:
			case 4:
				return l.state == 3 || l.state == 4;
			case 5:
				return l.state == 3 && l.last;
			default:
				throw new UnknownStateException(state);
		}
	}

	@Override
	public String getShortDescription() {
		StringBuilder builder = new StringBuilder();
		builder.append(getPositionAsString()).append(" <<");
		if (last) {
			builder.append('/');
		}
		switch (state) {
			case 0:
				builder.append("if");
				break;
			case 1:
				builder.append("elseif");
				break;
			case 2:
				builder.append("else");
				break;
			case 3:
				builder.append("switch");
				break;
			case 4:
				builder.append("case");
				break;
			case 5:
				builder.append("default");
				break;
			default:
				throw new UnknownStateException(state);
		}
		return builder.append(">>").toString();
	}
}


code:
package org.arkerthan.sanityCheck.element;

/**
 * @author Arkerthan
 */
public class KnownTwineElement extends KnownElement {

	private boolean opening;
	private String statement;

	/**
	 * @param line	  at which it begins
	 * @param pos	   at which it begins
	 * @param opening   if it opens a tag: <<tag>>  or closes it: <</tag>>
	 * @param statement statement inside the tag
	 */
	public KnownTwineElement(int line, int pos, boolean opening, String statement) {
		super(line, pos);
		this.opening = opening;
		this.statement = statement;
	}

	@Override
	public String getShortDescription() {
		StringBuilder builder = new StringBuilder();
		builder.append(getPositionAsString()).append(" <<");
		if (!opening) {
			builder.append("/");
		}
		return builder.append(statement).append(">>").toString();
	}

	@Override
	public boolean isOpening() {
		return opening;
	}

	@Override
	public boolean isClosing() {
		return !opening;
	}

	@Override
	public boolean isMatchingElement(KnownElement k) {
		if (k instanceof KnownTwineElement) {
			return ((KnownTwineElement) k).statement.equals(this.statement);
		}
		return false;
	}
}


code:
#Allowed twine tags
#Effectively everything that is allowed in a statements like this:
#<<tag>> or <<tag ???>>
#when ;1 is specified it expects a matching closing tag like this: <</tag>>
#Do not add HTML Tags here.
#Characters outside of ASCII scope are not supported.
#
#twine tags
capture;1
continue;0
for;1
foreach;1
goto;0
htag;1
include;0
link;1
nobr;1
options;1
print;0
replace;1
run;0
script;1
set;0
silently;1
span;1
textbox;0
timed;1
unset;0
widget;1
=;0
#
# Twine logic ### DO NOT TOUCH ###
if;1
elseif;1
else;1
switch;1
case;1
default;0

path: devTools/javaSanityCheck/twineTags


code:
@echo off
:: Generates devNotes/twineCSS.txt from all .css files in src/ subdir

:: See if we can find a git installation
setlocal enabledelayedexpansion

for %%k in (HKCU HKLM) do (
	for %%w in (\ \Wow6432Node\) do (
		for /f "skip=2 delims=: tokens=1*" %%a in ('reg query "%%k\SOFTWARE%%wMicrosoft\Windows\CurrentVersion\Uninstall\Git_is1" /v InstallLocation 2^> nul') do (
			for /f "tokens=3" %%z in ("%%a") do (
				set GIT=%%z:%%b
				set GITFOUND=yes
				goto FOUND
			)
		)
	)
)

:FOUND
if %GITFOUND% == yes (
	set "PATH=%GIT%bin;%PATH%"
	bash --login -c ./makeTwineCSSPassage.sh
)


code:
#!/bin/sh
# Generates devNotes/twine CSS.txt from all .css files in src/ subdir

# Joins all .css files from the current dir (recursive) into a Twee [script] passage
# arguments:
# $1: root repo dir
# $2: output file name
collectCSSForTwine() {
	local files=$(find . -iname '*.css' -print)
	files=$(echo "$files" | sort)
	echo "" > "$2"
	for f in $files; do
		echo -e "\n/* ${f} */\n" >> "$2"
		cat "$f" >> "$2"
	done
}

ROOT_REPO_DIR="$(git rev-parse --show-toplevel)"
cd "${ROOT_REPO_DIR}"/src
collectCSSForTwine "${ROOT_REPO_DIR}" "${ROOT_REPO_DIR}/devNotes/twine CSS.txt"


code:
@echo off
:: Generates devNotes/twineJS.txt from all .js files in src/ subdir

:: See if we can find a git installation
setlocal enabledelayedexpansion

for %%k in (HKCU HKLM) do (
	for %%w in (\ \Wow6432Node\) do (
		for /f "skip=2 delims=: tokens=1*" %%a in ('reg query "%%k\SOFTWARE%%wMicrosoft\Windows\CurrentVersion\Uninstall\Git_is1" /v InstallLocation 2^> nul') do (
			for /f "tokens=3" %%z in ("%%a") do (
				set GIT=%%z:%%b
				set GITFOUND=yes
				goto FOUND
			)
		)
	)
)

:FOUND
if %GITFOUND% == yes (
	set "PATH=%GIT%bin;%PATH%"
	bash --login -c ./makeTwineJSPassage.sh
)


code:
#!/bin/sh
# Generates devNotes/twine JS.txt from all .js files in src/ subdir

# Joins all .js files from the current dir (recursive) into a Twee [script] passage
# arguments:
# $1: root repo dir
# $2: output file name
collectJSForTwine() {
	local files=$(find . -path ./art/assistantArt.js -prune -o -name '*.js' -print)
	files=$(echo "$files" | sort)
	echo "" > "$2"
	for f in $files; do
		echo -e "\n/* ${f} */\n" >> "$2"
		sed -nf "$1"/devTools/stripComments.sed "$f" >> "$2"
	done
}

ROOT_REPO_DIR="$(git rev-parse --show-toplevel)"
cd "${ROOT_REPO_DIR}"/src
collectJSForTwine "${ROOT_REPO_DIR}" "${ROOT_REPO_DIR}/devNotes/twine JS.txt"