├── .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 | 
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 |
--------------------------------------------------------------------------------