mirror of
https://github.com/Julow/Unexpected-Keyboard.git
synced 2025-08-17 01:02:02 +02:00
Add complex keys (#774)
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.
This commit is contained in:
150
srcs/juloo.keyboard2/KeyValueParser.java
Normal file
150
srcs/juloo.keyboard2/KeyValueParser.java
Normal file
@ -0,0 +1,150 @@
|
||||
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); }
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user