abouttreesummaryrefslogcommitdiff
path: root/srcs/juloo.keyboard2/KeyValueParser.java
diff options
context:
space:
mode:
authorJules Aguillon2024-09-29 21:58:22 +0200
committerGitHub2024-09-29 21:58:22 +0200
commit9f22e53a3ba8f064e69e3a84c371a7f29ee9e05c (patch)
tree96735329102cdde5460818f22803e67dc42c6356 /srcs/juloo.keyboard2/KeyValueParser.java
parente309b76c0a8cb4c322b5fa902a080e19c2fe1f08 (diff)
downloadunexpected-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.java150
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); }
+ };
+}