diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 78617629c1..4cae372d73 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -1,4 +1,4 @@ -# https://pre-commit.com/ +# https://github.com/j178/prek name: pre-commit on: [pull_request] @@ -13,17 +13,8 @@ jobs: steps: - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )" uses: actions/checkout@v6 - - name: Install - run: | - python -m pip install --upgrade pip - pip install pre-commit - - name: Set PY - run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> "$GITHUB_ENV" - - uses: actions/cache@v5 + - uses: j178/prek-action@v1 with: - path: ~/.cache/pre-commit - key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} - - name: Run pre-commit - run: pre-commit run --color=always --all-files + extra-args: --color=always --all-files - name: Run manual pre-commit hooks - run: pre-commit run --color=always --all-files --hook-stage manual + run: prek run --color=always --all-files --hook-stage manual diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f85fa50a8e..37b6566284 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ --- -# https://pre-commit.com/ +# https://github.com/j178/prek default_stages: [pre-commit, pre-push] default_language_version: python: python3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 52d1235290..fac854ab98 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,41 +27,41 @@ If you discover a security vulnerability: For detailed guidance on what qualifies as a security issue and what doesn't, see [SECURITY.md](SECURITY.md). -## pre-commit +## prek -A framework for managing and maintaining multi-language `pre-commit` hooks. -`pre-commit` can be [installed](https://pre-commit.com/#installation) with `pip`, `curl`, `brew` or `conda`. +We use [prek](https://github.com/j178/prek), a fast Rust-based pre-commit hook manager. +It reads the standard `.pre-commit-config.yaml` format. -You need to first install `pre-commit` and then install the `pre-commit` hooks with `pre-commit install`. -Now `pre-commit` will run automatically on git commit! +Install `prek` following the [installation guide](https://github.com/j178/prek#installation), +then install the hooks with `prek install`. +Now `prek` will run automatically on git commit! -It's usually a good idea to run the hooks against all the files when adding new hooks (usually `pre-commit` -will only run on the changed files during git hooks). Use `pre-commit run --all-files` to check all files. +It's usually a good idea to run the hooks against all the files when adding new hooks (usually `prek` +will only run on the changed files during git hooks). Use `prek run --all-files` to check all files. -To run a single hook use `pre-commit run --all-files ` +To run a single hook use `prek run --all-files ` -To update use `pre-commit autoupdate` +To update use `prek autoupdate` Sometimes you might need to skip one or more hooks which can be done with the `SKIP` environment variable. `$ SKIP=yamllint git commit -m "foo"` -For convenience, we have added `pre-commit run --all-files`, `pre-commit install` and `pre-commit autoupdate` +For convenience, we have added `prek run --all-files`, `prek install` and `prek autoupdate` to both the Makefile and the Rakefile. Run them with: - `make check` or `rake check` - `make checkinstall` or `rake checkinstall` - `make checkupdate` or `rake checkupdate` -To configure `pre-commit` you can modify the config file [.pre-commit-config.yaml](.pre-commit-config.yaml). -We use [GitHub Actions](.github/workflows/pre-commit.yml) to run `pre-commit` on every pull request. +To configure hooks you can modify the config file [.pre-commit-config.yaml](.pre-commit-config.yaml). +We use [GitHub Actions](.github/workflows/pre-commit.yml) to run `prek` on every pull request. -### pre-commit quick links +### prek quick links -- [Quick start](https://pre-commit.com/#quick-start) -- [Usage](https://pre-commit.com/#usage) -- [pre-commit autoupdate](https://pre-commit.com/#pre-commit-autoupdate) -- [Temporarily disabling hooks](https://pre-commit.com/#temporarily-disabling-hooks) +- [prek GitHub](https://github.com/j178/prek) +- [Installation](https://github.com/j178/prek#installation) +- [Usage](https://github.com/j178/prek#usage) ## Docker @@ -97,20 +97,19 @@ mruby-test latest ec60f9536948 29 seconds ago 1.29GB ``` You can also run any custom `docker-compose` command which will override -the default. For example to run `pre-commit run --all-files` type: +the default. For example to run `prek run --all-files` type: -`$ docker-compose -p mruby run test pre-commit run --all-files` +`$ docker-compose -p mruby run test prek run --all-files` -For convenience, you can also run `pre-commit` with: +For convenience, you can also run `prek` with: - `make composecheck` - `rake composecheck` -The bonus of running `pre-commit` with `docker-compose` is that you won't need -to install `pre-commit` and the hooks on your local machine. And that also -means you won't need to install `brew`, `conda` or `pip`. +The bonus of running `prek` with `docker-compose` is that you won't need +to install `prek` and the hooks on your local machine. -Note limitation: currently running `pre-commit` with `docker-compose` we +Note limitation: currently running `prek` with `docker-compose` we skip the `check-executables-have-shebangs` hook. Two more examples of custom `docker-compose` commands are: @@ -128,7 +127,7 @@ can use the `-f` flag: ## Spell Checking -We are using `pre-commit` to run [codespell](https://github.com/codespell-project/codespell) +We are using `prek` to run [codespell](https://github.com/codespell-project/codespell) to check code for common misspellings. We have a small custom dictionary file [codespell.txt](.github/linters/codespell.txt). ## Coding conventions diff --git a/Makefile b/Makefile index 20dbf3874a..65a5231dcb 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ RAKE = rake DOCKER_COMPOSE = docker-compose -PRE_COMMIT = pre-commit +PRE_COMMIT = prek define check_command @command -v $(1) >/dev/null 2>&1 || { \ @@ -32,16 +32,16 @@ test: check_rake all ## build and run all mruby tests clean: check_rake ## clean all built and in-repo installed artifacts $(RAKE) clean -check: check_pre_commit ## run all pre-commit hooks against all files +check: check_pre_commit ## run all prek hooks against all files $(PRE_COMMIT) run --all-files -checkinstall: check_pre_commit ## install the pre-commit hooks +checkinstall: check_pre_commit ## install the prek hooks $(PRE_COMMIT) install -checkupdate: check_pre_commit ## check the pre-commit hooks for updates +checkupdate: check_pre_commit ## check the prek hooks for updates $(PRE_COMMIT) autoupdate -composecheck: check_docker_compose check_pre_commit ## run all pre-commit hooks against all files with docker-compose +composecheck: check_docker_compose check_pre_commit ## run all prek hooks against all files with docker-compose $(DOCKER_COMPOSE) -p mruby run test $(PRE_COMMIT) run --all-files composetest: check_docker_compose ## build and run all mruby tests with docker-compose @@ -53,7 +53,7 @@ check_rake: ## check if Rake is installed check_docker_compose: ## check if docker-compose is installed $(call check_command, $(DOCKER_COMPOSE)) -check_pre_commit: ## check if pre-commit is installed +check_pre_commit: ## check if prek is installed $(call check_command, $(PRE_COMMIT)) help: ## display this help message diff --git a/Rakefile b/Rakefile index ec73b2c587..3bab7ad7cb 100644 --- a/Rakefile +++ b/Rakefile @@ -78,22 +78,22 @@ end desc "run all pre-commit hooks against all files" task :check do - sh "pre-commit run --all-files" + sh "prek run --all-files" end desc "install the pre-commit hooks" task :checkinstall do - sh "pre-commit install" + sh "prek install" end desc "check the pre-commit hooks for updates" task :checkupdate do - sh "pre-commit autoupdate" + sh "prek autoupdate" end desc "run all pre-commit hooks against all files with docker-compose" task :composecheck do - sh "docker-compose -p mruby run test pre-commit run --all-files" + sh "docker-compose -p mruby run test prek run --all-files" end desc "build and run all mruby tests with docker-compose" diff --git a/include/mruby.h b/include/mruby.h index b5a66dc184..d65056604c 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -301,6 +301,7 @@ typedef struct mrb_state { const char **symtbl; size_t symcapa; struct mrb_sym_hash_table *symhash; + void *sym_pool; #ifndef MRB_USE_ALL_SYMBOLS char symbuf[8]; /* buffer for small symbol names */ #endif diff --git a/mrbgems/mruby-bigint/core/bigint.c b/mrbgems/mruby-bigint/core/bigint.c index af85a5ddc5..4ef5319f6f 100644 --- a/mrbgems/mruby-bigint/core/bigint.c +++ b/mrbgems/mruby-bigint/core/bigint.c @@ -2016,13 +2016,25 @@ mpz_sparse_p(mpz_t *x) * * O(k * n) where k = popcount, much faster than Karatsuba when k is small. */ -static void -mpz_mul_sparse(mpz_ctx_t *ctx, mpz_t *w, mpz_t *sparse, mpz_t *dense) +struct mpz_mul_sparse_data { + mpz_ctx_t *ctx; + mpz_t *w; + mpz_t *sparse; + mpz_t *dense; + mpz_t shifted, temp; /* cleanup targets */ +}; + +static mrb_value +mpz_mul_sparse_body(mrb_state *mrb, void *userdata) { - mpz_t shifted, temp; + struct mpz_mul_sparse_data *d = (struct mpz_mul_sparse_data *)userdata; + mpz_ctx_t *ctx = d->ctx; + mpz_t *w = d->w; + mpz_t *sparse = d->sparse; + mpz_t *dense = d->dense; - mpz_init(ctx, &shifted); - mpz_init(ctx, &temp); + mpz_init(ctx, &d->shifted); + mpz_init(ctx, &d->temp); zero(w); for (size_t i = 0; i < sparse->sz; i++) { @@ -2042,9 +2054,9 @@ mpz_mul_sparse(mpz_ctx_t *ctx, mpz_t *w, mpz_t *sparse, mpz_t *dense) #endif /* Add dense << (base_bit + bit) to result */ - mpz_mul_2exp(ctx, &shifted, dense, base_bit + bit); - mpz_add(ctx, &temp, w, &shifted); - mpz_set(ctx, w, &temp); + mpz_mul_2exp(ctx, &d->shifted, dense, base_bit + bit); + mpz_add(ctx, &d->temp, w, &d->shifted); + mpz_set(ctx, w, &d->temp); /* Clear this bit */ limb &= limb - 1; @@ -2054,8 +2066,19 @@ mpz_mul_sparse(mpz_ctx_t *ctx, mpz_t *w, mpz_t *sparse, mpz_t *dense) /* Handle sign */ if (sparse->sn < 0) w->sn = -w->sn; - mpz_clear(ctx, &shifted); - mpz_clear(ctx, &temp); + return mrb_nil_value(); +} + +static void +mpz_mul_sparse(mpz_ctx_t *ctx, mpz_t *w, mpz_t *sparse, mpz_t *dense) +{ + struct mpz_mul_sparse_data d = {ctx, w, sparse, dense, {0,0,0}, {0,0,0}}; + mrb_value exc; + MRB_ENSURE(MPZ_MRB(ctx), exc, mpz_mul_sparse_body, &d) { + /* Cleanup always runs (mpz_clear is safe on zero-initialized mpz_t) */ + mpz_clear(ctx, &d.shifted); + mpz_clear(ctx, &d.temp); + } } /* @@ -4491,6 +4514,15 @@ mpz_powm(mpz_ctx_t *ctx, mpz_t *zz, mpz_t *x, mpz_t *ex, mpz_t *n) return; } + /* Check modulus size before allocating large temporaries. */ + { + size_t mod_bits = (size_t)n->sz * DIG_SIZE; + if (mod_bits > MRB_BIGINT_BIT_LIMIT / 2) { + mrb_state *mrb = MPZ_MRB(ctx); + mrb_raise(mrb, E_RANGE_ERROR, "modulus too large"); + } + } + /* * Use Montgomery reduction for odd moduli >= 4 limbs. * Montgomery is faster because it replaces division with multiplication. @@ -4569,6 +4601,17 @@ mpz_powm_i(mpz_ctx_t *ctx, mpz_t *zz, mpz_t *x, mrb_int ex, mpz_t *n) return; } + /* Check modulus size before allocating large temporaries. + * Both Barrett and Montgomery need 2^(2k) internally, + * which would exceed MRB_BIGINT_BIT_LIMIT for large moduli. */ + { + size_t mod_bits = (size_t)n->sz * DIG_SIZE; + if (mod_bits > MRB_BIGINT_BIT_LIMIT / 2) { + mrb_state *mrb = MPZ_MRB(ctx); + mrb_raise(mrb, E_RANGE_ERROR, "modulus too large"); + } + } + /* * Use Montgomery reduction for odd moduli (common in cryptography). * Convert integer exponent to mpz_t and use the main Montgomery implementation. @@ -5075,8 +5118,10 @@ mpz_montgomery_reduce(mpz_ctx_t *ctx, mpz_t *result, size_t k = n->sz; /* Number of limbs in modulus */ size_t x_len = x->sz; - /* Allocate workspace: need k+1 extra limbs for the product accumulation */ - size_t work_size = x_len + k + 2; + /* Allocate workspace: Montgomery reduction writes k limbs at work[i] for i=0..k-1, + * so the maximum index accessed is work[2k-1] (from carry propagation). + * We need at least 2k limbs, plus extra if x_len > k. */ + size_t work_size = (x_len > k) ? (x_len + k + 2) : (2 * k + 2); size_t pool_state = pool_save(ctx); mp_limb *work = NULL; @@ -5240,6 +5285,7 @@ bint_set(mpz_ctx_t *ctx, struct RBigint *b, mpz_t *x) } else { RBIGINT_SET_HEAP(b); + mpz_init(ctx, &b->as.heap); /* Initialize before mpz_move */ mpz_move(ctx, &b->as.heap, x); } } @@ -5576,19 +5622,41 @@ mrb_bint_sub(mrb_state *mrb, mrb_value x, mrb_value y) return bint_norm(mrb, RBIGINT(x)); } +struct bint_mul_data { + mpz_ctx_t *ctx; + mpz_t *a; + mpz_t *b; + mpz_t z; /* cleanup target */ +}; + +static mrb_value +bint_mul_body(mrb_state *mrb, void *userdata) +{ + struct bint_mul_data *d = (struct bint_mul_data *)userdata; + mpz_init(d->ctx, &d->z); + mpz_mul(d->ctx, &d->z, d->a, d->b); + return mrb_nil_value(); +} + static struct RBigint* bint_mul(mrb_state *mrb, mrb_value x, mrb_value y) { - mpz_t a, b, z; + mpz_t a, b; y = mrb_as_bint(mrb, y); bint_as_mpz(RBIGINT(x), &a); bint_as_mpz(RBIGINT(y), &b); MPZ_CTX_INIT(mrb, ctx, pool); - mpz_init(ctx, &z); - mpz_mul(ctx, &z, &a, &b); - return bint_new(ctx, &z); + struct bint_mul_data d = {ctx, &a, &b, {0,0,0}}; + mrb_value exc; + MRB_ENSURE(mrb, exc, bint_mul_body, &d) { + /* On exception, cleanup z (mpz_clear is safe on zero-initialized mpz_t) */ + if (mrb->exc) { + mpz_clear(ctx, &d.z); + } + } + return bint_new(ctx, &d.z); } mrb_value diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c b/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c index 8f475122f5..590bc13b58 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb.c @@ -37,6 +37,7 @@ #include "mirb_editor.h" #include "mirb_completion.h" +#include "mirb_highlight.h" /* obsolete configuration */ #ifdef DISABLE_MIRB_UNDERSCORE @@ -44,26 +45,22 @@ #endif static void -p(mrb_state *mrb, mrb_value obj) +p(mrb_state *mrb, mrb_value obj, mirb_highlighter *hl) { mrb_value val = mrb_funcall_argv(mrb, obj, MRB_SYM(inspect), 0, NULL); - if (!mrb->exc) { - fputs(" => ", stdout); - } - else { + if (mrb->exc) { val = mrb_exc_get_output(mrb, mrb->exc); } if (!mrb_string_p(val)) { val = mrb_obj_as_string(mrb, obj); } char* msg = mrb_locale_from_utf8(RSTRING_PTR(val), (int)RSTRING_LEN(val)); - fwrite(msg, strlen(msg), 1, stdout); + mirb_highlight_print_result(hl, msg); mrb_locale_free(msg); - putc('\n', stdout); } static void -p_error(mrb_state *mrb, struct RObject* exc, mrb_ccontext *cxt) +p_error(mrb_state *mrb, struct RObject* exc, mrb_ccontext *cxt, mirb_highlighter *hl) { mrb_value val = mrb_exc_get_output(mrb, exc); if (!mrb_string_p(val)) { @@ -103,9 +100,8 @@ p_error(mrb_state *mrb, struct RObject* exc, mrb_ccontext *cxt) } char* msg = mrb_locale_from_utf8(RSTRING_PTR(val), (int)RSTRING_LEN(val)); - fwrite(msg, strlen(msg), 1, stdout); + mirb_highlight_print_error(hl, msg); mrb_locale_free(msg); - putc('\n', stdout); } /* Guess if the user might want to enter more @@ -481,6 +477,8 @@ main(int argc, char **argv) mirb_editor editor; mirb_check_data check_data; mrb_bool use_editor = FALSE; + + memset(&editor, 0, sizeof(editor)); mrb_ccontext *cxt; struct mrb_parser_state *parser; mrb_state *mrb; @@ -520,6 +518,11 @@ main(int argc, char **argv) mrb_define_global_const(mrb, "ARGV", ARGV); mrb_gv_set(mrb, mrb_intern_lit(mrb, "$DEBUG"), mrb_bool_value(args.debug)); + /* Query terminal background color before any output */ + if (isatty(fileno(stdin)) && isatty(fileno(stdout))) { + mirb_highlight_query_terminal(); + } + print_hint(); cxt = mrb_ccontext_new(mrb); @@ -756,7 +759,7 @@ main(int argc, char **argv) /* did an exception occur? */ if (mrb->exc) { MRB_EXC_CHECK_EXIT(mrb, mrb->exc); - p_error(mrb, mrb->exc, cxt); + p_error(mrb, mrb->exc, cxt, &editor.highlight); mrb->exc = 0; } else { @@ -764,7 +767,7 @@ main(int argc, char **argv) if (!mrb_respond_to(mrb, result, MRB_SYM(inspect))){ result = mrb_any_to_s(mrb, result); } - p(mrb, result); + p(mrb, result, &editor.highlight); #ifndef MRB_NO_MIRB_UNDERSCORE *(mrb->c->ci->stack + 1) = result; #endif diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_highlight.c b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_highlight.c index 3d1c4f1f1b..16e371fc73 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_highlight.c +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_highlight.c @@ -5,6 +5,8 @@ */ #include "mirb_highlight.h" +#include "mirb_term.h" +#include #include #include #include @@ -21,6 +23,9 @@ #define DARK_IVAR "\033[34m" /* blue */ #define DARK_GVAR "\033[1;34m" /* bold blue */ #define DARK_REGEXP "\033[31m" /* red */ +#define DARK_RESULT "\033[36m" /* cyan (same as number) */ +#define DARK_ERROR "\033[1;31m" /* bold red */ +#define DARK_ARROW "\033[90m" /* gray */ /* Light theme colors (dark colors on light background) */ #define LIGHT_KEYWORD "\033[35m" /* magenta */ @@ -32,6 +37,9 @@ #define LIGHT_IVAR "\033[34m" /* blue */ #define LIGHT_GVAR "\033[34m" /* blue */ #define LIGHT_REGEXP "\033[31m" /* red */ +#define LIGHT_RESULT "\033[36m" /* cyan */ +#define LIGHT_ERROR "\033[31m" /* red */ +#define LIGHT_ARROW "\033[90m" /* gray */ #define COLOR_RESET "\033[0m" @@ -140,35 +148,66 @@ print_colored(mirb_highlighter *hl, const char *start, size_t len, mirb_token_ty if (*color) printf("%s", reset); } +/* Cached terminal background color from pre-query */ +static mirb_bg_color cached_bg_color = MIRB_BG_UNKNOWN; +static mrb_bool bg_color_queried = FALSE; + +/* + * Pre-query terminal background color + * Must be called before any output to avoid response appearing on screen + */ +void +mirb_highlight_query_terminal(void) +{ + if (!bg_color_queried) { + cached_bg_color = mirb_term_query_bg_color(500); /* 500ms timeout */ + bg_color_queried = TRUE; + } +} + /* - * Detect theme from environment + * Detect theme from terminal background color + * + * Priority: + * 1. MIRB_THEME environment variable (explicit override) + * 2. Cached OSC 11 result (from mirb_highlight_query_terminal) + * 3. COLORFGBG environment variable (rxvt, some xterm) + * 4. Default to dark theme */ mirb_theme mirb_highlight_detect_theme(void) { const char *env; - /* Check explicit MIRB_THEME first */ + /* 1. Check explicit MIRB_THEME first (user override) */ env = getenv("MIRB_THEME"); if (env) { if (strcmp(env, "light") == 0) return MIRB_THEME_LIGHT; if (strcmp(env, "dark") == 0) return MIRB_THEME_DARK; } - /* Check COLORFGBG (format: "fg;bg" where bg > 6 usually means light) */ + /* 2. Use cached OSC 11 result (must call mirb_highlight_query_terminal first) */ + if (cached_bg_color == MIRB_BG_LIGHT) return MIRB_THEME_LIGHT; + if (cached_bg_color == MIRB_BG_DARK) return MIRB_THEME_DARK; + + /* 3. Check COLORFGBG (format: "fg;bg" where bg > 6 usually means light) */ env = getenv("COLORFGBG"); if (env) { const char *semi = strchr(env, ';'); if (semi) { - int bg = atoi(semi + 1); + int bg_color = atoi(semi + 1); /* Background colors 7, 15, or high values typically mean light theme */ - if (bg == 7 || bg == 15 || (bg >= 230 && bg <= 255)) { + if (bg_color == 7 || bg_color == 15 || (bg_color >= 230 && bg_color <= 255)) { return MIRB_THEME_LIGHT; } + /* Low values (0-6, 8) typically mean dark theme */ + if (bg_color <= 8) { + return MIRB_THEME_DARK; + } } } - /* Default to dark theme (more common in terminals) */ + /* 4. Default to dark theme (more common in terminals) */ return MIRB_THEME_DARK; } @@ -404,7 +443,12 @@ mirb_highlight_print_line(mirb_highlighter *hl, const char *line) size_t len = (size_t)(p - token_start); - if (is_const) { + /* Check for hash key symbol syntax: identifier followed by ': ' */ + if (*p == ':' && (p[1] == ' ' || p[1] == '\0' || p[1] == ',' || p[1] == '}')) { + p++; /* include the colon */ + print_colored(hl, token_start, (size_t)(p - token_start), MIRB_TOK_SYMBOL); + } + else if (is_const) { print_colored(hl, token_start, len, MIRB_TOK_CONSTANT); } else if (!after_dot && is_keyword(token_start, len)) { @@ -423,3 +467,50 @@ mirb_highlight_print_line(mirb_highlighter *hl, const char *line) putchar(*p++); } } + +/* + * Print result value with highlighting + */ +void +mirb_highlight_print_result(mirb_highlighter *hl, const char *result) +{ + if (!hl->enabled) { + fputs(" => ", stdout); + fputs(result, stdout); + putchar('\n'); + return; + } + + /* Print arrow in gray */ + if (hl->theme == MIRB_THEME_DARK) { + fputs(DARK_ARROW " => " COLOR_RESET, stdout); + } + else { + fputs(LIGHT_ARROW " => " COLOR_RESET, stdout); + } + /* Syntax highlight the result value */ + mirb_highlight_print_line(hl, result); + putchar('\n'); +} + +/* + * Print error message with highlighting + */ +void +mirb_highlight_print_error(mirb_highlighter *hl, const char *error) +{ + if (!hl->enabled) { + fputs(error, stdout); + putchar('\n'); + return; + } + + if (hl->theme == MIRB_THEME_DARK) { + fputs(DARK_ERROR, stdout); + } + else { + fputs(LIGHT_ERROR, stdout); + } + fputs(error, stdout); + fputs(COLOR_RESET "\n", stdout); +} diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_highlight.h b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_highlight.h index b81a238450..a6383f9fa4 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_highlight.h +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_highlight.h @@ -59,11 +59,17 @@ void mirb_highlight_init(mirb_highlighter *hl, mrb_bool enabled); void mirb_highlight_set_theme(mirb_highlighter *hl, mirb_theme theme); /* - * Detect theme from environment variables + * Detect theme from environment variables and terminal query * Returns MIRB_THEME_DARK if cannot detect */ mirb_theme mirb_highlight_detect_theme(void); +/* + * Pre-query terminal background color (call before any output) + * This caches the result for later use by mirb_highlight_detect_theme() + */ +void mirb_highlight_query_terminal(void); + /* * Print a line with syntax highlighting * Handles multi-line strings/comments by tracking state @@ -75,4 +81,15 @@ void mirb_highlight_print_line(mirb_highlighter *hl, const char *line); */ void mirb_highlight_reset(mirb_highlighter *hl); +/* + * Print result value with highlighting + * Prints " => " prefix and the result string with appropriate colors + */ +void mirb_highlight_print_result(mirb_highlighter *hl, const char *result); + +/* + * Print error message with highlighting + */ +void mirb_highlight_print_error(mirb_highlighter *hl, const char *error); + #endif /* MIRB_HIGHLIGHT_H */ diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_term.c b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_term.c index ced2b4c2fc..68759e2622 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_term.c +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_term.c @@ -348,3 +348,144 @@ mirb_term_clear_below(void) { printf("\033[J"); } + +#if !defined(_WIN32) && !defined(_WIN64) +/* + * Query terminal background color using OSC 11 escape sequence + * + * Protocol: + * Send: ESC ] 11 ; ? ESC \ (or BEL instead of ESC \) + * Recv: ESC ] 11 ; rgb:RRRR/GGGG/BBBB ESC \ + * + * The response uses 16-bit color values (0000-FFFF per component). + * Some terminals use 8-bit (00-FF) format instead. + */ +mirb_bg_color +mirb_term_query_bg_color(int timeout_ms) +{ + struct termios old_term, new_term; + char buf[64]; + size_t total = 0; + ssize_t n; + int r, g, b; + mirb_bg_color result = MIRB_BG_UNKNOWN; + fd_set fds; + struct timeval tv; + const char *p; + + /* Must be a terminal */ + if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) { + return MIRB_BG_UNKNOWN; + } + + /* Save original terminal settings */ + if (tcgetattr(STDIN_FILENO, &old_term) < 0) { + return MIRB_BG_UNKNOWN; + } + + /* Flush any pending input first */ + tcflush(STDIN_FILENO, TCIFLUSH); + + /* Disable echo and canonical mode for raw read */ + new_term = old_term; + new_term.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL); + new_term.c_iflag &= ~(IXON | IXOFF | ICRNL); + new_term.c_cc[VMIN] = 0; + new_term.c_cc[VTIME] = 0; + + /* TCSAFLUSH: flush I/O and apply settings */ + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_term) < 0) { + return MIRB_BG_UNKNOWN; + } + + /* Wait for terminal settings to take effect */ + tcdrain(STDOUT_FILENO); + + /* Send OSC 11 query: ESC ] 11 ; ? ESC \ */ + if (write(STDOUT_FILENO, "\033]11;?\033\\", 8) != 8) { + goto restore; + } + /* Ensure query is sent to terminal */ + tcdrain(STDOUT_FILENO); + /* Give terminal time to process and respond */ + usleep(50000); /* 50ms */ + + /* Read response with timeout - loop to get complete response */ + while (total < sizeof(buf) - 1) { + int sel; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + sel = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); + if (sel < 0) { + if (errno == EINTR) continue; /* Retry on signal interrupt */ + break; /* Other error */ + } + if (sel == 0) break; /* Timeout */ + + n = read(STDIN_FILENO, buf + total, sizeof(buf) - 1 - total); + if (n <= 0) { + if (n < 0 && errno == EINTR) continue; /* Retry on signal interrupt */ + break; + } + total += (size_t)n; + + /* Check for terminator: ESC \ (0x1b 0x5c) or BEL (0x07) */ + if (total >= 2 && buf[total-2] == '\033' && buf[total-1] == '\\') break; + if (total >= 1 && buf[total-1] == '\007') break; + + /* Reduce timeout for subsequent reads */ + timeout_ms = 10; + } + + if (total == 0) { + goto restore; + } + buf[total] = '\0'; + + /* Parse response: look for "rgb:" followed by hex values */ + p = strstr(buf, "rgb:"); + if (p) { + p += 4; + /* Try 16-bit format: rgb:RRRR/GGGG/BBBB */ + if (sscanf(p, "%4x/%4x/%4x", &r, &g, &b) == 3) { + /* Normalize to 8-bit range */ + r >>= 8; g >>= 8; b >>= 8; + } + /* Try 8-bit format: rgb:RR/GG/BB */ + else if (sscanf(p, "%2x/%2x/%2x", &r, &g, &b) == 3) { + /* Already 8-bit */ + } + else { + goto restore; + } + + /* Calculate relative luminance (ITU-R BT.709 simplified) */ + /* Y = 0.2126*R + 0.7152*G + 0.0722*B */ + /* Scale: 0-255 input, threshold at ~127.5 */ + int luminance = (2126 * r + 7152 * g + 722 * b) / 10000; + result = (luminance < 128) ? MIRB_BG_DARK : MIRB_BG_LIGHT; + } + +restore: + /* Flush any remaining input before restoring terminal */ + tcflush(STDIN_FILENO, TCIFLUSH); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_term); + return result; +} + +#else /* Windows */ + +mirb_bg_color +mirb_term_query_bg_color(int timeout_ms) +{ + (void)timeout_ms; + /* Windows Terminal supports OSC 11, but implementation requires + * different I/O handling. For now, return unknown and rely on + * fallback detection methods. */ + return MIRB_BG_UNKNOWN; +} + +#endif /* _WIN32 */ diff --git a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_term.h b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_term.h index 6995bb15fd..8c9c5d1f26 100644 --- a/mrbgems/mruby-bin-mirb/tools/mirb/mirb_term.h +++ b/mrbgems/mruby-bin-mirb/tools/mirb/mirb_term.h @@ -118,4 +118,20 @@ void mirb_term_get_size(mirb_term *term); */ void mirb_term_flush(void); +/* + * Background color detection result + */ +typedef enum mirb_bg_color { + MIRB_BG_UNKNOWN, /* could not detect */ + MIRB_BG_DARK, /* dark background (luminance < 0.5) */ + MIRB_BG_LIGHT /* light background (luminance >= 0.5) */ +} mirb_bg_color; + +/* + * Query terminal background color using OSC 11 + * Returns MIRB_BG_UNKNOWN if terminal doesn't respond within timeout + * timeout_ms: timeout in milliseconds (recommended: 100) + */ +mirb_bg_color mirb_term_query_bg_color(int timeout_ms); + #endif /* MIRB_TERM_H */ diff --git a/mrbgems/mruby-compiler/core/codegen.c b/mrbgems/mruby-compiler/core/codegen.c index b9f2781b76..db7b3af020 100644 --- a/mrbgems/mruby-compiler/core/codegen.c +++ b/mrbgems/mruby-compiler/core/codegen.c @@ -4498,9 +4498,12 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos, * 1. Left pattern is not another NODE_PAT_ALT (avoid recursion issues) * 2. Left pattern generated at least one JMPNOT * 3. The last JMPNOT is immediately before current position + * 4. The instruction is actually OP_JMPNOT (not OP_JMP which has + * different format S vs BS - converting OP_JMP would corrupt bytecode) * In this case, convert JMPNOT to JMPIF and skip generating JMP */ if (node_type(pat_alt->left) != NODE_PAT_ALT && - left_fail != JMPLINK_START && left_fail + 2 == s->pc) { + left_fail != JMPLINK_START && left_fail + 2 == s->pc && + s->iseq[left_fail - 2] == OP_JMPNOT) { /* Extract the previous link from the JMPNOT chain. * The chain uses relative offsets where the end is marked by * an offset that points to address 0 (i.e., (pos+2)+offset == 0) */ @@ -4564,9 +4567,11 @@ codegen_pattern(codegen_scope *s, node *pattern, int target, uint32_t *fail_pos, *fail_pos = tmp; } else { - /* Variable not found - this is an error case, but for robustness fail the match */ - tmp = genjmp(s, OP_JMP, *fail_pos); - *fail_pos = tmp; + /* Variable not found - raise compile error like CRuby */ + const char *name = mrb_sym_name_len(s->mrb, pat_pin->name, NULL); + char buf[256]; + snprintf(buf, sizeof(buf), "%.200s: no such local variable", name); + codegen_error(s, buf); } } break; @@ -6696,11 +6701,12 @@ codegen(codegen_scope *s, node *tree, int val) fail_pos + 2 == s->pc && s->iseq[fail_pos - 2] == OP_JMPNOT) { if (mp->raise_on_fail) { - /* Single failure point with raise - replace JMPNOT with MATCHERR */ - int reg = s->iseq[fail_pos - 1]; /* Register from JMPNOT */ - s->pc = fail_pos - 2; /* Rewind past JMPNOT */ - s->lastpc = s->pc; - genop_1(s, OP_MATCHERR, reg); /* Emit MATCHERR with the register */ + /* Replace JMPNOT(BS,4bytes) with MATCHERR(B,2bytes)+NOP+NOP; + * keep the same size so that any jump targeting s->pc stays valid */ + s->iseq[fail_pos - 2] = OP_MATCHERR; + /* fail_pos-1 already holds the register operand */ + s->iseq[fail_pos] = OP_NOP; + s->iseq[fail_pos + 1] = OP_NOP; s->sp = saved_sp - 1; if (val) push(); break; /* Pattern matching complete */ diff --git a/src/load.c b/src/load.c index ac850373d2..6f9954b829 100644 --- a/src/load.c +++ b/src/load.c @@ -189,7 +189,7 @@ read_irep_record_1(mrb_state *mrb, const uint8_t *bin, const uint8_t *end, size_ case IREP_TT_STR: pool_data_len = bin_to_uint16(src); /* pool data length */ src += sizeof(uint16_t); - if (src + pool_data_len > end) return FALSE; + if (src + pool_data_len + 1 > end) return FALSE; if (st) { pool[i].tt = (pool_data_len<<2) | IREP_TT_SSTR; pool[i].u.str = (const char*)src; @@ -231,7 +231,7 @@ read_irep_record_1(mrb_state *mrb, const uint8_t *bin, const uint8_t *end, size_ continue; } - if (src + snl > end) return FALSE; + if (src + snl + 1 > end) return FALSE; if (flags & FLAG_SRC_MALLOC) { syms[i] = mrb_intern(mrb, (char*)src, snl); } diff --git a/src/string.c b/src/string.c index a0dee0f858..0cab3d80e6 100644 --- a/src/string.c +++ b/src/string.c @@ -40,7 +40,7 @@ const char mrb_digitmap[] = "0123456789abcdefghijklmnopqrstuvwxyz"; static void str_check_length(mrb_state *mrb, mrb_int len) { - if (len < 0) { + if (len < 0 || len == MRB_INT_MAX) { mrb_raise(mrb, E_ARGUMENT_ERROR, "negative (or overflowed) string size"); } #if MRB_STR_LENGTH_MAX != 0 diff --git a/src/symbol.c b/src/symbol.c index 04d00d2607..c91f64a078 100644 --- a/src/symbol.c +++ b/src/symbol.c @@ -86,6 +86,35 @@ sym_validate_len(mrb_state *mrb, size_t len) } } +/* Chunk-based string pool for heap-allocated symbol names */ +#define MRB_SYM_POOL_CHUNK_SIZE 4096 + +struct sym_pool_chunk { + struct sym_pool_chunk *next; + size_t used; + char buf[]; /* flexible array */ +}; + +static char* +sym_pool_alloc(mrb_state *mrb, size_t size) +{ + /* round up to even size to keep pointers even-aligned (LSB tagging) */ + size_t asize = (size + 1) & ~(size_t)1; + struct sym_pool_chunk *chunk = (struct sym_pool_chunk*)mrb->sym_pool; + if (chunk && chunk->used + asize <= MRB_SYM_POOL_CHUNK_SIZE) { + char *p = chunk->buf + chunk->used; + chunk->used += asize; + return p; + } + size_t csize = asize > MRB_SYM_POOL_CHUNK_SIZE ? asize : MRB_SYM_POOL_CHUNK_SIZE; + chunk = (struct sym_pool_chunk*)mrb_malloc(mrb, + offsetof(struct sym_pool_chunk, buf) + csize); + chunk->next = (struct sym_pool_chunk*)mrb->sym_pool; + chunk->used = asize; + mrb->sym_pool = (void*)chunk; + return chunk->buf; +} + /* Hash table for symbols (allocated on demand when symbols exceed threshold) */ struct mrb_sym_hash_table { uint8_t *symlink; /* collision resolution chains */ @@ -316,7 +345,7 @@ sym_intern_common(mrb_state *mrb, const char *name, size_t len, mrb_bool lit) /* Always heap-allocate when not explicitly literal */ uint32_t ulen = (uint32_t)len; size_t ilen = mrb_packed_int_len(ulen); - char *p = (char*)mrb_malloc(mrb, len+ilen+1); + char *p = sym_pool_alloc(mrb, len+ilen+1); mrb_packed_int_encode(ulen, (uint8_t*)p); memcpy(p+ilen, name, len); p[ilen+len] = 0; @@ -604,16 +633,15 @@ mrb_sym_name_len(mrb_state *mrb, mrb_sym sym, mrb_int *lenp) void mrb_free_symtbl(mrb_state *mrb) { - mrb_sym i, lim; - - for (i=1,lim=mrb->symidx+1; isymtbl[i]; - if (!symtbl_is_literal(tagged_ptr)) { - /* CRITICAL: Untag before mrb_free */ - const char *clean_ptr = symtbl_get_ptr(tagged_ptr); - mrb_free(mrb, (char*)clean_ptr); - } + /* Free symbol string pool chunks */ + struct sym_pool_chunk *chunk = (struct sym_pool_chunk*)mrb->sym_pool; + while (chunk) { + struct sym_pool_chunk *next = chunk->next; + mrb_free(mrb, chunk); + chunk = next; } + mrb->sym_pool = NULL; + mrb_free(mrb, (void*)mrb->symtbl); /* Free hash table if allocated */ @@ -629,6 +657,7 @@ mrb_init_symtbl(mrb_state *mrb) { /* Initialize in linear mode - hash table allocated on demand */ mrb->symhash = NULL; + mrb->sym_pool = NULL; } /**********************************************************************