commit 0eb2eea9a25d0b034bbc2082f66e5d75e757c51b
Author: Thomas Vigouroux <thomas.vigouroux@univ-grenoble-alpes.fr>
Date: Wed, 5 Jun 2024 09:04:41 +0200
Initial commit
Diffstat:
A | .gitignore | | | 2 | ++ |
A | build.zig | | | 94 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | build.zig.zon | | | 64 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/chunk.zig | | | 70 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/crossline.c | | | 1444 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/crossline.h | | | 178 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/main.zig | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/root.zig | | | 10 | ++++++++++ |
A | src/scan.zig | | | 101 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/value.zig | | | 94 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | src/vm.zig | | | 119 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
11 files changed, 2234 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+.zig-cache
+zig-out
diff --git a/build.zig b/build.zig
@@ -0,0 +1,94 @@
+const std = @import("std");
+
+// Although this function looks imperative, note that its job is to
+// declaratively construct a build graph that will be executed by an external
+// runner.
+pub fn build(b: *std.Build) void {
+ // Standard target options allows the person running `zig build` to choose
+ // what target to build for. Here we do not override the defaults, which
+ // means any target is allowed, and the default is native. Other options
+ // for restricting supported target set are available.
+ const target = b.standardTargetOptions(.{});
+
+ // Standard optimization options allow the person running `zig build` to select
+ // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
+ // set a preferred release mode, allowing the user to decide how to optimize.
+ const optimize = b.standardOptimizeOption(.{});
+
+ const lib = b.addStaticLibrary(.{
+ .name = "zahl",
+ // In this case the main source file is merely a path, however, in more
+ // complicated build scripts, this could be a generated file.
+ .root_source_file = b.path("src/root.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+
+ // This declares intent for the library to be installed into the standard
+ // location when the user invokes the "install" step (the default step when
+ // running `zig build`).
+ b.installArtifact(lib);
+
+ const exe = b.addExecutable(.{
+ .name = "zahl",
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+ exe.addCSourceFile(.{ .file = b.path("src/crossline.c"), .flags = &.{} });
+ exe.addIncludePath(b.path("src"));
+ exe.linkLibC();
+
+ // This declares intent for the executable to be installed into the
+ // standard location when the user invokes the "install" step (the default
+ // step when running `zig build`).
+ b.installArtifact(exe);
+
+ // This *creates* a Run step in the build graph, to be executed when another
+ // step is evaluated that depends on it. The next line below will establish
+ // such a dependency.
+ const run_cmd = b.addRunArtifact(exe);
+
+ // By making the run step depend on the install step, it will be run from the
+ // installation directory rather than directly from within the cache directory.
+ // This is not necessary, however, if the application depends on other installed
+ // files, this ensures they will be present and in the expected location.
+ run_cmd.step.dependOn(b.getInstallStep());
+
+ // This allows the user to pass arguments to the application in the build
+ // command itself, like this: `zig build run -- arg1 arg2 etc`
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ // This creates a build step. It will be visible in the `zig build --help` menu,
+ // and can be selected like this: `zig build run`
+ // This will evaluate the `run` step rather than the default, which is "install".
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ // Creates a step for unit testing. This only builds the test executable
+ // but does not run it.
+ const lib_unit_tests = b.addTest(.{
+ .root_source_file = b.path("src/root.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+
+ const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
+
+ const exe_unit_tests = b.addTest(.{
+ .root_source_file = b.path("src/main.zig"),
+ .target = target,
+ .optimize = optimize,
+ });
+
+ const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
+
+ // Similar to creating the run step earlier, this exposes a `test` step to
+ // the `zig build --help` menu, providing a way for the user to request
+ // running the unit tests.
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&run_lib_unit_tests.step);
+ test_step.dependOn(&run_exe_unit_tests.step);
+}
diff --git a/build.zig.zon b/build.zig.zon
@@ -0,0 +1,64 @@
+.{
+ .name = "zahl",
+ // This is a [Semantic Version](https://semver.org/).
+ // In a future version of Zig it will be used for package deduplication.
+ .version = "0.0.0",
+
+ // This field is optional.
+ // This is currently advisory only; Zig does not yet do anything
+ // with this value.
+ //.minimum_zig_version = "0.11.0",
+
+ // This field is optional.
+ // Each dependency must either provide a `url` and `hash`, or a `path`.
+ // `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
+ // Once all dependencies are fetched, `zig build` no longer requires
+ // internet connectivity.
+ .dependencies = .{
+ // See `zig fetch --save <url>` for a command-line interface for adding dependencies.
+ //.example = .{
+ // // When updating this field to a new URL, be sure to delete the corresponding
+ // // `hash`, otherwise you are communicating that you expect to find the old hash at
+ // // the new URL.
+ // .url = "https://example.com/foo.tar.gz",
+ //
+ // // This is computed from the file contents of the directory of files that is
+ // // obtained after fetching `url` and applying the inclusion rules given by
+ // // `paths`.
+ // //
+ // // This field is the source of truth; packages do not come from a `url`; they
+ // // come from a `hash`. `url` is just one of many possible mirrors for how to
+ // // obtain a package matching this `hash`.
+ // //
+ // // Uses the [multihash](https://multiformats.io/multihash/) format.
+ // .hash = "...",
+ //
+ // // When this is provided, the package is found in a directory relative to the
+ // // build root. In this case the package's hash is irrelevant and therefore not
+ // // computed. This field and `url` are mutually exclusive.
+ // .path = "foo",
+
+ // // When this is set to `true`, a package is declared to be lazily
+ // // fetched. This makes the dependency only get fetched if it is
+ // // actually used.
+ // .lazy = false,
+ //},
+ },
+
+ // Specifies the set of files and directories that are included in this package.
+ // Only files and directories listed here are included in the `hash` that
+ // is computed for this package. Only files listed here will remain on disk
+ // when using the zig package manager. As a rule of thumb, one should list
+ // files required for compilation plus any license(s).
+ // Paths are relative to the build root. Use the empty string (`""`) to refer to
+ // the build root itself.
+ // A directory listed here means that all files within, recursively, are included.
+ .paths = .{
+ "build.zig",
+ "build.zig.zon",
+ "src",
+ // For example...
+ //"LICENSE",
+ //"README.md",
+ },
+}
diff --git a/src/chunk.zig b/src/chunk.zig
@@ -0,0 +1,70 @@
+const std = @import("std");
+const value = @import("value.zig");
+
+pub const Op = union(enum) {
+ @"return",
+
+ @"constant": usize,
+
+ negate,
+ add,
+ substract,
+
+ multiply,
+};
+
+pub const Chunk = struct {
+ const Self = @This();
+
+ const Instrs = std.ArrayList(Op);
+ const Values = std.ArrayList(*value.Value);
+
+ instrs: Instrs,
+ values: Values,
+
+ pub fn init(all: std.mem.Allocator) Self {
+ return Self {
+ .instrs = Instrs.init(all),
+ .values = Values.init(all)
+ };
+ }
+
+ pub fn deinit(self: *Self) void {
+ self.instrs.deinit();
+
+ for (self.values.items) |val| {
+ val.unref();
+ }
+
+ self.values.deinit();
+ }
+
+ pub fn write(self: *Self, instr: Op) !void {
+ try self.instrs.append(instr);
+ }
+
+ pub fn addConstant(self: *Self, val: *value.Value) !usize {
+ try self.values.append(val);
+ return self.values.items.len - 1;
+ }
+
+ pub fn disassembleInstr(self: Self, instr: Op) void {
+ switch (instr) {
+ .@"constant" => |vindex| {
+ const num = self.values.items[vindex];
+ std.debug.print("constant {}\n", .{num.val.number});
+ },
+
+ inline .@"return", .negate, .add, .substract, .multiply => |_, tag| {
+ std.debug.print("{s}\n", .{@tagName(tag)});
+ },
+ }
+ }
+
+ pub fn disassemble(self: Self) void {
+ for (self.instrs.items) |instr| {
+ self.disassembleInstr(instr);
+ }
+ }
+};
+
diff --git a/src/crossline.c b/src/crossline.c
@@ -0,0 +1,1444 @@
+/* crossline.c -- Version 1.0
+ *
+ * Crossline is a small, self-contained, zero-config, MIT licensed,
+ * cross-platform, readline and libedit replacement.
+ *
+ * Press <F1> to get full shortcuts list.
+ *
+ * You can find the latest source code and description at:
+ *
+ * https://github.com/jcwangxp/crossline
+ *
+ * ------------------------------------------------------------------------
+ *
+ * MIT License
+ *
+ * Copyright (c) 2019, JC Wang (wang_junchuan@163.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * ------------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdint.h>
+
+#ifdef _WIN32
+ #include <io.h>
+ #include <conio.h>
+ #include <windows.h>
+ #ifndef STDIN_FILENO
+ #define STDIN_FILENO _fileno(stdin)
+ #define STDOUT_FILENO _fileno(stdout)
+ #endif
+ #define isatty _isatty
+ #define strcasecmp _stricmp
+ #define strncasecmp _strnicmp
+ static int s_crossline_win = 1;
+#else
+ #include <unistd.h>
+ #include <termios.h>
+ #include <fcntl.h>
+ #include <signal.h>
+ #include <sys/ioctl.h>
+ #include <sys/stat.h>
+ static int s_crossline_win = 0;
+#endif
+
+#include "crossline.h"
+
+/*****************************************************************************/
+
+// Default word delimiters for move and cut
+#define CROSS_DFT_DELIMITER " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
+
+#define CROSS_HISTORY_MAX_LINE 256 // Maximum history line number
+#define CROSS_HISTORY_BUF_LEN 4096 // History line length
+#define CROSS_HIS_MATCH_PAT_NUM 16 // History search pattern number
+
+#define CROSS_COMPLET_MAX_LINE 1024 // Maximum completion word number
+#define CROSS_COMPLET_WORD_LEN 64 // Completion word length
+#define CROSS_COMPLET_HELP_LEN 256 // Completion word's help length
+#define CROSS_COMPLET_HINT_LEN 128 // Completion syntax hints length
+
+// Make control-characters readable
+#define CTRL_KEY(key) (key - 0x40)
+// Build special key code for escape sequences
+#define ALT_KEY(key) (key + ((KEY_ESC+1)<<8))
+#define ESC_KEY3(ch) ((KEY_ESC<<8) + ch)
+#define ESC_KEY4(ch1,ch2) ((KEY_ESC<<8) + ((ch1)<<16) + ch2)
+#define ESC_KEY6(ch1,ch2,ch3) ((KEY_ESC<<8) + ((ch1)<<16) + ((ch2)<<24) + ch3)
+#define ESC_OKEY(ch) ((KEY_ESC<<8) + ('O'<<16) + ch)
+
+/*****************************************************************************/
+
+enum {
+ KEY_TAB = 9, // Autocomplete.
+ KEY_BACKSPACE = 8, // Delete character before cursor.
+ KEY_ENTER = 13, // Accept line. (Linux)
+ KEY_ENTER2 = 10, // Accept line. (Windows)
+ KEY_ESC = 27, // Escapce
+ KEY_DEL2 = 127, // It's treaded as Backspace is Linux
+ KEY_DEBUG = 30, // Ctrl-^ Enter keyboard debug mode
+
+#ifdef _WIN32 // Windows
+
+ KEY_INSERT = (KEY_ESC<<8) + 'R', // Paste last cut text.
+ KEY_DEL = (KEY_ESC<<8) + 'S', // Delete character under cursor.
+ KEY_HOME = (KEY_ESC<<8) + 'G', // Move cursor to start of line.
+ KEY_END = (KEY_ESC<<8) + 'O', // Move cursor to end of line.
+ KEY_PGUP = (KEY_ESC<<8) + 'I', // Move to first line in history.
+ KEY_PGDN = (KEY_ESC<<8) + 'Q', // Move to end of input history.
+ KEY_UP = (KEY_ESC<<8) + 'H', // Fetch previous line in history.
+ KEY_DOWN = (KEY_ESC<<8) + 'P', // Fetch next line in history.
+ KEY_LEFT = (KEY_ESC<<8) + 'K', // Move back a character.
+ KEY_RIGHT = (KEY_ESC<<8) + 'M', // Move forward a character.
+
+ KEY_CTRL_DEL = (KEY_ESC<<8) + 147, // Cut word following cursor.
+ KEY_CTRL_HOME = (KEY_ESC<<8) + 'w', // Cut from start of line to cursor.
+ KEY_CTRL_END = (KEY_ESC<<8) + 'u', // Cut from cursor to end of line.
+ KEY_CTRL_UP = (KEY_ESC<<8) + 141, // Uppercase current or following word.
+ KEY_CTRL_DOWN = (KEY_ESC<<8) + 145, // Lowercase current or following word.
+ KEY_CTRL_LEFT = (KEY_ESC<<8) + 's', // Move back a word.
+ KEY_CTRL_RIGHT = (KEY_ESC<<8) + 't', // Move forward a word.
+ KEY_CTRL_BACKSPACE = (KEY_ESC<<8) + 127, // Cut from start of line to cursor.
+
+ KEY_ALT_DEL = ALT_KEY(163), // Cut word following cursor.
+ KEY_ALT_HOME = ALT_KEY(151), // Cut from start of line to cursor.
+ KEY_ALT_END = ALT_KEY(159), // Cut from cursor to end of line.
+ KEY_ALT_UP = ALT_KEY(152), // Uppercase current or following word.
+ KEY_ALT_DOWN = ALT_KEY(160), // Lowercase current or following word.
+ KEY_ALT_LEFT = ALT_KEY(155), // Move back a word.
+ KEY_ALT_RIGHT = ALT_KEY(157), // Move forward a word.
+ KEY_ALT_BACKSPACE = ALT_KEY(KEY_BACKSPACE), // Cut from start of line to cursor.
+
+ KEY_F1 = (KEY_ESC<<8) + ';', // Show help.
+ KEY_F2 = (KEY_ESC<<8) + '<', // Show history.
+ KEY_F3 = (KEY_ESC<<8) + '=', // Clear history (need confirm).
+ KEY_F4 = (KEY_ESC<<8) + '>', // Search history with current input.
+
+#else // Linux
+
+ KEY_INSERT = ESC_KEY4('2','~'), // vt100 Esc[2~: Paste last cut text.
+ KEY_DEL = ESC_KEY4('3','~'), // vt100 Esc[3~: Delete character under cursor.
+ KEY_HOME = ESC_KEY4('1','~'), // vt100 Esc[1~: Move cursor to start of line.
+ KEY_END = ESC_KEY4('4','~'), // vt100 Esc[4~: Move cursor to end of line.
+ KEY_PGUP = ESC_KEY4('5','~'), // vt100 Esc[5~: Move to first line in history.
+ KEY_PGDN = ESC_KEY4('6','~'), // vt100 Esc[6~: Move to end of input history.
+ KEY_UP = ESC_KEY3('A'), // Esc[A: Fetch previous line in history.
+ KEY_DOWN = ESC_KEY3('B'), // Esc[B: Fetch next line in history.
+ KEY_LEFT = ESC_KEY3('D'), // Esc[D: Move back a character.
+ KEY_RIGHT = ESC_KEY3('C'), // Esc[C: Move forward a character.
+ KEY_HOME2 = ESC_KEY3('H'), // xterm Esc[H: Move cursor to start of line.
+ KEY_END2 = ESC_KEY3('F'), // xterm Esc[F: Move cursor to end of line.
+
+ KEY_CTRL_DEL = ESC_KEY6('3','5','~'), // xterm Esc[3;5~: Cut word following cursor.
+ KEY_CTRL_HOME = ESC_KEY6('1','5','H'), // xterm Esc[1;5H: Cut from start of line to cursor.
+ KEY_CTRL_END = ESC_KEY6('1','5','F'), // xterm Esc[1;5F: Cut from cursor to end of line.
+ KEY_CTRL_UP = ESC_KEY6('1','5','A'), // xterm Esc[1;5A: Uppercase current or following word.
+ KEY_CTRL_DOWN = ESC_KEY6('1','5','B'), // xterm Esc[1;5B: Lowercase current or following word.
+ KEY_CTRL_LEFT = ESC_KEY6('1','5','D'), // xterm Esc[1;5D: Move back a word.
+ KEY_CTRL_RIGHT = ESC_KEY6('1','5','C'), // xterm Esc[1;5C: Move forward a word.
+ KEY_CTRL_BACKSPACE = 31, // xterm Cut from start of line to cursor.
+ KEY_CTRL_UP2 = ESC_OKEY('A'), // vt100 EscOA: Uppercase current or following word.
+ KEY_CTRL_DOWN2 = ESC_OKEY('B'), // vt100 EscOB: Lowercase current or following word.
+ KEY_CTRL_LEFT2 = ESC_OKEY('D'), // vt100 EscOD: Move back a word.
+ KEY_CTRL_RIGHT2 = ESC_OKEY('C'), // vt100 EscOC: Move forward a word.
+
+ KEY_ALT_DEL = ESC_KEY6('3','3','~'), // xterm Esc[3;3~: Cut word following cursor.
+ KEY_ALT_HOME = ESC_KEY6('1','3','H'), // xterm Esc[1;3H: Cut from start of line to cursor.
+ KEY_ALT_END = ESC_KEY6('1','3','F'), // xterm Esc[1;3F: Cut from cursor to end of line.
+ KEY_ALT_UP = ESC_KEY6('1','3','A'), // xterm Esc[1;3A: Uppercase current or following word.
+ KEY_ALT_DOWN = ESC_KEY6('1','3','B'), // xterm Esc[1;3B: Lowercase current or following word.
+ KEY_ALT_LEFT = ESC_KEY6('1','3','D'), // xterm Esc[1;3D: Move back a word.
+ KEY_ALT_RIGHT = ESC_KEY6('1','3','C'), // xterm Esc[1;3C: Move forward a word.
+ KEY_ALT_BACKSPACE = ALT_KEY(KEY_DEL2), // Cut from start of line to cursor.
+
+ KEY_F1 = ESC_OKEY('P'), // EscOP: Show help.
+ KEY_F2 = ESC_OKEY('Q'), // EscOQ: Show history.
+ KEY_F3 = ESC_OKEY('R'), // EscOP: Clear history (need confirm).
+ KEY_F4 = ESC_OKEY('S'), // EscOP: Search history with current input.
+
+ KEY_F1_2 = ESC_KEY4('[', 'A'), // linux Esc[[A: Show help.
+ KEY_F2_2 = ESC_KEY4('[', 'B'), // linux Esc[[B: Show history.
+ KEY_F3_2 = ESC_KEY4('[', 'C'), // linux Esc[[C: Clear history (need confirm).
+ KEY_F4_2 = ESC_KEY4('[', 'D'), // linux Esc[[D: Search history with current input.
+
+#endif
+};
+
+/*****************************************************************************/
+
+typedef struct crossline_completions_t {
+ int num;
+ char word[CROSS_COMPLET_MAX_LINE][CROSS_COMPLET_WORD_LEN];
+ char help[CROSS_COMPLET_MAX_LINE][CROSS_COMPLET_HELP_LEN];
+ char hints[CROSS_COMPLET_HINT_LEN];
+ crossline_color_e color_word[CROSS_COMPLET_MAX_LINE];
+ crossline_color_e color_help[CROSS_COMPLET_MAX_LINE];
+ crossline_color_e color_hints;
+} crossline_completions_t;
+
+static char s_word_delimiter[64] = CROSS_DFT_DELIMITER;
+static char s_history_buf[CROSS_HISTORY_MAX_LINE][CROSS_HISTORY_BUF_LEN];
+static uint32_t s_history_id = 0; // Increase always, wrap until UINT_MAX
+static char s_clip_buf[CROSS_HISTORY_BUF_LEN]; // Buf to store cut text
+static crossline_completion_callback s_completion_callback = NULL;
+static int s_paging_print_line = 0; // For paging control
+static int s_got_resize = 0; // Window size changed
+static crossline_color_e s_prompt_color = CROSSLINE_COLOR_DEFAULT;
+
+static char* crossline_readline_edit (char *buf, int size, const char *prompt, int has_input, int in_his);
+static int crossline_history_dump (FILE *file, int print_id, char *patterns, int sel_id, int paging);
+
+#define isdelim(ch) (NULL != strchr(s_word_delimiter, ch)) // Check ch is word delimiter
+
+// Debug macro.
+#if 0
+static FILE *s_crossline_debug_fp = NULL;
+#define crossline_debug(...) \
+ do { \
+ if (NULL == s_crossline_debug_fp) { s_crossline_debug_fp = fopen("crossline_debug.txt", "a"); } \
+ fprintf (s_crossline_debug_fp, __VA_ARGS__); \
+ fflush (s_crossline_debug_fp); \
+ } while (0)
+#else
+#define crossline_debug(...)
+#endif
+
+/*****************************************************************************/
+
+static char* s_crossline_help[] = {
+" Misc Commands",
+" +-------------------------+--------------------------------------------------+",
+" | F1 | Show edit shortcuts help. |",
+" | Ctrl-^ | Enter keyboard debugging mode. |",
+" +-------------------------+--------------------------------------------------+",
+" Move Commands",
+" +-------------------------+--------------------------------------------------+",
+" | Ctrl-B, Left | Move back a character. |",
+" | Ctrl-F, Right | Move forward a character. |",
+" | Up, ESC+Up | Move cursor to up line. (For multiple lines) |",
+" | Ctrl-Up, Alt-Up | (Ctrl-Up, Alt-Up only supports Windows/Xterm) |",
+" | Down, ESC+Down | Move cursor to down line. (For multiple lines) |",
+" | Ctrl-Down,Alt-Down | (Ctrl-Down, Alt-Down only support Windows/Xterm)|",
+" | Alt-B, ESC+Left, | Move back a word. |",
+" | Ctrl-Left, Alt-Left | (Ctrl-Left, Alt-Left only support Windows/Xterm)|",
+" | Alt-F, ESC+Right, | Move forward a word. |",
+" | Ctrl-Right, Alt-Right | (Ctrl-Right,Alt-Right only support Windows/Xterm)|",
+" | Ctrl-A, Home | Move cursor to start of line. |",
+" | Ctrl-E, End | Move cursor to end of line. |",
+" | Ctrl-L | Clear screen and redisplay line. |",
+" +-------------------------+--------------------------------------------------+",
+" Edit Commands",
+" +-------------------------+--------------------------------------------------+",
+" | Ctrl-H, Backspace | Delete character before cursor. |",
+" | Ctrl-D, DEL | Delete character under cursor. |",
+" | Alt-U | Uppercase current or following word. |",
+" | Alt-L | Lowercase current or following word. |",
+" | Alt-C | Capitalize current or following word. |",
+" | Alt-\\ | Delete whitespace around cursor. |",
+" | Ctrl-T | Transpose character. |",
+" +-------------------------+--------------------------------------------------+",
+" Cut&Paste Commands",
+" +-------------------------+--------------------------------------------------+",
+" | Ctrl-K, ESC+End, | Cut from cursor to end of line. |",
+" | Ctrl-End, Alt-End | (Ctrl-End, Alt-End only support Windows/Xterm) |",
+" | Ctrl-U, ESC+Home, | Cut from start of line to cursor. |",
+" | Ctrl-Home, Alt-Home | (Ctrl-Home, Alt-Home only support Windows/Xterm)|",
+" | Ctrl-X | Cut whole line. |",
+" | Alt-Backspace, | Cut word to left of cursor. |",
+" | Esc+Backspace, | |",
+" | Clt-Backspace | (Clt-Backspace only supports Windows/Xterm) |",
+" | Alt-D, ESC+Del, | Cut word following cursor. |",
+" | Alt-Del, Ctrl-Del | (Alt-Del,Ctrl-Del only support Windows/Xterm) |",
+" | Ctrl-W | Cut to left till whitespace (not word). |",
+" | Ctrl-Y, Ctrl-V, Insert | Paste last cut text. |",
+" +-------------------------+--------------------------------------------------+",
+" Complete Commands",
+" +-------------------------+--------------------------------------------------+",
+" | TAB, Ctrl-I | Autocomplete. |",
+" | Alt-=, Alt-? | List possible completions. |",
+" +-------------------------+--------------------------------------------------+",
+" History Commands",
+" +-------------------------+--------------------------------------------------+",
+" | Ctrl-P, Up | Fetch previous line in history. |",
+" | Ctrl-N, Down | Fetch next line in history. |",
+" | Alt-<, PgUp | Move to first line in history. |",
+" | Alt->, PgDn | Move to end of input history. |",
+" | Ctrl-R, Ctrl-S | Search history. |",
+" | F4 | Search history with current input. |",
+" | F1 | Show search help when in search mode. |",
+" | F2 | Show history. |",
+" | F3 | Clear history (need confirm). |",
+" +-------------------------+--------------------------------------------------+",
+" Control Commands",
+" +-------------------------+--------------------------------------------------+",
+" | Enter, Ctrl-J, Ctrl-M | EOL and accept line. |",
+" | Ctrl-C, Ctrl-G | EOF and abort line. |",
+" | Ctrl-D | EOF if line is empty. |",
+" | Alt-R | Revert line. |",
+" | Ctrl-Z | Suspend Job. (Linux Only, fg will resume edit) |",
+" +-------------------------+--------------------------------------------------+",
+" Note: If Alt-key doesn't work, an alternate way is to press ESC first then press key, see above ESC+Key.",
+" Note: In multiple lines:",
+" Up/Down and Ctrl/Alt-Up, Ctrl/Alt-Down will move between lines.",
+" Up key will fetch history when cursor in first line or end of last line(for quick history move)",
+" Down key will fetch history when cursor in last line.",
+" Ctrl/Alt-Up, Ctrl/Alt-Down will just move between lines.",
+NULL};
+
+static char* s_search_help[] = {
+"Patterns are separated by ' ', patter match is case insensitive:",
+" (Hint: use Ctrl-Y/Ctrl-V/Insert to paste last paterns)",
+" select: choose line including 'select'",
+" -select: choose line excluding 'select'",
+" \"select from\": choose line including \"select from\"",
+" -\"select from\": choose line excluding \"select from\"",
+"Example:",
+" \"select from\" where -\"order by\" -limit: ",
+" choose line including \"select from\" and 'where'",
+" and excluding \"order by\" or 'limit'",
+NULL};
+
+/*****************************************************************************/
+
+// Main API to read a line, return buf if get line, return NULL if EOF.
+static char* crossline_readline_internal (const char *prompt, char *buf, int size, int has_input)
+{
+ int not_support = 0, len;
+
+ if ((NULL == buf) || (size <= 1))
+ { return NULL; }
+ if (!isatty(STDIN_FILENO)) { // input is not from a terminal
+ not_support = 1;
+ } else {
+ char *term = getenv("TERM");
+ if (NULL != term) {
+ if (!strcasecmp(term, "dumb") || !strcasecmp(term, "cons25") || !strcasecmp(term, "emacs"))
+ { not_support = 1; }
+ }
+ }
+ if (not_support) {
+ if (NULL == fgets(buf, size, stdin))
+ { return NULL; }
+ for (len = (int)strlen(buf); (len > 0) && (('\n'==buf[len-1]) || ('\r'==buf[len-1])); --len)
+ { buf[len-1] = '\0'; }
+ return buf;
+ }
+
+ return crossline_readline_edit (buf, size, prompt, has_input, 0);
+}
+char* crossline_readline (const char *prompt, char *buf, int size)
+{
+ return crossline_readline_internal (prompt, buf, size, 0);
+}
+char* crossline_readline2 (const char *prompt, char *buf, int size)
+{
+ return crossline_readline_internal (prompt, buf, size, 1);
+}
+
+// Set move/cut word delimiter, defaut is all not digital and alphabetic characters.
+void crossline_delimiter_set (const char *delim)
+{
+ if (NULL != delim) {
+ strncpy (s_word_delimiter, delim, sizeof(s_word_delimiter) - 1);
+ s_word_delimiter[sizeof(s_word_delimiter) - 1] = '\0';
+ }
+}
+
+void crossline_history_show (void)
+{
+ crossline_history_dump (stdout, 1, NULL, 0, isatty(STDIN_FILENO));
+}
+
+void crossline_history_clear (void)
+{
+ memset (s_history_buf, 0, sizeof (s_history_buf));
+ s_history_id = 0;
+}
+
+int crossline_history_save (const char *filename)
+{
+ if (NULL == filename) {
+ return -1;
+ } else {
+ FILE *file = fopen(filename, "wt");
+ if (file == NULL) { return -1; }
+ crossline_history_dump (file, 0, NULL, 0, 0);
+ fclose(file);
+ }
+ return 0;
+}
+
+int crossline_history_load (const char* filename)
+{
+ int len;
+ char buf[CROSS_HISTORY_BUF_LEN];
+ FILE *file;
+
+ if (NULL == filename) { return -1; }
+ file = fopen(filename, "rt");
+ if (NULL == file) { return -1; }
+ while (NULL != fgets(buf, CROSS_HISTORY_BUF_LEN, file)) {
+ for (len = (int)strlen(buf); (len > 0) && (('\n'==buf[len-1]) || ('\r'==buf[len-1])); --len)
+ { buf[len-1] = '\0'; }
+ if (len > 0) {
+ buf[CROSS_HISTORY_BUF_LEN-1] = '\0';
+ strcpy (s_history_buf[(s_history_id++) % CROSS_HISTORY_MAX_LINE], buf);
+ }
+ }
+ fclose(file);
+ return 0;
+}
+
+// Register completion callback.
+void crossline_completion_register (crossline_completion_callback pCbFunc)
+{
+ s_completion_callback = pCbFunc;
+}
+
+// Add completion in callback. Word is must, help for word is optional.
+void crossline_completion_add_color (crossline_completions_t *pCompletions, const char *word,
+ crossline_color_e wcolor, const char *help, crossline_color_e hcolor)
+{
+ if ((NULL != pCompletions) && (NULL != word) && (pCompletions->num < CROSS_COMPLET_MAX_LINE)) {
+ strncpy (pCompletions->word[pCompletions->num], word, CROSS_COMPLET_WORD_LEN);
+ pCompletions->word[pCompletions->num][CROSS_COMPLET_WORD_LEN - 1] = '\0';
+ pCompletions->color_word[pCompletions->num] = wcolor;
+ pCompletions->help[pCompletions->num][0] = '\0';
+ if (NULL != help) {
+ strncpy (pCompletions->help[pCompletions->num], help, CROSS_COMPLET_HELP_LEN);
+ pCompletions->help[pCompletions->num][CROSS_COMPLET_HELP_LEN - 1] = '\0';
+ pCompletions->color_help[pCompletions->num] = hcolor;
+ }
+ pCompletions->num++;
+ }
+}
+void crossline_completion_add (crossline_completions_t *pCompletions, const char *word, const char *help)
+{
+ crossline_completion_add_color (pCompletions, word, CROSSLINE_COLOR_DEFAULT, help, CROSSLINE_COLOR_DEFAULT);
+}
+
+// Set syntax hints in callback.
+void crossline_hints_set_color (crossline_completions_t *pCompletions, const char *hints, crossline_color_e color)
+{
+ if ((NULL != pCompletions) && (NULL != hints)) {
+ strncpy (pCompletions->hints, hints, CROSS_COMPLET_HINT_LEN - 1);
+ pCompletions->hints[CROSS_COMPLET_HINT_LEN - 1] = '\0';
+ pCompletions->color_hints = color;
+ }
+}
+void crossline_hints_set (crossline_completions_t *pCompletions, const char *hints)
+{
+ crossline_hints_set_color (pCompletions, hints, CROSSLINE_COLOR_DEFAULT);
+}
+
+/*****************************************************************************/
+
+int crossline_paging_set (int enable)
+{
+ int prev = s_paging_print_line >=0;
+ s_paging_print_line = enable ? 0 : -1;
+ return prev;
+}
+
+int crossline_paging_check (int line_len)
+{
+ char *paging_hints = "*** Press <Space> or <Enter> to continue . . .";
+ int i, ch, rows, cols, len = (int)strlen(paging_hints);
+
+ if ((s_paging_print_line < 0) || !isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) { return 0; }
+ crossline_screen_get (&rows, &cols);
+ s_paging_print_line += (line_len + cols - 1) / cols;
+ if (s_paging_print_line >= (rows - 1)) {
+ printf ("%s", paging_hints);
+ ch = crossline_getch();
+ if (0 == ch) { crossline_getch(); } // some terminal server may send 0 after Enter
+ // clear paging hints
+ for (i = 0; i < len; ++i) { printf ("\b"); }
+ for (i = 0; i < len; ++i) { printf (" "); }
+ for (i = 0; i < len; ++i) { printf ("\b"); }
+ s_paging_print_line = 0;
+ if ((' ' != ch) && (KEY_ENTER != ch) && (KEY_ENTER2 != ch)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*****************************************************************************/
+
+void crossline_prompt_color_set (crossline_color_e color)
+{
+ s_prompt_color = color;
+}
+
+void crossline_screen_clear ()
+{
+ int ret = system (s_crossline_win ? "cls" : "clear");
+ (void) ret;
+}
+
+#ifdef _WIN32 // Windows
+
+int crossline_getch (void)
+{
+ fflush (stdout);
+ return _getch();
+}
+void crossline_screen_get (int *pRows, int *pCols)
+{
+ CONSOLE_SCREEN_BUFFER_INFO inf;
+ GetConsoleScreenBufferInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf);
+ *pCols = inf.srWindow.Right - inf.srWindow.Left + 1;
+ *pRows = inf.srWindow.Bottom - inf.srWindow.Top + 1;
+ *pCols = *pCols > 1 ? *pCols : 160;
+ *pRows = *pRows > 1 ? *pRows : 24;
+}
+int crossline_cursor_get (int *pRow, int *pCol)
+{
+ CONSOLE_SCREEN_BUFFER_INFO inf;
+ GetConsoleScreenBufferInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf);
+ *pRow = inf.dwCursorPosition.Y - inf.srWindow.Top;
+ *pCol = inf.dwCursorPosition.X - inf.srWindow.Left;
+ return 0;
+}
+void crossline_cursor_set (int row, int col)
+{
+ CONSOLE_SCREEN_BUFFER_INFO inf;
+ GetConsoleScreenBufferInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf);
+ inf.dwCursorPosition.Y = (SHORT)row + inf.srWindow.Top;
+ inf.dwCursorPosition.X = (SHORT)col + inf.srWindow.Left;
+ SetConsoleCursorPosition (GetStdHandle(STD_OUTPUT_HANDLE), inf.dwCursorPosition);
+}
+void crossline_cursor_move (int row_off, int col_off)
+{
+ CONSOLE_SCREEN_BUFFER_INFO inf;
+ GetConsoleScreenBufferInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf);
+ inf.dwCursorPosition.Y += (SHORT)row_off;
+ inf.dwCursorPosition.X += (SHORT)col_off;
+ SetConsoleCursorPosition (GetStdHandle(STD_OUTPUT_HANDLE), inf.dwCursorPosition);
+}
+void crossline_cursor_hide (int bHide)
+{
+ CONSOLE_CURSOR_INFO inf;
+ GetConsoleCursorInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf);
+ inf.bVisible = !bHide;
+ SetConsoleCursorInfo (GetStdHandle(STD_OUTPUT_HANDLE), &inf);
+}
+
+void crossline_color_set (crossline_color_e color)
+{
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ static WORD dft_wAttributes = 0;
+ WORD wAttributes = 0;
+ if (!dft_wAttributes) {
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info);
+ dft_wAttributes = info.wAttributes;
+ }
+ if (CROSSLINE_FGCOLOR_DEFAULT == (color&CROSSLINE_FGCOLOR_MASK)) {
+ wAttributes |= dft_wAttributes & (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);
+ } else {
+ wAttributes |= (color&CROSSLINE_FGCOLOR_BRIGHT) ? FOREGROUND_INTENSITY : 0;
+ switch (color&CROSSLINE_FGCOLOR_MASK) {
+ case CROSSLINE_FGCOLOR_RED: wAttributes |= FOREGROUND_RED; break;
+ case CROSSLINE_FGCOLOR_GREEN: wAttributes |= FOREGROUND_GREEN;break;
+ case CROSSLINE_FGCOLOR_BLUE: wAttributes |= FOREGROUND_BLUE; break;
+ case CROSSLINE_FGCOLOR_YELLOW: wAttributes |= FOREGROUND_RED | FOREGROUND_GREEN; break;
+ case CROSSLINE_FGCOLOR_MAGENTA: wAttributes |= FOREGROUND_RED | FOREGROUND_BLUE; break;
+ case CROSSLINE_FGCOLOR_CYAN: wAttributes |= FOREGROUND_GREEN | FOREGROUND_BLUE; break;
+ case CROSSLINE_FGCOLOR_WHITE: wAttributes |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;break;
+ }
+ }
+ if (CROSSLINE_BGCOLOR_DEFAULT == (color&CROSSLINE_BGCOLOR_MASK)) {
+ wAttributes |= dft_wAttributes & (BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE | BACKGROUND_INTENSITY);
+ } else {
+ wAttributes |= (color&CROSSLINE_BGCOLOR_BRIGHT) ? BACKGROUND_INTENSITY : 0;
+ switch (color&CROSSLINE_BGCOLOR_MASK) {
+ case CROSSLINE_BGCOLOR_RED: wAttributes |= BACKGROUND_RED; break;
+ case CROSSLINE_BGCOLOR_GREEN: wAttributes |= BACKGROUND_GREEN;break;
+ case CROSSLINE_BGCOLOR_BLUE: wAttributes |= BACKGROUND_BLUE; break;
+ case CROSSLINE_BGCOLOR_YELLOW: wAttributes |= BACKGROUND_RED | BACKGROUND_GREEN; break;
+ case CROSSLINE_BGCOLOR_MAGENTA: wAttributes |= BACKGROUND_RED | BACKGROUND_BLUE; break;
+ case CROSSLINE_BGCOLOR_CYAN: wAttributes |= BACKGROUND_GREEN | BACKGROUND_BLUE; break;
+ case CROSSLINE_BGCOLOR_WHITE: wAttributes |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;break;
+ }
+ }
+ if (color & CROSSLINE_UNDERLINE)
+ { wAttributes |= COMMON_LVB_UNDERSCORE; }
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), wAttributes);
+}
+
+#else // Linux
+
+int crossline_getch ()
+{
+ char ch = 0;
+ struct termios old_term, cur_term;
+ fflush (stdout);
+ if (tcgetattr(STDIN_FILENO, &old_term) < 0) { perror("tcsetattr"); }
+ cur_term = old_term;
+ cur_term.c_lflag &= ~(ICANON | ECHO | ISIG); // echoing off, canonical off, no signal chars
+ cur_term.c_cc[VMIN] = 1;
+ cur_term.c_cc[VTIME] = 0;
+ if (tcsetattr(STDIN_FILENO, TCSANOW, &cur_term) < 0) { perror("tcsetattr"); }
+ if (read(STDIN_FILENO, &ch, 1) < 0) { /* perror("read()"); */ } // signal will interrupt
+ if (tcsetattr(STDIN_FILENO, TCSADRAIN, &old_term) < 0) { perror("tcsetattr"); }
+ return ch;
+}
+void crossline_screen_get (int *pRows, int *pCols)
+{
+ struct winsize ws = {};
+ (void)ioctl (1, TIOCGWINSZ, &ws);
+ *pCols = ws.ws_col;
+ *pRows = ws.ws_row;
+ *pCols = *pCols > 1 ? *pCols : 160;
+ *pRows = *pRows > 1 ? *pRows : 24;
+}
+int crossline_cursor_get (int *pRow, int *pCol)
+{
+ int i;
+ char buf[32];
+ printf ("\e[6n");
+ for (i = 0; i < (char)sizeof(buf)-1; ++i) {
+ buf[i] = (char)crossline_getch ();
+ if ('R' == buf[i]) { break; }
+ }
+ buf[i] = '\0';
+ if (2 != sscanf (buf, "\e[%d;%dR", pRow, pCol)) { return -1; }
+ (*pRow)--; (*pCol)--;
+ return 0;
+}
+void crossline_cursor_set (int row, int col)
+{
+ printf("\e[%d;%dH", row+1, col+1);
+}
+void crossline_cursor_move (int row_off, int col_off)
+{
+ if (col_off > 0) { printf ("\e[%dC", col_off); }
+ else if (col_off < 0) { printf ("\e[%dD", -col_off); }
+ if (row_off > 0) { printf ("\e[%dB", row_off); }
+ else if (row_off < 0) { printf ("\e[%dA", -row_off); }
+}
+void crossline_cursor_hide (int bHide)
+{
+ printf("\e[?25%c", bHide?'l':'h');
+}
+
+void crossline_color_set (crossline_color_e color)
+{
+ if (!isatty(STDOUT_FILENO)) { return; }
+ printf ("\033[m");
+ if (CROSSLINE_FGCOLOR_DEFAULT != (color&CROSSLINE_FGCOLOR_MASK))
+ { printf ("\033[%dm", 29 + (color&CROSSLINE_FGCOLOR_MASK) + ((color&CROSSLINE_FGCOLOR_BRIGHT)?60:0)); }
+ if (CROSSLINE_BGCOLOR_DEFAULT != (color&CROSSLINE_BGCOLOR_MASK))
+ { printf ("\033[%dm", 39 + ((color&CROSSLINE_BGCOLOR_MASK)>>8) + ((color&CROSSLINE_BGCOLOR_BRIGHT)?60:0)); }
+ if (color & CROSSLINE_UNDERLINE)
+ { printf ("\033[4m"); }
+}
+
+#endif // #ifdef _WIN32
+
+/*****************************************************************************/
+
+static void crossline_show_help (int show_search)
+{
+ int i;
+ char **help = show_search ? s_search_help : s_crossline_help;
+ printf (" \b\n");
+ for (i = 0; NULL != help[i]; ++i) {
+ printf ("%s\n", help[i]);
+ if (crossline_paging_check ((int)strlen(help[i])+1))
+ { break; }
+ }
+}
+
+static void str_to_lower (char *str)
+{
+ for (; '\0' != *str; ++str)
+ { *str = (char)tolower (*str); }
+}
+
+// Match including(no prefix) and excluding(with prefix: '-') patterns.
+static int crossline_match_patterns (const char *str, char *word[], int num)
+{
+ int i;
+ char buf[CROSS_HISTORY_BUF_LEN];
+
+ strncpy (buf, str, sizeof(buf) - 1);
+ buf[sizeof(buf) - 1] = '\0';
+ str_to_lower (buf);
+ for (i = 0; i < num; ++i) {
+ if ('-' == word[i][0]) {
+ if (NULL != strstr (buf, &word[i][1]))
+ { return 0; }
+ } else if (NULL == strstr (buf, word[i]))
+ { return 0; }
+ }
+ return 1;
+}
+
+// Split pattern string to individual pattern list, handle composite words embraced with " ".
+static int crossline_split_patterns (char *patterns, char *pat_list[], int max)
+{
+ int i, num = 0;
+ char *pch = patterns;
+
+ if (NULL == patterns) { return 0; }
+ while (' ' == *pch) { ++pch; }
+ while ((num < max) && (NULL != pch)) {
+ if (('"' == *pch) || (('-' == *pch) && ('"' == *(pch+1)))) {
+ if ('"' != *pch) { *(pch+1) = '-'; }
+ pat_list[num++] = ++pch;
+ if (NULL != (pch = strchr(pch, '"'))) {
+ *pch++ = '\0';
+ while (' ' == *pch) { ++pch; }
+ }
+ } else {
+ pat_list[num++] = pch;
+ if (NULL != (pch = strchr (pch, ' '))) {
+ *pch = '\0';
+ while (' ' == *(++pch)) ;
+ }
+ }
+ }
+ for (i = 0; i < num; ++i)
+ { str_to_lower (pat_list[i]); }
+ return num;
+}
+
+// If patterns is not NULL, will filter history.
+// If sel_id > 0, return the real id+1 in history buf, else return history number dumped.
+static int crossline_history_dump (FILE *file, int print_id, char *patterns, int sel_id, int paging)
+{
+ uint32_t i;
+ int id = 0, num=0;
+ char *pat_list[CROSS_HIS_MATCH_PAT_NUM], *history;
+
+ num = crossline_split_patterns (patterns, pat_list, CROSS_HIS_MATCH_PAT_NUM);
+ for (i = s_history_id; i < s_history_id + CROSS_HISTORY_MAX_LINE; ++i) {
+ history = s_history_buf[i % CROSS_HISTORY_MAX_LINE];
+ if ('\0' != history[0]) {
+ if ((NULL != patterns) && !crossline_match_patterns (history, pat_list, num))
+ { continue; }
+ if (sel_id > 0) {
+ if (++id == sel_id)
+ { return (i % CROSS_HISTORY_MAX_LINE) + 1; }
+ continue;
+ }
+ if (print_id) { fprintf (file, "%4d %s\n", ++id, history); }
+ else { fprintf (file, "%s\n", history); }
+ if (paging) {
+ if (crossline_paging_check ((int)strlen(history)+(print_id?7:1)))
+ { break; }
+ }
+ }
+ }
+ return id;
+}
+
+// Search history, input will be initial search patterns.
+static int crossline_history_search (char *input)
+{
+ uint32_t his_id = 0, count;
+ char pattern[CROSS_HISTORY_BUF_LEN], buf[8] = "1";
+
+ printf (" \b\n");
+ if (NULL != input) {
+ strncpy (pattern, input, sizeof(pattern) - 1);
+ pattern[sizeof(pattern) - 1] = '\0';
+ }
+ // Get search patterns
+ if (NULL == crossline_readline_edit(pattern, sizeof (pattern), "Input Patterns <F1> help: ", (NULL!=input), 1))
+ { return 0; }
+ strncpy (s_clip_buf, pattern, sizeof(s_clip_buf) - 1);
+ s_clip_buf[sizeof(s_clip_buf) - 1] = '\0';
+ count = crossline_history_dump (stdout, 1, pattern, 0, 1);
+ if (0 == count) { return 0; } // Nothing found, just return
+ // Get choice
+ if (NULL == crossline_readline_edit (buf, sizeof (buf), "Input history id: ", (1==count), 1))
+ { return 0; }
+ his_id = atoi (buf);
+ if (('\0' != buf[0]) && ((his_id > count) || (his_id <= 0))) {
+ printf ("Invalid history id: %s\n", buf);
+ return 0;
+ }
+ return crossline_history_dump (stdout, 1, pattern, his_id, 0);
+}
+
+// Show completions returned by callback.
+static int crossline_show_completions (crossline_completions_t *pCompletions)
+{
+ int i, j, ret = 0, word_len = 0, with_help = 0, rows, cols, word_num;
+
+ if (('\0' != pCompletions->hints[0]) || (pCompletions->num > 0)) {
+ printf (" \b\n");
+ ret = 1;
+ }
+ // Print syntax hints.
+ if ('\0' != pCompletions->hints[0]) {
+ printf ("Please input: ");
+ crossline_color_set (pCompletions->color_hints);
+ printf ("%s", pCompletions->hints);
+ crossline_color_set (CROSSLINE_COLOR_DEFAULT);
+ printf ("\n");
+ }
+ if (0 == pCompletions->num) { return ret; }
+ for (i = 0; i < pCompletions->num; ++i) {
+ if ((int)strlen(pCompletions->word[i]) > word_len)
+ { word_len = (int)strlen(pCompletions->word[i]); }
+ if ('\0' != pCompletions->help[i][0]) { with_help = 1; }
+ }
+ if (with_help) {
+ // Print words with help format.
+ for (i = 0; i < pCompletions->num; ++i) {
+ crossline_color_set (pCompletions->color_word[i]);
+ printf ("%s", pCompletions->word[i]);
+ for (j = 0; j < 4+word_len-(int)strlen(pCompletions->word[i]); ++j)
+ { printf (" "); }
+ crossline_color_set (pCompletions->color_help[i]);
+ printf ("%s", pCompletions->help[i]);
+ crossline_color_set (CROSSLINE_COLOR_DEFAULT);
+ printf ("\n");
+ if (crossline_paging_check((int)strlen(pCompletions->help[i])+4+word_len+1))
+ { break; }
+ }
+ return ret;
+ }
+
+ // Print words list in multiple columns.
+ crossline_screen_get (&rows, &cols);
+ word_num = (cols - 1 - word_len) / (word_len + 4) + 1;
+ for (i = 1; i <= pCompletions->num; ++i) {
+ crossline_color_set (pCompletions->color_word[i-1]);
+ printf ("%s", pCompletions->word[i-1]);
+ crossline_color_set (CROSSLINE_COLOR_DEFAULT);
+ for (j = 0; j < ((i%word_num)?4:0)+word_len-(int)strlen(pCompletions->word[i-1]); ++j)
+ { printf (" "); }
+ if (0 == (i % word_num)) {
+ printf ("\n");
+ if (crossline_paging_check (word_len))
+ { return ret; }
+ }
+ }
+
+ if (pCompletions->num % word_num) { printf ("\n"); }
+ return ret;
+}
+
+static int crossline_updown_move (const char *prompt, int *pCurPos, int *pCurNum, int off, int bForce)
+{
+ int rows, cols, len = (int)strlen(prompt), cur_pos=*pCurPos;
+ crossline_screen_get (&rows, &cols);
+ if (!bForce && (*pCurPos == *pCurNum)) { return 0; } // at end of last line
+ if (off < 0) {
+ if ((*pCurPos+len)/cols == 0) { return 0; } // at first line
+ *pCurPos -= cols;
+ if (*pCurPos < 0) { *pCurPos = 0; }
+ crossline_cursor_move (-1, (*pCurPos+len)%cols-(cur_pos+len)%cols);
+ } else {
+ if ((*pCurPos+len)/cols == (*pCurNum+len)/cols) { return 0; } // at last line
+ *pCurPos += cols;
+ if (*pCurPos > *pCurNum) { *pCurPos = *pCurNum - 1; } // one char left to avoid history shortcut
+ crossline_cursor_move (1, (*pCurPos+len)%cols-(cur_pos+len)%cols);
+ }
+ return 1;
+}
+
+// Refreash current print line and move cursor to new_pos.
+static void crossline_refreash (const char *prompt, char *buf, int *pCurPos, int *pCurNum, int new_pos, int new_num, int bChg)
+{
+ int i, pos_row, pos_col, len = (int)strlen(prompt);
+ static int rows = 0, cols = 0;
+
+ if (bChg || !rows || s_crossline_win) { crossline_screen_get (&rows, &cols); }
+ if (!bChg) { // just move cursor
+ pos_row = (new_pos+len)/cols - (*pCurPos+len)/cols;
+ pos_col = (new_pos+len)%cols - (*pCurPos+len)%cols;
+ crossline_cursor_move (pos_row, pos_col);
+ } else {
+ buf[new_num] = '\0';
+ if (bChg > 1) { // refreash as less as possbile
+ printf ("%s", &buf[bChg-1]);
+ } else {
+ pos_row = (*pCurPos + len) / cols;
+ crossline_cursor_move (-pos_row, 0);
+ crossline_color_set (s_prompt_color);
+ printf ("\r%s", prompt);
+ crossline_color_set (CROSSLINE_COLOR_DEFAULT);
+ printf ("%s", buf);
+ }
+ if (!s_crossline_win && new_num>0 && !((new_num+len)%cols)) { printf("\n"); }
+ for (i=*pCurNum-new_num; i > 0; --i) { printf (" "); }
+ if (!s_crossline_win && *pCurNum>new_num && !((*pCurNum+len)%cols)) { printf("\n"); }
+ pos_row = (new_num+len)/cols - (*pCurNum+len)/cols;
+ if (pos_row < 0) { crossline_cursor_move (pos_row, 0); }
+ printf ("\r");
+ pos_row = (new_pos+len)/cols - (new_num+len)/cols;
+ crossline_cursor_move (pos_row, (new_pos+len)%cols);
+ }
+ *pCurPos = new_pos;
+ *pCurNum = new_num;
+}
+
+static void crossline_print (const char *prompt, char *buf, int *pCurPos, int *pCurNum, int new_pos, int new_num)
+{
+ *pCurPos = *pCurNum = 0;
+ crossline_refreash (prompt, buf, pCurPos, pCurNum, new_pos, new_num, 1);
+}
+
+// Copy part text[cut_beg, cut_end] from src to dest
+static void crossline_text_copy (char *dest, const char *src, int cut_beg, int cut_end)
+{
+ int len = cut_end - cut_beg;
+ len = (len < CROSS_HISTORY_BUF_LEN) ? len : (CROSS_HISTORY_BUF_LEN - 1);
+ if (len > 0) {
+ memcpy (dest, &src[cut_beg], len);
+ dest[len] = '\0';
+ }
+}
+
+// Copy from history buffer to dest
+static void crossline_history_copy (const char *prompt, char *buf, int size, int *pos, int *num, int history_id)
+{
+ strncpy (buf, s_history_buf[history_id % CROSS_HISTORY_MAX_LINE], size - 1);
+ buf[size - 1] = '\0';
+ crossline_refreash (prompt, buf, pos, num, (int)strlen(buf), (int)strlen(buf), 1);
+}
+
+/*****************************************************************************/
+
+// Convert ESC+Key to Alt-Key
+static int crossline_key_esc2alt (int ch)
+{
+ switch (ch) {
+ case KEY_DEL: ch = KEY_ALT_DEL; break;
+ case KEY_HOME: ch = KEY_ALT_HOME; break;
+ case KEY_END: ch = KEY_ALT_END; break;
+ case KEY_UP: ch = KEY_ALT_UP; break;
+ case KEY_DOWN: ch = KEY_ALT_DOWN; break;
+ case KEY_LEFT: ch = KEY_ALT_LEFT; break;
+ case KEY_RIGHT: ch = KEY_ALT_RIGHT; break;
+ case KEY_BACKSPACE: ch = KEY_ALT_BACKSPACE; break;
+ }
+ return ch;
+}
+
+// Map other function keys to main key
+static int crossline_key_mapping (int ch)
+{
+ switch (ch) {
+#ifndef _WIN32
+ case KEY_HOME2: ch = KEY_HOME; break;
+ case KEY_END2: ch = KEY_END; break;
+ case KEY_CTRL_UP2: ch = KEY_CTRL_UP; break;
+ case KEY_CTRL_DOWN2: ch = KEY_CTRL_DOWN; break;
+ case KEY_CTRL_LEFT2: ch = KEY_CTRL_LEFT; break;
+ case KEY_CTRL_RIGHT2: ch = KEY_CTRL_RIGHT; break;
+ case KEY_F1_2: ch = KEY_F1; break;
+ case KEY_F2_2: ch = KEY_F2; break;
+ case KEY_F3_2: ch = KEY_F3; break;
+ case KEY_F4_2: ch = KEY_F4; break;
+#endif
+ case KEY_DEL2: ch = KEY_BACKSPACE; break;
+ }
+ return ch;
+}
+
+#ifdef _WIN32 // Windows
+// Read a KEY from keyboard, is_esc indicats whether it's a function key.
+static int crossline_getkey (int *is_esc)
+{
+ int ch = crossline_getch (), esc;
+ if ((GetKeyState (VK_CONTROL) & 0x8000) && (KEY_DEL2 == ch)) {
+ ch = KEY_CTRL_BACKSPACE;
+ } else if ((224 == ch) || (0 == ch)) {
+ *is_esc = 1;
+ ch = crossline_getch ();
+ ch = (GetKeyState (VK_MENU) & 0x8000) ? ALT_KEY(ch) : ch + (KEY_ESC<<8);
+ } else if (KEY_ESC == ch) { // Handle ESC+Key
+ *is_esc = 1;
+ ch = crossline_getkey (&esc);
+ ch = crossline_key_esc2alt (ch);
+ } else if (GetKeyState (VK_MENU) & 0x8000 && !(GetKeyState (VK_CONTROL) & 0x8000) ) {
+ *is_esc = 1; ch = ALT_KEY(ch);
+ }
+ return ch;
+}
+
+void crossline_winchg_reg (void) { }
+
+#else // Linux
+
+// Convert escape sequences to internal special function key
+static int crossline_get_esckey (int ch)
+{
+ int ch2;
+ if (0 == ch) { ch = crossline_getch (); }
+ if ('[' == ch) {
+ ch = crossline_getch ();
+ if ((ch>='0') && (ch<='6')) {
+ ch2 = crossline_getch ();
+ if ('~' == ch2) { ch = ESC_KEY4 (ch, ch2); } // ex. Esc[4~
+ else if (';' == ch2) {
+ ch2 = crossline_getch();
+ if (('5' != ch2) && ('3' != ch2))
+ { return 0; }
+ ch = ESC_KEY6 (ch, ch2, crossline_getch()); // ex. Esc[1;5B
+ }
+ } else if ('[' == ch) {
+ ch = ESC_KEY4 ('[', crossline_getch()); // ex. Esc[[A
+ } else { ch = ESC_KEY3 (ch); } // ex. Esc[A
+ } else if ('O' == ch) {
+ ch = ESC_OKEY (crossline_getch()); // ex. EscOP
+ } else { ch = ALT_KEY (ch); } // ex. Alt+Backspace
+ return ch;
+}
+
+// Read a KEY from keyboard, is_esc indicats whether it's a function key.
+static int crossline_getkey (int *is_esc)
+{
+ int ch = crossline_getch();
+ if (KEY_ESC == ch) {
+ *is_esc = 1;
+ ch = crossline_getch ();
+ if (KEY_ESC == ch) { // Handle ESC+Key
+ ch = crossline_get_esckey (0);
+ ch = crossline_key_mapping (ch);
+ ch = crossline_key_esc2alt (ch);
+ } else { ch = crossline_get_esckey (ch); }
+ }
+ return ch;
+}
+
+static void crossline_winchg_event (int arg)
+{ s_got_resize = 1; }
+static void crossline_winchg_reg (void)
+{
+ struct sigaction sa;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = &crossline_winchg_event;
+ sigaction (SIGWINCH, &sa, NULL);
+ s_got_resize = 0;
+}
+
+#endif // #ifdef _WIN32
+
+/*****************************************************************************/
+
+/* Internal readline from terminal. has_input indicates buf has inital input.
+ * in_his will disable history and complete shortcuts
+ */
+static char* crossline_readline_edit (char *buf, int size, const char *prompt, int has_input, int in_his)
+{
+ int pos = 0, num = 0, read_end = 0, is_esc;
+ int ch, len, new_pos, copy_buf = 0, i, len2;
+ uint32_t history_id = s_history_id, search_his;
+ char input[CROSS_HISTORY_BUF_LEN];
+ crossline_completions_t completions;
+
+ prompt = (NULL != prompt) ? prompt : "";
+ if (has_input) {
+ num = pos = (int)strlen (buf);
+ crossline_text_copy (input, buf, pos, num);
+ } else
+ { buf[0] = input[0] = '\0'; }
+ crossline_print (prompt, buf, &pos, &num, pos, num);
+ crossline_winchg_reg ();
+
+ do {
+ is_esc = 0;
+ ch = crossline_getkey (&is_esc);
+ ch = crossline_key_mapping (ch);
+
+ if (s_got_resize) { // Handle window resizing for Linux, Windows can handle it automatically
+ new_pos = pos;
+ crossline_refreash (prompt, buf, &pos, &num, 0, num, 0); // goto beginning of line
+ printf ("\x1b[J"); // clear to end of screen
+ crossline_refreash (prompt, buf, &pos, &num, new_pos, num, 1);
+ s_got_resize = 0;
+ }
+
+ switch (ch) {
+/* Misc Commands */
+ case KEY_F1: // Show help
+ crossline_show_help (in_his);
+ crossline_print (prompt, buf, &pos, &num, pos, num);
+ break;
+
+ case KEY_DEBUG: // Enter keyboard debug mode
+ printf(" \b\nEnter keyboard debug mode, <Ctrl-C> to exit debug\n");
+ while (CTRL_KEY('C') != (ch=crossline_getch()))
+ { printf ("%3d 0x%02x (%c)\n", ch, ch, isprint(ch) ? ch : ' '); }
+ crossline_print (prompt, buf, &pos, &num, pos, num);
+ break;
+
+/* Move Commands */
+ case KEY_LEFT: // Move back a character.
+ case CTRL_KEY('B'):
+ if (pos > 0)
+ { crossline_refreash (prompt, buf, &pos, &num, pos-1, num, 0); }
+ break;
+
+ case KEY_RIGHT: // Move forward a character.
+ case CTRL_KEY('F'):
+ if (pos < num)
+ { crossline_refreash (prompt, buf, &pos, &num, pos+1, num, 0); }
+ break;
+
+ case ALT_KEY('b'): // Move back a word.
+ case ALT_KEY('B'):
+ case KEY_CTRL_LEFT:
+ case KEY_ALT_LEFT:
+ for (new_pos=pos-1; (new_pos > 0) && isdelim(buf[new_pos]); --new_pos) ;
+ for (; (new_pos > 0) && !isdelim(buf[new_pos]); --new_pos) ;
+ crossline_refreash (prompt, buf, &pos, &num, new_pos?new_pos+1:new_pos, num, 0);
+ break;
+
+ case ALT_KEY('f'): // Move forward a word.
+ case ALT_KEY('F'):
+ case KEY_CTRL_RIGHT:
+ case KEY_ALT_RIGHT:
+ for (new_pos=pos; (new_pos < num) && isdelim(buf[new_pos]); ++new_pos) ;
+ for (; (new_pos < num) && !isdelim(buf[new_pos]); ++new_pos) ;
+ crossline_refreash (prompt, buf, &pos, &num, new_pos, num, 0);
+ break;
+
+ case CTRL_KEY('A'): // Move cursor to start of line.
+ case KEY_HOME:
+ crossline_refreash (prompt, buf, &pos, &num, 0, num, 0);
+ break;
+
+ case CTRL_KEY('E'): // Move cursor to end of line
+ case KEY_END:
+ crossline_refreash (prompt, buf, &pos, &num, num, num, 0);
+ break;
+
+ case CTRL_KEY('L'): // Clear screen and redisplay line
+ crossline_screen_clear ();
+ crossline_print (prompt, buf, &pos, &num, pos, num);
+ break;
+
+ case KEY_CTRL_UP: // Move to up line
+ case KEY_ALT_UP:
+ crossline_updown_move (prompt, &pos, &num, -1, 1);
+ break;
+
+ case KEY_ALT_DOWN: // Move to down line
+ case KEY_CTRL_DOWN:
+ crossline_updown_move (prompt, &pos, &num, 1, 1);
+ break;
+
+/* Edit Commands */
+ case KEY_BACKSPACE: // Delete char to left of cursor (same with CTRL_KEY('H'))
+ if (pos > 0) {
+ memmove (&buf[pos-1], &buf[pos], num - pos);
+ crossline_refreash (prompt, buf, &pos, &num, pos-1, num-1, 1);
+ }
+ break;
+
+ case KEY_DEL: // Delete character under cursor
+ case CTRL_KEY('D'):
+ if (pos < num) {
+ memmove (&buf[pos], &buf[pos+1], num - pos - 1);
+ crossline_refreash (prompt, buf, &pos, &num, pos, num - 1, 1);
+ } else if ((0 == num) && (ch == CTRL_KEY('D'))) // On an empty line, EOF
+ { printf (" \b\n"); read_end = -1; }
+ break;
+
+ case ALT_KEY('u'): // Uppercase current or following word.
+ case ALT_KEY('U'):
+ for (new_pos = pos; (new_pos < num) && isdelim(buf[new_pos]); ++new_pos) ;
+ for (; (new_pos < num) && !isdelim(buf[new_pos]); ++new_pos)
+ { buf[new_pos] = (char)toupper (buf[new_pos]); }
+ crossline_refreash (prompt, buf, &pos, &num, new_pos, num, 1);
+ break;
+
+ case ALT_KEY('l'): // Lowercase current or following word.
+ case ALT_KEY('L'):
+ for (new_pos = pos; (new_pos < num) && isdelim(buf[new_pos]); ++new_pos) ;
+ for (; (new_pos < num) && !isdelim(buf[new_pos]); ++new_pos)
+ { buf[new_pos] = (char)tolower (buf[new_pos]); }
+ crossline_refreash (prompt, buf, &pos, &num, new_pos, num, 1);
+ break;
+
+ case ALT_KEY('c'): // Capitalize current or following word.
+ case ALT_KEY('C'):
+ for (new_pos = pos; (new_pos < num) && isdelim(buf[new_pos]); ++new_pos) ;
+ if (new_pos<num)
+ { buf[new_pos] = (char)toupper (buf[new_pos]); }
+ for (; new_pos<num && !isdelim(buf[new_pos]); ++new_pos) ;
+ crossline_refreash (prompt, buf, &pos, &num, new_pos, num, 1);
+ break;
+
+ case ALT_KEY('\\'): // Delete whitespace around cursor.
+ for (new_pos = pos; (new_pos > 0) && (' ' == buf[new_pos]); --new_pos) ;
+ memmove (&buf[new_pos], &buf[pos], num - pos);
+ crossline_refreash (prompt, buf, &pos, &num, new_pos, num - (pos-new_pos), 1);
+ for (new_pos = pos; (new_pos < num) && (' ' == buf[new_pos]); ++new_pos) ;
+ memmove (&buf[pos], &buf[new_pos], num - new_pos);
+ crossline_refreash (prompt, buf, &pos, &num, pos, num - (new_pos-pos), 1);
+ break;
+
+ case CTRL_KEY('T'): // Transpose previous character with current character.
+ if ((pos > 0) && !isdelim(buf[pos]) && !isdelim(buf[pos-1])) {
+ ch = buf[pos];
+ buf[pos] = buf[pos-1];
+ buf[pos-1] = (char)ch;
+ crossline_refreash (prompt, buf, &pos, &num, pos<num?pos+1:pos, num, 1);
+ } else if ((pos > 1) && !isdelim(buf[pos-1]) && !isdelim(buf[pos-2])) {
+ ch = buf[pos-1];
+ buf[pos-1] = buf[pos-2];
+ buf[pos-2] = (char)ch;
+ crossline_refreash (prompt, buf, &pos, &num, pos, num, 1);
+ }
+ break;
+
+/* Cut&Paste Commands */
+ case CTRL_KEY('K'): // Cut from cursor to end of line.
+ case KEY_CTRL_END:
+ case KEY_ALT_END:
+ crossline_text_copy (s_clip_buf, buf, pos, num);
+ crossline_refreash (prompt, buf, &pos, &num, pos, pos, 1);
+ break;
+
+ case CTRL_KEY('U'): // Cut from start of line to cursor.
+ case KEY_CTRL_HOME:
+ case KEY_ALT_HOME:
+ crossline_text_copy (s_clip_buf, buf, 0, pos);
+ memmove (&buf[0], &buf[pos], num-pos);
+ crossline_refreash (prompt, buf, &pos, &num, 0, num - pos, 1);
+ break;
+
+ case CTRL_KEY('X'): // Cut whole line.
+ crossline_text_copy (s_clip_buf, buf, 0, num);
+ // fall through
+ case ALT_KEY('r'): // Revert line
+ case ALT_KEY('R'):
+ crossline_refreash (prompt, buf, &pos, &num, 0, 0, 1);
+ break;
+
+ case CTRL_KEY('W'): // Cut whitespace (not word) to left of cursor.
+ case KEY_ALT_BACKSPACE: // Cut word to left of cursor.
+ case KEY_CTRL_BACKSPACE:
+ new_pos = pos;
+ if ((new_pos > 1) && isdelim(buf[new_pos-1])) { --new_pos; }
+ for (; (new_pos > 0) && isdelim(buf[new_pos]); --new_pos) ;
+ if (CTRL_KEY('W') == ch) {
+ for (; (new_pos > 0) && (' ' != buf[new_pos]); --new_pos) ;
+ } else {
+ for (; (new_pos > 0) && !isdelim(buf[new_pos]); --new_pos) ;
+ }
+ if ((new_pos>0) && (new_pos<pos) && isdelim(buf[new_pos])) { new_pos++; }
+ crossline_text_copy (s_clip_buf, buf, new_pos, pos);
+ memmove (&buf[new_pos], &buf[pos], num - pos);
+ crossline_refreash (prompt, buf, &pos, &num, new_pos, num - (pos-new_pos), 1);
+ break;
+
+ case ALT_KEY('d'): // Cut word following cursor.
+ case ALT_KEY('D'):
+ case KEY_ALT_DEL:
+ case KEY_CTRL_DEL:
+ for (new_pos = pos; (new_pos < num) && isdelim(buf[new_pos]); ++new_pos) ;
+ for (; (new_pos < num) && !isdelim(buf[new_pos]); ++new_pos) ;
+ crossline_text_copy (s_clip_buf, buf, pos, new_pos);
+ memmove (&buf[pos], &buf[new_pos], num - new_pos);
+ crossline_refreash (prompt, buf, &pos, &num, pos, num - (new_pos-pos), 1);
+ break;
+
+ case CTRL_KEY('Y'): // Paste last cut text.
+ case CTRL_KEY('V'):
+ case KEY_INSERT:
+ if ((len=(int)strlen(s_clip_buf)) + num < size) {
+ memmove (&buf[pos+len], &buf[pos], num - pos);
+ memcpy (&buf[pos], s_clip_buf, len);
+ crossline_refreash (prompt, buf, &pos, &num, pos+len, num+len, 1);
+ }
+ break;
+
+/* Complete Commands */
+ case KEY_TAB: // Autocomplete (same with CTRL_KEY('I'))
+ case ALT_KEY('='): // List possible completions.
+ case ALT_KEY('?'):
+ if (in_his || (NULL == s_completion_callback) || (pos != num))
+ { break; }
+ buf[pos] = '\0';
+ completions.num = 0;
+ completions.hints[0] = '\0';
+ s_completion_callback (buf, &completions);
+ if (completions.num >= 1) {
+ if (KEY_TAB == ch) {
+ len2 = len = (int)strlen(completions.word[0]);
+ // Find common string for autocompletion
+ for (i = 1; (i < completions.num) && (len > 0); ++i) {
+ while ((len > 0) && strncasecmp(completions.word[0], completions.word[i], len)) { len--; }
+ }
+ if (len > 0) {
+ if (len2 > num) len2 = num;
+ while ((len2 > 0) && strncasecmp(completions.word[0], &buf[num-len2], len2)) { len2--; }
+ new_pos = num - len2;
+ if (new_pos+i+1 < size) {
+ for (i = 0; i < len; ++i) { buf[new_pos+i] = completions.word[0][i]; }
+ if (1 == completions.num) { buf[new_pos + (i++)] = ' '; }
+ crossline_refreash (prompt, buf, &pos, &num, new_pos+i, new_pos+i, 1);
+ }
+ }
+ }
+ }
+ if (((completions.num != 1) || (KEY_TAB != ch)) && crossline_show_completions(&completions))
+ { crossline_print (prompt, buf, &pos, &num, pos, num); }
+ break;
+
+/* History Commands */
+ case KEY_UP: // Fetch previous line in history.
+ if (crossline_updown_move (prompt, &pos, &num, -1, 0)) { break; } // check multi line move up
+ case CTRL_KEY('P'):
+ if (in_his) { break; }
+ if (!copy_buf)
+ { crossline_text_copy (input, buf, 0, num); copy_buf = 1; }
+ if ((history_id > 0) && (history_id+CROSS_HISTORY_MAX_LINE > s_history_id))
+ { crossline_history_copy (prompt, buf, size, &pos, &num, --history_id); }
+ break;
+
+ case KEY_DOWN: // Fetch next line in history.
+ if (crossline_updown_move (prompt, &pos, &num, 1, 0)) { break; } // check multi line move down
+ case CTRL_KEY('N'):
+ if (in_his) { break; }
+ if (!copy_buf)
+ { crossline_text_copy (input, buf, 0, num); copy_buf = 1; }
+ if (history_id+1 < s_history_id)
+ { crossline_history_copy (prompt, buf, size, &pos, &num, ++history_id); }
+ else {
+ history_id = s_history_id;
+ strncpy (buf, input, size - 1);
+ buf[size - 1] = '\0';
+ crossline_refreash (prompt, buf, &pos, &num, (int)strlen(buf), (int)strlen(buf), 1);
+ }
+ break; //case UP/DOWN
+
+ case ALT_KEY('<'): // Move to first line in history.
+ case KEY_PGUP:
+ if (in_his) { break; }
+ if (!copy_buf)
+ { crossline_text_copy (input, buf, 0, num); copy_buf = 1; }
+ if (s_history_id > 0) {
+ history_id = s_history_id < CROSS_HISTORY_MAX_LINE ? 0 : s_history_id-CROSS_HISTORY_MAX_LINE;
+ crossline_history_copy (prompt, buf, size, &pos, &num, history_id);
+ }
+ break;
+
+ case ALT_KEY('>'): // Move to end of input history.
+ case KEY_PGDN:
+ if (in_his) { break; }
+ if (!copy_buf)
+ { crossline_text_copy (input, buf, 0, num); copy_buf = 1; }
+ history_id = s_history_id;
+ strncpy (buf, input, size-1);
+ buf[size-1] = '\0';
+ crossline_refreash (prompt, buf, &pos, &num, (int)strlen(buf), (int)strlen(buf), 1);
+ break;
+
+ case CTRL_KEY('R'): // Search history
+ case CTRL_KEY('S'):
+ case KEY_F4: // Search history with current input.
+ if (in_his) { break; }
+ crossline_text_copy (input, buf, 0, num);
+ search_his = crossline_history_search ((KEY_F4 == ch) ? buf : NULL);
+ if (search_his > 0)
+ { strncpy (buf, s_history_buf[search_his-1], size-1); }
+ else { strncpy (buf, input, size-1); }
+ buf[size-1] = '\0';
+ crossline_print (prompt, buf, &pos, &num, (int)strlen(buf), (int)strlen(buf));
+ break;
+
+ case KEY_F2: // Show history
+ if (in_his || (0 == s_history_id)) { break; }
+ printf (" \b\n");
+ crossline_history_show ();
+ crossline_print (prompt, buf, &pos, &num, pos, num);
+ break;
+
+ case KEY_F3: // Clear history
+ if (in_his) { break; }
+ printf(" \b\n!!! Confirm to clear history [y]: ");
+ if ('y' == crossline_getch()) {
+ printf(" \b\nHistory are cleared!");
+ crossline_history_clear ();
+ history_id = 0;
+ }
+ printf (" \b\n");
+ crossline_print (prompt, buf, &pos, &num, pos, num);
+ break;
+
+/* Control Commands */
+ case KEY_ENTER: // Accept line (same with CTRL_KEY('M'))
+ case KEY_ENTER2: // same with CTRL_KEY('J')
+ crossline_refreash (prompt, buf, &pos, &num, num, num, 0);
+ printf (" \b\n");
+ read_end = 1;
+ break;
+
+ case CTRL_KEY('C'): // Abort line.
+ case CTRL_KEY('G'):
+ crossline_refreash (prompt, buf, &pos, &num, num, num, 0);
+ if (CTRL_KEY('C') == ch) { printf (" \b^C\n"); }
+ else { printf (" \b\n"); }
+ num = pos = 0;
+ errno = EAGAIN;
+ read_end = -1;
+ break;;
+
+ case CTRL_KEY('Z'):
+#ifndef _WIN32
+ raise(SIGSTOP); // Suspend current process
+ crossline_print (prompt, buf, &pos, &num, pos, num);
+#endif
+ break;
+
+ default:
+ if (!is_esc && isprint(ch) && (num < size-1)) {
+ memmove (&buf[pos+1], &buf[pos], num - pos);
+ buf[pos] = (char)ch;
+ crossline_refreash (prompt, buf, &pos, &num, pos+1, num+1, pos+1);
+ copy_buf = 0;
+ }
+ break;
+ } // switch( ch )
+ fflush(stdout);
+ } while ( !read_end );
+
+ if (read_end < 0) { return NULL; }
+ if ((num > 0) && (' ' == buf[num-1])) { num--; }
+ buf[num] = '\0';
+ if (!in_his && (num > 0) && strcmp(buf,"history")) { // Save history
+ if ((0 == s_history_id) || strncmp (buf, s_history_buf[(s_history_id-1)%CROSS_HISTORY_MAX_LINE], CROSS_HISTORY_BUF_LEN)) {
+ strncpy (s_history_buf[s_history_id % CROSS_HISTORY_MAX_LINE], buf, CROSS_HISTORY_BUF_LEN);
+ s_history_buf[s_history_id % CROSS_HISTORY_MAX_LINE][CROSS_HISTORY_BUF_LEN - 1] = '\0';
+ history_id = ++s_history_id;
+ copy_buf = 0;
+ }
+ }
+
+ return buf;
+}
diff --git a/src/crossline.h b/src/crossline.h
@@ -0,0 +1,178 @@
+/* crossline.h -- Version 1.0
+ *
+ * Crossline is a small, self-contained, zero-config, MIT licensed,
+ * cross-platform, readline and libedit replacement.
+ *
+ * Press <F1> to get full shortcuts list.
+ *
+ * See crossline.c for more information.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * MIT License
+ *
+ * Copyright (c) 2019, JC Wang (wang_junchuan@163.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * ------------------------------------------------------------------------
+ */
+
+#ifndef __CROSSLINE_H
+#define __CROSSLINE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ CROSSLINE_FGCOLOR_DEFAULT = 0x00,
+ CROSSLINE_FGCOLOR_BLACK = 0x01,
+ CROSSLINE_FGCOLOR_RED = 0x02,
+ CROSSLINE_FGCOLOR_GREEN = 0x03,
+ CROSSLINE_FGCOLOR_YELLOW = 0x04,
+ CROSSLINE_FGCOLOR_BLUE = 0x05,
+ CROSSLINE_FGCOLOR_MAGENTA = 0x06,
+ CROSSLINE_FGCOLOR_CYAN = 0x07,
+ CROSSLINE_FGCOLOR_WHITE = 0x08,
+ CROSSLINE_FGCOLOR_BRIGHT = 0x80,
+ CROSSLINE_FGCOLOR_MASK = 0x7F,
+
+ CROSSLINE_BGCOLOR_DEFAULT = 0x0000,
+ CROSSLINE_BGCOLOR_BLACK = 0x0100,
+ CROSSLINE_BGCOLOR_RED = 0x0200,
+ CROSSLINE_BGCOLOR_GREEN = 0x0300,
+ CROSSLINE_BGCOLOR_YELLOW = 0x0400,
+ CROSSLINE_BGCOLOR_BLUE = 0x0500,
+ CROSSLINE_BGCOLOR_MAGENTA = 0x0600,
+ CROSSLINE_BGCOLOR_CYAN = 0x0700,
+ CROSSLINE_BGCOLOR_WHITE = 0x0800,
+ CROSSLINE_BGCOLOR_BRIGHT = 0x8000,
+ CROSSLINE_BGCOLOR_MASK = 0x7F00,
+
+ CROSSLINE_UNDERLINE = 0x10000,
+
+ CROSSLINE_COLOR_DEFAULT = CROSSLINE_FGCOLOR_DEFAULT | CROSSLINE_BGCOLOR_DEFAULT
+} crossline_color_e;
+
+// Main API to read a line, return buf if get line, return NULL if EOF.
+extern char* crossline_readline (const char *prompt, char *buf, int size);
+
+// Same with crossline_readline except buf holding initial input for editing.
+extern char* crossline_readline2 (const char *prompt, char *buf, int size);
+
+// Set move/cut word delimiter, default is all not digital and alphabetic characters.
+extern void crossline_delimiter_set (const char *delim);
+
+// Read a character from terminal without echo
+extern int crossline_getch (void);
+
+
+/*
+ * History APIs
+ */
+
+// Save history to file
+extern int crossline_history_save (const char *filename);
+
+// Load history from file
+extern int crossline_history_load (const char *filename);
+
+// Show history in buffer
+extern void crossline_history_show (void);
+
+// Clear history
+extern void crossline_history_clear (void);
+
+
+/*
+ * Completion APIs
+ */
+
+typedef struct crossline_completions_t crossline_completions_t;
+typedef void (*crossline_completion_callback) (const char *buf, crossline_completions_t *pCompletions);
+
+// Register completion callback
+extern void crossline_completion_register (crossline_completion_callback pCbFunc);
+
+// Add completion in callback. Word is must, help for word is optional.
+extern void crossline_completion_add (crossline_completions_t *pCompletions, const char *word, const char *help);
+
+// Add completion with color.
+extern void crossline_completion_add_color (crossline_completions_t *pCompletions, const char *word,
+ crossline_color_e wcolor, const char *help, crossline_color_e hcolor);
+
+// Set syntax hints in callback
+extern void crossline_hints_set (crossline_completions_t *pCompletions, const char *hints);
+
+// Set syntax hints with color
+extern void crossline_hints_set_color (crossline_completions_t *pCompletions, const char *hints, crossline_color_e color);
+
+
+/*
+ * Paging APIs
+ */
+
+// Enable/Disble paging control
+extern int crossline_paging_set (int enable);
+
+// Check paging after print a line, return 1 means quit, 0 means continue
+// if you know only one line is printed, just give line_len = 1
+extern int crossline_paging_check (int line_len);
+
+
+/*
+ * Cursor APIs
+ */
+
+// Get screen rows and columns
+extern void crossline_screen_get (int *pRows, int *pCols);
+
+// Clear current screen
+extern void crossline_screen_clear (void);
+
+// Get cursor postion (0 based)
+extern int crossline_cursor_get (int *pRow, int *pCol);
+
+// Set cursor postion (0 based)
+extern void crossline_cursor_set (int row, int col);
+
+// Move cursor with row and column offset, row_off>0 move up row_off lines, <0 move down abs(row_off) lines
+// =0 no move for row, similar with col_off
+extern void crossline_cursor_move (int row_off, int col_off);
+
+// Hide or show cursor
+extern void crossline_cursor_hide (int bHide);
+
+
+/*
+ * Color APIs
+ */
+
+// Set text color, CROSSLINE_COLOR_DEFAULT will revert to default setting
+// `\t` is not supported in Linux terminal, same below. Don't use `\n` in Linux terminal, same below.
+extern void crossline_color_set (crossline_color_e color);
+
+// Set default prompt color
+extern void crossline_prompt_color_set (crossline_color_e color);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __CROSSLINE_H */
diff --git a/src/main.zig b/src/main.zig
@@ -0,0 +1,58 @@
+const std = @import("std");
+const Int = std.math.big.int;
+const value = @import("value.zig");
+const chunk = @import("chunk.zig");
+const vm = @import("vm.zig");
+const cl = @cImport(@cInclude("crossline.h"));
+const s = @import("scan.zig");
+
+const GPAll = std.heap.GeneralPurposeAllocator(.{});
+
+pub const std_options = .{
+ .log_level = std.log.Level.debug,
+};
+
+pub fn main() !void {
+
+ var gp = GPAll{};
+ const all = gp.allocator();
+
+ defer std.debug.assert(gp.deinit() == .ok);
+
+ // Initialize bytecode
+ var c = chunk.Chunk.init(all);
+ defer c.deinit();
+
+ const cst = try c.addConstant(try value.Value.newInt(all, 50000000));
+ try c.write(.{ .@"constant" = cst });
+
+ const cst2 = try c.addConstant(try value.Value.newInt(all, 439134597143958149358));
+ try c.write(.{ .@"constant" = cst2 });
+
+ try c.write(.multiply);
+
+ try c.write(.@"return");
+
+ var v = try vm.VM.init(all);
+ defer v.deinit();
+
+ // var args = std.process.args();
+ // _ = args.next();
+ //
+ // if (args.next()) |file| {
+ // // TODO: file parsing stuff
+ // _ = file;
+ // } else {
+ // var buf: [1024:0]u8 = undefined;
+ //
+ // while (cl.crossline_readline("> ", &buf, buf.len)) |obuf| {
+ // const input = obuf[0..std.mem.len(obuf)];
+ // var reader = try s.Reader.init(input, all);
+ //
+ // while (reader.peekTok() != .eof) : (try reader.nextTok()) {}
+ // }
+ // }
+ //
+ //
+ try v.interpret(&c);
+}
diff --git a/src/root.zig b/src/root.zig
@@ -0,0 +1,10 @@
+const std = @import("std");
+const testing = std.testing;
+
+export fn add(a: i32, b: i32) i32 {
+ return a + b;
+}
+
+test "basic add functionality" {
+ try testing.expect(add(3, 7) == 10);
+}
diff --git a/src/scan.zig b/src/scan.zig
@@ -0,0 +1,101 @@
+const std = @import("std");
+const v = @import("value.zig");
+
+const ascii = std.ascii;
+
+const readlog = std.log.scoped(.read);
+
+pub const Reader = struct {
+ const Self = @This();
+
+ const Token = union(enum) {
+ number: v.Int,
+ symbol: []const u8,
+ oparen,
+ cparen,
+ eof,
+ };
+
+ input: []const u8,
+ curpos: usize,
+ curtok: Token,
+ all: std.mem.Allocator,
+
+ pub fn init(input: []const u8, all: std.mem.Allocator) !Self {
+ var self: Self = .{
+ .input = input,
+ .curpos = 0,
+ .curtok = undefined,
+ .all = all,
+ };
+
+ try self.nextTok();
+
+ return self;
+ }
+
+ fn skipWhitespaceComment(self: *Self) void {
+ while (self.curpos < self.input.len and
+ (ascii.isWhitespace(self.input[self.curpos]) or
+ self.input[self.curpos] == ';')) : (self.curpos += 1)
+ {
+ // skip whitespace and ','
+ if (self.input[self.curpos] == ';') {
+ const endcomment = std.mem.indexOfScalar(u8, self.input[self.curpos..], '\n') orelse {
+ self.curpos = self.input.len;
+ break;
+ };
+ self.curpos += endcomment - 1;
+ }
+ }
+ }
+
+ fn isSymChar(char: u8) bool {
+ return switch (char) {
+ '(', ')', ';' => false,
+ else => true,
+ };
+ }
+
+ pub inline fn peekTok(self: *const Reader) Token {
+ return self.curtok;
+ }
+
+ pub fn nextTok(self: *Reader) !void {
+ self.skipWhitespaceComment();
+ if (self.curpos >= self.input.len) {
+ self.curtok = .eof;
+ return;
+ }
+
+ self.curtok = switch (self.input[self.curpos]) {
+ '(' => onechar: {
+ self.curpos += 1;
+ break :onechar .oparen;
+ },
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => .{ .number = try self.tokInt() },
+ else => sym: {
+ var offset = self.curpos;
+ while (offset < self.input.len and isSymChar(self.input[offset])) : (offset += 1) {}
+ const tok = .{ .symbol = self.input[self.curpos..offset] };
+ self.curpos = offset;
+ break :sym tok;
+ },
+ };
+ readlog.debug("Curtok: {}", .{self.curtok});
+ }
+
+ fn tokInt(self: *Self) !v.Int {
+ var offset = self.curpos;
+
+ // TODO: hex/binary...
+ while (offset < self.input.len and ascii.isDigit(self.input[offset])) : (offset += 1) {}
+
+ var ret = try v.Int.init(self.all);
+ try ret.setString(10, self.input[self.curpos..offset]);
+
+ self.curpos = offset;
+
+ return ret;
+ }
+};
diff --git a/src/value.zig b/src/value.zig
@@ -0,0 +1,94 @@
+const std = @import("std");
+pub const Int = std.math.big.int.Managed;
+
+const vallog = std.log.scoped(.val);
+pub const Value = struct {
+ const Self = @This();
+
+ const Payload = union(enum) {
+ number: Int,
+ list: []Value,
+ };
+
+ val: Payload,
+ all: std.mem.Allocator,
+ refcount: usize,
+
+ pub const Tag: type = @typeInfo(Payload).Union.tag_type.?;
+
+ fn init(payload: Payload, all: std.mem.Allocator) !*Self {
+ var new = try all.create(Self);
+ new.val = payload;
+ new.all = all;
+ new.refcount = 1;
+
+ return new;
+ }
+
+ pub fn newInt(all: std.mem.Allocator, val: anytype) !*Self {
+ var payload = try Int.initSet(all, val);
+ errdefer payload.deinit();
+ return try Self.init(.{ .number = payload }, all);
+ }
+
+ pub fn ref(self: *Self) *Self {
+ self.refcount += 1;
+ return self;
+ }
+
+ pub fn weakref(self: *Self) *Self {
+ // TODO: implement something to avoid invalid accesses later ?
+ return self;
+ }
+
+ pub fn unref(self: *Self) void {
+ self.refcount -= 1;
+
+ if (self.refcount == 0) {
+ self.deinit();
+ }
+ }
+
+ pub fn deinit(self: *Self) void {
+ vallog.debug("Destroy {}", .{self.formatter()});
+ switch (self.val) {
+ .number => |*num| {
+ num.deinit();
+ },
+ .list => |lst| {
+ for (lst) |*val| {
+ val.unref();
+ }
+ self.all.free(lst);
+ }
+ }
+ self.all.destroy(self);
+ }
+
+ pub fn isA(self: Self, tag: Tag) bool {
+ return @as(Tag, self.val) == tag;
+ }
+
+ pub fn formatter(self: *const Self) std.fmt.Formatter(Self.format) {
+ return .{ .data = self };
+ }
+
+ fn format(self: *const Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
+ switch (self.val) {
+ .number => |num| {
+ try std.fmt.format(writer, "{}", .{num});
+ },
+ .list => |lst| {
+ try writer.writeByte('(');
+ for (lst, 0..) |sub, idx| {
+ if (idx > 0) {
+ try writer.writeByte(' ');
+ }
+ try sub.format(fmt, options, writer);
+ }
+ try writer.writeByte(')');
+ }
+ }
+ }
+};
+
diff --git a/src/vm.zig b/src/vm.zig
@@ -0,0 +1,119 @@
+const std = @import("std");
+const c = @import("chunk.zig");
+const v = @import("value.zig");
+
+const vmlog = std.log.scoped(.vm);
+
+// TODO: efficient stack implementation at some point ?
+const Stack = std.ArrayList(*v.Value);
+
+pub const VM = struct {
+ const Self = @This();
+
+ stack: Stack,
+ cchunk: *const c.Chunk,
+ all: std.mem.Allocator,
+ ip: []const c.Op,
+
+ pub fn init(all: std.mem.Allocator) !Self {
+ return Self{ .cchunk = undefined, .ip = undefined, .stack = try Stack.initCapacity(all, 256), .all = all };
+ }
+
+ fn push(self: *Self, val: *v.Value) !void {
+ try self.stack.append(val);
+ }
+
+ fn pop(self: *Self) !*v.Value {
+ return self.stack.pop();
+ }
+
+ pub fn interpret(self: *Self, chunk: *const c.Chunk) !void {
+ self.cchunk = chunk;
+ self.ip = self.cchunk.instrs.items;
+ return self.run();
+ }
+
+ fn run(self: *Self) !void {
+ while (true) : (self.ip = self.ip[1..]) {
+ const instr = self.ip[0];
+
+ if (comptime std.log.logEnabled(.debug, .vm)) {
+ // TODO: proper logging here
+ for (self.stack.items) |val| {
+ std.debug.print("[{}] ", .{val.formatter()});
+ }
+ std.debug.print("\n", .{});
+ self.cchunk.disassembleInstr(instr);
+ }
+
+ switch (instr) {
+ .constant => |vindex| {
+ const val = self.cchunk.values.items[vindex];
+ try self.push(val.ref());
+ },
+
+ .@"return" => {
+ var val = try self.pop();
+ defer val.unref();
+
+ const stdout = std.io.getStdOut();
+ try std.fmt.format(stdout.writer(), "{}\n", .{val.formatter()});
+ return;
+ },
+
+ .negate => {
+ var val = try self.pop();
+ defer val.unref();
+
+ std.debug.assert(val.isA(.number));
+
+ var new = try v.Value.newInt(self.all, 0);
+ errdefer new.unref();
+
+ try new.val.number.copy(val.val.number.toConst());
+ new.val.number.negate();
+
+ try self.push(new);
+ },
+
+ inline .add, .multiply, .substract => |_, tag| {
+ var left = try self.pop();
+ defer left.unref();
+
+ var right = try self.pop();
+ defer right.unref();
+
+ const lnum = &left.val.number;
+ const rnum = &right.val.number;
+
+ var new = try v.Value.newInt(self.all, 0);
+ errdefer new.unref();
+
+ switch (tag) {
+ .add => {
+ try new.val.number.add(lnum, rnum);
+ },
+ .substract => {
+ try new.val.number.sub(lnum, rnum);
+ },
+ .multiply => {
+ try new.val.number.mul(lnum, rnum);
+ },
+ else => {
+ // Unreachable
+ }
+ }
+
+ try self.push(new);
+ }
+ }
+ }
+ }
+
+ pub fn deinit(self: *Self) void {
+ for (self.stack.items) |val| {
+ val.unref();
+ }
+ self.stack.deinit();
+ }
+};