diff options
| author | Jules Aguillon | 2024-09-29 21:58:22 +0200 |
|---|---|---|
| committer | GitHub | 2024-09-29 21:58:22 +0200 |
| commit | 9f22e53a3ba8f064e69e3a84c371a7f29ee9e05c (patch) | |
| tree | 96735329102cdde5460818f22803e67dc42c6356 /srcs/juloo.keyboard2/KeyValueParser.java | |
| parent | e309b76c0a8cb4c322b5fa902a080e19c2fe1f08 (diff) | |
| download | unexpected-keyboard-9f22e53a3ba8f064e69e3a84c371a7f29ee9e05c.tar.gz unexpected-keyboard-9f22e53a3ba8f064e69e3a84c371a7f29ee9e05c.zip | |
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.
Diffstat (limited to 'srcs/juloo.keyboard2/KeyValueParser.java')
| -rw-r--r-- | srcs/juloo.keyboard2/KeyValueParser.java | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/srcs/juloo.keyboard2/KeyValueParser.java b/srcs/juloo.keyboard2/KeyValueParser.java new file mode 100644 index 0000000..178046e --- /dev/null +++ b/srcs/juloo.keyboard2/KeyValueParser.java @@ -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); } + }; +} |
