├── .gitignore ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── build.yml ├── man ├── CMakeLists.txt ├── hexxed.1.scd └── hexxed-tutorial.7.scd ├── README.md ├── panes.h ├── LICENSE ├── CMakeLists.txt ├── render.h ├── buffer.h ├── calculator_test.c ├── main.c ├── buffer.c ├── calculator.lemon ├── render.c ├── panes.c └── lempar.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.1 2 | *.7 3 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Checklist** 4 | 5 | - [ ] Closing issues: 6 | - [ ] Tests have been added or updated if necessary 7 | - [ ] Documentation has been updated 8 | 9 | **Description** 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Hexxed 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Environment set-up 16 | run: | 17 | sudo apt-get update && \ 18 | sudo apt-get install -y build-essential cmake ninja-build libncursesw5-dev zlib1g-dev 19 | - name: Build 20 | run: | 21 | mkdir -p build && \ 22 | cd build && \ 23 | cmake -G Ninja .. && \ 24 | ninja 25 | - name: Test 26 | run: | 27 | cd build && \ 28 | ninja test 29 | -------------------------------------------------------------------------------- /man/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(MAN_PAGES hexxed.1 hexxed-tutorial.7) 2 | set(MAN_OUTPUT) 3 | 4 | foreach(PAGE IN LISTS MAN_PAGES) 5 | set(OUTPUT ${CMAKE_BINARY_DIR}/${PAGE}) 6 | set(SOURCE ${CMAKE_SOURCE_DIR}/man/${PAGE}.scd) 7 | 8 | add_custom_command(OUTPUT ${OUTPUT} 9 | COMMAND "scdoc" "<" ${SOURCE} ">" ${OUTPUT} 10 | DEPENDS ${SOURCE} 11 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 12 | VERBATIM 13 | ) 14 | 15 | list(APPEND MAN_OUTPUT ${OUTPUT}) 16 | 17 | get_filename_component(MAN_EXTENSION ${PAGE} EXT) 18 | string(SUBSTRING ${MAN_EXTENSION} 1 1 MAN_SECTION) 19 | install(FILES ${OUTPUT} DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man${MAN_SECTION}) 20 | endforeach() 21 | 22 | add_custom_target(man ALL DEPENDS ${MAN_OUTPUT}) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hexxed 2 | 3 | A portable, public-domain hex editor released under the Unlicense, or the public domain otherwise. 4 | 5 | ![Hexxed with an open calculator dialog](https://i.imgur.com/66jwWmv.png) 6 | 7 | ## Features 8 | 9 | * Multi-pane: Text and Hex pane (Code planned) 10 | * Integrated 64-bit calculator 11 | * Goto/Search 12 | * Comments 13 | * Bookmarks 14 | * Highlighting and selection 15 | 16 | ## Building 17 | 18 | ### Debian-based Linux 19 | 20 | ``` 21 | apt install g++ cmake pkg-config libglib2.0-dev libncurses-dev 22 | mkdir build && cd build 23 | cmake .. && make 24 | ``` 25 | ### Fedora 26 | 27 | ``` 28 | dnf install gcc-c++ cmake glib2-devel ncurses-devel 29 | mkdir build && cd build 30 | cmake .. && make 31 | ``` 32 | 33 | ## Usage 34 | 35 | ``` 36 | ./hexxed [path] 37 | ``` 38 | 39 | See *hexxed*(1) and *hexxed-tutorial*(7) for documentation and tutorials. 40 | -------------------------------------------------------------------------------- /panes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "render.h" 4 | #include "buffer.h" 5 | 6 | // Pointer into a buffer_t, used to maintain buffer cursor positioning. 7 | // MAY NOT point outside of a buffer. 8 | // 9 | typedef uintptr_t cursor_t; 10 | 11 | typedef enum { 12 | PANE_HEX, 13 | PANE_TEXT, 14 | } pane_type_t; 15 | 16 | typedef struct { 17 | pane_type_t type; 18 | void (*driver)(void *user_data, int input); 19 | void (*unpost)(void *user_data); 20 | void (*scroll)(void *user_data, uint64_t offset); 21 | const options_t *options; 22 | void *user_data; 23 | } pane_t; 24 | 25 | void pane_drive(pane_t *pane, int input); 26 | void pane_unpost(pane_t *pane); 27 | // Scroll to a PHYSICAL offset. 28 | // 29 | void pane_scroll(pane_t *pane, uint64_t offset); 30 | 31 | extern const options_t HEX_OPT; 32 | pane_t *hex_post(buffer_t *buffer, int width, int height); 33 | 34 | extern const options_t TEXT_OPT; 35 | pane_t *text_post(buffer_t *buffer, int width, int height); 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | enable_testing() 3 | 4 | option(ENABLE_ASAN "Enable ASan" OFF) 5 | 6 | if(ENABLE_ASAN) 7 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -g") 8 | endif() 9 | 10 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-unused-variable -Wno-initializer-overrides") 11 | 12 | find_package(PkgConfig REQUIRED) 13 | 14 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 15 | pkg_check_modules(NCURSES REQUIRED IMPORTED_TARGET ncursesw) 16 | pkg_check_modules(FORM REQUIRED IMPORTED_TARGET formw) 17 | pkg_check_modules(MENU REQUIRED IMPORTED_TARGET menuw) 18 | 19 | find_program(SCDOC scdoc) 20 | 21 | add_executable(lemon lemon.c) 22 | 23 | add_custom_command( 24 | OUTPUT calculator.c calculator.h 25 | COMMAND lemon "-d${CMAKE_CURRENT_BINARY_DIR}" 26 | "-T${CMAKE_SOURCE_DIR}/lempar.c" 27 | "${CMAKE_SOURCE_DIR}/calculator.lemon" 28 | ) 29 | 30 | add_executable(hexxed calculator.c main.c buffer.c buffer.h panes.c panes.h render.c render.h) 31 | target_include_directories(hexxed PUBLIC ${CMAKE_SOURCE_DIR}) 32 | target_link_libraries(hexxed PkgConfig::NCURSES PkgConfig::FORM PkgConfig::MENU PkgConfig::GLIB) 33 | install(TARGETS hexxed DESTINATION bin) 34 | 35 | add_executable(calculator_test calculator_test.c calculator.c buffer.c) 36 | target_include_directories(calculator_test PUBLIC ${CMAKE_SOURCE_DIR}) 37 | target_link_libraries(calculator_test PkgConfig::GLIB) 38 | add_test(calculator calculator_test) 39 | 40 | if(SCDOC) 41 | add_subdirectory(man) 42 | endif() 43 | -------------------------------------------------------------------------------- /render.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef _XOPEN_SOURCE_EXTENDED 4 | #define _XOPEN_SOURCE_EXTENDED 5 | #endif 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "buffer.h" 12 | 13 | // Thanks, ncurses. 14 | // 15 | #ifdef scroll 16 | #undef scroll 17 | #endif 18 | 19 | // Options to be rendered while the dialog is active. The position indicates 20 | // the corresponding input: KEY_F(N). 21 | // 22 | typedef char options_t[10][7]; 23 | 24 | static const options_t EMPTY_OPT = { 25 | [0 ... 9] = " " 26 | }; 27 | 28 | static const wchar_t *FILL_CHAR = L"▒"; 29 | 30 | enum { 31 | COLOR_STATUS = 1, 32 | COLOR_SELECTED, 33 | COLOR_STANDARD, 34 | COLOR_OPTION_NAME, 35 | COLOR_OPTION_KEY, 36 | HIGHLIGHT_BLUE, 37 | HIGHLIGHT_WHITE, 38 | COLOR_BRIGHT_WHITE 39 | }; 40 | 41 | void render_status(const char *path); 42 | void render_options(const options_t *options); 43 | void render_border(WINDOW *window); 44 | 45 | // Prompts the user for a message setting user_input which must be free'd. 46 | // user_input is set to NULL if the prompt is cancelled with ESC. 47 | // The state of the screen is UNDEFINED after this function returns. 48 | // 49 | size_t prompt_input(const char *title, const char *placeholder, char **user_input); 50 | // Prompts the user for a menu item returning the index into options. 51 | // The result is -1 if the prompt is cancelled with ESC. 52 | // The state of the screen is UNDEFINED after this function returns. 53 | int prompt_menu(const char *title, const char **options, size_t options_size, int width, int start_item); 54 | // Displays a non-fatal error to the user. The state of the screen is UNDEFINED 55 | // after this function returns. 56 | // 57 | void prompt_error(const char *message); 58 | // The state of the screen is UNDEFINED after this function returns. 59 | // 60 | void prompt_calculator(buffer_t *buffer); 61 | -------------------------------------------------------------------------------- /buffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #define BOOKMARK_STACK_SIZE 8 8 | 9 | typedef uintptr_t cursor_t; 10 | 11 | typedef struct { 12 | size_t size; 13 | uint8_t *data; 14 | int f; 15 | const char *path; 16 | // If a mark is set, both marks != -1. Otherwise, the end mark is set to the cursor 17 | // position. 18 | // 19 | cursor_t start_mark; 20 | cursor_t end_mark; 21 | cursor_t cursor; 22 | 23 | GHashTable *comments; 24 | GSList *highlights; 25 | uintptr_t bookmarks[BOOKMARK_STACK_SIZE]; 26 | int bookmarks_head; 27 | int editable; 28 | } buffer_t; 29 | 30 | typedef struct { 31 | uintptr_t address; 32 | uint32_t size; 33 | uint32_t color; 34 | } range_t; 35 | 36 | void buffer_from_data(buffer_t *buffer, const uint8_t *data, size_t size); 37 | int buffer_open(buffer_t *buffer, const char *path); 38 | int buffer_close(buffer_t *buffer); 39 | // Attempt to reopen the current buffer as read-write, if it fails, the current 40 | // buffer is left intact. If successful, all pointers into the previous map are 41 | // invalidated. 42 | // 43 | int buffer_try_reopen(buffer_t *buffer); 44 | 45 | int buffer_read(buffer_t *buffer, void *data, size_t size); 46 | int buffer_read_u8(buffer_t *buffer, uint8_t *data); 47 | int buffer_read_i8(buffer_t *buffer, int8_t *data); 48 | int buffer_read_lu16(buffer_t *buffer, uint16_t *data); 49 | int buffer_read_bu16(buffer_t *buffer, uint16_t *data); 50 | int buffer_read_lu32(buffer_t *buffer, uint32_t *data); 51 | int buffer_read_bu32(buffer_t *buffer, uint32_t *data); 52 | int buffer_read_lu64(buffer_t *buffer, uint64_t *data); 53 | int buffer_read_bu64(buffer_t *buffer, uint64_t *data); 54 | int buffer_read_li16(buffer_t *buffer, int16_t *data); 55 | int buffer_read_bi16(buffer_t *buffer, int16_t *data); 56 | int buffer_read_li32(buffer_t *buffer, int32_t *data); 57 | int buffer_read_bi32(buffer_t *buffer, int32_t *data); 58 | int buffer_read_li64(buffer_t *buffer, int64_t *data); 59 | int buffer_read_bi64(buffer_t *buffer, int64_t *data); 60 | 61 | // Returns the scroll required to centre the offset in a buffer view, and sets 62 | // the cursor to "offset", or the start/end of the buffer if the offset is out 63 | // of range. 64 | // 65 | uint64_t buffer_scroll(buffer_t *buffer, uint64_t offset, int width, int height); 66 | 67 | void buffer_add_comment(buffer_t *buffer, uintptr_t address, char *message); 68 | void buffer_remove_comment(buffer_t *buffer, uintptr_t address); 69 | const char *buffer_lookup_comment(buffer_t *buffer, uintptr_t address); 70 | 71 | // If size is 0, remove the highlight. 72 | // 73 | void buffer_highlight_range(buffer_t *buffer, uintptr_t address, uint32_t size, uint32_t color); 74 | 75 | void buffer_bookmark_push(buffer_t *buffer, uintptr_t address); 76 | uint64_t buffer_bookmark_pop(buffer_t *buffer, int width, int height, int *error); 77 | -------------------------------------------------------------------------------- /man/hexxed.1.scd: -------------------------------------------------------------------------------- 1 | hexxed(1) 2 | 3 | # NAME 4 | 5 | hexxed - A portable, public-domain hex editor 6 | 7 | # SYNOPSIS 8 | 9 | _hexxed_ [path] 10 | 11 | For a guided tutorial, use *man hexxed-tutorial* from your terminal. 12 | 13 | # DESCRIPTION 14 | 15 | Hexxed is a mode editor, like vi. It uses panes and dialogs to convey information. 16 | 17 | Panes are views over of buffers, which are views into files on disk. Each 18 | pane has its own set of commands, with additional commands that are global to 19 | every pane. 20 | 21 | Dialogs are simple popups that prompt the user or display a message. Dialogs 22 | are _not_ stacking. 23 | 24 | # OPTIONS 25 | 26 | *path* 27 | Opens the specified file as read-only. Edit mode requires the file to have 28 | writable permissions. 29 | 30 | # GLOBAL COMMANDS 31 | 32 | *F3* 33 | Enter edit mode. The cursor shape will change to a single cell, and navigation 34 | will move to the nearest odd or even hex value under the cursor. Escape exits 35 | edit mode. 36 | 37 | *F5* 38 | Open the Goto dialog. This dialog supports full expression evaluation like 39 | the *Calculator*. Hit enter after entering an expression, or Escape to exit. 40 | 41 | *F9* 42 | List all comments. Select a comment and hit *Enter* to go to it. 43 | 44 | *F10* 45 | Save changes and exit. 46 | 47 | *Enter* 48 | Cycle through current modes. There are two modes in Hexxed, Raw and Hex. 49 | 50 | # LOCAL COMMANDS 51 | 52 | *v* 53 | Enters block-selection mode. Move your cursor to the end of the block and 54 | hit *v* again to select. The block will remain selected as you move around 55 | the pane. Blocks are persisted across buffers. 56 | 57 | *[0-9a-f]* 58 | Inserts hex over existing values, if in edit mode. 59 | 60 | *;* 61 | Inserts a comment at the current position. 62 | 63 | *=* 64 | Opens the calculator. See *Calculator*. 65 | 66 | *+* 67 | Push the cursor position to the bookmark stack. 68 | 69 | *-* 70 | Pop a bookmark from the stack and move to that location. 71 | 72 | # CALCULATOR 73 | 74 | Hexxed can also be used as a 64-bit calculator. The following operators are 75 | supported, and follow the C operator precendence order. 76 | 77 | - && || 78 | - & ^ | 79 | - == != 80 | - > >= < <= 81 | - << >> 82 | - + \- 83 | - \* / % 84 | - ! 85 | - ~ 86 | 87 | The calculator is _hex by default_, but operands base can be changed using 88 | a prefix. The following prefixes are supported: 89 | 90 | - 0n for base 10 91 | - 0o for base 8 92 | - 0x for base 16 (the default if no prefix is specified) 93 | - 0b for base 2 94 | 95 | The buffer can also be accessed from the calculator using the following data 96 | specifiers and a prefix: 97 | 98 | - @ for unsigned data 99 | - # for signed data 100 | 101 | - b for a byte 102 | - s for a 16-bit integer 103 | - i for a 32-bit integer 104 | - l for a 64-bit integer 105 | 106 | Lower-case specifiers read little-endian data, while upper-case specifiers read 107 | big-endian data. For example, *@S* will read an unsigned big-endian short while a 108 | *#l* will read a little-endian signed long. 109 | 110 | # SEE ALSO 111 | 112 | *hexxed-tutorial*(7) 113 | 114 | # AUTHORS 115 | 116 | For more information about Hexxed development, see 117 | https://github.com/meme/hexxed. 118 | -------------------------------------------------------------------------------- /calculator_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "buffer.h" 5 | 6 | const uint8_t TEST_DATA[] = { 7 | 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 8 | }; 9 | 10 | buffer_t g_buffer; 11 | 12 | int calculator_eval(buffer_t *buffer, const char *input, int64_t *result); 13 | 14 | void 15 | calculator_assert(const char *input, int64_t expected) 16 | { 17 | int64_t result; 18 | assert(calculator_eval(&g_buffer, input, &result) == 0); 19 | assert(result == expected); 20 | } 21 | 22 | void 23 | calculator_err(const char *input) 24 | { 25 | int64_t result; 26 | assert(calculator_eval(&g_buffer, input, &result) != 0); 27 | } 28 | 29 | int 30 | main(int argc, char *argv[]) 31 | { 32 | buffer_from_data(&g_buffer, TEST_DATA, sizeof(TEST_DATA)); 33 | 34 | // Number formatting. 35 | // 36 | calculator_assert("101", 257); 37 | calculator_assert("aaa", 2730); 38 | calculator_assert("0x101", 257); 39 | calculator_assert("0xaaa", 2730); 40 | calculator_assert("0n101", 101); 41 | calculator_assert("0b101", 5); 42 | calculator_assert("0101", 65); 43 | 44 | // Basic arithmetic. 45 | // 46 | calculator_assert("1 + 1", 2); 47 | calculator_assert("100 + 100", 512); 48 | calculator_assert("1 + 2 + 3", 6); 49 | 50 | calculator_assert("1 - 1", 0); 51 | calculator_assert("0 - 1", -1); 52 | calculator_assert("1 - 0", 1); 53 | calculator_assert("0 - 0", 0); 54 | calculator_assert("10 - 4 - 7", 5); 55 | 56 | calculator_assert("1 * 1", 1); 57 | calculator_assert("0 * 1", 0); 58 | calculator_assert("1 * 0", 0); 59 | calculator_assert("0 * 0", 0); 60 | calculator_assert("42 * 42", 4356); 61 | calculator_assert("3 * 100 * 100", 196608); 62 | 63 | calculator_assert("1 / 1", 1); 64 | calculator_assert("0 / 1", 0); 65 | calculator_assert("1 / 0", 0); 66 | calculator_assert("0 / 0", 0); 67 | calculator_assert("42 / 100", 0); 68 | calculator_assert("84 / 42", 2); 69 | 70 | calculator_assert("1 % 1", 0); 71 | calculator_assert("0 % 1", 0); 72 | calculator_assert("1 % 0", 0); 73 | calculator_assert("0 % 0", 0); 74 | calculator_assert("2 % 100", 2); 75 | calculator_assert("20 % 2", 0); 76 | 77 | // Bit operators. 78 | // 79 | calculator_assert("20 & 1", 0); 80 | calculator_assert("21 & 1", 1); 81 | 82 | calculator_assert("20 | 1", 33); 83 | calculator_assert("21 | 1", 33); 84 | 85 | calculator_assert("20 ^ 1", 33); 86 | calculator_assert("21 ^ 1", 32); 87 | 88 | calculator_assert("20 << 1", 64); 89 | calculator_assert("20 >> 1", 16); 90 | 91 | calculator_assert("~0", -1); 92 | calculator_assert("~20", -33); 93 | 94 | // Logic operators. 95 | // 96 | calculator_assert("1 || 0", 1); 97 | calculator_assert("1 && 0", 0); 98 | calculator_assert("!0", 1); 99 | calculator_assert("!1", 0); 100 | 101 | // Comparison operators. 102 | // 103 | calculator_assert("20 > 1", 1); 104 | calculator_assert("20 > 21", 0); 105 | calculator_assert("20 > 20", 0); 106 | 107 | calculator_assert("20 >= 1", 1); 108 | calculator_assert("20 >= 21", 0); 109 | calculator_assert("20 >= 20", 1); 110 | 111 | calculator_assert("20 < 1", 0); 112 | calculator_assert("20 < 21", 1); 113 | calculator_assert("20 < 20", 0); 114 | 115 | calculator_assert("20 <= 1", 0); 116 | calculator_assert("20 <= 21", 1); 117 | calculator_assert("20 <= 20", 1); 118 | 119 | calculator_assert("20 == 1", 0); 120 | calculator_assert("20 == 20", 1); 121 | 122 | calculator_assert("20 != 1", 1); 123 | calculator_assert("20 != 20", 0); 124 | 125 | // Expressions. 126 | // 127 | calculator_assert("1 + 2 * 3 / 4", 2); 128 | calculator_assert("3 + 4 % 10", 7); 129 | calculator_assert("~0b00 + 0n10 * 0x20 << 030 != 40 < 50", 1); 130 | calculator_assert("~0b00 + 0n10 * 0x20 << ((030 != 40) < 50)", 638); 131 | calculator_assert("(1) + (2) + (3)", 6); 132 | 133 | // Buffer read. 134 | // 135 | calculator_assert("@b", 1); 136 | calculator_assert("@B", 1); 137 | calculator_assert("@s", 8961); 138 | calculator_assert("@S", 291); 139 | calculator_assert("@i", 1732584193); 140 | calculator_assert("@I", 19088743); 141 | calculator_assert("@l", 17279655951921914625UL); 142 | calculator_assert("@L", 81985529216486895); 143 | 144 | calculator_assert("#b", 1); 145 | calculator_assert("#B", 1); 146 | calculator_assert("#s", 8961); 147 | calculator_assert("#S", 291); 148 | calculator_assert("#i", 1732584193); 149 | calculator_assert("#I", 19088743); 150 | calculator_assert("#l", -1167088121787636991); 151 | calculator_assert("#L", 81985529216486895); 152 | 153 | // Buffer reading off end of file. 154 | // 155 | g_buffer.cursor = 6; 156 | calculator_err("@l"); 157 | 158 | buffer_close(&g_buffer); 159 | return 0; 160 | } 161 | -------------------------------------------------------------------------------- /man/hexxed-tutorial.7.scd: -------------------------------------------------------------------------------- 1 | hexxed-tutorial(7) 2 | 3 | # NAME 4 | 5 | hexxed - Hexxed tutorial 6 | 7 | # CONTENTS 8 | 9 | . Starting Hexxed 10 | . Navigating 11 | . Selecting Blocks 12 | . Changing Representations 13 | . Editing 14 | . Calculator 15 | . Quit 16 | . Further Reading 17 | 18 | # Step 1: Starting Hexxed 19 | 20 | Launch Hexxed by typing *hexxed* in the terminal and passing a file that is 21 | writeable. Hexxed will open the Hex pane: 22 | 23 | ``` 24 | [example] {UNK+.00000000`00000000} 25 | 00000000: |7f|45 4c 46-02 01 01 00-00 00 00 00 .ELF........ 26 | 0000000c: 02 00 3e 00-01 00 00 00-a0 12 40 00 ..>.......@. 27 | 00000018: 40 00 00 00-00 00 00 00-80 ad 01 00 @........... 28 | 00000024: 00 00 00 00-40 00 38 00-0d 00 40 00 ....@.8...@. 29 | 00000030: 06 00 00 00-04 00 00 00-40 00 00 00 ........@... 30 | 0000003c: 40 00 40 00-00 00 00 00-40 00 40 00 @.@.....@.@. 31 | 00000048: d8 02 00 00-00 00 00 00-d8 02 00 00 ............ 32 | 00000054: 08 00 00 00-00 00 00 00-03 00 00 00 ............ 33 | 00000060: 18 03 00 00-00 00 00 00-18 03 40 00 ..........@. 34 | 0000006c: 18 03 40 00-00 00 00 00-1c 00 00 00 ..@......... 35 | 00000078: 1c 00 00 00-00 00 00 00-01 00 00 00 ............ 36 | 00000084: 01 00 00 00-04 00 00 00-00 00 00 00 ............ 37 | 00000090: 00 00 40 00-00 00 00 00-00 00 40 00 ..@.......@. 38 | 0000009c: e0 0d 00 00-00 00 00 00-e0 0d 00 00 ............ 39 | 000000a8: 00 10 00 00-00 00 00 00-01 00 00 00 ............ 40 | 000000b4: 00 10 00 00-00 00 00 00-00 10 40 00 ..........@. 41 | 000000c0: 00 10 40 00-00 00 00 00-45 0b 01 00 ..@.....E... 42 | 000000cc: 45 0b 01 00-00 00 00 00-00 10 00 00 E........... 43 | 000000d8: 01 00 00 00-04 00 00 00-00 20 01 00 ......... .. 44 | 000000e4: 00 20 41 00-00 00 00 00-00 20 41 00 . A...... A. 45 | 000000f0: b8 3e 00 00-00 00 00 00-b8 3e 00 00 .>.......>.. 46 | 000000fc: 00 10 00 00-00 00 00 00-01 00 00 00 ............ 47 | ``` 48 | 49 | - The *[]* enclosed characters are the *buffer* path name 50 | - The *{}* enclosed characters are the *file type* and *virtual address* of the current buffer 51 | - The *||* enclosed characters are the selected *hex pair* 52 | 53 | The ascending numbers on the right are the offset into the file, which may be 54 | virtual addresses depending on the file type. 55 | 56 | The data on the far right is the ASCII representation of the buffer data, see 57 | *isprint*(3) for more information. 58 | 59 | # Step 2: Navigating 60 | 61 | Navigating in Hexxed is straightforward, use the arrow keys to move around the 62 | buffer and *Page up* and *Page down* to advance by the current terminal height. 63 | The *Home* and *End* keys go to the beginning and end of the buffer, 64 | respectively. Hexxed uses an overscroll of 3 rows. Additionally, *+* and *-* 65 | can be used to push and pop bookmarks on a stack, respectively. A maximum of 8 66 | bookmarks is supported by default. 67 | 68 | # Step 3: Selecting Blocks 69 | 70 | To select a block of *data*, use the *v* key. Hit *Home* or scroll to the start 71 | of the buffer, and hit *v* then move to the end of the line. Then, hit *v* 72 | again and move to the right. The cursor will be placed on the next line. 73 | 74 | ``` 75 | example UNK+.00000000`00000000 76 | 00000000: [7f 45 4c 46-02 01 01 00-00 00 00 00] .ELF........ 77 | 0000000c: {02}00 3e 00-01 00 00 00-a0 12 40 00 ..>.......@. 78 | 00000018: 40 00 00 00-00 00 00 00-80 ad 01 00 @........... 79 | 00000024: 00 00 00 00-40 00 38 00-0d 00 40 00 ....@.8...@. 80 | 00000030: 06 00 00 00-04 00 00 00-40 00 00 00 ........@... 81 | 0000003c: 40 00 40 00-00 00 00 00-40 00 40 00 @.@.....@.@. 82 | 00000048: d8 02 00 00-00 00 00 00-d8 02 00 00 ............ 83 | 00000054: 08 00 00 00-00 00 00 00-03 00 00 00 ............ 84 | 00000060: 18 03 00 00-00 00 00 00-18 03 40 00 ..........@. 85 | 0000006c: 18 03 40 00-00 00 00 00-1c 00 00 00 ..@......... 86 | 00000078: 1c 00 00 00-00 00 00 00-01 00 00 00 ............ 87 | 00000084: 01 00 00 00-04 00 00 00-00 00 00 00 ............ 88 | 00000090: 00 00 40 00-00 00 00 00-00 00 40 00 ..@.......@. 89 | 0000009c: e0 0d 00 00-00 00 00 00-e0 0d 00 00 ............ 90 | 000000a8: 00 10 00 00-00 00 00 00-01 00 00 00 ............ 91 | 000000b4: 00 10 00 00-00 00 00 00-00 10 40 00 ..........@. 92 | 000000c0: 00 10 40 00-00 00 00 00-45 0b 01 00 ..@.....E... 93 | 000000cc: 45 0b 01 00-00 00 00 00-00 10 00 00 E........... 94 | 000000d8: 01 00 00 00-04 00 00 00-00 20 01 00 ......... .. 95 | 000000e4: 00 20 41 00-00 00 00 00-00 20 41 00 . A...... A. 96 | 000000f0: b8 3e 00 00-00 00 00 00-b8 3e 00 00 .>.......>.. 97 | 000000fc: 00 10 00 00-00 00 00 00-01 00 00 00 ............ 98 | ``` 99 | 100 | - The *[]* enclosed characters are the selected *block* 101 | - The *{}* enclosed characters denote the cursor location 102 | 103 | # Step 4: Changing Representations 104 | 105 | In addition to the Hex representation, Hexxed also offers a *Raw* view. Since 106 | the panes share a buffer, they will also share underyling *comments* and blocks. 107 | Hit *Enter* to cycle panes into the *Raw* pane. Here, only printable characters 108 | are displayed on the screen. See *isprint(3)* for more information. Hit *Enter* 109 | to cycle back to the *Hex* view 110 | 111 | # Step 5: Editing 112 | 113 | Hexxed is fundamentally a hex editor, so it is straightforward to perform 114 | edit operations. Hit *F3* to enter edit mode, and the cursor will change to select 115 | a single *nibble* instead of a *pair*. To overwrite the data under the cursor, 116 | enter a character in the range *0-9* or *a-f*. The change will be reflected in the 117 | buffer. Hit *Escape* to exit. 118 | 119 | # Step 6: Calculator 120 | 121 | Hexxed can also be used as a calculator. Hit *=* to open the calculator in any 122 | pane. 123 | 124 | ``` 125 | +----------------------------- Calculator -----------------------------+ 126 | | | 127 | | Sig:0 | 128 | | Uns:0 | 129 | | Bin:0000000000000000000000000000000000000000000000000000000000000000 | 130 | | Hex:00000000`00000000 | 131 | +----------------------------------------------------------------------+ 132 | ``` 133 | 134 | Enter an expression and hit *Enter*. The following representations are listed: 135 | 136 | - *Sig* for a signed decimal representation 137 | - *Uns* for an unsigned decimal representation 138 | - *Bin* for a binary representation 139 | - *Hex* for a hexadecimal representation 140 | 141 | Hit *Escape* to exit the calculator. 142 | 143 | See *hexxed*(1) for a list of all operators and prefixes. 144 | 145 | # Step 7: Quit 146 | 147 | Congratulations, this has concluded the Hexxed tutorial. Hit *F10* to exit 148 | the editor and persist changes to disk. 149 | 150 | # SEE ALSO 151 | 152 | *hexxed*(1) 153 | 154 | # AUTHORS 155 | 156 | For more information about Hexxed development, see 157 | https://github.com/meme/hexxed. 158 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #ifndef _GNU_SOURCE 2 | #define _GNU_SOURCE 3 | #endif 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "buffer.h" 12 | #include "panes.h" 13 | #include "render.h" 14 | 15 | int calculator_eval(buffer_t *buffer, const char *input, int64_t *result); 16 | 17 | static char* 18 | trim(char *str) 19 | { 20 | char *end = NULL; 21 | 22 | // Remove leading spaces. 23 | // 24 | while (*str == ' ') { 25 | str++; 26 | } 27 | 28 | // If the string is ALL spaces. 29 | // 30 | if (*str == 0) { 31 | return str; 32 | } 33 | 34 | end = str + strnlen(str, 128) - 1; 35 | 36 | while(end > str && *end == ' ') { 37 | end--; 38 | } 39 | 40 | *(end + 1) = '\0'; 41 | return str; 42 | } 43 | 44 | static pane_t* 45 | next_pane(pane_type_t type, buffer_t *buffer, int width, int height) 46 | { 47 | if (type == PANE_HEX) { 48 | return text_post(buffer, width, height); 49 | } else if (type == PANE_TEXT) { 50 | return hex_post(buffer, width, height); 51 | } else { 52 | __builtin_unreachable(); 53 | } 54 | } 55 | 56 | static void 57 | driver(int input, int width, int height, pane_t **pane, buffer_t *buffer) 58 | { 59 | switch (input) { 60 | case '=': 61 | render_options(&EMPTY_OPT); 62 | prompt_calculator(buffer); 63 | goto reset; 64 | case ';': { 65 | render_options(&EMPTY_OPT); 66 | 67 | char name[72]; 68 | snprintf(name, sizeof(name), "Comment at offset .%08lx`%08lx", 69 | buffer->cursor & 0xffffffff00000000, buffer->cursor & 0x00000000ffffffff); 70 | 71 | char *user_input = NULL; 72 | { 73 | const char *comment = buffer_lookup_comment(buffer, buffer->cursor); 74 | prompt_input(name, comment, &user_input); 75 | } 76 | 77 | if (user_input != NULL) { 78 | char *comment = trim(user_input); 79 | 80 | if (strlen(comment) == 0) { 81 | buffer_remove_comment(buffer, buffer->cursor); 82 | } else { 83 | buffer_add_comment(buffer, buffer->cursor, comment); 84 | } 85 | 86 | free(user_input); 87 | } 88 | goto reset; 89 | } 90 | case KEY_F(5): { 91 | render_options(&EMPTY_OPT); 92 | 93 | char *user_input = NULL; 94 | prompt_input("Goto", NULL, &user_input); 95 | 96 | int64_t result = -1; 97 | if (user_input != NULL && calculator_eval(buffer, user_input, &result) == 0 && result >= 0) { 98 | pane_scroll(*pane, (uint64_t) result); 99 | } 100 | 101 | free(user_input); 102 | goto reset; 103 | } 104 | case KEY_F(9): { 105 | size_t comments_size = g_hash_table_size(buffer->comments); 106 | if (comments_size == 0) { 107 | prompt_error("No names."); 108 | goto reset; 109 | } 110 | 111 | char **comments_data = malloc(sizeof(char*) * comments_size); 112 | 113 | GHashTableIter i; 114 | gpointer key, value; 115 | g_hash_table_iter_init(&i, buffer->comments); 116 | int n = 0; 117 | while (g_hash_table_iter_next(&i, &key, &value)) { 118 | char *comment; 119 | asprintf(&comment, "%08x %s", (uint32_t) (GPOINTER_TO_SIZE(key) & 0x00000000ffffffff), (char*) value); 120 | comments_data[n++] = comment; 121 | } 122 | 123 | int selected = prompt_menu("Names", (const char**) comments_data, comments_size, 64, 0); 124 | 125 | // Find the address of the selected name. 126 | // 127 | g_hash_table_iter_init(&i, buffer->comments); 128 | n = 0; 129 | while (g_hash_table_iter_next(&i, &key, &value)) { 130 | if (n++ == selected) { 131 | pane_scroll(*pane, GPOINTER_TO_SIZE(key)); 132 | } 133 | } 134 | 135 | for (int j = 0; j < comments_size; j++) { 136 | free(comments_data[j]); 137 | } 138 | 139 | free(comments_data); 140 | goto reset; 141 | } 142 | case '\x0a': 143 | case KEY_ENTER: { 144 | pane_type_t previous = (*pane)->type; 145 | pane_unpost(*pane); 146 | clear(); 147 | render_status(buffer->path); 148 | *pane = next_pane(previous, buffer, width, height); 149 | goto reset; 150 | } 151 | case KEY_F(3): 152 | if (!buffer->editable) { 153 | if (buffer_try_reopen(buffer)) { 154 | prompt_error("The file could not be opened as writable."); 155 | goto reset; 156 | } 157 | } 158 | goto drive; 159 | default: 160 | drive: 161 | render_status(buffer->path); 162 | render_options((*pane)->options); 163 | pane_drive(*pane, input); 164 | } 165 | 166 | return; 167 | reset: 168 | clear(); 169 | goto drive; 170 | } 171 | 172 | inline static void __attribute__ ((noreturn)) 173 | error(const char *message) 174 | { 175 | endwin(); 176 | fprintf(stderr, "error: %s\n", message); 177 | exit(1); 178 | } 179 | 180 | int 181 | main(int argc, char *argv[]) 182 | { 183 | if (argc < 2) { 184 | fprintf(stderr, "error: no path provided\n"); 185 | return 1; 186 | } 187 | 188 | setlocale(LC_ALL, ""); 189 | 190 | initscr(); 191 | cbreak(); 192 | noecho(); 193 | keypad(stdscr, TRUE); 194 | set_escdelay(15); 195 | 196 | // Ensure colours are active. 197 | // 198 | if (!has_colors() || start_color() != OK) { 199 | error("screen does not support colors"); 200 | } 201 | 202 | if (can_change_color() && COLORS >= 16) { 203 | init_color(COLOR_BRIGHT_WHITE, 1000, 1000, 1000); 204 | } 205 | 206 | if (COLORS >= 16) { 207 | init_pair(HIGHLIGHT_WHITE, COLOR_BLACK, COLOR_BRIGHT_WHITE); 208 | } else { 209 | init_pair(HIGHLIGHT_WHITE, COLOR_BLACK, COLOR_WHITE); 210 | } 211 | 212 | init_pair(COLOR_STATUS, COLOR_BLACK, COLOR_WHITE); 213 | init_pair(COLOR_SELECTED, COLOR_BLACK, COLOR_WHITE); 214 | init_pair(COLOR_OPTION_NAME, COLOR_BLACK, COLOR_WHITE); 215 | init_pair(COLOR_OPTION_KEY, COLOR_WHITE, COLOR_BLACK); 216 | init_pair(COLOR_STANDARD, COLOR_WHITE, COLOR_BLACK); 217 | init_pair(HIGHLIGHT_BLUE, COLOR_WHITE, COLOR_BLUE); 218 | 219 | // Exit if the terminal is too small. 220 | // 221 | int height, width; 222 | getmaxyx(stdscr, height, width); 223 | 224 | if (width < 86 || height < 24) { 225 | error("screen must be >=86x24"); 226 | } 227 | 228 | // Make the default cursor invisible. 229 | // 230 | curs_set(0); 231 | clear(); 232 | 233 | buffer_t buffer; 234 | if (buffer_open(&buffer, argv[1]) != 0) { 235 | error("cannot open path"); 236 | } 237 | 238 | // Render the first time to the screen. 239 | // 240 | render_status(buffer.path); 241 | pane_t *hex_pane = hex_post(&buffer, width, height); 242 | render_options(hex_pane->options); 243 | 244 | int input; 245 | pane_t *active_pane = hex_pane; 246 | while ((input = getch()) != KEY_F(10)) { 247 | driver(input, width, height, &active_pane, &buffer); 248 | } 249 | 250 | if (active_pane != NULL) { 251 | pane_unpost(active_pane); 252 | } 253 | 254 | if (buffer_close(&buffer) != 0) { 255 | error("cannot close buffer"); 256 | } 257 | 258 | endwin(); 259 | return 0; 260 | } 261 | -------------------------------------------------------------------------------- /buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | void 10 | buffer_from_data(buffer_t *buffer, const uint8_t *data, size_t size) 11 | { 12 | buffer->data = (uint8_t*) data; 13 | buffer->size = size; 14 | buffer->path = NULL; 15 | buffer->f = -1; 16 | buffer->start_mark = -1; 17 | buffer->end_mark = -1; 18 | buffer->cursor = 0; 19 | buffer->comments = g_hash_table_new_full(NULL, NULL, NULL, g_free); 20 | buffer->highlights = NULL; 21 | buffer->bookmarks_head = -1; 22 | buffer->editable = 0; 23 | } 24 | 25 | int 26 | buffer_open(buffer_t *buffer, const char *path) 27 | { 28 | int f = 0; 29 | struct stat status = {}; 30 | uint8_t *data = NULL; 31 | 32 | f = open(path, O_RDONLY); 33 | if (f < 0) { 34 | perror("open"); 35 | goto error; 36 | } 37 | 38 | buffer->f = f; 39 | 40 | if (fstat(f, &status) < 0) { 41 | perror("fstat"); 42 | goto error; 43 | } 44 | 45 | buffer->size = status.st_size; 46 | 47 | data = mmap(NULL, status.st_size, PROT_READ, MAP_PRIVATE, f, 0); 48 | if (data == MAP_FAILED) { 49 | perror("mmap"); 50 | goto error; 51 | } 52 | 53 | buffer->data = data; 54 | buffer->path = strdup(path); 55 | buffer->start_mark = -1; 56 | buffer->end_mark = -1; 57 | buffer->cursor = 0; 58 | buffer->comments = g_hash_table_new_full(NULL, NULL, NULL, g_free); 59 | buffer->highlights = NULL; 60 | buffer->bookmarks_head = -1; 61 | buffer->editable = 0; 62 | return 0; 63 | error: 64 | if (f > 0) { 65 | close(f); 66 | } 67 | 68 | return 1; 69 | } 70 | 71 | int 72 | buffer_close(buffer_t *buffer) 73 | { 74 | if (buffer->f < 0) { 75 | g_hash_table_unref(buffer->comments); 76 | if (buffer->highlights) { 77 | g_slist_free_full(buffer->highlights, g_free); 78 | } 79 | return 0; 80 | } 81 | 82 | int status = 0; 83 | 84 | if (munmap(buffer->data, buffer->size) != 0) { 85 | perror("munmap"); 86 | status = 1; 87 | } 88 | 89 | if (close(buffer->f) != 0) { 90 | perror("close"); 91 | status = 1; 92 | } 93 | 94 | g_hash_table_unref(buffer->comments); 95 | if (buffer->highlights) { 96 | g_slist_free_full(buffer->highlights, g_free); 97 | } 98 | free((void*) buffer->path); 99 | return status; 100 | } 101 | 102 | int 103 | buffer_try_reopen(buffer_t *buffer) 104 | { 105 | // Not possible to reopen a in-memory buffer. 106 | // 107 | if (buffer->path == NULL) { 108 | return 1; 109 | } 110 | 111 | // See if the file can be opened as writable. 112 | // 113 | int f = open(buffer->path, O_RDWR); 114 | if (f < 0) { 115 | return 1; 116 | } 117 | 118 | struct stat status = {}; 119 | if (fstat(f, &status) < 0) { 120 | close(f); 121 | return 1; 122 | } 123 | 124 | uint8_t *data = mmap(NULL, status.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, f, 0); 125 | if (data == MAP_FAILED) { 126 | close(f); 127 | return 1; 128 | } 129 | 130 | (void) munmap(buffer->data, buffer->size); 131 | (void) close(buffer->f); 132 | 133 | buffer->data = data; 134 | buffer->f = f; 135 | buffer->size = status.st_size; 136 | buffer->editable = 1; 137 | return 0; 138 | } 139 | 140 | int 141 | buffer_read(buffer_t *buffer, void *data, size_t size) 142 | { 143 | if (buffer->cursor + size > buffer->size) { 144 | return 1; 145 | } 146 | 147 | memcpy(data, &buffer->data[buffer->cursor], size); 148 | return 0; 149 | } 150 | 151 | int 152 | buffer_read_u8(buffer_t *buffer, uint8_t *data) 153 | { 154 | return buffer_read(buffer, data, sizeof(*data)); 155 | } 156 | 157 | int 158 | buffer_read_i8(buffer_t *buffer, int8_t *data) 159 | { 160 | return buffer_read(buffer, data, sizeof(*data)); 161 | } 162 | 163 | int 164 | buffer_read_lu16(buffer_t *buffer, uint16_t *data) 165 | { 166 | uint8_t v[sizeof(*data)]; 167 | if (buffer_read(buffer, v, sizeof(v))) { 168 | return 1; 169 | } 170 | 171 | *data = (uint16_t) v[0] | (uint16_t) v[1] << 8; 172 | return 0; 173 | } 174 | 175 | int 176 | buffer_read_bu16(buffer_t *buffer, uint16_t *data) 177 | { 178 | uint8_t v[sizeof(*data)]; 179 | if (buffer_read(buffer, v, sizeof(v))) { 180 | return 1; 181 | } 182 | 183 | *data = (uint16_t) v[1] | (uint16_t) v[0] << 8; 184 | return 0; 185 | } 186 | 187 | int 188 | buffer_read_lu32(buffer_t *buffer, uint32_t *data) 189 | { 190 | uint8_t v[sizeof(*data)]; 191 | if (buffer_read(buffer, v, sizeof(v))) { 192 | return 1; 193 | } 194 | 195 | *data = (uint32_t) v[0] | (uint32_t) v[1] << 8 | (uint32_t) v[2] << 16 | (uint32_t) v[3] << 24; 196 | return 0; 197 | } 198 | 199 | int 200 | buffer_read_bu32(buffer_t *buffer, uint32_t *data) 201 | { 202 | uint8_t v[sizeof(*data)]; 203 | if (buffer_read(buffer, v, sizeof(v))) { 204 | return 1; 205 | } 206 | 207 | *data = (uint32_t) v[3] | (uint32_t) v[2] << 8 | (uint32_t) v[1] << 16 | (uint32_t) v[0] << 24; 208 | return 0; 209 | } 210 | 211 | int 212 | buffer_read_lu64(buffer_t *buffer, uint64_t *data) 213 | { 214 | uint8_t v[sizeof(*data)]; 215 | if (buffer_read(buffer, v, sizeof(v))) { 216 | return 1; 217 | } 218 | 219 | *data = (uint64_t) v[0] | (uint64_t) v[1] << 8 | (uint64_t) v[2] << 16 | (uint64_t) v[3] << 24 | (uint64_t) v[4] << 32 | (uint64_t) v[5] << 40 | (uint64_t) v[6] << 48 | (uint64_t) v[7] << 56; 220 | return 0; 221 | } 222 | 223 | int 224 | buffer_read_bu64(buffer_t *buffer, uint64_t *data) 225 | { 226 | uint8_t v[sizeof(*data)]; 227 | if (buffer_read(buffer, v, sizeof(v))) { 228 | return 1; 229 | } 230 | 231 | *data = (uint64_t) v[7] | (uint64_t) v[6] << 8 | (uint64_t) v[5] << 16 | (uint64_t) v[4] << 24 | (uint64_t) v[3] << 32 | (uint64_t) v[2] << 40 | (uint64_t) v[1] << 48 | (uint64_t) v[0] << 56; 232 | return 0; 233 | } 234 | 235 | int 236 | buffer_read_li16(buffer_t *buffer, int16_t *data) 237 | { 238 | return buffer_read_lu16(buffer, (uint16_t*) data); 239 | } 240 | 241 | int 242 | buffer_read_bi16(buffer_t *buffer, int16_t *data) 243 | { 244 | return buffer_read_bu16(buffer, (uint16_t*) data); 245 | } 246 | 247 | int 248 | buffer_read_li32(buffer_t *buffer, int32_t *data) 249 | { 250 | return buffer_read_lu32(buffer, (uint32_t*) data); 251 | } 252 | 253 | int 254 | buffer_read_bi32(buffer_t *buffer, int32_t *data) 255 | { 256 | return buffer_read_bu32(buffer, (uint32_t*) data); 257 | } 258 | 259 | int 260 | buffer_read_li64(buffer_t *buffer, int64_t *data) 261 | { 262 | return buffer_read_lu64(buffer, (uint64_t*) data); 263 | } 264 | 265 | int 266 | buffer_read_bi64(buffer_t *buffer, int64_t *data) 267 | { 268 | return buffer_read_bu64(buffer, (uint64_t*) data); 269 | } 270 | 271 | uint64_t 272 | buffer_scroll(buffer_t *buffer, uint64_t offset, int width, int height) 273 | { 274 | uint64_t rows = height - 2; 275 | uint64_t middle = rows / 2; 276 | 277 | uint64_t start = offset - (offset % width); 278 | int64_t scroll = start / width - middle; 279 | 280 | if (scroll < 0) { 281 | scroll = 0; 282 | } 283 | 284 | if (scroll > buffer->size / width - height + 3) { 285 | scroll = buffer->size / width - height + 3; 286 | } 287 | 288 | if (offset > buffer->size - 1) { 289 | offset = buffer->size - 1; 290 | } 291 | 292 | buffer->cursor = offset; 293 | return scroll; 294 | } 295 | 296 | void 297 | buffer_add_comment(buffer_t *buffer, uintptr_t address, char *message) 298 | { 299 | g_hash_table_insert(buffer->comments, GSIZE_TO_POINTER(address), g_strdup(message)); 300 | } 301 | 302 | void 303 | buffer_remove_comment(buffer_t *buffer, uintptr_t address) 304 | { 305 | g_hash_table_remove(buffer->comments, GSIZE_TO_POINTER(address)); 306 | } 307 | 308 | const char* 309 | buffer_lookup_comment(buffer_t *buffer, uintptr_t address) 310 | { 311 | return g_hash_table_lookup(buffer->comments, GSIZE_TO_POINTER(address)); 312 | } 313 | 314 | void 315 | buffer_highlight_range(buffer_t *buffer, uintptr_t address, uint32_t size, uint32_t color) 316 | { 317 | range_t *range = g_malloc(sizeof(range_t)); 318 | range->address = address; 319 | range->size = size; 320 | range->color = color; 321 | buffer->highlights = g_slist_append(buffer->highlights, range); 322 | } 323 | 324 | void 325 | buffer_bookmark_push(buffer_t *buffer, uintptr_t address) 326 | { 327 | if (buffer->bookmarks_head < BOOKMARK_STACK_SIZE - 1) { 328 | buffer->bookmarks[++buffer->bookmarks_head] = address; 329 | } 330 | } 331 | 332 | uint64_t 333 | buffer_bookmark_pop(buffer_t *buffer, int width, int height, int *error) 334 | { 335 | if (buffer->bookmarks_head >= 0) { 336 | uintptr_t address = buffer->bookmarks[buffer->bookmarks_head--]; 337 | *error = 0; 338 | return buffer_scroll(buffer, address, width, height); 339 | } 340 | 341 | *error = 1; 342 | return 0; 343 | } 344 | -------------------------------------------------------------------------------- /calculator.lemon: -------------------------------------------------------------------------------- 1 | %include { 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "buffer.h" 9 | 10 | #undef NDEBUG 11 | 12 | typedef struct { 13 | int64_t result; 14 | int error; 15 | } calculator_t; 16 | } 17 | 18 | %code { 19 | 20 | int 21 | calculator_eval(buffer_t *buffer, const char *input, int64_t* result) 22 | { 23 | calculator_t state = {}; 24 | 25 | void* parser = (void*) ParseAlloc(malloc); 26 | 27 | for (char i; (i = *input) != '\0'; input++) { 28 | switch (i) { 29 | case '0': { 30 | char *end; 31 | int64_t n; 32 | switch (*++input) { 33 | default: 34 | n = strtoull(input, &end, 8); 35 | if (input == end) { 36 | return 1; 37 | } 38 | break; 39 | case 'n': 40 | n = strtoull(input + 1, &end, 10); 41 | if (input == end) { 42 | return 1; 43 | } 44 | break; 45 | case 'b': 46 | n = strtoull(++input, &end, 2); 47 | if (input == end) { 48 | return 1; 49 | } 50 | break; 51 | case 'x': 52 | n = strtoull(input + 1, &end, 16); 53 | if (input == end) { 54 | return 1; 55 | } 56 | break; 57 | case '\0': 58 | case ' ': 59 | // A lone 0. 60 | // 61 | input--; 62 | Parse(parser, INTEGER, 0, &state); 63 | continue; 64 | } 65 | 66 | Parse(parser, INTEGER, n, &state); 67 | 68 | // Advance the stream. 69 | // 70 | input = end - 1; 71 | } break; 72 | case 'a'...'f': 73 | case '1'...'9': { 74 | char* end; 75 | int64_t n = strtoull(input, &end, 16); 76 | 77 | Parse(parser, INTEGER, n, &state); 78 | 79 | // Advance the stream. 80 | // 81 | input = end - 1; 82 | } break; 83 | case '+': 84 | Parse(parser, PLUS, 0, &state); 85 | break; 86 | case '-': 87 | Parse(parser, MINUS, 0, &state); 88 | break; 89 | case '*': 90 | Parse(parser, TIMES, 0, &state); 91 | break; 92 | case '/': 93 | Parse(parser, DIVIDE, 0, &state); 94 | break; 95 | case '%': 96 | Parse(parser, REMAINDER, 0, &state); 97 | break; 98 | case '<': 99 | if (*(input + 1) == '<') { 100 | input++; 101 | Parse(parser, LEFT_SHIFT, 0, &state); 102 | } else if (*(input + 1) == '=') { 103 | input++; 104 | Parse(parser, LT_EQ, 0, &state); 105 | } else { 106 | Parse(parser, LT, 0, &state); 107 | } 108 | break; 109 | case '>': 110 | if (*(input + 1) == '>') { 111 | input++; 112 | Parse(parser, RIGHT_SHIFT, 0, &state); 113 | } else if (*(input + 1) == '=') { 114 | input++; 115 | Parse(parser, GT_EQ, 0, &state); 116 | } else { 117 | Parse(parser, GT, 0, &state); 118 | } 119 | break; 120 | case '=': 121 | if (*(input + 1) == '=') { 122 | input++; 123 | Parse(parser, EQEQ, 0, &state); 124 | } else { 125 | return 1; 126 | } 127 | break; 128 | case '!': 129 | if (*(input + 1) == '=') { 130 | input++; 131 | Parse(parser, NOT_EQEQ, 0, &state); 132 | } else { 133 | Parse(parser, NOT, 0, &state); 134 | } 135 | break; 136 | case '&': 137 | if (*(input + 1) == '&') { 138 | input++; 139 | Parse(parser, LOGICAL_AND, 0, &state); 140 | } else { 141 | Parse(parser, BIT_AND, 0, &state); 142 | } 143 | break; 144 | case '|': 145 | if (*(input + 1) == '|') { 146 | input++; 147 | Parse(parser, LOGICAL_OR, 0, &state); 148 | } else { 149 | Parse(parser, BIT_OR, 0, &state); 150 | } 151 | break; 152 | case '^': 153 | Parse(parser, BIT_XOR, 0, &state); 154 | break; 155 | case '~': 156 | Parse(parser, BIT_NOT, 0, &state); 157 | break; 158 | case '(': 159 | Parse(parser, LPAR, 0, &state); 160 | break; 161 | case ')': 162 | Parse(parser, RPAR, 0, &state); 163 | break; 164 | case '@': 165 | switch (*++input) { 166 | case 'b': { 167 | uint8_t data; 168 | if (buffer_read_u8(buffer, &data)) { 169 | return 1; 170 | } 171 | 172 | Parse(parser, INTEGER, data, &state); 173 | } break; 174 | case 'B': { 175 | uint8_t data; 176 | if (buffer_read_u8(buffer, &data)) { 177 | return 1; 178 | } 179 | 180 | Parse(parser, INTEGER, data, &state); 181 | } break; 182 | case 's': { 183 | uint16_t data; 184 | if (buffer_read_lu16(buffer, &data)) { 185 | return 1; 186 | } 187 | 188 | Parse(parser, INTEGER, data, &state); 189 | } break; 190 | case 'S': { 191 | uint16_t data; 192 | if (buffer_read_bu16(buffer, &data)) { 193 | return 1; 194 | } 195 | 196 | Parse(parser, INTEGER, data, &state); 197 | } break; 198 | case 'i': { 199 | uint32_t data; 200 | if (buffer_read_lu32(buffer, &data)) { 201 | return 1; 202 | } 203 | 204 | Parse(parser, INTEGER, data, &state); 205 | } break; 206 | case 'I': { 207 | uint32_t data; 208 | if (buffer_read_bu32(buffer, &data)) { 209 | return 1; 210 | } 211 | 212 | Parse(parser, INTEGER, data, &state); 213 | } break; 214 | case 'l': { 215 | uint64_t data; 216 | if (buffer_read_lu64(buffer, &data)) { 217 | return 1; 218 | } 219 | 220 | Parse(parser, INTEGER, data, &state); 221 | } break; 222 | case 'L': { 223 | uint64_t data; 224 | if (buffer_read_bu64(buffer, &data)) { 225 | return 1; 226 | } 227 | 228 | Parse(parser, INTEGER, data, &state); 229 | } break; 230 | default: 231 | return 1; 232 | } 233 | break; 234 | case '#': 235 | switch (*++input) { 236 | case 'b': { 237 | int8_t data; 238 | if (buffer_read_i8(buffer, &data)) { 239 | return 1; 240 | } 241 | 242 | Parse(parser, INTEGER, data, &state); 243 | } break; 244 | case 'B': { 245 | int8_t data; 246 | if (buffer_read_i8(buffer, &data)) { 247 | return 1; 248 | } 249 | 250 | Parse(parser, INTEGER, data, &state); 251 | } break; 252 | case 's': { 253 | int16_t data; 254 | if (buffer_read_li16(buffer, &data)) { 255 | return 1; 256 | } 257 | 258 | Parse(parser, INTEGER, data, &state); 259 | } break; 260 | case 'S': { 261 | int16_t data; 262 | if (buffer_read_bi16(buffer, &data)) { 263 | return 1; 264 | } 265 | 266 | Parse(parser, INTEGER, data, &state); 267 | } break; 268 | case 'i': { 269 | int32_t data; 270 | if (buffer_read_li32(buffer, &data)) { 271 | return 1; 272 | } 273 | 274 | Parse(parser, INTEGER, data, &state); 275 | } break; 276 | case 'I': { 277 | int32_t data; 278 | if (buffer_read_bi32(buffer, &data)) { 279 | return 1; 280 | } 281 | 282 | Parse(parser, INTEGER, data, &state); 283 | } break; 284 | case 'l': { 285 | int64_t data; 286 | if (buffer_read_li64(buffer, &data)) { 287 | return 1; 288 | } 289 | 290 | Parse(parser, INTEGER, data, &state); 291 | } break; 292 | case 'L': { 293 | int64_t data; 294 | if (buffer_read_bi64(buffer, &data)) { 295 | return 1; 296 | } 297 | 298 | Parse(parser, INTEGER, data, &state); 299 | } break; 300 | default: 301 | return 1; 302 | } 303 | break; 304 | case '\n': 305 | case '\r': 306 | case '\t': 307 | case ' ': 308 | break; 309 | default: 310 | return 1; 311 | } 312 | } 313 | 314 | Parse(parser, 0, 0, &state); 315 | ParseFree(parser, free); 316 | 317 | if (state.error) { 318 | return state.error; 319 | } 320 | 321 | *result = state.result; 322 | return 0; 323 | } 324 | } 325 | 326 | %syntax_error { 327 | state->error = 1; 328 | } 329 | 330 | %extra_argument { calculator_t *state } 331 | %token_type { int64_t } 332 | %type expr { int64_t } 333 | 334 | %left LOGICAL_AND LOGICAL_OR. 335 | %left BIT_AND BIT_XOR BIT_OR. 336 | %left EQEQ NOT_EQEQ. 337 | %left GT GT_EQ LT LT_EQ. 338 | %left LEFT_SHIFT RIGHT_SHIFT. 339 | %left PLUS MINUS. 340 | %left TIMES DIVIDE REMAINDER. 341 | %right NOT. 342 | %right BIT_NOT. 343 | 344 | program ::= expr(A). { 345 | state->result = A; 346 | } 347 | 348 | expr(A) ::= expr(B) PLUS expr(C). { 349 | A = B + C; 350 | } 351 | 352 | expr(A) ::= expr(B) MINUS expr(C). { 353 | A = B - C; 354 | } 355 | 356 | expr(A) ::= expr(B) TIMES expr(C). { 357 | A = B * C; 358 | } 359 | 360 | expr(A) ::= expr(B) DIVIDE expr(C). { 361 | if (C == 0) { 362 | A = 0; 363 | } else { 364 | A = B / C; 365 | } 366 | } 367 | 368 | expr(A) ::= expr(B) REMAINDER expr(C). { 369 | if (C == 0) { 370 | A = 0; 371 | } else { 372 | A = B % C; 373 | } 374 | } 375 | 376 | expr(A) ::= expr(B) LEFT_SHIFT expr(C). { 377 | A = B << C; 378 | } 379 | 380 | expr(A) ::= expr(B) RIGHT_SHIFT expr(C). { 381 | A = B >> C; 382 | } 383 | 384 | expr(A) ::= expr(B) GT expr(C). { 385 | A = B > C; 386 | } 387 | 388 | expr(A) ::= expr(B) GT_EQ expr(C). { 389 | A = B >= C; 390 | } 391 | 392 | expr(A) ::= expr(B) LT expr(C). { 393 | A = B < C; 394 | } 395 | 396 | expr(A) ::= expr(B) LT_EQ expr(C). { 397 | A = B <= C; 398 | } 399 | 400 | expr(A) ::= expr(B) EQEQ expr(C). { 401 | A = B == C; 402 | } 403 | 404 | expr(A) ::= expr(B) NOT_EQEQ expr(C). { 405 | A = B != C; 406 | } 407 | 408 | expr(A) ::= expr(B) BIT_AND expr(C). { 409 | A = B & C; 410 | } 411 | 412 | expr(A) ::= expr(B) BIT_XOR expr(C). { 413 | A = B ^ C; 414 | } 415 | 416 | expr(A) ::= expr(B) BIT_OR expr(C). { 417 | A = B | C; 418 | } 419 | 420 | expr(A) ::= expr(B) LOGICAL_AND expr(C). { 421 | A = B && C; 422 | } 423 | 424 | expr(A) ::= expr(B) LOGICAL_OR expr(C). { 425 | A = B || C; 426 | } 427 | 428 | expr(A) ::= BIT_NOT expr(B). { 429 | A = ~B; 430 | } 431 | 432 | expr(A) ::= NOT expr(B). { 433 | A = !B; 434 | } 435 | 436 | expr(A) ::= LPAR expr(B) RPAR. { 437 | A = B; 438 | } 439 | 440 | expr(A) ::= INTEGER(B). { 441 | A = B; 442 | } 443 | -------------------------------------------------------------------------------- /render.c: -------------------------------------------------------------------------------- 1 | #include "render.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int calculator_eval(buffer_t *buffer, const char *input, int64_t *result); 9 | 10 | static inline int 11 | input_is_esc(int input) 12 | { 13 | if (input == '\x1b') { 14 | nodelay(stdscr, TRUE); 15 | int n; 16 | if ((n = getch()) == ERR) { 17 | return 1; 18 | } else { 19 | ungetch(n); 20 | } 21 | nodelay(stdscr, FALSE); 22 | } 23 | 24 | return 0; 25 | } 26 | 27 | void 28 | render_status(const char *path) 29 | { 30 | int height, width; 31 | getmaxyx(stdscr, height, width); 32 | (void) height; 33 | 34 | attrset(COLOR_PAIR(COLOR_STATUS)); 35 | char status_bar[width + 1]; 36 | 37 | memset(status_bar, ' ', width); 38 | status_bar[width] = '\0'; 39 | 40 | char status_message[81]; 41 | snprintf(status_message, sizeof(status_message), " %-36s%40s", 42 | path, " UNK+.00000000`00000000"); 43 | memcpy(status_bar, status_message, sizeof(status_message) - 1 /* NUL */); 44 | 45 | mvaddstr(0, 0, status_bar); 46 | } 47 | 48 | void 49 | render_options(const options_t *options) 50 | { 51 | int height, width; 52 | getmaxyx(stdscr, height, width); 53 | 54 | attrset(COLOR_PAIR(COLOR_STATUS)); 55 | 56 | char options_bar[width + 1]; 57 | memset(options_bar, ' ', width); 58 | 59 | for (int i = 0; i < 10; i++) { 60 | int start = i * 8; 61 | 62 | char bind[3]; 63 | snprintf(bind, sizeof(bind), "%2d", i + 1); 64 | 65 | attrset(COLOR_PAIR(COLOR_OPTION_KEY)); 66 | mvaddstr(height - 1, start, bind); 67 | attrset(COLOR_PAIR(COLOR_OPTION_NAME)); 68 | if (i == 9) { 69 | // Overwrite the F10 input as Quit across all panes. 70 | // 71 | mvaddstr(height - 1, start + 2, "Quit "); 72 | } else { 73 | mvaddstr(height - 1, start + 2, (*options)[i]); 74 | } 75 | } 76 | 77 | // Draw the hanging characters (if the options window does not fill the 78 | // screen.) 79 | // There are 8 characters per option, 6 for the hint and 2 spaces for the 80 | // bind; 10 options total. 81 | // 82 | for (int i = 8 * 10; i < width; i++) { 83 | mvaddwstr(height - 1, i, FILL_CHAR); 84 | } 85 | } 86 | 87 | void 88 | render_border(WINDOW *window) 89 | { 90 | static const cchar_t BOX_VERT = { 0, { L'║' } }; 91 | static const cchar_t BOX_HORIZ = { 0, { L'═' } }; 92 | static const cchar_t BOX_TOP_LEFT = { 0, { L'╔' } }; 93 | static const cchar_t BOX_TOP_RIGHT = { 0, { L'╗' } }; 94 | static const cchar_t BOX_BOTTOM_LEFT = { 0, { L'╚' } }; 95 | static const cchar_t BOX_BOTTOM_RIGHT = { 0, { L'╝' } }; 96 | 97 | wborder_set(window, &BOX_VERT, &BOX_VERT, &BOX_HORIZ, &BOX_HORIZ, 98 | &BOX_TOP_LEFT, &BOX_TOP_RIGHT, &BOX_BOTTOM_LEFT, &BOX_BOTTOM_RIGHT); 99 | } 100 | 101 | static int 102 | calculator_driver(int input, buffer_t *buffer, WINDOW *window, FORM *form, FIELD **fields) 103 | { 104 | if (input_is_esc(input)) { 105 | return 0; 106 | } 107 | 108 | switch (input) { 109 | case KEY_ENTER: 110 | case '\x0a': 111 | // Sychronize the field so we can get the buffer data. 112 | // 113 | form_driver(form, REQ_VALIDATION); 114 | 115 | char *expression = field_buffer(fields[0], 0); 116 | 117 | // Evaluate the input. If an error occurs, zero out the result fields. 118 | int64_t result; 119 | if (calculator_eval(buffer, expression, &result) != 0) { 120 | set_field_buffer(fields[1], 0, "Sig:0"); 121 | set_field_buffer(fields[2], 0, "Uns:0"); 122 | set_field_buffer(fields[3], 0, "Bin:0000000000000000000000000000000000000000000000000000000000000000"); 123 | set_field_buffer(fields[4], 0, "Hex:00000000`00000000"); 124 | } else { 125 | char sig_message[69]; 126 | snprintf(sig_message, sizeof(sig_message), "Sig:%" PRId64, result); 127 | set_field_buffer(fields[1], 0, sig_message); 128 | 129 | char uns_message[69]; 130 | snprintf(uns_message, sizeof(uns_message), "Uns:%" PRIu64, result); 131 | set_field_buffer(fields[2], 0, uns_message); 132 | 133 | char bin_message[69]; 134 | snprintf(bin_message, sizeof(bin_message), "Bin:"); 135 | for (int i = 0; i < 64; i++) { 136 | snprintf(bin_message + i + 4, sizeof(bin_message) - i - 4, "%d", (result & ((uint64_t) 1 << (63 - i))) != 0); 137 | } 138 | set_field_buffer(fields[3], 0, bin_message); 139 | 140 | char hex_message[69]; 141 | snprintf(hex_message, sizeof(hex_message), "Hex:%08x`%08x", 142 | (uint32_t) ((result & 0xffffffff00000000) >> 32), 143 | (uint32_t) (result & 0x00000000ffffffff)); 144 | set_field_buffer(fields[4], 0, hex_message); 145 | } 146 | 147 | refresh(); 148 | pos_form_cursor(form); 149 | break; 150 | case KEY_LEFT: 151 | form_driver(form, REQ_PREV_CHAR); 152 | break; 153 | case KEY_RIGHT: 154 | form_driver(form, REQ_NEXT_CHAR); 155 | break; 156 | case KEY_BACKSPACE: 157 | case '\x7f': 158 | form_driver(form, REQ_DEL_PREV); 159 | break; 160 | // DEL. 161 | // 162 | case KEY_DC: 163 | form_driver(form, REQ_DEL_CHAR); 164 | break; 165 | default: 166 | form_driver(form, input); 167 | break; 168 | } 169 | 170 | wrefresh(window); 171 | return 1; 172 | } 173 | 174 | void 175 | prompt_calculator(buffer_t *buffer) 176 | { 177 | // Create a window to contain the calculator, factoring in the border sizes. 178 | // +2 for the vertical border, and +4 for the left and right padding on the 179 | // horizontal border. 180 | // 181 | int height, width; 182 | getmaxyx(stdscr, height, width); 183 | WINDOW *window = newwin(5 + 2, 68 + 4, (height / 2) - (7 / 2), (width / 2) - (72 / 2)); 184 | assert(window != NULL); 185 | 186 | render_border(window); 187 | 188 | FIELD *fields[6]; 189 | fields[0] = new_field(1, 68, 0, 1, 0, 0); 190 | fields[1] = new_field(1, 68, 1, 1, 0, 0); 191 | fields[2] = new_field(1, 68, 2, 1, 0, 0); 192 | fields[3] = new_field(1, 68, 3, 1, 0, 0); 193 | fields[4] = new_field(1, 68, 4, 1, 0, 0); 194 | fields[5] = NULL; 195 | assert(fields[0] != NULL && fields[1] != NULL && fields[2] != NULL && fields[3] != NULL && fields[4] != NULL); 196 | 197 | set_field_buffer(fields[1], 0, "Sig:0"); 198 | set_field_buffer(fields[2], 0, "Uns:0"); 199 | set_field_buffer(fields[3], 0, "Bin:0000000000000000000000000000000000000000000000000000000000000000"); 200 | set_field_buffer(fields[4], 0, "Hex:00000000`00000000"); 201 | 202 | set_field_opts(fields[0], O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE); 203 | set_field_opts(fields[1], O_VISIBLE | O_PUBLIC | O_AUTOSKIP); 204 | set_field_opts(fields[2], O_VISIBLE | O_PUBLIC | O_AUTOSKIP); 205 | set_field_opts(fields[3], O_VISIBLE | O_PUBLIC | O_AUTOSKIP); 206 | set_field_opts(fields[4], O_VISIBLE | O_PUBLIC | O_AUTOSKIP); 207 | 208 | // Underline the field to indicate it can be edited. 209 | // 210 | set_field_back(fields[0], A_UNDERLINE); 211 | 212 | FORM *form = new_form(fields); 213 | assert(form != NULL); 214 | 215 | set_form_win(form, window); 216 | set_form_sub(form, derwin(window, 9 - 4, 72 - 2, 1, 1)); 217 | post_form(form); 218 | 219 | // Draw the header for the dialog, and reset the cursor back to the input. 220 | // 221 | mvwprintw(window, 0, 72 / 2 - ((sizeof(" Calculator ") - 1) / 2), " Calculator "); 222 | wmove(window, 1, 2); 223 | 224 | refresh(); 225 | wrefresh(window); 226 | 227 | // Set the cursor to visible. 228 | // 229 | curs_set(1); 230 | 231 | while (calculator_driver(getch(), buffer, window, form, fields)); 232 | 233 | // Restore the cursor state. 234 | // 235 | curs_set(0); 236 | 237 | unpost_form(form); 238 | free_form(form); 239 | free_field(fields[0]); 240 | free_field(fields[1]); 241 | free_field(fields[2]); 242 | free_field(fields[3]); 243 | free_field(fields[4]); 244 | delwin(window); 245 | } 246 | 247 | static int 248 | input_driver(int input, WINDOW *window, FORM *form, FIELD **fields) 249 | { 250 | switch (input) { 251 | case KEY_ENTER: 252 | case '\x0a': 253 | // Sychronize the field so we can get the buffer data. 254 | // 255 | form_driver(form, REQ_VALIDATION); 256 | refresh(); 257 | pos_form_cursor(form); 258 | return 0; 259 | case KEY_LEFT: 260 | form_driver(form, REQ_PREV_CHAR); 261 | break; 262 | case KEY_RIGHT: 263 | form_driver(form, REQ_NEXT_CHAR); 264 | break; 265 | case KEY_BACKSPACE: 266 | case '\x7f': 267 | form_driver(form, REQ_DEL_PREV); 268 | break; 269 | // DEL. 270 | // 271 | case KEY_DC: 272 | form_driver(form, REQ_DEL_CHAR); 273 | break; 274 | default: 275 | form_driver(form, input); 276 | break; 277 | } 278 | 279 | wrefresh(window); 280 | return 1; 281 | } 282 | 283 | size_t 284 | prompt_input(const char *title, const char *placeholder, char **user_input) 285 | { 286 | // Create a window to contain the menu, factoring in the border sizes. 287 | // +2 for the vertical border, and +4 for the left and right padding on the 288 | // horizontal border. 289 | // 290 | int height, width; 291 | getmaxyx(stdscr, height, width); 292 | WINDOW *window = newwin(1 + 2, 68 + 4, (height / 2) - (3 / 2), (width / 2) - (72 / 2)); 293 | assert(window != NULL); 294 | 295 | render_border(window); 296 | 297 | FIELD *fields[2]; 298 | fields[0] = new_field(1, 68, 0, 1, 0, 0); 299 | fields[1] = NULL; 300 | assert(fields[0] != NULL); 301 | 302 | set_field_opts(fields[0], O_VISIBLE | O_PUBLIC | O_EDIT | O_ACTIVE); 303 | 304 | // Underline the field to indicate it can be edited. 305 | // 306 | set_field_back(fields[0], A_UNDERLINE); 307 | 308 | FORM *form = new_form(fields); 309 | assert(form != NULL); 310 | 311 | set_form_win(form, window); 312 | set_form_sub(form, derwin(window, 5 - 4, 72 - 2, 1, 1)); 313 | post_form(form); 314 | 315 | // Draw the header for the dialog, and reset the cursor back to the input. 316 | // 317 | int start = 72 / 2 - (strlen(title) / 2); 318 | mvwprintw(window, 0, start - 1, " "); 319 | mvwprintw(window, 0, start, title); 320 | mvwprintw(window, 0, start + strlen(title), " "); 321 | wmove(window, 1, 2); 322 | 323 | if (placeholder != NULL) { 324 | set_field_buffer(fields[0], 0, placeholder); 325 | while (*placeholder++ != '\0') { 326 | form_driver(form, REQ_NEXT_CHAR); 327 | } 328 | } 329 | 330 | refresh(); 331 | wrefresh(window); 332 | 333 | // Set the cursor to visible. 334 | // 335 | curs_set(1); 336 | 337 | int input; 338 | while ((input = getch()) && !input_is_esc(input)) { 339 | if (input_driver(input, window, form, fields) == 0) { 340 | break; 341 | } 342 | } 343 | 344 | // If ESC is pressed "cancel" the input request and set user_input to NULL. 345 | // 346 | size_t input_size; 347 | if (input_is_esc(input)) { 348 | *user_input = NULL; 349 | input_size = 0; 350 | } else { 351 | char *input = field_buffer(fields[0], 0); 352 | *user_input = strdup(input); 353 | input_size = strlen(*user_input); 354 | } 355 | 356 | // Restore the cursor state. 357 | // 358 | curs_set(0); 359 | 360 | unpost_form(form); 361 | free_form(form); 362 | free_field(fields[0]); 363 | delwin(window); 364 | 365 | return input_size; 366 | } 367 | 368 | static int 369 | rmenu_driver(int input, WINDOW *window, MENU *menu, int *index) 370 | { 371 | switch(input) { 372 | case KEY_DOWN: 373 | menu_driver(menu, REQ_DOWN_ITEM); 374 | break; 375 | case KEY_UP: 376 | menu_driver(menu, REQ_UP_ITEM); 377 | break; 378 | case KEY_NPAGE: 379 | menu_driver(menu, REQ_SCR_DPAGE); 380 | break; 381 | case KEY_PPAGE: 382 | menu_driver(menu, REQ_SCR_UPAGE); 383 | break; 384 | case '\x0a': 385 | case KEY_ENTER: { 386 | ITEM *selected = current_item(menu); 387 | *index = item_index(selected); 388 | return 0; 389 | } 390 | } 391 | 392 | wrefresh(window); 393 | return 1; 394 | } 395 | 396 | int 397 | prompt_menu(const char *title, const char **options, size_t options_size, int req_width, int start_item) 398 | { 399 | assert(options_size > 0); 400 | assert(req_width > 0); 401 | assert(start_item >= 0); 402 | 403 | int screen_height, screen_width; 404 | getmaxyx(stdscr, screen_height, screen_width); 405 | 406 | // If the width is greater than the screen width with padding lock it to the 407 | // screen. 408 | // 409 | int width = screen_width - 8; 410 | width = req_width > width ? width : req_width; 411 | int height = screen_height - 6; 412 | height = options_size > height ? height : options_size; 413 | 414 | int window_height = height + 2; 415 | int window_width = width + 4; 416 | WINDOW *window = newwin(window_height, window_width, (screen_height / 2) - (window_height / 2), (screen_width / 2) - (window_width / 2)); 417 | assert(window != NULL); 418 | 419 | render_border(window); 420 | 421 | ITEM **items = (ITEM**) malloc((options_size + 1) * sizeof(ITEM*)); 422 | for(int i = 0; i < options_size; i++) { 423 | items[i] = new_item(options[i], NULL); 424 | } 425 | items[options_size] = NULL; 426 | 427 | MENU *menu = new_menu(items); 428 | 429 | set_menu_win(menu, window); 430 | set_menu_sub(menu, derwin(window, height, width, 1, 2)); 431 | set_menu_mark(menu, NULL); 432 | set_menu_format(menu, height, 1); 433 | post_menu(menu); 434 | 435 | int start = window_width / 2 - (strlen(title) / 2); 436 | mvwprintw(window, 0, start - 1, " "); 437 | mvwprintw(window, 0, start, title); 438 | mvwprintw(window, 0, start + strlen(title), " "); 439 | wmove(window, 1, 2); 440 | 441 | refresh(); 442 | wrefresh(window); 443 | 444 | int input, index = -1; 445 | while ((input = getch()) && !input_is_esc(input)) { 446 | if (rmenu_driver(input, window, menu, &index) == 0) { 447 | break; 448 | } 449 | } 450 | 451 | unpost_menu(menu); 452 | free_menu(menu); 453 | for(int i = 0; i < options_size; i++) { 454 | free_item(items[i]); 455 | } 456 | free(items); 457 | delwin(window); 458 | 459 | return index; 460 | } 461 | 462 | void 463 | prompt_error(const char *message) 464 | { 465 | assert(strlen(message) < 68); 466 | 467 | int height, width; 468 | getmaxyx(stdscr, height, width); 469 | WINDOW *window = newwin(1 + 2, 68 + 4, (height / 2) - (7 / 2), (width / 2) - (72 / 2)); 470 | assert(window != NULL); 471 | 472 | render_border(window); 473 | 474 | FIELD *fields[2]; 475 | fields[0] = new_field(1, 68, 0, 1, 0, 0); 476 | fields[1] = NULL; 477 | assert(fields[0] != NULL); 478 | 479 | set_field_buffer(fields[0], 0, message); 480 | 481 | set_field_opts(fields[0], O_VISIBLE | O_PUBLIC | O_AUTOSKIP); 482 | 483 | FORM *form = new_form(fields); 484 | assert(form != NULL); 485 | 486 | set_form_win(form, window); 487 | set_form_sub(form, derwin(window, 1, 72 - 2, 1, 1)); 488 | post_form(form); 489 | 490 | mvwprintw(window, 0, 72 / 2 - ((sizeof(" Error ") - 1) / 2), " Error "); 491 | wmove(window, 1, 2); 492 | 493 | refresh(); 494 | wrefresh(window); 495 | 496 | int input; 497 | while ((input = getch()) && !input_is_esc(input) && input != KEY_ENTER); 498 | 499 | unpost_form(form); 500 | free_form(form); 501 | free_field(fields[0]); 502 | delwin(window); 503 | } 504 | -------------------------------------------------------------------------------- /panes.c: -------------------------------------------------------------------------------- 1 | #include "panes.h" 2 | #include "buffer.h" 3 | #include "render.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | void 10 | pane_drive(pane_t *pane, int input) 11 | { 12 | pane->driver(pane->user_data, input); 13 | } 14 | 15 | void 16 | pane_unpost(pane_t *pane) 17 | { 18 | pane->unpost(pane->user_data); 19 | free(pane); 20 | } 21 | 22 | void 23 | pane_scroll(pane_t *pane, uint64_t offset) 24 | { 25 | pane->scroll(pane->user_data, offset); 26 | } 27 | 28 | // If a character can be printed to the screen SAFELY. 29 | // 30 | static inline int 31 | can_print(char c) 32 | { 33 | return (unsigned) c - 0x20 < 0x5f; 34 | } 35 | 36 | const options_t HEX_OPT = { 37 | [0 ... 9] = " ", 38 | [2] = "Edit ", 39 | // Handled in main driver. 40 | // 41 | [4] = "Goto ", 42 | [8] = "Names ", 43 | }; 44 | 45 | typedef struct { 46 | buffer_t *buffer; 47 | // If on the odd-end of a pair, e.g.: 0 for "[0]0" and 1 for "0[0]" 48 | // 49 | int odd; 50 | // If in edit mode, != 0. 51 | // 52 | int edit; 53 | uint64_t scroll; 54 | } hex_pane_t; 55 | 56 | static void 57 | hex_unpost(void *user_data) 58 | { 59 | free(user_data); 60 | } 61 | 62 | static void 63 | hex_update(hex_pane_t *pane, int width, int height) 64 | { 65 | attrset(COLOR_PAIR(COLOR_STANDARD)); 66 | buffer_t *buffer = pane->buffer; 67 | 68 | uint8_t *data = buffer->data + pane->scroll * 16; 69 | cursor_t current; 70 | for (int i = 1; (current = data - buffer->data) < buffer->size && i < (height - 1); data += 16, i++) { 71 | // Determine the number of characters left in this row: 16 unless there 72 | // is no more data to print. 73 | // 74 | size_t size = buffer->size - current; 75 | if (size >= 16) { 76 | size = 16; 77 | } 78 | 79 | // .00000000`00000000: 80 | // 81 | size_t offset = 0, wrote; 82 | char addr_str[22]; 83 | wrote = snprintf(addr_str, sizeof(addr_str), ".%08lx`%08lx: ", 84 | current & 0xffffffff00000000, 85 | current & 0x00000000ffffffff); 86 | mvaddstr(i, offset, addr_str); 87 | offset += wrote; 88 | 89 | // 00 00 00 00-00 00 00 00-00 00 00 00-00 00 00 00 90 | // 91 | for (int j = 0; j < size; j++) { 92 | char hex_str[2]; 93 | 94 | range_t *range = NULL; 95 | for (GSList *highlight = buffer->highlights; highlight != NULL; highlight = g_slist_next(highlight)) { 96 | range = (range_t*) highlight->data; 97 | uintptr_t start = range->address; 98 | uintptr_t end = start + range->size - 1; 99 | if (current >= start && current <= end) { 100 | attrset(COLOR_PAIR(range->color)); 101 | break; 102 | } 103 | } 104 | 105 | // If the block is under the cursor or inside of a mark, color it. 106 | // 107 | cursor_t mark_end = buffer->end_mark == -1 ? buffer->cursor : buffer->end_mark; 108 | int mark_forwards = buffer->start_mark != -1 && current >= buffer->start_mark && current <= mark_end; 109 | int mark_backwards = buffer->start_mark != -1 && current <= buffer->start_mark && current >= mark_end; 110 | if (!pane->edit && (buffer->cursor == current || mark_forwards || mark_backwards)) { 111 | attrset(COLOR_PAIR(COLOR_SELECTED)); 112 | } 113 | 114 | snprintf(hex_str, sizeof(hex_str), "%x", data[j] & 0xf0); 115 | if (pane->edit && !pane->odd && buffer->cursor == current) { 116 | attrset(COLOR_PAIR(COLOR_SELECTED)); 117 | } 118 | mvaddstr(i, offset, hex_str); 119 | if (pane->edit && !pane->odd && buffer->cursor == current) { 120 | attrset(COLOR_PAIR(COLOR_STANDARD)); 121 | } 122 | 123 | snprintf(hex_str, sizeof(hex_str), "%x", data[j] & 0x0f); 124 | if (pane->edit && pane->odd && buffer->cursor == current) { 125 | attrset(COLOR_PAIR(COLOR_SELECTED)); 126 | } 127 | mvaddstr(i, offset + 1, hex_str); 128 | if (pane->edit && pane->odd && buffer->cursor == current) { 129 | attrset(COLOR_PAIR(COLOR_STANDARD)); 130 | } 131 | 132 | offset += 2; 133 | 134 | // If there is no mark set or the cursor is on the start/end of the 135 | // mark or it's the last pair on the row, don't color the space. 136 | // 137 | if (mark_forwards && buffer->end_mark == current) { 138 | attrset(COLOR_PAIR(COLOR_STANDARD)); 139 | } 140 | 141 | if (mark_backwards && buffer->start_mark == current) { 142 | attrset(COLOR_PAIR(COLOR_STANDARD)); 143 | } 144 | 145 | int in_range = range != NULL && range->address + range->size - 1 == current; 146 | if ((buffer->cursor == current && !mark_backwards && !mark_forwards) || j == 15 || in_range) { 147 | attrset(COLOR_PAIR(COLOR_STANDARD)); 148 | } 149 | 150 | if (buffer->end_mark == -1 && mark_forwards && buffer->cursor == current) { 151 | attrset(COLOR_PAIR(COLOR_STANDARD)); 152 | } 153 | 154 | current++; 155 | 156 | // Terminate the hex pair with a dash if it is a multiple of 4. 157 | // 158 | char end = (j + 1) % 4 == 0 && j != 15 ? '-' : ' '; 159 | char term_str[2] = { end, '\0' }; 160 | mvaddstr(i, offset, term_str); 161 | offset += 1; 162 | 163 | attrset(COLOR_PAIR(COLOR_STANDARD)); 164 | } 165 | 166 | current -= size; 167 | 168 | // If size <= 16, add more spaces. 169 | // 170 | for (int j = 0; j < 16 - size; j++) { 171 | // For each character in the pair, e.g.: "00 ". 172 | // 173 | mvaddstr(i, offset, " "); 174 | offset += 3; 175 | } 176 | 177 | // Move the cursor over to add one more space separator. NULL is added, 178 | // so replace it with a space. 179 | // 180 | mvaddstr(i, offset, " "); 181 | offset++; 182 | 183 | // ................ 184 | // 185 | for (int j = 0; j < size; j++) { 186 | char print_str[2]; 187 | wrote = snprintf(print_str, sizeof(print_str), 188 | "%c", can_print(data[j]) ? data[j] & 0xff : '.'); 189 | 190 | cursor_t mark_end = buffer->end_mark == -1 ? buffer->cursor : buffer->end_mark; 191 | int mark_forwards = buffer->start_mark != -1 && current >= buffer->start_mark && current <= mark_end; 192 | int mark_backwards = buffer->start_mark != -1 && current <= buffer->start_mark && current >= mark_end; 193 | if (buffer->cursor == current || (!pane->edit && (mark_forwards || mark_backwards))) { 194 | attrset(COLOR_PAIR(COLOR_SELECTED)); 195 | } 196 | 197 | mvaddstr(i, offset, print_str); 198 | offset += wrote; 199 | current++; 200 | 201 | attrset(COLOR_PAIR(COLOR_STANDARD)); 202 | } 203 | 204 | // If size <= 16, add more spaces. 205 | // 206 | for (int j = 0; j < 16 - size; j++) { 207 | mvaddstr(i, offset, " "); 208 | offset++; 209 | } 210 | } 211 | 212 | // Convert 1D cursor coordinate to 2D. 213 | // 214 | cursor_t cursor = buffer->cursor; 215 | int x = strlen(".00000000`00000000: "); 216 | int y = (cursor / 16 - pane->scroll) + 1; // Account for the status bar 217 | 218 | // If there is a comment present: print the comment shifted down or up 219 | // depending if there is space. 220 | // 221 | const char *comment = buffer_lookup_comment(buffer, cursor); 222 | if (comment != NULL) { 223 | if (y + 5 >= height) { 224 | y -= 3; 225 | } else { 226 | y += 3; 227 | } 228 | attrset(COLOR_PAIR(COLOR_SELECTED)); 229 | mvaddstr(y, x, "; "); 230 | mvaddstr(y, x + 2, comment); 231 | attrset(COLOR_PAIR(COLOR_STANDARD)); 232 | } 233 | } 234 | 235 | static void 236 | hex_driver(void *user_data, int input) 237 | { 238 | hex_pane_t *pane = (hex_pane_t*) user_data; 239 | buffer_t *buffer = pane->buffer; 240 | 241 | int height, width; 242 | getmaxyx(stdscr, height, width); 243 | width = 16; 244 | 245 | cursor_t top = pane->scroll * width; 246 | cursor_t bottom = top + (height - 3) * width; 247 | 248 | switch (input) { 249 | case 'h': 250 | case KEY_LEFT: 251 | if (buffer->cursor > 0) { 252 | if (pane->odd == 0 || !pane->edit) { 253 | buffer->cursor--; 254 | } 255 | pane->odd = !pane->odd; 256 | } else if (buffer->cursor == 0 && pane->odd) { 257 | // If the user is on the second nibble of the buffer and moves to 258 | // the left. 259 | // 260 | pane->odd = 0; 261 | } 262 | 263 | // If moving the cursor caused it to move off the screen, overflow scroll 264 | // upwards. 265 | // 266 | if (buffer->cursor - (buffer->cursor % width) < (top + width * 2)) { 267 | // If scrolling won't go off the screen. 268 | // 269 | if (pane->scroll > 0) { 270 | pane->scroll--; 271 | } 272 | } 273 | break; 274 | case 'j': 275 | case KEY_DOWN: 276 | if (buffer->cursor + width < buffer->size) { 277 | buffer->cursor += width; 278 | } else { 279 | // If on last row: snap cursor to end of the buffer. 280 | // 281 | buffer->cursor = buffer->size - 1; 282 | } 283 | 284 | // If overflow scrolled off the screen, seek downwards. 285 | // 286 | if (buffer->cursor - (buffer->cursor % width) > (bottom - width * 2)) { 287 | // If scrolling won't go off the screen. 288 | // 289 | if (pane->scroll + height - 3 < buffer->size / width) { 290 | pane->scroll++; 291 | } 292 | } 293 | break; 294 | advance: 295 | case 'l': 296 | case KEY_RIGHT: 297 | if (buffer->cursor < buffer->size - 1) { 298 | if (pane->odd == 1 || !pane->edit) { 299 | buffer->cursor++; 300 | } 301 | pane->odd = !pane->odd; 302 | } else if (buffer->cursor == buffer->size - 1 && !pane->odd) { 303 | // If the user is on the 2nd last nibble of the buffer and moves to 304 | // the right. 305 | // 306 | pane->odd = 1; 307 | } 308 | 309 | // If moving the cursor caused it to move off the screen, overflow scroll 310 | // downwards. 311 | // 312 | if (buffer->cursor - (buffer->cursor % width) > (bottom - width * 2)) { 313 | // If scrolling won't go off the screen. 314 | // 315 | if (pane->scroll + height - 3 < buffer->size / width) { 316 | pane->scroll++; 317 | } 318 | } 319 | break; 320 | case 'k': 321 | case KEY_UP: 322 | if (buffer->cursor >= width) { 323 | buffer->cursor -= width; 324 | } else { 325 | // If on 1st row: snap cursor to the beginning of the buffer. 326 | // 327 | buffer->cursor = 0; 328 | } 329 | 330 | // If overflow scrolled off the screen, seek upwards. 331 | // 332 | if (buffer->cursor - (buffer->cursor % width) < (top + width * 2)) { 333 | // If scrolling won't go off the screen. 334 | // 335 | if (pane->scroll > 0) { 336 | pane->scroll--; 337 | } 338 | } 339 | break; 340 | case KEY_PPAGE: 341 | // If scrolling would send the cursor off the screen, reset to the 342 | // beginning. 343 | // 344 | if (pane->scroll < height - 3) { 345 | pane->scroll = 0; 346 | buffer->cursor = 0; 347 | } else { 348 | pane->scroll -= height - 3; 349 | buffer->cursor = pane->scroll * width; 350 | } 351 | break; 352 | case KEY_NPAGE: 353 | // If scrolling would send the cursor out of bounds, set the cursor 354 | // to the end of the buffer. 355 | // 356 | if (pane->scroll + height - 3 >= buffer->size / width) { 357 | goto end; 358 | } else { 359 | pane->scroll += height - 3; 360 | buffer->cursor = pane->scroll * width; 361 | } 362 | break; 363 | end: 364 | case KEY_END: 365 | pane->scroll = buffer->size / width - height + 3; 366 | buffer->cursor = buffer->size - 1; 367 | pane->odd = 1; 368 | break; 369 | case KEY_HOME: 370 | pane->scroll = 0; 371 | buffer->cursor = 0; 372 | pane->odd = 0; 373 | break; 374 | case 'v': 375 | // No selection supported inside of edit mode. 376 | // 377 | if (pane->edit) 378 | break; 379 | 380 | if (buffer->start_mark == -1) { 381 | buffer->start_mark = buffer->cursor; 382 | } else if (buffer->end_mark == -1 && buffer->start_mark != -1) { 383 | buffer->end_mark = buffer->cursor; 384 | } else if (buffer->end_mark != -1 && buffer->start_mark != -1) { 385 | buffer->start_mark = -1; 386 | buffer->end_mark = -1; 387 | } 388 | break; 389 | case KEY_F(3): 390 | if (!pane->edit && buffer->editable) { 391 | pane->edit = 1; 392 | } 393 | break; 394 | case '\x1b': { 395 | // If in edit mode and ESC is hit, disable edit mode. 396 | // 397 | if (!pane->edit) 398 | break; 399 | 400 | nodelay(stdscr, TRUE); 401 | int n; 402 | if ((n = getch()) == ERR) { 403 | pane->edit = 0; 404 | } else { 405 | ungetch(n); 406 | } 407 | nodelay(stdscr, FALSE); 408 | } break; 409 | case 'a'...'f': 410 | case '0'...'9': { 411 | if (!pane->edit) 412 | break; 413 | 414 | // Convert character to integral representation. 415 | // 416 | int n; 417 | if (input >= '0' && input <= '9') { 418 | n = input - '0'; 419 | } else if (input >= 'a' && input <= 'f') { 420 | n = 10 + input - 'a'; 421 | } 422 | 423 | uint8_t b = buffer->data[buffer->cursor]; 424 | uint8_t st = b & 0x0f; 425 | uint8_t nd = (b & 0xf0) >> 4; 426 | 427 | if (pane->odd) { 428 | st = n; 429 | } else { 430 | nd = n; 431 | } 432 | 433 | buffer->data[buffer->cursor] = ((nd << 4) | st); 434 | goto advance; 435 | } break; 436 | case '+': 437 | buffer_bookmark_push(buffer, buffer->cursor); 438 | break; 439 | case '-': { 440 | int error; 441 | uint64_t scroll = buffer_bookmark_pop(buffer, width, height, &error); 442 | if (!error) { 443 | pane->scroll = scroll; 444 | } 445 | } break; 446 | } 447 | 448 | hex_update(pane, width, height); 449 | } 450 | 451 | static void 452 | hex_scroll(void *user_data, uint64_t offset) 453 | { 454 | hex_pane_t *pane = (hex_pane_t*) user_data; 455 | 456 | int height, width; 457 | getmaxyx(stdscr, height, width); 458 | (void) width; 459 | 460 | pane->scroll = buffer_scroll(pane->buffer, offset, 16, height); 461 | pane->odd = 0; 462 | } 463 | 464 | pane_t* 465 | hex_post(buffer_t *buffer, int width, int height) 466 | { 467 | hex_pane_t *hex_pane = malloc(sizeof(hex_pane_t)); 468 | hex_pane->buffer = buffer; 469 | hex_pane->odd = 0; 470 | hex_pane->edit = 0; 471 | hex_scroll((void*) hex_pane, buffer->cursor); 472 | 473 | pane_t *pane = malloc(sizeof(pane_t)); 474 | pane->driver = hex_driver; 475 | pane->unpost = hex_unpost; 476 | pane->scroll = hex_scroll; 477 | pane->options = &HEX_OPT; 478 | pane->user_data = (void*) hex_pane; 479 | pane->type = PANE_HEX; 480 | 481 | hex_update(hex_pane, width, height); 482 | 483 | return pane; 484 | } 485 | 486 | const options_t TEXT_OPT = { 487 | [0 ... 9] = " ", 488 | // Handled in main driver. 489 | // 490 | [4] = "Goto ", 491 | [8] = "Names ", 492 | }; 493 | 494 | typedef struct { 495 | buffer_t *buffer; 496 | uint64_t scroll; 497 | } text_pane_t; 498 | 499 | static void 500 | text_update(text_pane_t *pane, int width, int height) 501 | { 502 | attrset(COLOR_PAIR(COLOR_STANDARD)); 503 | 504 | buffer_t *buffer = pane->buffer; 505 | 506 | uint8_t *data = buffer->data + pane->scroll * width; 507 | cursor_t current; 508 | for (int i = 1; (current = data - buffer->data) < buffer->size && i < (height - 1); data += width, i++) { 509 | size_t size = buffer->size - current; 510 | if (size >= width) { 511 | size = width; 512 | } 513 | 514 | for (int j = 0; j < size; j++) { 515 | // If the character cannot be SAFELY printed, print a space instead. 516 | // 517 | char c = can_print(data[j]) ? data[j] : ' '; 518 | // If the character is under the cursor or the current mark, color 519 | // it with a selection. 520 | // 521 | cursor_t mark_end = buffer->end_mark == -1 ? buffer->cursor : buffer->end_mark; 522 | int mark_forwards = buffer->start_mark != -1 && current >= buffer->start_mark && current <= mark_end; 523 | int mark_backwards = buffer->start_mark != -1 && current <= buffer->start_mark && current >= mark_end; 524 | if (buffer->cursor == current || mark_forwards || mark_backwards) { 525 | attrset(COLOR_PAIR(COLOR_SELECTED)); 526 | } 527 | mvaddch(i, j, c); 528 | attrset(COLOR_PAIR(COLOR_STANDARD)); 529 | current++; 530 | } 531 | 532 | // If size <= width, add more spaces. 533 | // 534 | attrset(COLOR_PAIR(COLOR_STANDARD)); 535 | for (int j = 0; j < width - size; j++) { 536 | mvaddch(i, size + j, ' '); 537 | } 538 | } 539 | } 540 | 541 | static void 542 | text_driver(void *user_data, int input) 543 | { 544 | text_pane_t *pane = (text_pane_t*) user_data; 545 | buffer_t *buffer = pane->buffer; 546 | 547 | int height, width; 548 | getmaxyx(stdscr, height, width); 549 | 550 | cursor_t top = pane->scroll * width; 551 | cursor_t bottom = top + (height - 3) * width; 552 | 553 | switch (input) { 554 | case 'h': 555 | case KEY_LEFT: 556 | if (buffer->cursor > 0) { 557 | buffer->cursor--; 558 | } 559 | 560 | // If moving the cursor caused it to move off the screen, overflow scroll 561 | // upwards. 562 | // 563 | if (buffer->cursor - (buffer->cursor % width) < (top + width * 2)) { 564 | // If scrolling won't go off the screen. 565 | // 566 | if (pane->scroll > 0) { 567 | pane->scroll--; 568 | } 569 | } 570 | break; 571 | case 'j': 572 | case KEY_DOWN: 573 | if (buffer->cursor + width < buffer->size) { 574 | buffer->cursor += width; 575 | } else { 576 | // If on last row: snap cursor to end of the buffer. 577 | // 578 | buffer->cursor = buffer->size - 1; 579 | } 580 | 581 | // If overflow scrolled off the screen, seek downwards. 582 | // 583 | if (buffer->cursor - (buffer->cursor % width) > (bottom - width * 2)) { 584 | // If scrolling won't go off the screen. 585 | // 586 | if (pane->scroll + height - 3 < buffer->size / width) { 587 | pane->scroll++; 588 | } 589 | } 590 | break; 591 | case 'l': 592 | case KEY_RIGHT: 593 | if (buffer->cursor < buffer->size - 1) { 594 | buffer->cursor++; 595 | } 596 | 597 | // If moving the cursor caused it to move off the screen, overflow scroll 598 | // downwards. 599 | // 600 | if (buffer->cursor - (buffer->cursor % width) > (bottom - width * 2)) { 601 | // If scrolling won't go off the screen. 602 | // 603 | if (pane->scroll + height - 3 < buffer->size / width) { 604 | pane->scroll++; 605 | } 606 | } 607 | break; 608 | case 'k': 609 | case KEY_UP: 610 | if (buffer->cursor >= width) { 611 | buffer->cursor -= width; 612 | } else { 613 | // If on 1st row: snap cursor to the beginning of the buffer. 614 | // 615 | buffer->cursor = 0; 616 | } 617 | 618 | // If overflow scrolled off the screen, seek upwards. 619 | // 620 | if (buffer->cursor - (buffer->cursor % width) < (top + width * 2)) { 621 | // If scrolling won't go off the screen. 622 | // 623 | if (pane->scroll > 0) { 624 | pane->scroll--; 625 | } 626 | } 627 | break; 628 | case KEY_PPAGE: 629 | // If scrolling would send the cursor off the screen, reset to the 630 | // beginning. 631 | // 632 | if (pane->scroll < height - 3) { 633 | pane->scroll = 0; 634 | buffer->cursor = 0; 635 | } else { 636 | pane->scroll -= height - 3; 637 | buffer->cursor = pane->scroll * width; 638 | } 639 | break; 640 | case KEY_NPAGE: 641 | // If scrolling would send the cursor out of bounds, set the cursor 642 | // to the end of the buffer. 643 | // 644 | if (pane->scroll + height - 3 >= buffer->size / width) { 645 | goto end; 646 | } else { 647 | pane->scroll += height - 3; 648 | buffer->cursor = pane->scroll * width; 649 | } 650 | break; 651 | end: 652 | case KEY_END: 653 | pane->scroll = buffer->size / width - height + 3; 654 | buffer->cursor = buffer->size - 1; 655 | break; 656 | case KEY_HOME: 657 | pane->scroll = 0; 658 | buffer->cursor = 0; 659 | break; 660 | case 'v': 661 | // If there is no mark, set it to the cursor. Otherwise, clear it. 662 | // 663 | if (buffer->start_mark == -1) { 664 | buffer->start_mark = buffer->cursor; 665 | } else if (buffer->end_mark == -1 && buffer->start_mark != -1) { 666 | buffer->end_mark = buffer->cursor; 667 | } else if (buffer->end_mark != -1 && buffer->start_mark != -1) { 668 | buffer->start_mark = buffer->cursor; 669 | buffer->end_mark = -1; 670 | } 671 | break; 672 | } 673 | 674 | text_update(pane, width, height); 675 | } 676 | 677 | static void 678 | text_unpost(void *user_data) 679 | { 680 | free(user_data); 681 | } 682 | 683 | static void 684 | text_scroll(void *user_data, uint64_t offset) 685 | { 686 | text_pane_t *pane = (text_pane_t*) user_data; 687 | 688 | int height, width; 689 | getmaxyx(stdscr, height, width); 690 | 691 | pane->scroll = buffer_scroll(pane->buffer, offset, width, height); 692 | } 693 | 694 | pane_t* 695 | text_post(buffer_t *buffer, int width, int height) 696 | { 697 | text_pane_t *text_pane = malloc(sizeof(text_pane_t)); 698 | text_pane->buffer = buffer; 699 | text_scroll((void*) text_pane, buffer->cursor); 700 | 701 | pane_t *pane = malloc(sizeof(pane_t)); 702 | pane->driver = text_driver; 703 | pane->unpost = text_unpost; 704 | pane->scroll = text_scroll; 705 | pane->options = &TEXT_OPT; 706 | pane->user_data = text_pane; 707 | pane->type = PANE_TEXT; 708 | 709 | text_update(text_pane, width, height); 710 | 711 | return pane; 712 | } 713 | -------------------------------------------------------------------------------- /lempar.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** 2000-05-29 3 | ** 4 | ** The author disclaims copyright to this source code. In place of 5 | ** a legal notice, here is a blessing: 6 | ** 7 | ** May you do good and not evil. 8 | ** May you find forgiveness for yourself and forgive others. 9 | ** May you share freely, never taking more than you give. 10 | ** 11 | ************************************************************************* 12 | ** Driver template for the LEMON parser generator. 13 | ** 14 | ** The "lemon" program processes an LALR(1) input grammar file, then uses 15 | ** this template to construct a parser. The "lemon" program inserts text 16 | ** at each "%%" line. Also, any "P-a-r-s-e" identifer prefix (without the 17 | ** interstitial "-" characters) contained in this template is changed into 18 | ** the value of the %name directive from the grammar. Otherwise, the content 19 | ** of this template is copied straight through into the generate parser 20 | ** source file. 21 | ** 22 | ** The following is the concatenation of all %include directives from the 23 | ** input grammar file: 24 | */ 25 | /************ Begin %include sections from the grammar ************************/ 26 | %% 27 | /**************** End of %include directives **********************************/ 28 | /* These constants specify the various numeric values for terminal symbols. 29 | ***************** Begin token definitions *************************************/ 30 | %% 31 | /**************** End token definitions ***************************************/ 32 | 33 | /* The next sections is a series of control #defines. 34 | ** various aspects of the generated parser. 35 | ** YYCODETYPE is the data type used to store the integer codes 36 | ** that represent terminal and non-terminal symbols. 37 | ** "unsigned char" is used if there are fewer than 38 | ** 256 symbols. Larger types otherwise. 39 | ** YYNOCODE is a number of type YYCODETYPE that is not used for 40 | ** any terminal or nonterminal symbol. 41 | ** YYFALLBACK If defined, this indicates that one or more tokens 42 | ** (also known as: "terminal symbols") have fall-back 43 | ** values which should be used if the original symbol 44 | ** would not parse. This permits keywords to sometimes 45 | ** be used as identifiers, for example. 46 | ** YYACTIONTYPE is the data type used for "action codes" - numbers 47 | ** that indicate what to do in response to the next 48 | ** token. 49 | ** ParseTOKENTYPE is the data type used for minor type for terminal 50 | ** symbols. Background: A "minor type" is a semantic 51 | ** value associated with a terminal or non-terminal 52 | ** symbols. For example, for an "ID" terminal symbol, 53 | ** the minor type might be the name of the identifier. 54 | ** Each non-terminal can have a different minor type. 55 | ** Terminal symbols all have the same minor type, though. 56 | ** This macros defines the minor type for terminal 57 | ** symbols. 58 | ** YYMINORTYPE is the data type used for all minor types. 59 | ** This is typically a union of many types, one of 60 | ** which is ParseTOKENTYPE. The entry in the union 61 | ** for terminal symbols is called "yy0". 62 | ** YYSTACKDEPTH is the maximum depth of the parser's stack. If 63 | ** zero the stack is dynamically sized using realloc() 64 | ** ParseARG_SDECL A static variable declaration for the %extra_argument 65 | ** ParseARG_PDECL A parameter declaration for the %extra_argument 66 | ** ParseARG_PARAM Code to pass %extra_argument as a subroutine parameter 67 | ** ParseARG_STORE Code to store %extra_argument into yypParser 68 | ** ParseARG_FETCH Code to extract %extra_argument from yypParser 69 | ** ParseCTX_* As ParseARG_ except for %extra_context 70 | ** YYERRORSYMBOL is the code number of the error symbol. If not 71 | ** defined, then do no error processing. 72 | ** YYNSTATE the combined number of states. 73 | ** YYNRULE the number of rules in the grammar 74 | ** YYNTOKEN Number of terminal symbols 75 | ** YY_MAX_SHIFT Maximum value for shift actions 76 | ** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions 77 | ** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions 78 | ** YY_ERROR_ACTION The yy_action[] code for syntax error 79 | ** YY_ACCEPT_ACTION The yy_action[] code for accept 80 | ** YY_NO_ACTION The yy_action[] code for no-op 81 | ** YY_MIN_REDUCE Minimum value for reduce actions 82 | ** YY_MAX_REDUCE Maximum value for reduce actions 83 | */ 84 | #ifndef INTERFACE 85 | # define INTERFACE 1 86 | #endif 87 | /************* Begin control #defines *****************************************/ 88 | %% 89 | /************* End control #defines *******************************************/ 90 | #define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0]))) 91 | 92 | /* Define the yytestcase() macro to be a no-op if is not already defined 93 | ** otherwise. 94 | ** 95 | ** Applications can choose to define yytestcase() in the %include section 96 | ** to a macro that can assist in verifying code coverage. For production 97 | ** code the yytestcase() macro should be turned off. But it is useful 98 | ** for testing. 99 | */ 100 | #ifndef yytestcase 101 | # define yytestcase(X) 102 | #endif 103 | 104 | 105 | /* Next are the tables used to determine what action to take based on the 106 | ** current state and lookahead token. These tables are used to implement 107 | ** functions that take a state number and lookahead value and return an 108 | ** action integer. 109 | ** 110 | ** Suppose the action integer is N. Then the action is determined as 111 | ** follows 112 | ** 113 | ** 0 <= N <= YY_MAX_SHIFT Shift N. That is, push the lookahead 114 | ** token onto the stack and goto state N. 115 | ** 116 | ** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then 117 | ** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE. 118 | ** 119 | ** N == YY_ERROR_ACTION A syntax error has occurred. 120 | ** 121 | ** N == YY_ACCEPT_ACTION The parser accepts its input. 122 | ** 123 | ** N == YY_NO_ACTION No such action. Denotes unused 124 | ** slots in the yy_action[] table. 125 | ** 126 | ** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE 127 | ** and YY_MAX_REDUCE 128 | ** 129 | ** The action table is constructed as a single large table named yy_action[]. 130 | ** Given state S and lookahead X, the action is computed as either: 131 | ** 132 | ** (A) N = yy_action[ yy_shift_ofst[S] + X ] 133 | ** (B) N = yy_default[S] 134 | ** 135 | ** The (A) formula is preferred. The B formula is used instead if 136 | ** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X. 137 | ** 138 | ** The formulas above are for computing the action when the lookahead is 139 | ** a terminal symbol. If the lookahead is a non-terminal (as occurs after 140 | ** a reduce action) then the yy_reduce_ofst[] array is used in place of 141 | ** the yy_shift_ofst[] array. 142 | ** 143 | ** The following are the tables generated in this section: 144 | ** 145 | ** yy_action[] A single table containing all actions. 146 | ** yy_lookahead[] A table containing the lookahead for each entry in 147 | ** yy_action. Used to detect hash collisions. 148 | ** yy_shift_ofst[] For each state, the offset into yy_action for 149 | ** shifting terminals. 150 | ** yy_reduce_ofst[] For each state, the offset into yy_action for 151 | ** shifting non-terminals after a reduce. 152 | ** yy_default[] Default action for each state. 153 | ** 154 | *********** Begin parsing tables **********************************************/ 155 | %% 156 | /********** End of lemon-generated parsing tables *****************************/ 157 | 158 | /* The next table maps tokens (terminal symbols) into fallback tokens. 159 | ** If a construct like the following: 160 | ** 161 | ** %fallback ID X Y Z. 162 | ** 163 | ** appears in the grammar, then ID becomes a fallback token for X, Y, 164 | ** and Z. Whenever one of the tokens X, Y, or Z is input to the parser 165 | ** but it does not parse, the type of the token is changed to ID and 166 | ** the parse is retried before an error is thrown. 167 | ** 168 | ** This feature can be used, for example, to cause some keywords in a language 169 | ** to revert to identifiers if they keyword does not apply in the context where 170 | ** it appears. 171 | */ 172 | #ifdef YYFALLBACK 173 | static const YYCODETYPE yyFallback[] = { 174 | %% 175 | }; 176 | #endif /* YYFALLBACK */ 177 | 178 | /* The following structure represents a single element of the 179 | ** parser's stack. Information stored includes: 180 | ** 181 | ** + The state number for the parser at this level of the stack. 182 | ** 183 | ** + The value of the token stored at this level of the stack. 184 | ** (In other words, the "major" token.) 185 | ** 186 | ** + The semantic value stored at this level of the stack. This is 187 | ** the information used by the action routines in the grammar. 188 | ** It is sometimes called the "minor" token. 189 | ** 190 | ** After the "shift" half of a SHIFTREDUCE action, the stateno field 191 | ** actually contains the reduce action for the second half of the 192 | ** SHIFTREDUCE. 193 | */ 194 | struct yyStackEntry { 195 | YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */ 196 | YYCODETYPE major; /* The major token value. This is the code 197 | ** number for the token at this stack level */ 198 | YYMINORTYPE minor; /* The user-supplied minor token value. This 199 | ** is the value of the token */ 200 | }; 201 | typedef struct yyStackEntry yyStackEntry; 202 | 203 | /* The state of the parser is completely contained in an instance of 204 | ** the following structure */ 205 | struct yyParser { 206 | yyStackEntry *yytos; /* Pointer to top element of the stack */ 207 | #ifdef YYTRACKMAXSTACKDEPTH 208 | int yyhwm; /* High-water mark of the stack */ 209 | #endif 210 | #ifndef YYNOERRORRECOVERY 211 | int yyerrcnt; /* Shifts left before out of the error */ 212 | #endif 213 | ParseARG_SDECL /* A place to hold %extra_argument */ 214 | ParseCTX_SDECL /* A place to hold %extra_context */ 215 | #if YYSTACKDEPTH<=0 216 | int yystksz; /* Current side of the stack */ 217 | yyStackEntry *yystack; /* The parser's stack */ 218 | yyStackEntry yystk0; /* First stack entry */ 219 | #else 220 | yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ 221 | yyStackEntry *yystackEnd; /* Last entry in the stack */ 222 | #endif 223 | }; 224 | typedef struct yyParser yyParser; 225 | 226 | #ifndef NDEBUG 227 | #include 228 | #include 229 | static FILE *yyTraceFILE = 0; 230 | static char *yyTracePrompt = 0; 231 | #endif /* NDEBUG */ 232 | 233 | #ifndef NDEBUG 234 | /* 235 | ** Turn parser tracing on by giving a stream to which to write the trace 236 | ** and a prompt to preface each trace message. Tracing is turned off 237 | ** by making either argument NULL 238 | ** 239 | ** Inputs: 240 | **
    241 | **
  • A FILE* to which trace output should be written. 242 | ** If NULL, then tracing is turned off. 243 | **
  • A prefix string written at the beginning of every 244 | ** line of trace output. If NULL, then tracing is 245 | ** turned off. 246 | **
247 | ** 248 | ** Outputs: 249 | ** None. 250 | */ 251 | void ParseTrace(FILE *TraceFILE, char *zTracePrompt){ 252 | yyTraceFILE = TraceFILE; 253 | yyTracePrompt = zTracePrompt; 254 | if( yyTraceFILE==0 ) yyTracePrompt = 0; 255 | else if( yyTracePrompt==0 ) yyTraceFILE = 0; 256 | } 257 | #endif /* NDEBUG */ 258 | 259 | #if defined(YYCOVERAGE) || !defined(NDEBUG) 260 | /* For tracing shifts, the names of all terminals and nonterminals 261 | ** are required. The following table supplies these names */ 262 | static const char *const yyTokenName[] = { 263 | %% 264 | }; 265 | #endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */ 266 | 267 | #ifndef NDEBUG 268 | /* For tracing reduce actions, the names of all rules are required. 269 | */ 270 | static const char *const yyRuleName[] = { 271 | %% 272 | }; 273 | #endif /* NDEBUG */ 274 | 275 | 276 | #if YYSTACKDEPTH<=0 277 | /* 278 | ** Try to increase the size of the parser stack. Return the number 279 | ** of errors. Return 0 on success. 280 | */ 281 | static int yyGrowStack(yyParser *p){ 282 | int newSize; 283 | int idx; 284 | yyStackEntry *pNew; 285 | 286 | newSize = p->yystksz*2 + 100; 287 | idx = p->yytos ? (int)(p->yytos - p->yystack) : 0; 288 | if( p->yystack==&p->yystk0 ){ 289 | pNew = malloc(newSize*sizeof(pNew[0])); 290 | if( pNew ) pNew[0] = p->yystk0; 291 | }else{ 292 | pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); 293 | } 294 | if( pNew ){ 295 | p->yystack = pNew; 296 | p->yytos = &p->yystack[idx]; 297 | #ifndef NDEBUG 298 | if( yyTraceFILE ){ 299 | fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n", 300 | yyTracePrompt, p->yystksz, newSize); 301 | } 302 | #endif 303 | p->yystksz = newSize; 304 | } 305 | return pNew==0; 306 | } 307 | #endif 308 | 309 | /* Datatype of the argument to the memory allocated passed as the 310 | ** second argument to ParseAlloc() below. This can be changed by 311 | ** putting an appropriate #define in the %include section of the input 312 | ** grammar. 313 | */ 314 | #ifndef YYMALLOCARGTYPE 315 | # define YYMALLOCARGTYPE size_t 316 | #endif 317 | 318 | /* Initialize a new parser that has already been allocated. 319 | */ 320 | void ParseInit(void *yypRawParser ParseCTX_PDECL){ 321 | yyParser *yypParser = (yyParser*)yypRawParser; 322 | ParseCTX_STORE 323 | #ifdef YYTRACKMAXSTACKDEPTH 324 | yypParser->yyhwm = 0; 325 | #endif 326 | #if YYSTACKDEPTH<=0 327 | yypParser->yytos = NULL; 328 | yypParser->yystack = NULL; 329 | yypParser->yystksz = 0; 330 | if( yyGrowStack(yypParser) ){ 331 | yypParser->yystack = &yypParser->yystk0; 332 | yypParser->yystksz = 1; 333 | } 334 | #endif 335 | #ifndef YYNOERRORRECOVERY 336 | yypParser->yyerrcnt = -1; 337 | #endif 338 | yypParser->yytos = yypParser->yystack; 339 | yypParser->yystack[0].stateno = 0; 340 | yypParser->yystack[0].major = 0; 341 | #if YYSTACKDEPTH>0 342 | yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1]; 343 | #endif 344 | } 345 | 346 | #ifndef Parse_ENGINEALWAYSONSTACK 347 | /* 348 | ** This function allocates a new parser. 349 | ** The only argument is a pointer to a function which works like 350 | ** malloc. 351 | ** 352 | ** Inputs: 353 | ** A pointer to the function used to allocate memory. 354 | ** 355 | ** Outputs: 356 | ** A pointer to a parser. This pointer is used in subsequent calls 357 | ** to Parse and ParseFree. 358 | */ 359 | void *ParseAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) ParseCTX_PDECL){ 360 | yyParser *yypParser; 361 | yypParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) ); 362 | if( yypParser ){ 363 | ParseCTX_STORE 364 | ParseInit(yypParser ParseCTX_PARAM); 365 | } 366 | return (void*)yypParser; 367 | } 368 | #endif /* Parse_ENGINEALWAYSONSTACK */ 369 | 370 | 371 | /* The following function deletes the "minor type" or semantic value 372 | ** associated with a symbol. The symbol can be either a terminal 373 | ** or nonterminal. "yymajor" is the symbol code, and "yypminor" is 374 | ** a pointer to the value to be deleted. The code used to do the 375 | ** deletions is derived from the %destructor and/or %token_destructor 376 | ** directives of the input grammar. 377 | */ 378 | static void yy_destructor( 379 | yyParser *yypParser, /* The parser */ 380 | YYCODETYPE yymajor, /* Type code for object to destroy */ 381 | YYMINORTYPE *yypminor /* The object to be destroyed */ 382 | ){ 383 | ParseARG_FETCH 384 | ParseCTX_FETCH 385 | switch( yymajor ){ 386 | /* Here is inserted the actions which take place when a 387 | ** terminal or non-terminal is destroyed. This can happen 388 | ** when the symbol is popped from the stack during a 389 | ** reduce or during error processing or when a parser is 390 | ** being destroyed before it is finished parsing. 391 | ** 392 | ** Note: during a reduce, the only symbols destroyed are those 393 | ** which appear on the RHS of the rule, but which are *not* used 394 | ** inside the C code. 395 | */ 396 | /********* Begin destructor definitions ***************************************/ 397 | %% 398 | /********* End destructor definitions *****************************************/ 399 | default: break; /* If no destructor action specified: do nothing */ 400 | } 401 | } 402 | 403 | /* 404 | ** Pop the parser's stack once. 405 | ** 406 | ** If there is a destructor routine associated with the token which 407 | ** is popped from the stack, then call it. 408 | */ 409 | static void yy_pop_parser_stack(yyParser *pParser){ 410 | yyStackEntry *yytos; 411 | assert( pParser->yytos!=0 ); 412 | assert( pParser->yytos > pParser->yystack ); 413 | yytos = pParser->yytos--; 414 | #ifndef NDEBUG 415 | if( yyTraceFILE ){ 416 | fprintf(yyTraceFILE,"%sPopping %s\n", 417 | yyTracePrompt, 418 | yyTokenName[yytos->major]); 419 | } 420 | #endif 421 | yy_destructor(pParser, yytos->major, &yytos->minor); 422 | } 423 | 424 | /* 425 | ** Clear all secondary memory allocations from the parser 426 | */ 427 | void ParseFinalize(void *p){ 428 | yyParser *pParser = (yyParser*)p; 429 | while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser); 430 | #if YYSTACKDEPTH<=0 431 | if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack); 432 | #endif 433 | } 434 | 435 | #ifndef Parse_ENGINEALWAYSONSTACK 436 | /* 437 | ** Deallocate and destroy a parser. Destructors are called for 438 | ** all stack elements before shutting the parser down. 439 | ** 440 | ** If the YYPARSEFREENEVERNULL macro exists (for example because it 441 | ** is defined in a %include section of the input grammar) then it is 442 | ** assumed that the input pointer is never NULL. 443 | */ 444 | void ParseFree( 445 | void *p, /* The parser to be deleted */ 446 | void (*freeProc)(void*) /* Function used to reclaim memory */ 447 | ){ 448 | #ifndef YYPARSEFREENEVERNULL 449 | if( p==0 ) return; 450 | #endif 451 | ParseFinalize(p); 452 | (*freeProc)(p); 453 | } 454 | #endif /* Parse_ENGINEALWAYSONSTACK */ 455 | 456 | /* 457 | ** Return the peak depth of the stack for a parser. 458 | */ 459 | #ifdef YYTRACKMAXSTACKDEPTH 460 | int ParseStackPeak(void *p){ 461 | yyParser *pParser = (yyParser*)p; 462 | return pParser->yyhwm; 463 | } 464 | #endif 465 | 466 | /* This array of booleans keeps track of the parser statement 467 | ** coverage. The element yycoverage[X][Y] is set when the parser 468 | ** is in state X and has a lookahead token Y. In a well-tested 469 | ** systems, every element of this matrix should end up being set. 470 | */ 471 | #if defined(YYCOVERAGE) 472 | static unsigned char yycoverage[YYNSTATE][YYNTOKEN]; 473 | #endif 474 | 475 | /* 476 | ** Write into out a description of every state/lookahead combination that 477 | ** 478 | ** (1) has not been used by the parser, and 479 | ** (2) is not a syntax error. 480 | ** 481 | ** Return the number of missed state/lookahead combinations. 482 | */ 483 | #if defined(YYCOVERAGE) 484 | int ParseCoverage(FILE *out){ 485 | int stateno, iLookAhead, i; 486 | int nMissed = 0; 487 | for(stateno=0; statenoYY_MAX_SHIFT ) return stateno; 514 | assert( stateno <= YY_SHIFT_COUNT ); 515 | #if defined(YYCOVERAGE) 516 | yycoverage[stateno][iLookAhead] = 1; 517 | #endif 518 | do{ 519 | i = yy_shift_ofst[stateno]; 520 | assert( i>=0 ); 521 | assert( i<=YY_ACTTAB_COUNT ); 522 | assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD ); 523 | assert( iLookAhead!=YYNOCODE ); 524 | assert( iLookAhead < YYNTOKEN ); 525 | i += iLookAhead; 526 | assert( i<(int)YY_NLOOKAHEAD ); 527 | if( yy_lookahead[i]!=iLookAhead ){ 528 | #ifdef YYFALLBACK 529 | YYCODETYPE iFallback; /* Fallback token */ 530 | assert( iLookAhead %s\n", 536 | yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); 537 | } 538 | #endif 539 | assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ 540 | iLookAhead = iFallback; 541 | continue; 542 | } 543 | #endif 544 | #ifdef YYWILDCARD 545 | { 546 | int j = i - iLookAhead + YYWILDCARD; 547 | assert( j<(int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])) ); 548 | if( yy_lookahead[j]==YYWILDCARD && iLookAhead>0 ){ 549 | #ifndef NDEBUG 550 | if( yyTraceFILE ){ 551 | fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", 552 | yyTracePrompt, yyTokenName[iLookAhead], 553 | yyTokenName[YYWILDCARD]); 554 | } 555 | #endif /* NDEBUG */ 556 | return yy_action[j]; 557 | } 558 | } 559 | #endif /* YYWILDCARD */ 560 | return yy_default[stateno]; 561 | }else{ 562 | assert( i>=0 && i<(int)(sizeof(yy_action)/sizeof(yy_action[0])) ); 563 | return yy_action[i]; 564 | } 565 | }while(1); 566 | } 567 | 568 | /* 569 | ** Find the appropriate action for a parser given the non-terminal 570 | ** look-ahead token iLookAhead. 571 | */ 572 | static YYACTIONTYPE yy_find_reduce_action( 573 | YYACTIONTYPE stateno, /* Current state number */ 574 | YYCODETYPE iLookAhead /* The look-ahead token */ 575 | ){ 576 | int i; 577 | #ifdef YYERRORSYMBOL 578 | if( stateno>YY_REDUCE_COUNT ){ 579 | return yy_default[stateno]; 580 | } 581 | #else 582 | assert( stateno<=YY_REDUCE_COUNT ); 583 | #endif 584 | i = yy_reduce_ofst[stateno]; 585 | assert( iLookAhead!=YYNOCODE ); 586 | i += iLookAhead; 587 | #ifdef YYERRORSYMBOL 588 | if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ 589 | return yy_default[stateno]; 590 | } 591 | #else 592 | assert( i>=0 && iyytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); 610 | /* Here code is inserted which will execute if the parser 611 | ** stack every overflows */ 612 | /******** Begin %stack_overflow code ******************************************/ 613 | %% 614 | /******** End %stack_overflow code ********************************************/ 615 | ParseARG_STORE /* Suppress warning about unused %extra_argument var */ 616 | ParseCTX_STORE 617 | } 618 | 619 | /* 620 | ** Print tracing information for a SHIFT action 621 | */ 622 | #ifndef NDEBUG 623 | static void yyTraceShift(yyParser *yypParser, int yyNewState, const char *zTag){ 624 | if( yyTraceFILE ){ 625 | if( yyNewStateyytos->major], 628 | yyNewState); 629 | }else{ 630 | fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n", 631 | yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major], 632 | yyNewState - YY_MIN_REDUCE); 633 | } 634 | } 635 | } 636 | #else 637 | # define yyTraceShift(X,Y,Z) 638 | #endif 639 | 640 | /* 641 | ** Perform a shift action. 642 | */ 643 | static void yy_shift( 644 | yyParser *yypParser, /* The parser to be shifted */ 645 | YYACTIONTYPE yyNewState, /* The new state to shift in */ 646 | YYCODETYPE yyMajor, /* The major token to shift in */ 647 | ParseTOKENTYPE yyMinor /* The minor token to shift in */ 648 | ){ 649 | yyStackEntry *yytos; 650 | yypParser->yytos++; 651 | #ifdef YYTRACKMAXSTACKDEPTH 652 | if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){ 653 | yypParser->yyhwm++; 654 | assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) ); 655 | } 656 | #endif 657 | #if YYSTACKDEPTH>0 658 | if( yypParser->yytos>yypParser->yystackEnd ){ 659 | yypParser->yytos--; 660 | yyStackOverflow(yypParser); 661 | return; 662 | } 663 | #else 664 | if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){ 665 | if( yyGrowStack(yypParser) ){ 666 | yypParser->yytos--; 667 | yyStackOverflow(yypParser); 668 | return; 669 | } 670 | } 671 | #endif 672 | if( yyNewState > YY_MAX_SHIFT ){ 673 | yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; 674 | } 675 | yytos = yypParser->yytos; 676 | yytos->stateno = yyNewState; 677 | yytos->major = yyMajor; 678 | yytos->minor.yy0 = yyMinor; 679 | yyTraceShift(yypParser, yyNewState, "Shift"); 680 | } 681 | 682 | /* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side 683 | ** of that rule */ 684 | static const YYCODETYPE yyRuleInfoLhs[] = { 685 | %% 686 | }; 687 | 688 | /* For rule J, yyRuleInfoNRhs[J] contains the negative of the number 689 | ** of symbols on the right-hand side of that rule. */ 690 | static const signed char yyRuleInfoNRhs[] = { 691 | %% 692 | }; 693 | 694 | static void yy_accept(yyParser*); /* Forward Declaration */ 695 | 696 | /* 697 | ** Perform a reduce action and the shift that must immediately 698 | ** follow the reduce. 699 | ** 700 | ** The yyLookahead and yyLookaheadToken parameters provide reduce actions 701 | ** access to the lookahead token (if any). The yyLookahead will be YYNOCODE 702 | ** if the lookahead token has already been consumed. As this procedure is 703 | ** only called from one place, optimizing compilers will in-line it, which 704 | ** means that the extra parameters have no performance impact. 705 | */ 706 | static YYACTIONTYPE yy_reduce( 707 | yyParser *yypParser, /* The parser */ 708 | unsigned int yyruleno, /* Number of the rule by which to reduce */ 709 | int yyLookahead, /* Lookahead token, or YYNOCODE if none */ 710 | ParseTOKENTYPE yyLookaheadToken /* Value of the lookahead token */ 711 | ParseCTX_PDECL /* %extra_context */ 712 | ){ 713 | int yygoto; /* The next state */ 714 | YYACTIONTYPE yyact; /* The next action */ 715 | yyStackEntry *yymsp; /* The top of the parser's stack */ 716 | int yysize; /* Amount to pop the stack */ 717 | ParseARG_FETCH 718 | (void)yyLookahead; 719 | (void)yyLookaheadToken; 720 | yymsp = yypParser->yytos; 721 | assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); 722 | #ifndef NDEBUG 723 | if( yyTraceFILE ){ 724 | yysize = yyRuleInfoNRhs[yyruleno]; 725 | if( yysize ){ 726 | fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n", 727 | yyTracePrompt, 728 | yyruleno, yyRuleName[yyruleno], 729 | yyrulenoyytos - yypParser->yystack)>yypParser->yyhwm ){ 745 | yypParser->yyhwm++; 746 | assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack)); 747 | } 748 | #endif 749 | #if YYSTACKDEPTH>0 750 | if( yypParser->yytos>=yypParser->yystackEnd ){ 751 | yyStackOverflow(yypParser); 752 | /* The call to yyStackOverflow() above pops the stack until it is 753 | ** empty, causing the main parser loop to exit. So the return value 754 | ** is never used and does not matter. */ 755 | return 0; 756 | } 757 | #else 758 | if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){ 759 | if( yyGrowStack(yypParser) ){ 760 | yyStackOverflow(yypParser); 761 | /* The call to yyStackOverflow() above pops the stack until it is 762 | ** empty, causing the main parser loop to exit. So the return value 763 | ** is never used and does not matter. */ 764 | return 0; 765 | } 766 | yymsp = yypParser->yytos; 767 | } 768 | #endif 769 | } 770 | 771 | switch( yyruleno ){ 772 | /* Beginning here are the reduction cases. A typical example 773 | ** follows: 774 | ** case 0: 775 | ** #line 776 | ** { ... } // User supplied code 777 | ** #line 778 | ** break; 779 | */ 780 | /********** Begin reduce actions **********************************************/ 781 | %% 782 | /********** End reduce actions ************************************************/ 783 | }; 784 | assert( yyrulenoYY_MAX_SHIFT && yyact<=YY_MAX_SHIFTREDUCE) ); 792 | 793 | /* It is not possible for a REDUCE to be followed by an error */ 794 | assert( yyact!=YY_ERROR_ACTION ); 795 | 796 | yymsp += yysize+1; 797 | yypParser->yytos = yymsp; 798 | yymsp->stateno = (YYACTIONTYPE)yyact; 799 | yymsp->major = (YYCODETYPE)yygoto; 800 | yyTraceShift(yypParser, yyact, "... then shift"); 801 | return yyact; 802 | } 803 | 804 | /* 805 | ** The following code executes when the parse fails 806 | */ 807 | #ifndef YYNOERRORRECOVERY 808 | static void yy_parse_failed( 809 | yyParser *yypParser /* The parser */ 810 | ){ 811 | ParseARG_FETCH 812 | ParseCTX_FETCH 813 | #ifndef NDEBUG 814 | if( yyTraceFILE ){ 815 | fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); 816 | } 817 | #endif 818 | while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); 819 | /* Here code is inserted which will be executed whenever the 820 | ** parser fails */ 821 | /************ Begin %parse_failure code ***************************************/ 822 | %% 823 | /************ End %parse_failure code *****************************************/ 824 | ParseARG_STORE /* Suppress warning about unused %extra_argument variable */ 825 | ParseCTX_STORE 826 | } 827 | #endif /* YYNOERRORRECOVERY */ 828 | 829 | /* 830 | ** The following code executes when a syntax error first occurs. 831 | */ 832 | static void yy_syntax_error( 833 | yyParser *yypParser, /* The parser */ 834 | int yymajor, /* The major type of the error token */ 835 | ParseTOKENTYPE yyminor /* The minor type of the error token */ 836 | ){ 837 | ParseARG_FETCH 838 | ParseCTX_FETCH 839 | #define TOKEN yyminor 840 | /************ Begin %syntax_error code ****************************************/ 841 | %% 842 | /************ End %syntax_error code ******************************************/ 843 | ParseARG_STORE /* Suppress warning about unused %extra_argument variable */ 844 | ParseCTX_STORE 845 | } 846 | 847 | /* 848 | ** The following is executed when the parser accepts 849 | */ 850 | static void yy_accept( 851 | yyParser *yypParser /* The parser */ 852 | ){ 853 | ParseARG_FETCH 854 | ParseCTX_FETCH 855 | #ifndef NDEBUG 856 | if( yyTraceFILE ){ 857 | fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); 858 | } 859 | #endif 860 | #ifndef YYNOERRORRECOVERY 861 | yypParser->yyerrcnt = -1; 862 | #endif 863 | assert( yypParser->yytos==yypParser->yystack ); 864 | /* Here code is inserted which will be executed whenever the 865 | ** parser accepts */ 866 | /*********** Begin %parse_accept code *****************************************/ 867 | %% 868 | /*********** End %parse_accept code *******************************************/ 869 | ParseARG_STORE /* Suppress warning about unused %extra_argument variable */ 870 | ParseCTX_STORE 871 | } 872 | 873 | /* The main parser program. 874 | ** The first argument is a pointer to a structure obtained from 875 | ** "ParseAlloc" which describes the current state of the parser. 876 | ** The second argument is the major token number. The third is 877 | ** the minor token. The fourth optional argument is whatever the 878 | ** user wants (and specified in the grammar) and is available for 879 | ** use by the action routines. 880 | ** 881 | ** Inputs: 882 | **
    883 | **
  • A pointer to the parser (an opaque structure.) 884 | **
  • The major token number. 885 | **
  • The minor token number. 886 | **
  • An option argument of a grammar-specified type. 887 | **
888 | ** 889 | ** Outputs: 890 | ** None. 891 | */ 892 | void Parse( 893 | void *yyp, /* The parser */ 894 | int yymajor, /* The major token code number */ 895 | ParseTOKENTYPE yyminor /* The value for the token */ 896 | ParseARG_PDECL /* Optional %extra_argument parameter */ 897 | ){ 898 | YYMINORTYPE yyminorunion; 899 | YYACTIONTYPE yyact; /* The parser action. */ 900 | #if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) 901 | int yyendofinput; /* True if we are at the end of input */ 902 | #endif 903 | #ifdef YYERRORSYMBOL 904 | int yyerrorhit = 0; /* True if yymajor has invoked an error */ 905 | #endif 906 | yyParser *yypParser = (yyParser*)yyp; /* The parser */ 907 | ParseCTX_FETCH 908 | ParseARG_STORE 909 | 910 | assert( yypParser->yytos!=0 ); 911 | #if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) 912 | yyendofinput = (yymajor==0); 913 | #endif 914 | 915 | yyact = yypParser->yytos->stateno; 916 | #ifndef NDEBUG 917 | if( yyTraceFILE ){ 918 | if( yyact < YY_MIN_REDUCE ){ 919 | fprintf(yyTraceFILE,"%sInput '%s' in state %d\n", 920 | yyTracePrompt,yyTokenName[yymajor],yyact); 921 | }else{ 922 | fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n", 923 | yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE); 924 | } 925 | } 926 | #endif 927 | 928 | do{ 929 | assert( yyact==yypParser->yytos->stateno ); 930 | yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact); 931 | if( yyact >= YY_MIN_REDUCE ){ 932 | yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor, 933 | yyminor ParseCTX_PARAM); 934 | }else if( yyact <= YY_MAX_SHIFTREDUCE ){ 935 | yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor); 936 | #ifndef YYNOERRORRECOVERY 937 | yypParser->yyerrcnt--; 938 | #endif 939 | break; 940 | }else if( yyact==YY_ACCEPT_ACTION ){ 941 | yypParser->yytos--; 942 | yy_accept(yypParser); 943 | return; 944 | }else{ 945 | assert( yyact == YY_ERROR_ACTION ); 946 | yyminorunion.yy0 = yyminor; 947 | #ifdef YYERRORSYMBOL 948 | int yymx; 949 | #endif 950 | #ifndef NDEBUG 951 | if( yyTraceFILE ){ 952 | fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); 953 | } 954 | #endif 955 | #ifdef YYERRORSYMBOL 956 | /* A syntax error has occurred. 957 | ** The response to an error depends upon whether or not the 958 | ** grammar defines an error token "ERROR". 959 | ** 960 | ** This is what we do if the grammar does define ERROR: 961 | ** 962 | ** * Call the %syntax_error function. 963 | ** 964 | ** * Begin popping the stack until we enter a state where 965 | ** it is legal to shift the error symbol, then shift 966 | ** the error symbol. 967 | ** 968 | ** * Set the error count to three. 969 | ** 970 | ** * Begin accepting and shifting new tokens. No new error 971 | ** processing will occur until three tokens have been 972 | ** shifted successfully. 973 | ** 974 | */ 975 | if( yypParser->yyerrcnt<0 ){ 976 | yy_syntax_error(yypParser,yymajor,yyminor); 977 | } 978 | yymx = yypParser->yytos->major; 979 | if( yymx==YYERRORSYMBOL || yyerrorhit ){ 980 | #ifndef NDEBUG 981 | if( yyTraceFILE ){ 982 | fprintf(yyTraceFILE,"%sDiscard input token %s\n", 983 | yyTracePrompt,yyTokenName[yymajor]); 984 | } 985 | #endif 986 | yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); 987 | yymajor = YYNOCODE; 988 | }else{ 989 | while( yypParser->yytos >= yypParser->yystack 990 | && (yyact = yy_find_reduce_action( 991 | yypParser->yytos->stateno, 992 | YYERRORSYMBOL)) > YY_MAX_SHIFTREDUCE 993 | ){ 994 | yy_pop_parser_stack(yypParser); 995 | } 996 | if( yypParser->yytos < yypParser->yystack || yymajor==0 ){ 997 | yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); 998 | yy_parse_failed(yypParser); 999 | #ifndef YYNOERRORRECOVERY 1000 | yypParser->yyerrcnt = -1; 1001 | #endif 1002 | yymajor = YYNOCODE; 1003 | }else if( yymx!=YYERRORSYMBOL ){ 1004 | yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor); 1005 | } 1006 | } 1007 | yypParser->yyerrcnt = 3; 1008 | yyerrorhit = 1; 1009 | if( yymajor==YYNOCODE ) break; 1010 | yyact = yypParser->yytos->stateno; 1011 | #elif defined(YYNOERRORRECOVERY) 1012 | /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to 1013 | ** do any kind of error recovery. Instead, simply invoke the syntax 1014 | ** error routine and continue going as if nothing had happened. 1015 | ** 1016 | ** Applications can set this macro (for example inside %include) if 1017 | ** they intend to abandon the parse upon the first syntax error seen. 1018 | */ 1019 | yy_syntax_error(yypParser,yymajor, yyminor); 1020 | yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); 1021 | break; 1022 | #else /* YYERRORSYMBOL is not defined */ 1023 | /* This is what we do if the grammar does not define ERROR: 1024 | ** 1025 | ** * Report an error message, and throw away the input token. 1026 | ** 1027 | ** * If the input token is $, then fail the parse. 1028 | ** 1029 | ** As before, subsequent error messages are suppressed until 1030 | ** three input tokens have been successfully shifted. 1031 | */ 1032 | if( yypParser->yyerrcnt<=0 ){ 1033 | yy_syntax_error(yypParser,yymajor, yyminor); 1034 | } 1035 | yypParser->yyerrcnt = 3; 1036 | yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); 1037 | if( yyendofinput ){ 1038 | yy_parse_failed(yypParser); 1039 | #ifndef YYNOERRORRECOVERY 1040 | yypParser->yyerrcnt = -1; 1041 | #endif 1042 | } 1043 | break; 1044 | #endif 1045 | } 1046 | }while( yypParser->yytos>yypParser->yystack ); 1047 | #ifndef NDEBUG 1048 | if( yyTraceFILE ){ 1049 | yyStackEntry *i; 1050 | char cDiv = '['; 1051 | fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt); 1052 | for(i=&yypParser->yystack[1]; i<=yypParser->yytos; i++){ 1053 | fprintf(yyTraceFILE,"%c%s", cDiv, yyTokenName[i->major]); 1054 | cDiv = ' '; 1055 | } 1056 | fprintf(yyTraceFILE,"]\n"); 1057 | } 1058 | #endif 1059 | return; 1060 | } 1061 | 1062 | /* 1063 | ** Return the fallback token corresponding to canonical token iToken, or 1064 | ** 0 if iToken has no fallback. 1065 | */ 1066 | int ParseFallback(int iToken){ 1067 | #ifdef YYFALLBACK 1068 | assert( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) ); 1069 | return yyFallback[iToken]; 1070 | #else 1071 | (void)iToken; 1072 | return 0; 1073 | #endif 1074 | } 1075 | --------------------------------------------------------------------------------