diff options
| author | DennyTom <denemark.tomas@gmail.com> | 2020-04-07 04:13:17 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-04-07 21:13:17 +1000 |
| commit | e409fb47f27f9cf56479928ed86eb2eb346eec54 (patch) | |
| tree | f7b27bec198b7bb6250fbcf73111c189bc22d107 /users/dennytom/chording_engine/parser.py | |
| parent | ae74922d1485e3c8e120dbc141d003ed7696b1f9 (diff) | |
| download | qmk_firmware-e409fb47f27f9cf56479928ed86eb2eb346eec54.tar.gz qmk_firmware-e409fb47f27f9cf56479928ed86eb2eb346eec54.zip | |
DennyTom's buttery_engine (#8138)
* Selectively adding pieces
* Adding georgi keymap
* Adding more files, fixing make
* Smaller makefiles
* Fixing make rules
* README more inline with QMK's guidelines
* Turning off buggy assert
* Improving documentation based on a user feedback.
* Slightly better schema
* Resurrected state machine diagram
Diffstat (limited to 'users/dennytom/chording_engine/parser.py')
| -rw-r--r-- | users/dennytom/chording_engine/parser.py | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/users/dennytom/chording_engine/parser.py b/users/dennytom/chording_engine/parser.py new file mode 100644 index 000000000..b62cf007e --- /dev/null +++ b/users/dennytom/chording_engine/parser.py | |||
| @@ -0,0 +1,231 @@ | |||
| 1 | #!/usr/bin/env python3 | ||
| 2 | |||
| 3 | import json | ||
| 4 | from functools import reduce | ||
| 5 | from chord import * | ||
| 6 | import sys | ||
| 7 | |||
| 8 | comma_separator = (lambda x, y: str(x) + ", " + str(y)) | ||
| 9 | string_sum = (lambda x, y: str(x) + " + " + str(y)) | ||
| 10 | newline_separator = (lambda x, y: str(x) + "\n" + str(y)) | ||
| 11 | |||
| 12 | def add_includes(data): | ||
| 13 | output_buffer = "" | ||
| 14 | if not ("do_not_include_QMK" in data["parameters"] and data["parameters"]["do_not_include_QMK"] == True): | ||
| 15 | output_buffer += "#include QMK_KEYBOARD_H\n" | ||
| 16 | if len(data["extra_dependencies"]) > 0: | ||
| 17 | for dependecy in data["extra_dependencies"]: | ||
| 18 | output_buffer += '#include "' + dependecy + '"\n' | ||
| 19 | |||
| 20 | return output_buffer + "\n" | ||
| 21 | |||
| 22 | def add_parameters(data): | ||
| 23 | output_buffer = "" | ||
| 24 | |||
| 25 | number_of_keys = len(data["keys"]) | ||
| 26 | if number_of_keys <= 8: | ||
| 27 | hash_type = "uint8_t" | ||
| 28 | elif number_of_keys <= 16: | ||
| 29 | hash_type = "uint16_t" | ||
| 30 | elif number_of_keys <= 32: | ||
| 31 | hash_type = "uint32_t" | ||
| 32 | elif number_of_keys <= 64: | ||
| 33 | hash_type = "uint64_t" | ||
| 34 | else: | ||
| 35 | raise Exception("The engine currently supports only up to 64 keys.") | ||
| 36 | |||
| 37 | output_buffer += "#define CHORD_TIMEOUT " + str(data["parameters"]["chord_timeout"]) + "\n" | ||
| 38 | output_buffer += "#define DANCE_TIMEOUT " + str(data["parameters"]["dance_timeout"]) + "\n" | ||
| 39 | output_buffer += "#define LEADER_TIMEOUT " + str(data["parameters"]["leader_timeout"]) + "\n" | ||
| 40 | output_buffer += "#define TAP_TIMEOUT " + str(data["parameters"]["tap_timeout"]) + "\n" | ||
| 41 | output_buffer += "#define LONG_PRESS_MULTIPLIER " + str(data["parameters"]["long_press_multiplier"]) + "\n" | ||
| 42 | output_buffer += "#define DYNAMIC_MACRO_MAX_LENGTH " + str(data["parameters"]["dynamic_macro_max_length"]) + "\n" | ||
| 43 | output_buffer += "#define COMMAND_MAX_LENGTH " + str(data["parameters"]["command_max_length"]) + "\n" | ||
| 44 | output_buffer += "#define STRING_MAX_LENGTH " + str(data["parameters"]["string_max_length"]) + "\n" | ||
| 45 | output_buffer += "#define LEADER_MAX_LENGTH " + str(data["parameters"]["leader_max_length"]) + "\n" | ||
| 46 | output_buffer += "#define HASH_TYPE " + hash_type + "\n" | ||
| 47 | output_buffer += "#define NUMBER_OF_KEYS " + str(len(data["keys"])) + "\n" | ||
| 48 | output_buffer += "#define DEFAULT_PSEUDOLAYER " + data["parameters"]["default_pseudolayer"] + "\n" | ||
| 49 | |||
| 50 | return output_buffer + "\n" | ||
| 51 | |||
| 52 | def add_keycodes(data): | ||
| 53 | output_buffer = "" | ||
| 54 | |||
| 55 | if not len(data["keys"]) == len(set(data["keys"])): | ||
| 56 | raise Exception("The keys must have unique names") | ||
| 57 | |||
| 58 | for key, counter in zip(data["keys"], range(0, len(data["keys"]))): | ||
| 59 | output_buffer += "#define H_" + key + " ((HASH_TYPE) 1 << " + str(counter) + ")\n" | ||
| 60 | output_buffer += "\n" | ||
| 61 | |||
| 62 | output_buffer += "enum internal_keycodes {\n" | ||
| 63 | output_buffer += " " + data["keys"][0] + " = SAFE_RANGE,\n" | ||
| 64 | output_buffer += " " + reduce(comma_separator, [key for key in data["keys"][1:]]) + ",\n" | ||
| 65 | output_buffer += " FIRST_INTERNAL_KEYCODE = " + data["keys"][0] + ",\n" | ||
| 66 | output_buffer += " LAST_INTERNAL_KEYCODE = " + data["keys"][-1] + "\n" | ||
| 67 | output_buffer += "};\n" | ||
| 68 | |||
| 69 | return output_buffer + "\n" | ||
| 70 | |||
| 71 | def add_pseudolayers(data): | ||
| 72 | output_buffer = "" | ||
| 73 | |||
| 74 | if len(data["pseudolayers"]) == 0: | ||
| 75 | raise Exception("You didn't define any pseudolayers") | ||
| 76 | |||
| 77 | if not len([pseudolayer["name"] for pseudolayer in data["pseudolayers"]]) == len(set([pseudolayer["name"] for pseudolayer in data["pseudolayers"]])): | ||
| 78 | raise Exception("The pseudolayers must have unique names") | ||
| 79 | |||
| 80 | pseudolayers = data["pseudolayers"] | ||
| 81 | if not "ALWAYS_ON" in [layer["name"] for layer in pseudolayers]: | ||
| 82 | pseudolayers += [{"name": "ALWAYS_ON", "chords": []}] # the engine expects ALWAYS_ON to exist | ||
| 83 | |||
| 84 | output_buffer += "enum pseudolayers {\n" | ||
| 85 | output_buffer += " " + reduce(comma_separator, [layer["name"] for layer in pseudolayers]) + "\n" | ||
| 86 | output_buffer += "};\n" | ||
| 87 | |||
| 88 | return output_buffer + "\n" | ||
| 89 | |||
| 90 | def add_layers(data): | ||
| 91 | output_buffer = "" | ||
| 92 | |||
| 93 | output_buffer += "const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {\n" | ||
| 94 | for layer, counter in zip(data["layers"], range(0,len(data["layers"]))): | ||
| 95 | if layer["type"] == "auto": | ||
| 96 | output_buffer += " [" + str(counter) + "] = " + data["parameters"]["layout_function_name"] + "(" + reduce(comma_separator, [key for key in data["keys"]]) + "),\n" | ||
| 97 | else: | ||
| 98 | output_buffer += " [" + str(counter) + "] = " + data["parameters"]["layout_function_name"] + "(" + reduce(comma_separator, [key for key in layer["keycodes"]]) + "),\n" | ||
| 99 | output_buffer += "};\n" | ||
| 100 | output_buffer += "size_t keymapsCount = " + str(len(data["layers"])) + ";\n" | ||
| 101 | |||
| 102 | return output_buffer + "\n" | ||
| 103 | |||
| 104 | def prep_buffers(data): | ||
| 105 | output_buffer = "" | ||
| 106 | |||
| 107 | output_buffer += "uint8_t keycodes_buffer_array[] = {\n" | ||
| 108 | output_buffer += " " + reduce(comma_separator, ["0"] * len(data["keys"])) + "\n" | ||
| 109 | output_buffer += "};\n" | ||
| 110 | output_buffer += "\n" | ||
| 111 | |||
| 112 | output_buffer += "uint8_t command_buffer[] = {\n" | ||
| 113 | output_buffer += " " + reduce(comma_separator, ["0"] * data["parameters"]["command_max_length"]) + "\n" | ||
| 114 | output_buffer += "};\n" | ||
| 115 | output_buffer += "\n" | ||
| 116 | |||
| 117 | output_buffer += "uint16_t leader_buffer[] = {\n" | ||
| 118 | output_buffer += " " + reduce(comma_separator, ["0"] * data["parameters"]["leader_max_length"]) + "\n" | ||
| 119 | output_buffer += "};\n" | ||
| 120 | output_buffer += "\n" | ||
| 121 | |||
| 122 | output_buffer += "uint8_t dynamic_macro_buffer[] = {\n" | ||
| 123 | output_buffer += " " + reduce(comma_separator, ["0"] * data["parameters"]["dynamic_macro_max_length"]) + "\n" | ||
| 124 | output_buffer += "};" | ||
| 125 | |||
| 126 | return output_buffer + "\n" | ||
| 127 | |||
| 128 | def parse_keyboard_specifics(data): | ||
| 129 | keyboard_part_0 = add_includes(data) | ||
| 130 | keyboard_part_0 += add_keycodes(data) | ||
| 131 | keyboard_part_0 += add_pseudolayers(data) | ||
| 132 | keyboard_part_0 += add_parameters(data) | ||
| 133 | keyboard_part_0 += add_layers(data) | ||
| 134 | keyboard_part_0 += prep_buffers(data) | ||
| 135 | |||
| 136 | return keyboard_part_0 + '\n' | ||
| 137 | |||
| 138 | def parse_chords(data): | ||
| 139 | keyboard_part_2 = "" | ||
| 140 | strings = [] | ||
| 141 | number_of_strings = 0 | ||
| 142 | number_of_chords = 0 | ||
| 143 | |||
| 144 | for pseudolayer in data["pseudolayers"]: | ||
| 145 | name = pseudolayer["name"] | ||
| 146 | for chord in pseudolayer["chords"]: | ||
| 147 | if chord["type"] == "chord_set": | ||
| 148 | keycodes = reduce(comma_separator, [word for word in chord["keycodes"]]) | ||
| 149 | [keyboard_part_2, number_of_chords, number_of_strings, strings] = add_chord_set(name, keycodes, chord["set"], data, keyboard_part_2, number_of_chords, number_of_strings, strings) | ||
| 150 | if chord["type"] == "visual_array": | ||
| 151 | [keyboard_part_2, number_of_chords, number_of_strings, strings] = add_dictionary(name, chord["keys"], chord["dictionary"], keyboard_part_2, number_of_chords, number_of_strings, strings) | ||
| 152 | if chord["type"] == "visual": | ||
| 153 | keycodes = reduce(comma_separator, [word for word in chord["chord"]]) | ||
| 154 | [keyboard_part_2, number_of_chords, number_of_strings, strings] = secret_chord(name, chord["keycode"], keycodes, data, keyboard_part_2, number_of_chords, number_of_strings, strings) | ||
| 155 | elif chord["type"] == "simple": | ||
| 156 | keycodes = reduce(string_sum, ["H_" + word for word in chord["chord"]]) | ||
| 157 | [keyboard_part_2, number_of_chords, number_of_strings, strings] = add_key(name, keycodes, chord["keycode"], keyboard_part_2, number_of_chords, number_of_strings, strings) | ||
| 158 | keyboard_part_2 += "\n" | ||
| 159 | |||
| 160 | keyboard_part_2 += "const struct Chord* const list_of_chords[] PROGMEM = {\n" | ||
| 161 | keyboard_part_2 += " " + reduce(comma_separator, ["&chord_" + str(i) for i in range(0, number_of_chords)]) + "\n" | ||
| 162 | keyboard_part_2 += "};\n" | ||
| 163 | keyboard_part_2 += "\n" | ||
| 164 | |||
| 165 | if len(data["leader_sequences"]) > 0: | ||
| 166 | keyboard_part_2 += reduce(newline_separator, [sequence["function"] for sequence in data["leader_sequences"]]) + "\n\n" | ||
| 167 | keyboard_part_2 += "const uint16_t leader_triggers[][LEADER_MAX_LENGTH] PROGMEM = {\n" | ||
| 168 | for sequence in data["leader_sequences"]: | ||
| 169 | keyboard_part_2 += " {" + reduce(comma_separator, sequence["sequence"] + ["0"] * (data["parameters"]["leader_max_length"] - len(sequence["sequence"]))) + "},\n" | ||
| 170 | keyboard_part_2 += "};\n\n" | ||
| 171 | keyboard_part_2 += "void (*leader_functions[]) (void) = {\n" | ||
| 172 | keyboard_part_2 += " " + reduce(comma_separator, ["&" + sequence["name"] for sequence in data["leader_sequences"]]) + "\n" | ||
| 173 | keyboard_part_2 += "};\n" | ||
| 174 | else: | ||
| 175 | keyboard_part_2 += "const uint16_t** const leader_triggers PROGMEM = NULL;\n" | ||
| 176 | keyboard_part_2 += "void (*leader_functions[]) (void) = {};\n" | ||
| 177 | keyboard_part_2 += "\n" | ||
| 178 | |||
| 179 | keyboard_part_2 += "#define NUMBER_OF_CHORDS " + str(number_of_chords) + "\n" | ||
| 180 | keyboard_part_2 += "#define NUMBER_OF_LEADER_COMBOS " + str(len(data["leader_sequences"])) | ||
| 181 | |||
| 182 | return keyboard_part_2 + "\n\n" | ||
| 183 | |||
| 184 | def parse_strings_for_chords(data): | ||
| 185 | keyboard_part_1 = "" | ||
| 186 | |||
| 187 | for string, i in zip(strings, range(0, len(strings))): | ||
| 188 | keyboard_part_1 += "const char string_" + str(i) + " [] PROGMEM = \"" + string + "\";\n" | ||
| 189 | |||
| 190 | keyboard_part_1 += "\n" | ||
| 191 | keyboard_part_1 += "const char * const strings[] PROGMEM = {\n" | ||
| 192 | if len(strings) > 0: | ||
| 193 | keyboard_part_1 += " " + reduce(comma_separator, ["string_" + str(i) for i in range(0, len(strings))]) | ||
| 194 | keyboard_part_1 += "\n};\n" | ||
| 195 | |||
| 196 | return keyboard_part_1 | ||
| 197 | |||
| 198 | def main(): | ||
| 199 | if len(sys.argv) != 3: | ||
| 200 | raise Exception("Wrong number of arguments.\n\nUsage: python parser.py keymap.json keymap.c") | ||
| 201 | |||
| 202 | input_filepath = sys.argv[1] | ||
| 203 | output_filepath = sys.argv[2] | ||
| 204 | |||
| 205 | with open(input_filepath, "r") as read_file: | ||
| 206 | data = json.load(read_file) | ||
| 207 | |||
| 208 | keyboard_part_0 = parse_keyboard_specifics(data) | ||
| 209 | keyboard_part_1 = parse_strings_for_chords(data) | ||
| 210 | keyboard_part_2 = parse_chords(data) | ||
| 211 | |||
| 212 | engine_part_1 = open("engine.part.1", "r").read() | ||
| 213 | engine_part_2 = open("engine.part.2", "r").read() + "\n" | ||
| 214 | engine_part_3 = open("engine.part.3", "r").read() | ||
| 215 | |||
| 216 | output_buffer = keyboard_part_0 | ||
| 217 | output_buffer += engine_part_1 | ||
| 218 | |||
| 219 | if len(data["extra_code"]) > 0: | ||
| 220 | output_buffer += data["extra_code"] + "\n" | ||
| 221 | |||
| 222 | output_buffer += keyboard_part_1 | ||
| 223 | output_buffer += engine_part_2 | ||
| 224 | output_buffer += keyboard_part_2 | ||
| 225 | output_buffer += engine_part_3 | ||
| 226 | |||
| 227 | with open(output_filepath, "w") as write_file: | ||
| 228 | write_file.write(output_buffer) | ||
| 229 | |||
| 230 | if __name__ == "__main__": | ||
| 231 | main() \ No newline at end of file | ||
