forked from extern/Unexpected-Keyboard
9f22e53a3b
This allows to add new kinds of keys that need more data without making KeyValue's footprint bigger for common keys. This changes the [_symbol] field into [_payload], which holds the same as the previous field for more common keys but can hold bigger objects for keys of the new "Complex" kind. This also adds a complex key: String keys with a symbol different than the outputted string. Unit tests are added as the Java language is not helpful in making robust code.
151 lines
4.1 KiB
Java
151 lines
4.1 KiB
Java
package juloo.keyboard2;
|
|
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
/**
|
|
Parse a key definition. The syntax for a key definition is:
|
|
- [:(kind) (attributes):(payload)].
|
|
- If [str] doesn't start with a [:] character, it is interpreted as an
|
|
arbitrary string key.
|
|
|
|
[(kind)] specifies the kind of the key, it can be:
|
|
- [str]: An arbitrary string key. The payload is the string to output when
|
|
typed and is quoted by single quotes ([']). The payload can contain single
|
|
quotes if they are escaped with a backslash ([\']).
|
|
|
|
The [(attributes)] part is a space-separated list of attributes, all optional,
|
|
of the form: [attrname='attrvalue'].
|
|
|
|
Attributes can be:
|
|
- [flags]: Add flags that change the behavior of the key.
|
|
Value is a coma separated list of:
|
|
- [dim]: Make the symbol dimmer on the keyboard.
|
|
- [small]: Make the symbol smaller on the keyboard.
|
|
- [symbol]: Specify the symbol that is rendered on the keyboard.
|
|
It can contain single quotes if they are escaped: ([\']).
|
|
|
|
Examples:
|
|
- [:str flags=dim,small symbol='MyKey':'My arbitrary string'].
|
|
- [:str:'My arbitrary string'].
|
|
|
|
*/
|
|
public final class KeyValueParser
|
|
{
|
|
static Pattern START_PAT;
|
|
static Pattern ATTR_PAT;
|
|
static Pattern QUOTED_PAT;
|
|
static Pattern PAYLOAD_START_PAT;
|
|
|
|
static public KeyValue parse(String str) throws ParseError
|
|
{
|
|
String symbol = null;
|
|
int flags = 0;
|
|
init();
|
|
// Kind
|
|
Matcher m = START_PAT.matcher(str);
|
|
if (!m.lookingAt())
|
|
parseError("Expected kind, for example \":str ...\".", m);
|
|
String kind = m.group(1);
|
|
// Attributes
|
|
while (true)
|
|
{
|
|
if (!match(m, ATTR_PAT))
|
|
break;
|
|
String attr_name = m.group(1);
|
|
String attr_value = parseSingleQuotedString(m);
|
|
switch (attr_name)
|
|
{
|
|
case "flags":
|
|
flags = parseFlags(attr_value, m);
|
|
break;
|
|
case "symbol":
|
|
symbol = attr_value;
|
|
break;
|
|
|
|
default:
|
|
parseError("Unknown attribute "+attr_name, m);
|
|
}
|
|
}
|
|
// Payload
|
|
if (!match(m, PAYLOAD_START_PAT))
|
|
parseError("Unexpected character", m);
|
|
switch (kind)
|
|
{
|
|
case "str":
|
|
String payload = parseSingleQuotedString(m);
|
|
if (symbol == null)
|
|
return KeyValue.makeStringKey(payload, flags);
|
|
return KeyValue.makeStringKeyWithSymbol(payload, symbol, flags);
|
|
|
|
default: break;
|
|
}
|
|
parseError("Unknown kind '"+kind+"'", m, 1);
|
|
return null; // Unreachable
|
|
}
|
|
|
|
static String parseSingleQuotedString(Matcher m) throws ParseError
|
|
{
|
|
if (!match(m, QUOTED_PAT))
|
|
parseError("Expected quoted string", m);
|
|
return m.group(1).replace("\\'", "'");
|
|
}
|
|
|
|
static int parseFlags(String s, Matcher m) throws ParseError
|
|
{
|
|
int flags = 0;
|
|
for (String f : s.split(","))
|
|
{
|
|
switch (f)
|
|
{
|
|
case "dim": flags |= KeyValue.FLAG_SECONDARY; break;
|
|
case "small": flags |= KeyValue.FLAG_SMALLER_FONT; break;
|
|
default: parseError("Unknown flag "+f, m);
|
|
}
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
static boolean match(Matcher m, Pattern pat)
|
|
{
|
|
try { m.region(m.end(), m.regionEnd()); } catch (Exception _e) {}
|
|
m.usePattern(pat);
|
|
return m.lookingAt();
|
|
}
|
|
|
|
static void init()
|
|
{
|
|
if (START_PAT != null)
|
|
return;
|
|
START_PAT = Pattern.compile(":(\\w+)");
|
|
ATTR_PAT = Pattern.compile("\\s*(\\w+)\\s*=");
|
|
QUOTED_PAT = Pattern.compile("'(([^'\\\\]+|\\\\')*)'");
|
|
PAYLOAD_START_PAT = Pattern.compile("\\s*:");
|
|
}
|
|
|
|
static void parseError(String msg, Matcher m) throws ParseError
|
|
{
|
|
parseError(msg, m, m.regionStart());
|
|
}
|
|
|
|
static void parseError(String msg, Matcher m, int i) throws ParseError
|
|
{
|
|
StringBuilder msg_ = new StringBuilder("Syntax error");
|
|
try
|
|
{
|
|
char c = m.group(0).charAt(0);
|
|
msg_.append(" at character '").append(c).append("'");
|
|
} catch (IllegalStateException _e) {}
|
|
msg_.append(" at position ");
|
|
msg_.append(i);
|
|
msg_.append(": ");
|
|
msg_.append(msg);
|
|
throw new ParseError(msg_.toString());
|
|
}
|
|
|
|
public static class ParseError extends Exception
|
|
{
|
|
public ParseError(String msg) { super(msg); }
|
|
};
|
|
}
|