Merge branch 'master' into stable

This commit is contained in:
mimaki
2025-04-20 13:25:22 +09:00
109 changed files with 2688 additions and 1478 deletions
+2 -1
View File
@@ -22,7 +22,8 @@ jobs:
- {os: ubuntu-22.04, cc: clang, cxx: clang++}
- {os: macos-15, cc: clang, cxx: clang++}
- {os: macos-14, cc: clang, cxx: clang++}
- {os: windows-latest, cc: gcc, cxx: g++, altname: "mingw-gcc"}
- {os: windows-2025, cc: gcc, cxx: g++, altname: "mingw-gcc"}
- {os: windows-2022, cc: gcc, cxx: g++, altname: "mingw-gcc"}
env:
MRUBY_CONFIG: ci/gcc-clang
CC: ${{ matrix.cc }}
+1 -1
View File
@@ -1,6 +1,6 @@
name: Coverage
on: [push, pull_request]
on: [push]
jobs:
coverage:
+22 -13
View File
@@ -4,15 +4,19 @@ default_stages: [pre-commit, pre-push]
default_language_version:
# force all unspecified Python hooks to run python3
python: python3
node: 22.2.0
# force all unspecified Node hooks to run Node.js v22.14.0 LTS
node: 22.14.0
minimum_pre_commit_version: "3.2.0"
exclude: "^tools/lrama/"
repos:
- repo: meta
hooks:
- id: identity
name: run identity check
name: run identity
description: check your identity
- id: check-hooks-apply
name: check hooks apply to the repository
name: run check-hooks-apply
description: check hooks apply to the repository
- repo: local
hooks:
- id: prettier
@@ -21,17 +25,19 @@ repos:
entry: prettier --write .
files: \.(json|md|ya?ml)$
language: node
additional_dependencies: ["prettier@3.3.3"]
additional_dependencies: ["prettier@3.5.3"]
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.0
rev: v8.24.3
hooks:
- id: gitleaks
name: detect hardcoded secrets
name: run gitleaks
description: detect hardcoded secrets with gitleaks
- repo: https://github.com/shssoichiro/oxipng
rev: v9.1.4
hooks:
- id: oxipng
name: run oxipng
description: use lossless compression to optimize PNG files
args: ["-o", "4", "--strip", "safe", "--alpha"]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
@@ -64,10 +70,12 @@ repos:
rev: v1.5.5
hooks:
- id: forbid-tabs
name: no-tabs checker
name: run no-tabs checker
description: check the codebase for tabs
exclude: Makefile$
- id: remove-tabs
name: tabs remover
name: run tabs remover
description: find and convert tabs to spaces
args: [--whitespaces-count, "2"]
exclude: Makefile$
- repo: https://github.com/rhysd/actionlint
@@ -81,7 +89,7 @@ repos:
hooks:
- id: codespell
name: run codespell
description: Check spelling with codespell
description: check spelling with codespell
args: [--ignore-words=.github/linters/codespell.txt]
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
@@ -95,7 +103,7 @@ repos:
hooks:
- id: markdownlint
name: run markdownlint
description: Checks the style of Markdown files
description: checks the style of Markdown files
args: [--config=.github/linters/.markdown-lint.yml]
types: [markdown]
files: \.md$
@@ -104,7 +112,7 @@ repos:
hooks:
- id: markdown-link-check
name: run markdown-link-check
description: Checks hyperlinks in Markdown files
description: checks hyperlinks in Markdown files
args: [--config=.github/linters/mlc_config.json, -q]
types: [markdown]
files: \.md$
@@ -113,12 +121,13 @@ repos:
hooks:
- id: shellcheck
name: run shellcheck
description: check shell scripts with a static analysis tool
- repo: https://github.com/adrienverge/yamllint
rev: v1.36.0
rev: v1.37.0
hooks:
- id: yamllint
name: run yamllint
description: Check YAML files with yamllint
description: check YAML files with yamllint
args: [--strict, -c=.github/linters/.yaml-lint.yml]
types: [yaml]
files: \.ya?ml$
+7 -6
View File
@@ -1,17 +1,17 @@
# Authors of mruby (mruby developers)
## The List of Contributors sorted by number of commits (as of 2025-03-01 8ba2ed6)
## The List of Contributors sorted by number of commits (as of 2025-04-02 07d7aa6)
6083 Yukihiro "Matz" Matsumoto (@matz)*
673 dearblue (@dearblue)*
6146 Yukihiro "Matz" Matsumoto (@matz)*
676 dearblue (@dearblue)*
587 KOBAYASHI Shuji (@shuujii)
353 Daniel Bovensiepen (@bovi)*
345 Takeshi Watanabe (@take-cheeze)*
333 Masaki Muranaka (@monaka)
234 Jun Hiroe (@suzukaze)
228 Tomoyuki Sahara (@tsahara)*
225 John Bampton (@jbampton)
220 Cremno (@cremno)*
219 John Bampton (@jbampton)
209 Yuki Kurihara (@ksss)+
144 Yasuhiro Matsumoto (@mattn)*
113 Carson McDonald (@carsonmcdonald)
@@ -103,6 +103,7 @@
5 Chris Reuter (@suetanvil)
5 Davide D'Agostino (@DAddYE)
5 Eric Hodel (@drbrain)
5 HASUMI Hitoshi (@hasumikin)
5 Hendrik (@Asmod4n)
5 Ichito Nagata (@i110)
5 Keita Obo (@ktaobo)*
@@ -117,7 +118,6 @@
5 xuejianqing (@joans321)
4 Dante Catalfamo (@dantecatalfamo)
4 Goro Kikuchi (@gorogit)
4 HASUMI Hitoshi (@hasumikin)
4 Herwin Weststrate (@herwinw)
4 Horimoto Yasuhiro (@komainu8)
4 Jon Moss (@maclover7)
@@ -126,6 +126,7 @@
4 Lanza (@LanzaSchneider)
4 Li Yazhou (@flaneur2020)
4 Marcus Stollsteimer (@stomar)
4 Mark Delk (@jethrodaniel)
4 NARUSE, Yui (@nurse)
4 Ravil Bayramgalin (@brainopia)*+
4 Satoshi Odawara (@SatoshiOdawara)
@@ -143,7 +144,6 @@
3 Jan Berdajs (@mrbrdo)
3 Jonas Minnberg (@sasq64)
3 Joseph McCullough (@joequery)
3 Mark Delk (@jethrodaniel)
3 Mark McCurry (@fundamental)
3 Nobuhiro Iwamatsu (@iwamatsu)
3 Per Lundberg (@perlun)*
@@ -303,6 +303,7 @@
1 arton (@arton)
1 duangsuse (@duangsuse)
1 fl0l0u (@fl0l0u)
1 Bradley Whited (@esotericpig)
1 hhc0null (@hhc0null)
1 iTitou (@titouanc)
1 javier ramírez (@javier)
+1 -1
View File
@@ -60,7 +60,7 @@ MRuby::Build.new do |conf|
# gperf settings
# conf.gperf do |gperf|
# gperf.command = 'gperf'
# gperf.compile_options = %q[-L ANSI-C -C -p -j1 -i 1 -g -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"]
# gperf.compile_options = %q[-L ANSI-C -C -j1 -i 1 -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"]
# end
# file extensions
+1 -1
View File
@@ -16,7 +16,7 @@ First, `realloc(NULL, size)` behaves the same as malloc(size). Second, `free(NUL
## Redefine `mrb_default_allocf()`
The only function in mruby that uses the standard C library's memory allocation functions is `mrb_default_allocf()`, defined in `alloc.c`. By defining this function within your application, you can customize the memory management of your application.
The only function in mruby that uses the standard C library's memory allocation functions is `mrb_default_allocf()`, defined in `allocf.c`. By defining this function within your application, you can customize the memory management of your application.
## Specify a function with `mrb_open_allocf()`
+1 -1
View File
@@ -71,7 +71,7 @@ header.
For `MRB_OPSYM()`, specify the names corresponding to operators (see
`MRuby::Presym::OPERATORS` in `lib/mruby/presym.rb` for the names that
can be specified for it). Other than that, describe only word characters
excluding leading and ending punctuations.
excluding leading and ending punctuation.
These macros are converted to static symbol IDs at compile time, unless
preallocate symbols are disabled by `conf.disable_presym`. In that case,
+2 -2
View File
@@ -4,7 +4,7 @@
# The language
- mruby now supports `private` and `protected` visibitily ([b0db0bd](https://github.com/mruby/mruby/commit/b0db0bd))
- mruby now supports `private` and `protected` visibility ([b0db0bd](https://github.com/mruby/mruby/commit/b0db0bd))
- Maximum length of inlined symbols reduced from 5 to 4 characters to provide space for visibility flags ([6442a01](https://github.com/mruby/mruby/commit/6442a01))
- Many methods are made private according to CRuby visibility ([4a0e806](https://github.com/mruby/mruby/commit/4a0e806))
- Generate OP_SSEND for `self.method` type calls ([111fe4b](https://github.com/mruby/mruby/commit/111fe4b))
@@ -131,7 +131,7 @@
- [#6243](https://github.com/mruby/mruby/pull/6243) Fixed base64 decoding in `mruby-pack`
- [#6244](https://github.com/mruby/mruby/pull/6244) Revise scope of role of `mrb_vm_run()`
- [#6246](https://github.com/mruby/mruby/pull/6246) Fix typo in `test/t/hash.rb`
- [#6249](https://github.com/mruby/mruby/pull/6249) Fix grammar in `src/vm.c`; `catched` -> `caught`
- [#6249](https://github.com/mruby/mruby/pull/6249) Fix spelling in `src/vm.c`
- [#6250](https://github.com/mruby/mruby/pull/6250) Fix spelling
- [#6251](https://github.com/mruby/mruby/pull/6251) Clean up root move `CODEOWNERS` to `.github` directory
- [#6253](https://github.com/mruby/mruby/pull/6253) Allow recycling fibers by GC if not referenced directly
+9 -2
View File
@@ -50,8 +50,9 @@ enum mrb_special_consts {
#define WORDBOX_FIXNUM_FLAG (1 << (WORDBOX_FIXNUM_BIT_POS - 1))
#define WORDBOX_FIXNUM_MASK ((1 << WORDBOX_FIXNUM_BIT_POS) - 1)
#if defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE)
#if defined(MRB_WORDBOX_NO_FLOAT_TRUNCATE) || defined(MRB_NO_FLOAT)
/* floats are allocated in heaps */
#define WORDBOX_IMMEDIATE_MASK 0x03
#define WORDBOX_SYMBOL_BIT_POS 2
#define WORDBOX_SYMBOL_SHIFT WORDBOX_SYMBOL_BIT_POS
#define WORDBOX_SYMBOL_FLAG (1 << (WORDBOX_SYMBOL_BIT_POS - 1))
@@ -68,7 +69,9 @@ enum mrb_special_consts {
#define WORDBOX_SYMBOL_MASK 0x1f
#endif
#ifndef WORDBOX_IMMEDIATE_MASK
#define WORDBOX_IMMEDIATE_MASK 0x07
#endif
#define WORDBOX_SET_SHIFT_VALUE(o,n,v) \
((o).w = (((uintptr_t)(v)) << WORDBOX_##n##_SHIFT) | WORDBOX_##n##_FLAG)
@@ -107,7 +110,7 @@ enum mrb_special_consts {
* undef : ...0001 0100
* fixnum: ...IIII III1
* symbol: ...SSSS SS10
* object: ...PPPP P000 (any bits are 1)
* object: ...PPPP PP00 (any bits are 1)
*/
typedef struct mrb_value {
uintptr_t w;
@@ -145,7 +148,11 @@ MRB_API mrb_value mrb_word_boxing_float_value(struct mrb_state*, mrb_float);
#endif
MRB_API mrb_value mrb_boxing_int_value(struct mrb_state*, mrb_int);
#if WORDBOX_IMMEDIATE_MASK == 0x3
#define mrb_immediate_p(o) ((o).w & WORDBOX_IMMEDIATE_MASK || (o).w <= MRB_Qundef)
#else
#define mrb_immediate_p(o) ((o).w & WORDBOX_IMMEDIATE_MASK || (o).w == MRB_Qnil)
#endif
#define mrb_ptr(o) mrb_val_union(o).p
#define mrb_cptr(o) mrb_val_union(o).vp->p
+4 -2
View File
@@ -176,11 +176,12 @@ size_t mrb_obj_iv_tbl_memsize(mrb_value);
mrb_value mrb_obj_iv_inspect(mrb_state*, struct RObject*);
void mrb_obj_iv_set_force(mrb_state *mrb, struct RObject *obj, mrb_sym sym, mrb_value v);
mrb_value mrb_mod_constants(mrb_state *mrb, mrb_value mod);
mrb_value mrb_mod_const_at(mrb_state *mrb, struct RClass *c, mrb_value ary);
mrb_value mrb_f_global_variables(mrb_state *mrb, mrb_value self);
mrb_value mrb_obj_instance_variables(mrb_state*, mrb_value);
mrb_value mrb_mod_class_variables(mrb_state*, mrb_value);
mrb_value mrb_mod_cv_get(mrb_state *mrb, struct RClass * c, mrb_sym sym);
mrb_bool mrb_mod_cv_defined(mrb_state *mrb, struct RClass * c, mrb_sym sym);
mrb_value mrb_mod_cv_get(mrb_state *mrb, struct RClass *c, mrb_sym sym);
mrb_bool mrb_mod_cv_defined(mrb_state *mrb, struct RClass *c, mrb_sym sym);
mrb_bool mrb_ident_p(const char *s, mrb_int len);
mrb_value mrb_exc_const_get(mrb_state *mrb, mrb_sym sym);
@@ -247,6 +248,7 @@ void mrb_gc_free_bint(mrb_state *mrb, struct RBasic *x);
void mrb_bint_copy(mrb_state *mrb, mrb_value x, mrb_value y);
size_t mrb_bint_memsize(mrb_value x);
mrb_value mrb_bint_hash(mrb_state *mrb, mrb_value x);
mrb_value mrb_bint_sqrt(mrb_state *mrb, mrb_value x);
#endif
#endif /* MRUBY_INTERNAL_H */
+1 -1
View File
@@ -30,7 +30,7 @@
* For `MRB_OPSYM`, specify the names corresponding to operators (see
* `MRuby::Presym::OPERATORS` in `lib/mruby/presym.rb` for the names that
* can be specified for it). Other than that, describe only word characters
* excluding leading and ending punctuations.
* excluding leading and ending punctuation.
*
* These macros are expanded to `mrb_intern_lit` if presym is disabled,
* therefore the mruby state variable is required. The above macros can be
-6
View File
@@ -94,12 +94,6 @@ struct mrb_state;
# define MRB_PRIx PRIx32
#endif
#ifdef MRB_ENDIAN_BIG
# define MRB_ENDIAN_LOHI(a,b) a b
#else
# define MRB_ENDIAN_LOHI(a,b) b a
#endif
MRB_API mrb_bool mrb_read_int(const char *p, const char *e, char **endp, mrb_int *np);
/* obsolete; do not use mrb_int_read() */
MRB_API mrb_int mrb_int_read(const char*, const char*, char**);
+1 -1
View File
@@ -266,7 +266,7 @@ module MRuby
def initialize(build)
super
@command = 'gperf'
@compile_options = %q[-L ANSI-C -C -p -j1 -i 1 -g -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"]
@compile_options = %q[-L ANSI-C -C -j1 -i 1 -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"]
end
def run(outfile, infile)
+74 -2
View File
@@ -882,7 +882,7 @@ mpz_get_str(mrb_state *mrb, char *s, mrb_int sz, mrb_int base, mpz_t *x)
if ((base & (base - 1)) == 0) { // base is a power of 2
int shift = 0;
while ((1 << shift) < base) shift++;
mp_limb mask = base - 1;
mp_limb mask = (mp_limb)base - 1;
mp_dbl_limb value = 0;
int bits = 0;
@@ -1246,6 +1246,61 @@ mpz_gcd(mrb_state *mrb, mpz_t *gg, mpz_t *aa, mpz_t *bb)
}
#endif
static size_t
mpz_bits(const mpz_t *x)
{
if (x->sz == 0 || x->sn == 0) return 0;
size_t limb_bits = sizeof(mp_limb) * 8;
// Get the most significant limb
size_t i = x->sz - 1;
mp_limb high = x->p[i];
// Number of bits = total full limbs + significant bits in top limb
return i * limb_bits + (limb_bits - lzb(high));
}
static void
mpz_sqrt(mrb_state *mrb, mpz_t *z, mpz_t *x)
{
mrb_assert(x->sn >= 0);
if (x->sz == 0) {
// sqrt(0) = 0
z->sn = 0;
z->sz = 0;
return;
}
// Estimate initial value: 1 << (bit_length(x) / 2)
size_t xbits = mpz_bits(x);
size_t sbit = (xbits + 1) / 2;
mpz_t s, t;
mpz_init_set_int(mrb, &s, 1);
mpz_mul_2exp(mrb, &s, &s, sbit);
mpz_init(mrb, &t);
// Iteratively refine s using Newton-Raphson method:
// s = (s + x / s) / 2
for (;;) {
mpz_mdiv(mrb, &t, x, &s); // t = x / s
mpz_add(mrb, &t, &t, &s); // t = s + x/s
mpz_div_2exp(mrb, &t, &t, 1); // t = (s + x/s) / 2
if (mpz_cmp(mrb, &t, &s) >= 0) {
// Converged: t >= s
break;
}
mpz_set(mrb, &s, &t);
}
mpz_move(mrb, z, &s);
mpz_clear(mrb, &t);
}
/* --- mruby functions --- */
/* initialize mpz_t from RBigint (not need to clear) */
static void
@@ -1861,7 +1916,7 @@ mrb_bint_to_s(mrb_state *mrb, mrb_value x, mrb_int base)
return mrb_str_new_lit(mrb, "0");
}
size_t len = mpz_sizeinbase(&a, (int)base);
if (MRB_INT_MAX-2 < len) {
if (sizeof(size_t) >= sizeof(mrb_int) && MRB_INT_MAX-2 < len) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "too long string from Integer");
}
mrb_value str = mrb_str_new(mrb, NULL, len+2);
@@ -2014,6 +2069,23 @@ mrb_bint_memsize(mrb_value x)
return z.sz * sizeof(mp_limb);
}
mrb_value
mrb_bint_sqrt(mrb_state *mrb, mrb_value x)
{
mpz_t a;
bint_as_mpz(RBIGINT(x), &a);
if (a.sn < 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "square root of negative number");
}
mpz_t z;
mpz_init(mrb, &z);
mpz_sqrt(mrb, &z, &a);
return bint_norm(mrb, bint_new(mrb, &z));
}
mrb_value
mrb_bint_hash(mrb_state *mrb, mrb_value x)
{
+2 -2
View File
@@ -811,7 +811,7 @@ realloc_pool_str(codegen_scope *s, mrb_irep_pool *p, mrb_int len)
str = (char*)p->u.str;
str = (char*)codegen_realloc(s, str, len+1);
}
p->tt = len<<2 | IREP_TT_STR;
p->tt = (uint32_t)(len<<2 | IREP_TT_STR);
str[len] = '\0';
p->u.str = (const char*)str;
}
@@ -4012,7 +4012,7 @@ scope_new(mrb_state *mrb, codegen_scope *prev, node *nlv)
s->lv = nlv;
s->sp += node_len(nlv)+1; /* add self */
s->nlocals = s->sp;
s->nlocals = s->nregs = s->sp;
if (nlv) {
mrb_sym *lv;
node *n = nlv;
+1 -1
View File
@@ -1540,7 +1540,7 @@ prohibit_literals(parser_state *p, node *n)
modifier_while "'while' modifier"
modifier_until "'until' modifier"
modifier_rescue "'rescue' modifier"
keyword_alias "'alis'"
keyword_alias "'alias'"
keyword_BEGIN "'BEGIN'"
keyword_END "'END'"
keyword__LINE__ "'__LINE__'"
+8 -7
View File
@@ -820,8 +820,8 @@ module Enumerable
return to_enum(:filter_map) unless blk
ary = []
self.each do |x|
x = blk.call(x)
self.each do |*x|
x = blk.call(*x)
ary.push x if x
end
ary
@@ -851,7 +851,8 @@ module Enumerable
# ["a", "b", "c", "b"].tally #=> {"a"=>1, "b"=>2, "c"=>1}
def tally
hash = {}
self.each do |x|
self.each do |*x|
x = x.__svalue
hash[x] = (hash[x]||0)+1
end
hash
@@ -870,12 +871,12 @@ module Enumerable
def sum(init=0,&block)
result=init
if block
self.each do |e|
result += block.call(e)
self.each do |*e|
result += block.call(*e)
end
else
self.each do |e|
result += e
self.each do |*e|
result += e.__svalue
end
end
result
+2 -2
View File
@@ -21,7 +21,7 @@ fiber_s_yield_by_c_method(mrb_state *mrb, mrb_value self)
static mrb_value
fiber_resume_by_c_func(mrb_state *mrb, mrb_value self)
{
int ci_index = mrb->c->ci - mrb->c->cibase;
ptrdiff_t ci_index = mrb->c->ci - mrb->c->cibase;
mrb_value ret = mrb_fiber_resume(mrb, self, 0, NULL);
if (ci_index != mrb->c->ci - mrb->c->cibase) {
mrb_raisef(mrb, E_EXCEPTION,
@@ -34,7 +34,7 @@ fiber_resume_by_c_func(mrb_state *mrb, mrb_value self)
static mrb_value
fiber_resume_by_c_method(mrb_state *mrb, mrb_value self)
{
int ci_index = mrb->c->ci - mrb->c->cibase;
ptrdiff_t ci_index = mrb->c->ci - mrb->c->cibase;
mrb_value ret = mrb_funcall_argv(mrb, self, mrb_intern_lit(mrb, "resume"), 0, NULL);
if (ci_index != mrb->c->ci - mrb->c->cibase) {
mrb_raisef(mrb, E_EXCEPTION,
+30 -31
View File
@@ -245,11 +245,9 @@ mrb_file_basename(mrb_state *mrb, mrb_value klass)
#if defined(_WIN32)
char bname[_MAX_DIR];
char extname[_MAX_EXT];
char buffer[_MAX_DIR + _MAX_EXT];
mrb_value s;
char *path;
mrb_get_args(mrb, "S", &s);
char *path = mrb_str_to_cstr(mrb, s);
mrb_get_args(mrb, "z", &path);
size_t ridx = strlen(path);
if (ridx > 0) {
ridx--;
@@ -257,22 +255,22 @@ mrb_file_basename(mrb_state *mrb, mrb_value klass)
path[ridx] = '\0';
ridx--;
}
if (strncmp(path, "/", 2) == 0) {
if (ridx == 0 && path[0] == '/') {
return mrb_str_new_cstr(mrb, path);
}
}
_splitpath((const char*)path, NULL, NULL, bname, extname);
snprintf(buffer, _MAX_DIR + _MAX_EXT, "%s%s", bname, extname);
return mrb_str_new_cstr(mrb, buffer);
mrb_value buffer = mrb_str_new_cstr(mrb, bname);
mrb_str_cat_cstr(mrb, buffer, extname);
return buffer;
#else
mrb_value s;
mrb_get_args(mrb, "S", &s);
char *path = mrb_str_to_cstr(mrb, s);
char *bname;
char *path, *bname;
mrb_get_args(mrb, "z", &path);
if ((bname = basename(path)) == NULL) {
mrb_sys_fail(mrb, "basename");
}
if (strncmp(bname, "//", 3) == 0) bname[1] = '\0'; /* patch for Cygwin */
if (strcmp(bname, "//") == 0) bname[1] = '\0'; /* patch for Cygwin */
return mrb_str_new_cstr(mrb, bname);
#endif
}
@@ -281,10 +279,10 @@ static mrb_value
mrb_file_realpath(mrb_state *mrb, mrb_value klass)
{
mrb_value pathname, dir_string;
mrb_int argc = mrb_get_args(mrb, "S|S", &pathname, &dir_string);
if (argc == 2) {
if (mrb_get_args(mrb, "S|S", &pathname, &dir_string) == 2) {
mrb_value s = mrb_str_dup(mrb, dir_string);
s = mrb_str_append(mrb, s, mrb_str_new_cstr(mrb, FILE_SEPARATOR));
s = mrb_str_cat_cstr(mrb, s, FILE_SEPARATOR);
s = mrb_str_append(mrb, s, pathname);
pathname = s;
}
@@ -363,7 +361,7 @@ path_parse(mrb_state *mrb, mrb_value ary, const char *path, int ai)
SKIP_DIRSEP(path);
const char *path0 = path;
NEXT_DIRSEP(path);
int len = path - path0;
ptrdiff_t len = path - path0;
if (len == 0) {
break;
}
@@ -414,7 +412,7 @@ path_gethome(mrb_state *mrb, const char **pathp)
const char *username = ++*pathp;
NEXT_DIRSEP(*pathp);
int len = *pathp - username;
ptrdiff_t len = *pathp - username;
if (len == 0) {
home = getenv("HOME");
@@ -431,7 +429,7 @@ path_gethome(mrb_state *mrb, const char **pathp)
}
}
else {
const char *uname = RSTRING_CSTR(mrb, mrb_str_new(mrb, username, len));
const char *uname = RSTRING_CSTR(mrb, mrb_str_new(mrb, username, (mrb_int)len));
#if defined(_WIN32) || defined(MRB_IO_NO_PWNAM)
mrb_raisef(mrb, E_ARGUMENT_ERROR, "user %s doesn't exist", uname);
#else
@@ -458,7 +456,7 @@ path_expand(mrb_state *mrb, const char *path, const char *base, mrb_bool tilda)
{
mrb_value ary;
// split path conponents as array and normalization
// split path components as array and normalization
if (tilda && path[0] == '~') {
base = path_gethome(mrb, &path);
ary = path_split(mrb, path, base, NULL);
@@ -639,7 +637,7 @@ mrb_file_flock(mrb_state *mrb, mrb_value self)
}
/* FALLTHRU - should not happen */
default:
mrb_sys_fail(mrb, "flock failed");
mrb_sys_fail(mrb, "flock");
break;
}
}
@@ -653,10 +651,10 @@ mrb_file_size(mrb_state *mrb, mrb_value self)
mrb_stat st;
int fd = mrb_io_fileno(mrb, self);
if (mrb_fstat(fd, &st) == -1) {
mrb_raise(mrb, E_RUNTIME_ERROR, "fstat failed");
mrb_sys_fail(mrb, "fstat");
}
if (st.st_size > MRB_INT_MAX) {
if (sizeof(st.st_size) >= sizeof(mrb_int) && st.st_size > MRB_INT_MAX) {
#ifdef MRB_NO_FLOAT
mrb_raise(mrb, E_RUNTIME_ERROR, "File#size too large for MRB_NO_FLOAT");
#else
@@ -702,7 +700,7 @@ mrb_file_truncate(mrb_state *mrb, mrb_value self)
int fd = mrb_io_fileno(mrb, self);
mrb_int length = mrb_as_int(mrb, lenv);
if (mrb_ftruncate(fd, length) != 0) {
mrb_raise(mrb, E_IO_ERROR, "ftruncate failed");
mrb_sys_fail(mrb, "ftruncate");
}
return mrb_fixnum_value(0);
@@ -740,6 +738,7 @@ mrb_file_s_chmod(mrb_state *mrb, mrb_value klass)
mrb_get_args(mrb, "i*", &mode, &filenames, &argc);
for (int i = 0; i < argc; i++) {
mrb_ensure_string_type(mrb, filenames[i]);
const char *utf8_path = RSTRING_CSTR(mrb, filenames[i]);
char *path = mrb_locale_from_utf8(utf8_path, -1);
if (CHMOD(path, mode) == -1) {
@@ -761,17 +760,16 @@ mrb_file_s_readlink(mrb_state *mrb, mrb_value klass)
return mrb_nil_value(); // unreachable
#else
const char *path;
char *buf, *tmp;
size_t bufsize = 100;
ssize_t rc;
mrb_value ret;
mrb_get_args(mrb, "z", &path);
tmp = mrb_locale_from_utf8(path, -1);
buf = (char*)mrb_malloc(mrb, bufsize);
while ((rc = readlink(tmp, buf, bufsize)) == (ssize_t)bufsize && rc != -1) {
bufsize *= 2;
char *tmp = mrb_locale_from_utf8(path, -1);
char *buf = (char*)mrb_malloc(mrb, bufsize);
ssize_t rc;
while ((rc = readlink(tmp, buf, bufsize)) == (ssize_t)bufsize) {
bufsize += 100;
buf = (char*)mrb_realloc(mrb, buf, bufsize);
}
mrb_locale_free(tmp);
@@ -780,7 +778,8 @@ mrb_file_s_readlink(mrb_state *mrb, mrb_value klass)
mrb_sys_fail(mrb, path);
}
tmp = mrb_utf8_from_locale(buf, -1);
ret = mrb_str_new(mrb, tmp, rc);
mrb_value ret = mrb_str_new(mrb, tmp, rc);
mrb_utf8_free(tmp);
mrb_free(mrb, buf);
+1 -1
View File
@@ -276,7 +276,7 @@ mrb_mruby_io_gem_test(mrb_state* mrb)
}
#endif
if (env_home) {
char *utf8 = mrb_utf8_from_locale(env_home, strlen(env_home));
char *utf8 = mrb_utf8_from_locale(env_home, -1);
mrb_value path = mrb_str_new_cstr(mrb, utf8);
#ifdef _WIN32
char *pathp = RSTRING_PTR(path);
+23 -2
View File
@@ -643,8 +643,29 @@ mrb_mod_remove_method(mrb_state *mrb, mrb_value mod)
static mrb_value
mrb_mod_s_constants(mrb_state *mrb, mrb_value mod)
{
mrb_raise(mrb, E_NOTIMP_ERROR, "Module.constants not implemented");
return mrb_nil_value(); /* not reached */
if (mrb_get_argc(mrb) > 0 || mrb_class_ptr(mod) != mrb->module_class) {
return mrb_mod_constants(mrb, mod);
}
const struct RProc *proc = mrb->c->ci[-1].proc;
struct RClass *c = MRB_PROC_TARGET_CLASS(proc);
mrb_value ary = mrb_ary_new(mrb);
if (!c) c = mrb->object_class;
mrb_mod_const_at(mrb, c, ary);
proc = proc->upper;
while (proc) {
struct RClass *c2 = MRB_PROC_TARGET_CLASS(proc);
if (!c2) c2 = mrb->object_class;
mrb_mod_const_at(mrb, c2, ary);
proc = proc->upper;
}
while (c) {
mrb_mod_const_at(mrb, c, ary);
c = c->super;
if (c == mrb->object_class) break;
}
return ary;
}
static mrb_value
@@ -262,6 +262,46 @@ flo_remainder(mrb_state *mrb, mrb_value self)
}
#endif
static mrb_int
isqrt(mrb_int n)
{
mrb_assert(n >= 0);
if (n < 2) return n;
mrb_int x = n;
mrb_int y = (x + 1) / 2;
// Babylonian method (integer version)
while (y < x) {
x = y;
y = (x + n / x) / 2;
}
return x;
}
static mrb_value
int_sqrt(mrb_state *mrb, mrb_value self)
{
mrb_value arg = mrb_get_arg1(mrb);
if (mrb_integer_p(arg)) {
mrb_int n = mrb_integer(arg);
if (n < 0) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "non-negative integer required");
}
return mrb_int_value(mrb, isqrt(n));
}
#ifdef MRB_USE_BIGINT
else if (mrb_bigint_p(arg)) {
return mrb_bint_sqrt(mrb, arg);
}
#endif
else {
mrb_raise(mrb, E_TYPE_ERROR, "expected Integer");
}
}
void
mrb_mruby_numeric_ext_gem_init(mrb_state* mrb)
{
@@ -275,6 +315,7 @@ mrb_mruby_numeric_ext_gem_init(mrb_state* mrb)
mrb_define_method_id(mrb, ic, MRB_SYM(size), int_size, MRB_ARGS_NONE());
mrb_define_method_id(mrb, ic, MRB_SYM_Q(odd), int_odd, MRB_ARGS_NONE());
mrb_define_method_id(mrb, ic, MRB_SYM_Q(even), int_even, MRB_ARGS_NONE());
mrb_define_class_method_id(mrb, ic, MRB_SYM(sqrt), int_sqrt, MRB_ARGS_REQ(1));
#ifndef MRB_NO_FLOAT
struct RClass *fc = mrb->float_class;
@@ -81,3 +81,9 @@ assert('Integer#digits') do
assert_equal([4, 6, 6, 0, 5], 12345.digits(7))
assert_equal([45, 23, 1], 12345.digits(100))
end
assert('Integer.sqrt') do
assert_equal(4, Integer.sqrt(16))
assert_equal(10, Integer.sqrt(100))
assert_equal(85, Integer.sqrt(7244))
end
+8 -9
View File
@@ -1188,7 +1188,7 @@ pack_nul(mrb_state *mrb, mrb_value dst, mrb_int didx, int count)
}
static void
check_x(mrb_state *mrb, int a, int count, char c)
check_x(mrb_state *mrb, mrb_int a, mrb_int count, char c)
{
if (a < count) {
mrb_raisef(mrb, E_ARGUMENT_ERROR, "%c outside of string", c);
@@ -1209,7 +1209,7 @@ has_tmpl(const struct tmpl *tmpl)
}
static enum pack_dir
read_tmpl(mrb_state *mrb, struct tmpl *tmpl, enum pack_type *typep, int *sizep, int *countp, unsigned int *flagsp)
read_tmpl(mrb_state *mrb, struct tmpl *tmpl, enum pack_type *typep, mrb_int *sizep, mrb_int *countp, unsigned int *flagsp)
{
mrb_int t, tlen;
int ch, size = 0;
@@ -1505,7 +1505,7 @@ mrb_pack_pack(mrb_state *mrb, mrb_value ary)
mrb_value o, result;
struct tmpl tmpl;
enum pack_type type;
int count, size;
mrb_int count, size;
unsigned int flags;
enum pack_dir dir;
@@ -1627,16 +1627,15 @@ mrb_pack_pack(mrb_state *mrb, mrb_value ary)
}
static mrb_value
pack_unpack(mrb_state *mrb, mrb_value str, int single)
pack_unpack(mrb_state *mrb, mrb_value str, mrb_bool single)
{
mrb_value result;
struct tmpl tmpl;
int count;
mrb_int count;
unsigned int flags;
enum pack_dir dir;
enum pack_type type;
int size;
int srcidx, srclen;
mrb_int size, srcidx, srclen;
const unsigned char *sptr;
prepare_tmpl(mrb, &tmpl);
@@ -1749,13 +1748,13 @@ pack_unpack(mrb_state *mrb, mrb_value str, int single)
static mrb_value
mrb_pack_unpack(mrb_state *mrb, mrb_value str)
{
return pack_unpack(mrb, str, 0);
return pack_unpack(mrb, str, FALSE);
}
static mrb_value
mrb_pack_unpack1(mrb_state *mrb, mrb_value str)
{
return pack_unpack(mrb, str, 1);
return pack_unpack(mrb, str, TRUE);
}
void
+2 -2
View File
@@ -1028,9 +1028,9 @@ rational_hash(mrb_state *mrb, mrb_value rat)
#ifdef RAT_BIGINT
if (RAT_BIGINT_P(rat)) {
mrb_value tmp = mrb_bint_hash(mrb, mrb_obj_value(r->b.num));
hash = mrb_integer(tmp);
hash = (uint32_t)mrb_integer(tmp);
tmp = mrb_bint_hash(mrb, mrb_obj_value(r->b.den));
hash ^= mrb_integer(tmp);
hash ^= (uint32_t)mrb_integer(tmp);
return mrb_int_value(mrb, hash);
}
#endif
+1 -1
View File
@@ -608,7 +608,7 @@ retry:
}
mrb_value str = mrb_bint_to_s(mrb, val, base);
s = RSTRING_PTR(str);
len = RSTRING_LEN(str);
len = (int)RSTRING_LEN(str);
}
goto str_skip;
#endif
+2 -2
View File
@@ -909,8 +909,8 @@ int_chr(mrb_state *mrb, mrb_value num)
*
* Returns next sequence of the string;
*
* a = "abc"
* a.succ #=> "abd"
* a = "bed"
* a.succ #=> "bee"
*/
static mrb_value
str_succ_bang(mrb_state *mrb, mrb_value self)
+1 -1
View File
@@ -1407,7 +1407,7 @@ mrb_ary_to_s(mrb_state *mrb, mrb_value self)
return ret;
}
/* check array equality: 1=equal,0=not_equal,-1=need_elments_check */
/* check array equality: 1=equal,0=not_equal,-1=need_elements_check */
static mrb_int
ary_eq(mrb_state *mrb, mrb_value ary1, mrb_value ary2)
{
+1 -1
View File
@@ -2789,7 +2789,7 @@ mrb_mod_module_function(mrb_state *mrb, mrb_value mod)
mrb_method_t m = mrb_method_search(mrb, rclass, mid);
prepare_singleton_class(mrb, (struct RBasic*)rclass);
MRB_METHOD_SET_VISIBILITY(m, MT_PRIVATE);
MRB_METHOD_SET_VISIBILITY(m, MT_PUBLIC);
mrb_define_method_raw(mrb, rclass->c, mid, m);
mrb_gc_arena_restore(mrb, ai);
}
+5 -3
View File
@@ -1274,12 +1274,13 @@ mrb_hash_set(mrb_state *mrb, mrb_value hash, mrb_value key, mrb_value val)
mrb_field_write_barrier_value(mrb, mrb_basic_ptr(hash), key);
mrb_field_write_barrier_value(mrb, mrb_basic_ptr(hash), val);
}
static void
hash_set_default_proc(mrb_state *mrb, mrb_value hash, mrb_value proc)
{
struct RProc *p = mrb_proc_ptr(proc);
if (MRB_PROC_STRICT_P(p)) {
int n = mrb_proc_arity(p);
mrb_int n = mrb_proc_arity(p);
if (n != 2 && (n >= 0 || n < -3)) {
if (n < 0) n = -n-1;
mrb_raisef(mrb, E_TYPE_ERROR, "default_proc takes two arguments (2 for %d)", n);
@@ -1493,11 +1494,12 @@ mrb_hash_set_default_proc(mrb_state *mrb, mrb_value hash)
mrb_value ifnone = mrb_get_arg1(mrb);
hash_modify(mrb, hash);
if (!mrb_nil_p(ifnone)) {
mrb_bool has_ifnone = !mrb_nil_p(ifnone);
if (has_ifnone) {
mrb_check_type(mrb, ifnone, MRB_TT_PROC);
}
mrb_iv_set(mrb, hash, MRB_SYM(ifnone), ifnone);
if (!mrb_nil_p(ifnone)) {
if (has_ifnone) {
hash_set_default_proc(mrb, hash, ifnone);
}
else {
+21 -18
View File
@@ -1181,32 +1181,32 @@ enum str_convert_range {
};
static enum str_convert_range
str_convert_range(mrb_state *mrb, mrb_value str, mrb_value indx, mrb_value alen, mrb_int *beg, mrb_int *len)
str_convert_range(mrb_state *mrb, mrb_value str, mrb_value idx, mrb_value alen, mrb_int *beg, mrb_int *len)
{
if (!mrb_undef_p(alen)) {
*beg = mrb_as_int(mrb, indx);
*beg = mrb_as_int(mrb, idx);
*len = mrb_as_int(mrb, alen);
return STR_CHAR_RANGE;
}
else {
switch (mrb_type(indx)) {
switch (mrb_type(idx)) {
default:
indx = mrb_ensure_int_type(mrb, indx);
idx = mrb_ensure_int_type(mrb, idx);
/* fall through */
case MRB_TT_INTEGER:
*beg = mrb_integer(indx);
*beg = mrb_integer(idx);
*len = 1;
return STR_CHAR_RANGE;
case MRB_TT_STRING:
*beg = str_index_str(mrb, str, indx, 0);
*beg = str_index_str(mrb, str, idx, 0);
if (*beg < 0) { break; }
*len = RSTRING_LEN(indx);
*len = RSTRING_LEN(idx);
return STR_BYTE_RANGE_CORRECTED;
case MRB_TT_RANGE:
*len = RSTRING_CHAR_LEN(str);
switch (mrb_range_beg_len(mrb, indx, beg, len, *len, TRUE)) {
switch (mrb_range_beg_len(mrb, idx, beg, len, *len, TRUE)) {
case MRB_RANGE_OK:
return STR_CHAR_RANGE_CORRECTED;
case MRB_RANGE_OUT:
@@ -1220,11 +1220,11 @@ str_convert_range(mrb_state *mrb, mrb_value str, mrb_value indx, mrb_value alen,
}
mrb_value
mrb_str_aref(mrb_state *mrb, mrb_value str, mrb_value indx, mrb_value alen)
mrb_str_aref(mrb_state *mrb, mrb_value str, mrb_value idx, mrb_value alen)
{
mrb_int beg, len;
switch (str_convert_range(mrb, str, indx, alen, &beg, &len)) {
switch (str_convert_range(mrb, str, idx, alen, &beg, &len)) {
case STR_CHAR_RANGE_CORRECTED:
return str_subseq(mrb, str, beg, len);
case STR_CHAR_RANGE:
@@ -1232,8 +1232,8 @@ mrb_str_aref(mrb_state *mrb, mrb_value str, mrb_value indx, mrb_value alen)
if (mrb_undef_p(alen) && !mrb_nil_p(str) && RSTRING_LEN(str) == 0) return mrb_nil_value();
return str;
case STR_BYTE_RANGE_CORRECTED:
if (mrb_string_p(indx)) {
return mrb_str_dup(mrb, indx);
if (mrb_string_p(idx)) {
return mrb_str_dup(mrb, idx);
}
else {
return mrb_str_byte_subseq(mrb, str, beg, len);
@@ -1417,12 +1417,12 @@ str_escape(mrb_state *mrb, mrb_value str, mrb_bool inspect)
}
static void
mrb_str_aset(mrb_state *mrb, mrb_value str, mrb_value indx, mrb_value alen, mrb_value replace)
mrb_str_aset(mrb_state *mrb, mrb_value str, mrb_value idx, mrb_value alen, mrb_value replace)
{
mrb_int beg, len, charlen;
mrb_ensure_string_type(mrb, replace);
switch (str_convert_range(mrb, str, indx, alen, &beg, &len)) {
switch (str_convert_range(mrb, str, idx, alen, &beg, &len)) {
case STR_OUT_OF_RANGE:
default:
mrb_raise(mrb, E_INDEX_ERROR, "string not matched");
@@ -1432,7 +1432,7 @@ mrb_str_aset(mrb_state *mrb, mrb_value str, mrb_value indx, mrb_value alen, mrb_
}
charlen = RSTRING_CHAR_LEN(str);
if (beg < 0) { beg += charlen; }
if (beg < 0 || beg > charlen) { str_out_of_index(mrb, indx); }
if (beg < 0 || beg > charlen) { str_out_of_index(mrb, idx); }
/* fall through */
case STR_CHAR_RANGE_CORRECTED:
beg = chars2bytes(str, 0, beg);
@@ -1460,9 +1460,9 @@ mrb_str_aset(mrb_state *mrb, mrb_value str, mrb_value indx, mrb_value alen, mrb_
static mrb_value
mrb_str_aset_m(mrb_state *mrb, mrb_value str)
{
mrb_value indx, alen, replace;
mrb_value idx, alen, replace;
switch (mrb_get_args(mrb, "oo|S!", &indx, &alen, &replace)) {
switch (mrb_get_args(mrb, "oo|S!", &idx, &alen, &replace)) {
case 2:
replace = alen;
alen = mrb_undef_value();
@@ -1470,7 +1470,7 @@ mrb_str_aset_m(mrb_state *mrb, mrb_value str)
case 3:
break;
}
mrb_str_aset(mrb, str, indx, alen, replace);
mrb_str_aset(mrb, str, idx, alen, replace);
return replace;
}
@@ -3046,6 +3046,9 @@ sub_replace(mrb_state *mrb, mrb_value self)
mrb_value result;
mrb_get_args(mrb, "ssi", &p, &plen, &match, &mlen, &found);
if (found < 0 || RSTRING_LEN(self) < found) {
mrb_raise(mrb, E_RUNTIME_ERROR, "argument out of range");
}
result = mrb_str_new(mrb, 0, 0);
for (mrb_int i=0; i<plen; i++) {
if (p[i] != '\\' || i+1==plen) {
+30 -23
View File
@@ -624,7 +624,7 @@ mrb_mod_cv_get(mrb_state *mrb, struct RClass *c, mrb_sym sym)
c = c->super;
}
if (given) return v;
if (cls && cls->tt == MRB_TT_SCLASS) {
if (cls->tt == MRB_TT_SCLASS) {
mrb_value klass;
klass = mrb_obj_iv_get(mrb, (struct RObject*)cls, MRB_SYM(__attached__));
@@ -669,7 +669,7 @@ mrb_mod_cv_set(mrb_state *mrb, struct RClass *c, mrb_sym sym, mrb_value v)
c = c->super;
}
if (cls && cls->tt == MRB_TT_SCLASS) {
if (cls->tt == MRB_TT_SCLASS) {
mrb_value klass;
klass = mrb_obj_iv_get(mrb, (struct RObject*)cls, MRB_SYM(__attached__));
@@ -684,7 +684,7 @@ mrb_mod_cv_set(mrb_state *mrb, struct RClass *c, mrb_sym sym, mrb_value v)
break;
}
}
else if (cls && cls->tt == MRB_TT_ICLASS) {
else if (cls->tt == MRB_TT_ICLASS) {
c = cls->c;
}
else {
@@ -825,35 +825,35 @@ mrb_const_get(mrb_state *mrb, mrb_value mod, mrb_sym sym)
mrb_value
mrb_vm_const_get(mrb_state *mrb, mrb_sym sym)
{
struct RClass *c;
struct RClass *c2;
mrb_value v;
const struct RProc *proc = mrb->c->ci->proc;
struct RClass *c = MRB_PROC_TARGET_CLASS(proc), *c2;
mrb_value v;
c = MRB_PROC_TARGET_CLASS(proc);
if (!c) c = mrb->object_class;
if (iv_get(mrb, class_iv_ptr(c), sym, &v)) {
return v;
}
c2 = c;
while (c2 && c2->tt == MRB_TT_SCLASS) {
mrb_value klass;
if (!iv_get(mrb, class_iv_ptr(c2), MRB_SYM(__attached__), &klass)) {
c2 = NULL;
break;
}
c2 = mrb_class_ptr(klass);
}
if (c2 && (c2->tt == MRB_TT_CLASS || c2->tt == MRB_TT_MODULE)) c = c2;
proc = proc->upper;
while (proc) {
for (proc = proc->upper; proc; proc = proc->upper) {
c2 = MRB_PROC_TARGET_CLASS(proc);
if (!c2) c2 = mrb->object_class;
if (c2 && iv_get(mrb, class_iv_ptr(c2), sym, &v)) {
if (iv_get(mrb, class_iv_ptr(c2), sym, &v)) {
return v;
}
proc = proc->upper;
}
if (c->tt == MRB_TT_SCLASS) {
v = const_get_nohook(mrb, c, sym, TRUE);
if (!mrb_undef_p(v)) {
return v;
}
mrb_value klass;
for (c2 = c; c2 && c2->tt == MRB_TT_SCLASS; c2 = mrb_class_ptr(klass)) {
if (!iv_get(mrb, class_iv_ptr(c2), MRB_SYM(__attached__), &klass)) {
c2 = NULL;
break;
}
}
if (c2 && (c2->tt == MRB_TT_CLASS || c2->tt == MRB_TT_MODULE)) c = c2;
}
return const_get(mrb, c, sym, TRUE);
}
@@ -926,6 +926,13 @@ const_i(mrb_state *mrb, mrb_sym sym, mrb_value v, void *p)
return 0;
}
mrb_value
mrb_mod_const_at(mrb_state *mrb, struct RClass *c, mrb_value ary)
{
iv_foreach(mrb, class_iv_ptr(c), const_i, &ary);
return ary;
}
/* 15.2.2.4.24 */
/*
* call-seq:
@@ -943,7 +950,7 @@ mrb_mod_constants(mrb_state *mrb, mrb_value mod)
mrb_get_args(mrb, "|b", &inherit);
ary = mrb_ary_new(mrb);
while (c) {
iv_foreach(mrb, class_iv_ptr(c), const_i, &ary);
mrb_mod_const_at(mrb, c, ary);
if (!inherit) break;
c = c->super;
if (c == mrb->object_class) break;
+2 -2
View File
@@ -845,7 +845,7 @@ mrb_value
mrb_object_exec(mrb_state *mrb, mrb_value self, struct RClass *target_class)
{
mrb_callinfo *ci = mrb->c->ci;
int bidx = mrb_ci_bidx(ci);
mrb_int bidx = ci_bidx(ci);
mrb_value blk = ci->stack[bidx];
if (mrb_nil_p(blk)) {
mrb_raise(mrb, E_ARGUMENT_ERROR, "no block given");
@@ -1915,7 +1915,7 @@ RETRY_TRY_BLOCK:
}
else if ((m.flags & MRB_METHOD_PROTECTED_FL) && mrb_obj_is_kind_of(mrb, recv, ci->u.target_class)) {
mrb_value args = (ci->n == 15) ? regs[1] : mrb_ary_new_from_values(mrb, ci->n, regs+1);
mrb_no_method_error(mrb, mid, args, "proteced method '%n' called for %T", mid, recv);
mrb_no_method_error(mrb, mid, args, "protected method '%n' called for %T", mid, recv);
}
}
ci->cci = CINFO_NONE;
+1 -1
View File
@@ -35,7 +35,7 @@ MRuby::Toolchain.new(:visualcpp) do |conf, _params|
conf.gperf do |gperf|
gperf.command = 'gperf.exe'
gperf.compile_options = %q[-L ANSI-C -C -p -j1 -i 1 -g -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"]
gperf.compile_options = %q[-L ANSI-C -C -j1 -i 1 -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"]
end
conf.exts do |exts|
+29 -2
View File
@@ -802,14 +802,18 @@ assert('method visibility') do
v = CallTypeTest.new
assert_raise(NoMethodError) { v.func { :test } }
assert_raise_with_message_pattern(NameError, "private method 'func' called for CallTypeTest") do
v.func { :test }
end
assert_equal :test, v.test_private { :test }
class CallTypeTest
protected :func
end
assert_raise(NoMethodError) { v.func { :test } }
assert_raise_with_message_pattern(NameError, "protected method 'func' called for CallTypeTest") do
v.func { :test }
end
assert_equal :test, v.test_protected { :test }
assert_equal :test, v.test_private { :test }
@@ -834,6 +838,9 @@ assert('Module#module_function') do
end
assert_true M.respond_to?(:modfunc)
assert_equal nil do
M.modfunc
end
end
assert('module with non-class/module outer raises TypeError') do
@@ -882,3 +889,23 @@ assert('shared empty iv_tbl (prepend)') do
m2::CONST2 = 2
assert_equal 2, c::CONST2
end
assert('constant lookup #6506') do
Module.new do
module X
module A
class WWW; end
end
end
module X::Y; end
module X::Y::Z
extend X::A
class << self
assert_nothing_raised{WWW}
end
end
end
end
+346 -31
View File
@@ -1,12 +1,327 @@
# NEWS for Lrama
## Lrama 0.7.0 (2025-01-21)
## [EXPERIMENTAL] Support the generation of the IELR(1) parser described in this paper
Support the generation of the IELR(1) parser described in this paper.
https://www.sciencedirect.com/science/article/pii/S0167642309001191
If you use IELR(1) parser, you can write the following directive in your grammar file.
```yacc
%define lr.type ielr
```
But, currently IELR(1) parser is experimental feature. If you find any bugs, please report it to us. Thank you.
## Support `-t` option as same as `--debug` option
Support to `-t` option as same as `--debug` option.
These options align with Bison behavior. So same as `--debug` option.
## Trace only explicit rules
Support to trace only explicit rules.
If you use `--trace=rules` option, it shows include mid-rule actions. If you want to show only explicit rules, you can use `--trace=only-explicit-rules` option.
Example:
```yacc
%{
%}
%union {
int i;
}
%token <i> number
%type <i> program
%%
program : number { printf("%d", $1); } number { $$ = $1 + $3; }
;
%%
```
Result of `--trace=rules`:
```console
$ exe/lrama --trace=rules sample.y
Grammar rules:
$accept -> program YYEOF
$@1 -> ε
program -> number $@1 number
```
Result of `--trace=only-explicit-rules`:
```console
$ exe/lrama --trace=explicit-rules sample.y
Grammar rules:
$accept -> program YYEOF
program -> number number
```
## Lrama 0.6.11 (2024-12-23)
### Add support for %type declarations using %nterm in Nonterminal Symbols
Allow to use `%nterm` in Nonterminal Symbols for `%type` declarations.
```yacc
%nterm <type> nonterminal…
```
This directive is also supported for compatibility with Bison, and only non-terminal symbols are allowed. In other words, definitions like the following will result in an error:
```yacc
%{
// Prologue
%}
%token EOI 0 "EOI"
%nterm EOI
%%
program: /* empty */
;
```
It show an error message like the following:
```command
exe/lrama nterm.y
nterm.y:6:7: symbol EOI redeclared as a nonterminal
%nterm EOI
^^^
```
## Lrama 0.6.10 (2024-09-11)
### Aliased Named References for actions of RHS in parameterizing rules
Allow to use aliased named references for actions of RHS in parameterizing rules.
```yacc
%rule sum(X, Y): X[summand] '+' Y[addend] { $$ = $summand + $addend }
;
```
https://github.com/ruby/lrama/pull/410
### Named References for actions of RHS in parameterizing rules caller side
Allow to use named references for actions of RHS in parameterizing rules caller side.
```yacc
opt_nl: '\n'?[nl] <str> { $$ = $nl; }
;
```
https://github.com/ruby/lrama/pull/414
### Widen the definable position of parameterizing rules
Allow to define parameterizing rules in the middle of the grammar.
```yacc
%rule defined_option(X): /* empty */
| X
;
%%
program : defined_option(number) <i>
| defined_list(number) <i>
;
%rule defined_list(X): /* empty */ /* <--- here */
| defined_list(X) number
;
```
https://github.com/ruby/lrama/pull/420
### Report unused terminal symbols
Support to report unused terminal symbols.
Run `exe/lrama --report=terms` to show unused terminal symbols.
```console
$ exe/lrama --report=terms sample/calc.y
11 Unused Terms
0 YYerror
1 YYUNDEF
2 '\\\\'
3 '\\13'
4 keyword_class2
5 tNUMBER
6 tPLUS
7 tMINUS
8 tEQ
9 tEQEQ
10 '>'
```
https://github.com/ruby/lrama/pull/439
### Report unused rules
Support to report unused rules.
Run `exe/lrama --report=rules` to show unused rules.
```console
$ exe/lrama --report=rules sample/calc.y
3 Unused Rules
0 unused_option
1 unused_list
2 unused_nonempty_list
```
https://github.com/ruby/lrama/pull/441
### Ensure compatibility with Bison for `%locations` directive
Support `%locations` directive to ensure compatibility with Bison.
Change to `%locations` directive not set by default.
https://github.com/ruby/lrama/pull/446
### Diagnostics report for parameterizing rules redefine
Support to warning redefined parameterizing rules.
Run `exe/lrama -W` or `exe/lrama --warnings` to show redefined parameterizing rules.
```console
$ exe/lrama -W sample/calc.y
parameterizing rule redefined: redefined_method(X)
parameterizing rule redefined: redefined_method(X)
```
https://github.com/ruby/lrama/pull/448
### Support `-v` and `--verbose` option
Support to `-v` and `--verbose` option.
These options align with Bison behavior. So same as '--report=state' option.
https://github.com/ruby/lrama/pull/457
## Lrama 0.6.9 (2024-05-02)
### Callee side tag specification of parameterizing rules
Allow to specify tag on callee side of parameterizing rules.
```yacc
%union {
int i;
}
%rule with_tag(X) <i>: X { $$ = $1; }
;
```
### Named References for actions of RHS in parameterizing rules
Allow to use named references for actions of RHS in parameterizing rules.
```yacc
%rule option(number): /* empty */
| number { $$ = $number; }
;
```
## Lrama 0.6.8 (2024-04-29)
### Nested parameterizing rules with tag
Allow to nested parameterizing rules with tag.
```yacc
%union {
int i;
}
%rule nested_nested_option(X): /* empty */
| X
;
%rule nested_option(X): /* empty */
| nested_nested_option(X) <i>
;
%rule option(Y): /* empty */
| nested_option(Y) <i>
;
```
## Lrama 0.6.7 (2024-04-28)
### RHS of user defined parameterizing rules contains `'symbol'?`, `'symbol'+` and `'symbol'*`.
User can use `'symbol'?`, `'symbol'+` and `'symbol'*` in RHS of user defined parameterizing rules.
```
%rule with_word_seps(X): /* empty */
| X ' '+
;
```
## Lrama 0.6.6 (2024-04-27)
### Trace actions
Support trace actions for debugging.
Run `exe/lrama --trace=actions` to show grammar rules with actions.
```console
$ exe/lrama --trace=actions sample/calc.y
Grammar rules with actions:
$accept -> list, YYEOF {}
list -> ε {}
list -> list, LF {}
list -> list, expr, LF { printf("=> %d\n", $2); }
expr -> NUM {}
expr -> expr, '+', expr { $$ = $1 + $3; }
expr -> expr, '-', expr { $$ = $1 - $3; }
expr -> expr, '*', expr { $$ = $1 * $3; }
expr -> expr, '/', expr { $$ = $1 / $3; }
expr -> '(', expr, ')' { $$ = $2; }
```
### Inlining
Support inlining for rules.
The `%inline` directive causes all references to symbols to be replaced with its definition.
```yacc
%rule %inline op: PLUS { + }
| TIMES { * }
;
%%
expr : number { $$ = $1; }
| expr op expr { $$ = $1 $2 $3; }
;
```
as same as
```yacc
expr : number { $$ = $1; }
| expr '+' expr { $$ = $1 + $3; }
| expr '*' expr { $$ = $1 * $3; }
;
```
## Lrama 0.6.5 (2024-03-25)
### Typed Midrule Actions
User can specify the type of mid rule action by tag (`<bar>`) instead of specifying it with in an action.
```
```yacc
primary: k_case expr_value terms?
{
$<val>$ = p->case_labels;
@@ -21,7 +336,7 @@ primary: k_case expr_value terms?
can be written as
```
```yacc
primary: k_case expr_value terms?
{
$$ = p->case_labels;
@@ -46,7 +361,7 @@ Bison supports this feature from 3.1.
Support `preceded`, `terminated` and `delimited` rules.
```
```text
program: preceded(opening, X)
// Expanded to
@@ -73,7 +388,7 @@ program: delimited_opening_X_closing
delimited_opening_X_closing: opening X closing
```
<https://github.com/ruby/lrama/pull/382>
https://github.com/ruby/lrama/pull/382
### Support `%destructor` declaration
@@ -82,7 +397,7 @@ In general, these resources are freed by actions or after parsing.
However if syntax error happens in parsing, these codes may not be executed.
Codes associated to `%destructor` are executed when semantic value is popped from the stack by an error.
```
```yacc
%token <val1> NUM
%type <val2> expr2
%type <val3> expr
@@ -102,7 +417,7 @@ Codes associated to `%destructor` are executed when semantic value is popped fro
Bison supports this feature from 1.75b.
<https://github.com/ruby/lrama/pull/385>
https://github.com/ruby/lrama/pull/385
## Lrama 0.6.3 (2024-02-15)
@@ -130,7 +445,7 @@ Lrama provides these five callbacks. Registered functions are called when each e
User also needs to access semantic value of their stack in grammar action. `$:n` provides the way to access to it. `$:n` is translated to the minus index from the top of the stack.
For example
```
```yacc
primary: k_if expr_value then compstmt if_tail k_end
{
/*% ripper: if!($:2, $:4, $:5) %*/
@@ -138,7 +453,7 @@ primary: k_if expr_value then compstmt if_tail k_end
}
```
<https://github.com/ruby/lrama/pull/367>
https://github.com/ruby/lrama/pull/367
## Lrama 0.6.2 (2024-01-27)
@@ -147,7 +462,7 @@ primary: k_if expr_value then compstmt if_tail k_end
If `%no-stdlib` directive is set, Lrama doesn't load Lrama standard library for
parameterizing rules, stdlib.y.
<https://github.com/ruby/lrama/pull/344>
https://github.com/ruby/lrama/pull/344
## Lrama 0.6.1 (2024-01-13)
@@ -155,7 +470,7 @@ parameterizing rules, stdlib.y.
Allow to pass an instantiated rule to other parameterizing rules.
```
```yacc
%rule constant(X) : X
;
@@ -172,7 +487,7 @@ program : option(constant(number)) // Nested rule
Allow to use nested parameterizing rules when define parameterizing rules.
```
```yacc
%rule option(x) : /* empty */
| X
;
@@ -191,7 +506,7 @@ program : double_opt(number)
%%
```
<https://github.com/ruby/lrama/pull/337>
https://github.com/ruby/lrama/pull/337
## Lrama 0.6.0 (2023-12-25)
@@ -199,7 +514,7 @@ program : double_opt(number)
Allow to define parameterizing rule by `%rule` directive.
```
```yacc
%rule pair(X, Y): X Y { $$ = $1 + $2; }
;
@@ -213,7 +528,7 @@ stmt: pair(ODD, EVEN) <num>
;
```
<https://github.com/ruby/lrama/pull/285>
https://github.com/ruby/lrama/pull/285
## Lrama 0.5.11 (2023-12-02)
@@ -222,7 +537,7 @@ stmt: pair(ODD, EVEN) <num>
Allow to specify type of rules by specifying tag, `<i>` in below example.
Tag is post-modification style.
```
```yacc
%union {
int i;
}
@@ -234,7 +549,7 @@ program : option(number) <i>
;
```
<https://github.com/ruby/lrama/pull/272>
https://github.com/ruby/lrama/pull/272
## Lrama 0.5.10 (2023-11-18)
@@ -242,13 +557,13 @@ program : option(number) <i>
Support function call style parameterizing rules for `option`, `nonempty_list` and `list`.
<https://github.com/ruby/lrama/pull/197>
https://github.com/ruby/lrama/pull/197
### Parameterizing rules (separated_list)
Support `separated_list` and `separated_nonempty_list` parameterizing rules.
```
```text
program: separated_list(',', number)
// Expanded to
@@ -270,7 +585,7 @@ separated_nonempty_list_number: number
separated_nonempty_list_number: separated_nonempty_list_number ',' number
```
<https://github.com/ruby/lrama/pull/204>
https://github.com/ruby/lrama/pull/204
## Lrama 0.5.9 (2023-11-05)
@@ -279,7 +594,7 @@ separated_nonempty_list_number: separated_nonempty_list_number ',' number
Parameterizing rules are template of rules.
It's very common pattern to write "list" grammar rule like:
```
```yacc
opt_args: /* none */
| args
;
@@ -294,18 +609,18 @@ Lrama supports these suffixes:
- `+`: nonempty list
- `*`: list
Idea of Parameterizing rules comes from Menhir LR(1) parser generator (<https://gallium.inria.fr/~fpottier/menhir/manual.html#sec32>).
Idea of Parameterizing rules comes from Menhir LR(1) parser generator (https://gallium.inria.fr/~fpottier/menhir/manual.html#sec32).
<https://github.com/ruby/lrama/pull/181>
https://github.com/ruby/lrama/pull/181
## Lrama 0.5.7 (2023-10-23)
### Racc parser
Replace Lrama's parser from hand written parser to LR parser generated by Racc.
Lrama uses `--embedded` option to generate LR parser because Racc is changed from default gem to bundled gem by Ruby 3.3 (<https://github.com/ruby/lrama/pull/132>).
Lrama uses `--embedded` option to generate LR parser because Racc is changed from default gem to bundled gem by Ruby 3.3 (https://github.com/ruby/lrama/pull/132).
<https://github.com/ruby/lrama/pull/62>
https://github.com/ruby/lrama/pull/62
## Lrama 0.5.4 (2023-08-17)
@@ -316,7 +631,7 @@ Make error recovery function configurable on runtime by two new macros.
- `YYMAXREPAIR`: Expected to return max length of repair operations. `%parse-param` is passed to this function.
- `YYERROR_RECOVERY_ENABLED`: Expected to return bool value to determine error recovery is enabled or not. `%parse-param` is passed to this function.
<https://github.com/ruby/lrama/pull/74>
https://github.com/ruby/lrama/pull/74
## Lrama 0.5.3 (2023-08-05)
@@ -325,7 +640,7 @@ Make error recovery function configurable on runtime by two new macros.
Support token insert base Error Recovery.
`-e` option is needed to generate parser with error recovery functions.
<https://github.com/ruby/lrama/pull/44>
https://github.com/ruby/lrama/pull/44
## Lrama 0.5.2 (2023-06-14)
@@ -334,7 +649,7 @@ Support token insert base Error Recovery.
Instead of positional references like `$1` or `$$`,
named references allow to access to symbol by name.
```
```yacc
primary: k_class cpath superclass bodystmt k_end
{
$primary = new_class($cpath, $bodystmt, $superclass);
@@ -343,7 +658,7 @@ primary: k_class cpath superclass bodystmt k_end
Alias name can be declared.
```
```yacc
expr[result]: expr[ex-left] '+' expr[ex.right]
{
$result = $[ex-left] + $[ex.right];
@@ -363,9 +678,9 @@ Bison supports this feature from 2.5.
- `YY_REDUCE_PRINT`
- `yysyntax_error`
<https://github.com/ruby/lrama/pull/40>
https://github.com/ruby/lrama/pull/40
See also: <https://github.com/ruby/ruby/pull/7807>
See also: https://github.com/ruby/ruby/pull/7807
## Lrama 0.5.0 (2023-05-17)
@@ -373,7 +688,7 @@ See also: <https://github.com/ruby/ruby/pull/7807>
When `-` is given as grammar file name, reads the grammar source from STDIN, and takes the next argument as the input file name. This mode helps pre-process a grammar source.
<https://github.com/ruby/lrama/pull/8>
https://github.com/ruby/lrama/pull/8
## Lrama 0.4.0 (2023-05-13)
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
$LOAD_PATH << File.join(__dir__, "../lib")
require "lrama"
+22 -17
View File
@@ -1,17 +1,22 @@
require "lrama/bitmap"
require "lrama/command"
require "lrama/context"
require "lrama/counterexamples"
require "lrama/digraph"
require "lrama/grammar"
require "lrama/lexer"
require "lrama/option_parser"
require "lrama/options"
require "lrama/output"
require "lrama/parser"
require "lrama/report"
require "lrama/state"
require "lrama/states"
require "lrama/states_reporter"
require "lrama/version"
require "lrama/warning"
# frozen_string_literal: true
require_relative "lrama/bitmap"
require_relative "lrama/command"
require_relative "lrama/context"
require_relative "lrama/counterexamples"
require_relative "lrama/diagnostics"
require_relative "lrama/digraph"
require_relative "lrama/grammar"
require_relative "lrama/grammar_validator"
require_relative "lrama/lexer"
require_relative "lrama/logger"
require_relative "lrama/option_parser"
require_relative "lrama/options"
require_relative "lrama/output"
require_relative "lrama/parser"
require_relative "lrama/report"
require_relative "lrama/state"
require_relative "lrama/states"
require_relative "lrama/states_reporter"
require_relative "lrama/trace_reporter"
require_relative "lrama/version"
+6 -1
View File
@@ -1,5 +1,9 @@
# rbs_inline: enabled
# frozen_string_literal: true
module Lrama
module Bitmap
# @rbs (Array[Integer] ary) -> Integer
def self.from_array(ary)
bit = 0
@@ -10,8 +14,9 @@ module Lrama
bit
end
# @rbs (Integer int) -> Array[Integer]
def self.to_array(int)
a = []
a = [] #: Array[Integer]
i = 0
while int > 0 do
+10 -15
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Command
LRAMA_LIB = File.realpath(File.join(File.dirname(__FILE__)))
@@ -14,11 +16,10 @@ module Lrama
Report::Duration.enable if options.trace_opts[:time]
warning = Lrama::Warning.new
text = options.y.read
options.y.close if options.y != STDIN
begin
grammar = Lrama::Parser.new(text, options.grammar_file, options.debug).parse
grammar = Lrama::Parser.new(text, options.grammar_file, options.debug, options.define).parse
unless grammar.no_stdlib
stdlib_grammar = Lrama::Parser.new(File.read(STDLIB_FILE_PATH), STDLIB_FILE_PATH, options.debug).parse
grammar.insert_before_parameterizing_rules(stdlib_grammar.parameterizing_rules)
@@ -31,8 +32,9 @@ module Lrama
message = message.gsub(/.+/, "\e[1m\\&\e[m") if Exception.to_tty?
abort message
end
states = Lrama::States.new(grammar, warning, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure]))
states = Lrama::States.new(grammar, trace_state: (options.trace_opts[:automaton] || options.trace_opts[:closure]))
states.compute
states.compute_ielr if grammar.ielr_defined?
context = Lrama::Context.new(states)
if options.report_file
@@ -42,15 +44,8 @@ module Lrama
end
end
if options.trace_opts && options.trace_opts[:rules]
puts "Grammar rules:"
puts grammar.rules
end
if options.trace_opts && options.trace_opts[:actions]
puts "Grammar rules with actions:"
grammar.rules.each { |rule| puts rule.with_actions }
end
reporter = Lrama::TraceReporter.new(grammar)
reporter.report(**options.trace_opts)
File.open(options.outfile, "w+") do |f|
Lrama::Output.new(
@@ -65,9 +60,9 @@ module Lrama
).render
end
if warning.has_error?
exit false
end
logger = Lrama::Logger.new
exit false unless Lrama::GrammarValidator.new(grammar, states, logger).valid?
Lrama::Diagnostics.new(grammar, states, logger).run(options.diagnostic)
end
end
end
+11 -9
View File
@@ -1,4 +1,6 @@
require "lrama/report/duration"
# frozen_string_literal: true
require_relative "report/duration"
module Lrama
# This is passed to a template
@@ -253,7 +255,7 @@ module Lrama
# If no default_reduction_rule, default behavior is an
# error then replace ErrorActionNumber with zero.
if !state.default_reduction_rule
unless state.default_reduction_rule
actions.map! do |e|
if e == ErrorActionNumber
0
@@ -301,10 +303,7 @@ module Lrama
end
@states.nterms.each do |nterm|
if !(states = nterm_to_next_states[nterm])
default_goto = 0
not_default_gotos = []
else
if (states = nterm_to_next_states[nterm])
default_state = states.map(&:last).group_by {|s| s }.max_by {|_, v| v.count }.first
default_goto = default_state.id
not_default_gotos = []
@@ -312,6 +311,9 @@ module Lrama
next if to_state.id == default_goto
not_default_gotos << [from_state.id, to_state.id]
end
else
default_goto = 0
not_default_gotos = []
end
k = nterm_number_to_sequence_number(nterm.number)
@@ -403,7 +405,7 @@ module Lrama
@check = []
# Key is froms_and_tos, value is index position
pushed = {}
userd_res = {}
used_res = {}
lowzero = 0
high = 0
@@ -428,7 +430,7 @@ module Lrama
end
end
if ok && userd_res[res]
if ok && used_res[res]
ok = false
end
@@ -456,7 +458,7 @@ module Lrama
@base[state_id] = res
pushed[froms_and_tos] = res
userd_res[res] = true
used_res[res] = true
end
@yylast = high
+36 -24
View File
@@ -1,13 +1,15 @@
# frozen_string_literal: true
require "set"
require "lrama/counterexamples/derivation"
require "lrama/counterexamples/example"
require "lrama/counterexamples/path"
require "lrama/counterexamples/production_path"
require "lrama/counterexamples/start_path"
require "lrama/counterexamples/state_item"
require "lrama/counterexamples/transition_path"
require "lrama/counterexamples/triple"
require_relative "counterexamples/derivation"
require_relative "counterexamples/example"
require_relative "counterexamples/path"
require_relative "counterexamples/production_path"
require_relative "counterexamples/start_path"
require_relative "counterexamples/state_item"
require_relative "counterexamples/transition_path"
require_relative "counterexamples/triple"
module Lrama
# See: https://www.cs.cornell.edu/andru/papers/cupex/cupex.pdf
@@ -30,8 +32,10 @@ module Lrama
conflict_state.conflicts.flat_map do |conflict|
case conflict.type
when :shift_reduce
# @type var conflict: State::ShiftReduceConflict
shift_reduce_example(conflict_state, conflict)
when :reduce_reduce
# @type var conflict: State::ReduceReduceConflict
reduce_reduce_examples(conflict_state, conflict)
end
end.compact
@@ -46,7 +50,7 @@ module Lrama
@reverse_transitions = {}
@states.states.each do |src_state|
trans = {}
trans = {} #: Hash[Grammar::Symbol, State]
src_state.transitions.each do |shift, next_state|
trans[shift.next_sym] = next_state
@@ -64,6 +68,7 @@ module Lrama
@transitions[[src_state_item, sym]] = dest_state_item
# @type var key: [StateItem, Grammar::Symbol]
key = [dest_state_item, sym]
@reverse_transitions[key] ||= Set.new
@reverse_transitions[key] << src_state_item
@@ -80,7 +85,7 @@ module Lrama
@states.states.each do |state|
# LHS => Set(Item)
h = {}
h = {} #: Hash[Grammar::Symbol, Set[States::Item]]
state.closure.each do |item|
sym = item.lhs
@@ -95,6 +100,7 @@ module Lrama
sym = item.next_sym
state_item = StateItem.new(state, item)
# @type var key: [State, Grammar::Symbol]
key = [state, sym]
@productions[state_item] = h[sym]
@@ -107,6 +113,7 @@ module Lrama
def shift_reduce_example(conflict_state, conflict)
conflict_symbol = conflict.symbols.first
# @type var shift_conflict_item: ::Lrama::States::Item
shift_conflict_item = conflict_state.items.find { |item| item.next_sym == conflict_symbol }
path2 = shortest_path(conflict_state, conflict.reduce.item, conflict_symbol)
path1 = find_shift_conflict_shortest_path(path2, conflict_state, shift_conflict_item)
@@ -151,12 +158,14 @@ module Lrama
prev_state_item = prev_path&.to
if target_state_item == state_item || target_state_item.item.start_item?
result.concat(reversed_reduce_path[_j..-1].map(&:to))
result.concat(
reversed_reduce_path[_j..-1] #: Array[StartPath|TransitionPath|ProductionPath]
.map(&:to))
break
end
if target_state_item.item.beginning_of_rule?
queue = []
queue = [] #: Array[Array[StateItem]]
queue << [target_state_item]
# Find reverse production
@@ -171,10 +180,18 @@ module Lrama
break
end
if !si.item.beginning_of_rule?
if si.item.beginning_of_rule?
# @type var key: [State, Grammar::Symbol]
key = [si.state, si.item.lhs]
@reverse_productions[key].each do |item|
state_item = StateItem.new(si.state, item)
queue << (sis + [state_item])
end
else
# @type var key: [StateItem, Grammar::Symbol]
key = [si, si.item.previous_sym]
@reverse_transitions[key].each do |prev_target_state_item|
next if prev_target_state_item.state != prev_state_item.state
next if prev_target_state_item.state != prev_state_item&.state
sis.shift
result.concat(sis)
result << prev_target_state_item
@@ -183,19 +200,14 @@ module Lrama
queue.clear
break
end
else
key = [si.state, si.item.lhs]
@reverse_productions[key].each do |item|
state_item = StateItem.new(si.state, item)
queue << (sis + [state_item])
end
end
end
else
# Find reverse transition
# @type var key: [StateItem, Grammar::Symbol]
key = [target_state_item, target_state_item.item.previous_sym]
@reverse_transitions[key].each do |prev_target_state_item|
next if prev_target_state_item.state != prev_state_item.state
next if prev_target_state_item.state != prev_state_item&.state
result << prev_target_state_item
target_state_item = prev_target_state_item
i = j
@@ -222,9 +234,9 @@ module Lrama
def shortest_path(conflict_state, conflict_reduce_item, conflict_term)
# queue: is an array of [Triple, [Path]]
queue = []
visited = {}
start_state = @states.states.first
queue = [] #: Array[[Triple, Array[StartPath|TransitionPath|ProductionPath]]]
visited = {} #: Hash[Triple, true]
start_state = @states.states.first #: Lrama::State
raise "BUG: Start state should be just one kernel." if start_state.kernels.count != 1
start = Triple.new(start_state, start_state.kernels.first, Set.new([@states.eof_symbol]))
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Counterexamples
class Derivation
@@ -16,7 +18,7 @@ module Lrama
alias :inspect :to_s
def render_strings_for_report
result = []
result = [] #: Array[String]
_render_for_report(self, 0, result, 0)
result.map(&:rstrip)
end
@@ -42,18 +44,19 @@ module Lrama
str << "#{item.next_sym.display_name}"
length = _render_for_report(derivation.left, len, strings, index + 1)
# I want String#ljust!
str << " " * (length - str.length)
str << " " * (length - str.length) if length > str.length
else
str << "#{item.symbols_after_dot.map(&:display_name).join(" ")} "
return str.length
end
if derivation.right&.left
length = _render_for_report(derivation.right.left, str.length, strings, index + 1)
str << "#{item.symbols_after_dot[1..-1].map(&:display_name).join(" ")} "
left = derivation.right&.left #: Derivation
length = _render_for_report(left, str.length, strings, index + 1)
str << "#{item.symbols_after_dot[1..-1].map(&:display_name).join(" ")} " # steep:ignore
str << " " * (length - str.length) if length > str.length
elsif item.next_next_sym
str << "#{item.symbols_after_dot[1..-1].map(&:display_name).join(" ")} "
str << "#{item.symbols_after_dot[1..-1].map(&:display_name).join(" ")} " # steep:ignore
end
return str.length
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Counterexamples
class Example
@@ -36,9 +38,10 @@ module Lrama
private
def _derivations(paths)
derivation = nil
derivation = nil #: Derivation
current = :production
lookahead_sym = paths.last.to.item.end_of_rule? ? @conflict_symbol : nil
last_path = paths.last #: Path
lookahead_sym = last_path.to.item.end_of_rule? ? @conflict_symbol : nil
paths.reverse_each do |path|
item = path.to.item
@@ -55,12 +58,14 @@ module Lrama
when ProductionPath
derivation = Derivation.new(item, derivation)
current = :production
else
raise "Unexpected. #{path}"
end
if lookahead_sym && item.next_next_sym && item.next_next_sym.first_set.include?(lookahead_sym)
state_item = @counterexamples.transitions[[path.to, item.next_sym]]
derivation2 = find_derivation_for_symbol(state_item, lookahead_sym)
derivation.right = derivation2
derivation.right = derivation2 # steep:ignore
lookahead_sym = nil
end
@@ -87,7 +92,7 @@ module Lrama
end
def find_derivation_for_symbol(state_item, sym)
queue = []
queue = [] #: Array[Array[StateItem]]
queue << [state_item]
while (sis = queue.shift)
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Counterexamples
class Path
@@ -18,6 +20,10 @@ module Lrama
"#<Path(#{type})>"
end
alias :inspect :to_s
def type
raise NotImplementedError
end
end
end
end
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Counterexamples
class ProductionPath < Path
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Counterexamples
class StartPath < Path
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Counterexamples
class StateItem < Struct.new(:state, :item)
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Counterexamples
class TransitionPath < Path
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Counterexamples
# s: state
+36
View File
@@ -0,0 +1,36 @@
# frozen_string_literal: true
module Lrama
class Diagnostics
def initialize(grammar, states, logger)
@grammar = grammar
@states = states
@logger = logger
end
def run(diagnostic)
if diagnostic
diagnose_conflict
diagnose_parameterizing_redefined
end
end
private
def diagnose_conflict
if @states.sr_conflicts_count != 0
@logger.warn("shift/reduce conflicts: #{@states.sr_conflicts_count} found")
end
if @states.rr_conflicts_count != 0
@logger.warn("reduce/reduce conflicts: #{@states.rr_conflicts_count} found")
end
end
def diagnose_parameterizing_redefined
@grammar.parameterizing_rule_resolver.redefined_rules.each do |rule|
@logger.warn("parameterizing rule redefined: #{rule}")
end
end
end
end
+32
View File
@@ -1,21 +1,52 @@
# rbs_inline: enabled
# frozen_string_literal: true
module Lrama
# Algorithm Digraph of https://dl.acm.org/doi/pdf/10.1145/69622.357187 (P. 625)
#
# @rbs generic X < Object -- Type of a member of `sets`
# @rbs generic Y < _Or -- Type of sets assigned to a member of `sets`
class Digraph
# TODO: rbs-inline 0.10.0 doesn't support instance variables.
# Move these type declarations above instance variable definitions, once it's supported.
#
# @rbs!
# interface _Or
# def |: (self) -> self
# end
# @sets: Array[X]
# @relation: Hash[X, Array[X]]
# @base_function: Hash[X, Y]
# @stack: Array[X]
# @h: Hash[X, (Integer|Float)?]
# @result: Hash[X, Y]
# @rbs sets: Array[X]
# @rbs relation: Hash[X, Array[X]]
# @rbs base_function: Hash[X, Y]
# @rbs return: void
def initialize(sets, relation, base_function)
# X in the paper
@sets = sets
# R in the paper
@relation = relation
# F' in the paper
@base_function = base_function
# S in the paper
@stack = []
# N in the paper
@h = Hash.new(0)
# F in the paper
@result = {}
end
# @rbs () -> Hash[X, Y]
def compute
@sets.each do |x|
next if @h[x] != 0
@@ -27,6 +58,7 @@ module Lrama
private
# @rbs (X x) -> void
def traverse(x)
@stack.push(x)
d = @stack.count
+59 -33
View File
@@ -1,44 +1,41 @@
# frozen_string_literal: true
require "forwardable"
require "lrama/grammar/auxiliary"
require "lrama/grammar/binding"
require "lrama/grammar/code"
require "lrama/grammar/counter"
require "lrama/grammar/destructor"
require "lrama/grammar/error_token"
require "lrama/grammar/parameterizing_rule"
require "lrama/grammar/percent_code"
require "lrama/grammar/precedence"
require "lrama/grammar/printer"
require "lrama/grammar/reference"
require "lrama/grammar/rule"
require "lrama/grammar/rule_builder"
require "lrama/grammar/symbol"
require "lrama/grammar/symbols"
require "lrama/grammar/type"
require "lrama/grammar/union"
require "lrama/lexer"
require_relative "grammar/auxiliary"
require_relative "grammar/binding"
require_relative "grammar/code"
require_relative "grammar/counter"
require_relative "grammar/destructor"
require_relative "grammar/error_token"
require_relative "grammar/parameterizing_rule"
require_relative "grammar/percent_code"
require_relative "grammar/precedence"
require_relative "grammar/printer"
require_relative "grammar/reference"
require_relative "grammar/rule"
require_relative "grammar/rule_builder"
require_relative "grammar/symbol"
require_relative "grammar/symbols"
require_relative "grammar/type"
require_relative "grammar/union"
require_relative "lexer"
module Lrama
# Grammar is the result of parsing an input grammar file
class Grammar
extend Forwardable
attr_reader :percent_codes, :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux
attr_accessor :union, :expect,
:printers, :error_tokens,
:lex_param, :parse_param, :initial_action,
attr_reader :percent_codes, :eof_symbol, :error_symbol, :undef_symbol, :accept_symbol, :aux, :parameterizing_rule_resolver
attr_accessor :union, :expect, :printers, :error_tokens, :lex_param, :parse_param, :initial_action,
:after_shift, :before_reduce, :after_reduce, :after_shift_error_token, :after_pop_stack,
:symbols_resolver, :types,
:rules, :rule_builders,
:sym_to_rules, :no_stdlib
:symbols_resolver, :types, :rules, :rule_builders, :sym_to_rules, :no_stdlib, :locations, :define
def_delegators "@symbols_resolver", :symbols, :nterms, :terms, :add_nterm, :add_term,
def_delegators "@symbols_resolver", :symbols, :nterms, :terms, :add_nterm, :add_term, :find_term_by_s_value,
:find_symbol_by_number!, :find_symbol_by_id!, :token_to_symbol,
:find_symbol_by_s_value!, :fill_symbol_number, :fill_nterm_type,
:fill_printer, :fill_destructor, :fill_error_token, :sort_by_number!
def initialize(rule_counter)
def initialize(rule_counter, define = {})
@rule_counter = rule_counter
# Code defined by "%code"
@@ -59,10 +56,16 @@ module Lrama
@accept_symbol = nil
@aux = Auxiliary.new
@no_stdlib = false
@locations = false
@define = define.map {|d| d.split('=') }.to_h
append_special_symbols
end
def create_rule_builder(rule_counter, midrule_action_counter)
RuleBuilder.new(rule_counter, midrule_action_counter, @parameterizing_rule_resolver)
end
def add_percent_code(id:, code:)
@percent_codes << PercentCode.new(id.s_value, code.s_value)
end
@@ -141,6 +144,7 @@ module Lrama
end
def prepare
resolve_inline_rules
normalize_rules
collect_symbols
set_lhs_and_rhs
@@ -149,6 +153,7 @@ module Lrama
fill_sym_to_rules
compute_nullable
compute_first_set
set_locations
end
# TODO: More validation methods
@@ -167,6 +172,10 @@ module Lrama
@sym_to_rules[sym.number]
end
def ielr_defined?
@define.key?('lr.type') && @define['lr.type'] == 'ielr'
end
private
def compute_nullable
@@ -255,7 +264,7 @@ module Lrama
def setup_rules
@rule_builders.each do |builder|
builder.setup_rules(@parameterizing_rule_resolver)
builder.setup_rules
end
end
@@ -289,10 +298,23 @@ module Lrama
@accept_symbol = term
end
def resolve_inline_rules
while @rule_builders.any?(&:has_inline_rules?) do
@rule_builders = @rule_builders.flat_map do |builder|
if builder.has_inline_rules?
builder.resolve_inline_rules
else
builder
end
end
end
end
def normalize_rules
# Add $accept rule to the top of rules
lineno = @rule_builders.first ? @rule_builders.first.line : 0
@rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [@rule_builders.first.lhs, @eof_symbol.id], token_code: nil, lineno: lineno)
rule_builder = @rule_builders.first # : RuleBuilder
lineno = rule_builder ? rule_builder.line : 0
@rules << Rule.new(id: @rule_counter.increment, _lhs: @accept_symbol.id, _rhs: [rule_builder.lhs, @eof_symbol.id], token_code: nil, lineno: lineno)
setup_rules
@@ -365,17 +387,21 @@ module Lrama
end
def validate_rule_lhs_is_nterm!
errors = []
errors = [] #: Array[String]
rules.each do |rule|
next if rule.lhs.nterm?
errors << "[BUG] LHS of #{rule} (line: #{rule.lineno}) is term. It should be nterm."
errors << "[BUG] LHS of #{rule.display_name} (line: #{rule.lineno}) is term. It should be nterm."
end
return if errors.empty?
raise errors.join("\n")
end
def set_locations
@locations = @locations || @rules.any? {|rule| rule.contains_at_reference? }
end
end
end
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
# Grammar file information not used by States but by Output
+53 -10
View File
@@ -1,22 +1,65 @@
# rbs_inline: enabled
# frozen_string_literal: true
module Lrama
class Grammar
class Binding
attr_reader :actual_args, :count
# @rbs @actual_args: Array[Lexer::Token]
# @rbs @param_to_arg: Hash[String, Lexer::Token]
def initialize(parameterizing_rule, actual_args)
@parameters = parameterizing_rule.parameters
# @rbs (Array[Lexer::Token] params, Array[Lexer::Token] actual_args) -> void
def initialize(params, actual_args)
@actual_args = actual_args
@parameter_to_arg = @parameters.zip(actual_args).map do |param, arg|
@param_to_arg = map_params_to_args(params, @actual_args)
end
# @rbs (Lexer::Token sym) -> Lexer::Token
def resolve_symbol(sym)
if sym.is_a?(Lexer::Token::InstantiateRule)
Lrama::Lexer::Token::InstantiateRule.new(
s_value: sym.s_value, location: sym.location, args: resolved_args(sym), lhs_tag: sym.lhs_tag
)
else
param_to_arg(sym)
end
end
# @rbs (Lexer::Token::InstantiateRule token) -> String
def concatenated_args_str(token)
"#{token.rule_name}_#{token_to_args_s_values(token).join('_')}"
end
private
# @rbs (Array[Lexer::Token] params, Array[Lexer::Token] actual_args) -> Hash[String, Lexer::Token]
def map_params_to_args(params, actual_args)
params.zip(actual_args).map do |param, arg|
[param.s_value, arg]
end.to_h
end
def resolve_symbol(symbol)
if symbol.is_a?(Lexer::Token::InstantiateRule)
resolved_args = symbol.args.map { |arg| resolve_symbol(arg) }
Lrama::Lexer::Token::InstantiateRule.new(s_value: symbol.s_value, location: symbol.location, args: resolved_args, lhs_tag: symbol.lhs_tag)
else
@parameter_to_arg[symbol.s_value] || symbol
# @rbs (Lexer::Token::InstantiateRule sym) -> Array[Lexer::Token]
def resolved_args(sym)
sym.args.map { |arg| resolve_symbol(arg) }
end
# @rbs (Lexer::Token sym) -> Lexer::Token
def param_to_arg(sym)
if (arg = @param_to_arg[sym.s_value].dup)
arg.alias_name = sym.alias_name
end
arg || sym
end
# @rbs (Lexer::Token::InstantiateRule token) -> Array[String]
def token_to_args_s_values(token)
token.args.flat_map do |arg|
resolved = resolve_symbol(arg)
if resolved.is_a?(Lexer::Token::InstantiateRule)
[resolved.s_value] + resolved.args.map(&:s_value)
else
[resolved.s_value]
end
end
end
end
+7 -5
View File
@@ -1,9 +1,11 @@
# frozen_string_literal: true
require "forwardable"
require "lrama/grammar/code/destructor_code"
require "lrama/grammar/code/initial_action_code"
require "lrama/grammar/code/no_reference_code"
require "lrama/grammar/code/printer_code"
require "lrama/grammar/code/rule_action"
require_relative "code/destructor_code"
require_relative "code/initial_action_code"
require_relative "code/no_reference_code"
require_relative "code/printer_code"
require_relative "code/rule_action"
module Lrama
class Grammar
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Code
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Code
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Code
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Code
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Code
@@ -41,6 +43,7 @@ module Lrama
when ref.type == :dollar && ref.name == "$" # $$
tag = ref.ex_tag || lhs.tag
raise_tag_not_found_error(ref) unless tag
# @type var tag: Lexer::Token::Tag
"(yyval.#{tag.member})"
when ref.type == :at && ref.name == "$" # @$
"(yyloc)"
@@ -50,6 +53,7 @@ module Lrama
i = -position_in_rhs + ref.index
tag = ref.ex_tag || rhs[ref.index - 1].tag
raise_tag_not_found_error(ref) unless tag
# @type var tag: Lexer::Token::Tag
"(yyvsp[#{i}].#{tag.member})"
when ref.type == :at # @n
i = -position_in_rhs + ref.index
@@ -69,18 +73,18 @@ module Lrama
@rule.position_in_original_rule_rhs || @rule.rhs.count
end
# If this is midrule action, RHS is a RHS of the original rule.
# If this is midrule action, RHS is an RHS of the original rule.
def rhs
(@rule.original_rule || @rule).rhs
end
# Unlike `rhs`, LHS is always a LHS of the rule.
# Unlike `rhs`, LHS is always an LHS of the rule.
def lhs
@rule.lhs
end
def raise_tag_not_found_error(ref)
raise "Tag is not specified for '$#{ref.value}' in '#{@rule}'"
raise "Tag is not specified for '$#{ref.value}' in '#{@rule.display_name}'"
end
end
end
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Counter
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Destructor < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true)
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class ErrorToken < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true)
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require_relative 'parameterizing_rule/resolver'
require_relative 'parameterizing_rule/rhs'
require_relative 'parameterizing_rule/rule'
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class ParameterizingRule
@@ -18,13 +20,17 @@ module Lrama
end
def find_inline(token)
@rules.select { |rule| rule.name == token.s_value && rule.is_inline }.last
@rules.reverse.find { |rule| rule.name == token.s_value && rule.is_inline }
end
def created_lhs(lhs_s_value)
@created_lhs_list.reverse.find { |created_lhs| created_lhs.s_value == lhs_s_value }
end
def redefined_rules
@rules.select { |rule| @rules.count { |r| r.name == rule.name && r.required_parameters_count == rule.required_parameters_count } > 1 }
end
private
def select_rules(rules, token)
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class ParameterizingRule
@@ -13,7 +15,8 @@ module Lrama
def resolve_user_code(bindings)
return unless user_code
var_to_arg = {}
resolved = Lexer::Token::UserCode.new(s_value: user_code.s_value, location: user_code.location)
var_to_arg = {} #: Hash[String, String]
symbols.each do |sym|
resolved_sym = bindings.resolve_symbol(sym)
if resolved_sym != sym
@@ -22,14 +25,14 @@ module Lrama
end
var_to_arg.each do |var, arg|
user_code.references.each do |ref|
resolved.references.each do |ref|
if ref.name == var
ref.name = arg
end
end
end
return user_code
return resolved
end
end
end
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class ParameterizingRule
@@ -12,6 +14,10 @@ module Lrama
@is_inline = is_inline
@required_parameters_count = parameters.count
end
def to_s
"#{@name}(#{@parameters.map(&:s_value).join(', ')})"
end
end
end
end
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class PercentCode
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Precedence < Struct.new(:type, :precedence, keyword_init: true)
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Printer < Struct.new(:ident_or_tags, :token_code, :lineno, keyword_init: true)
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
# type: :dollar or :at
+18 -3
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
# _rhs holds original RHS element. Use rhs to refer to Symbol.
@@ -16,10 +18,17 @@ module Lrama
self.lineno == other.lineno
end
# TODO: Change this to display_name
def to_s
def display_name
l = lhs.id.s_value
r = empty_rule? ? "ε" : rhs.map {|r| r.id.s_value }.join(" ")
"#{l} -> #{r}"
end
def display_name_without_action
l = lhs.id.s_value
r = empty_rule? ? "ε" : rhs.map do |r|
r.id.s_value if r.first_set.any?
end.compact.join(" ")
"#{l} -> #{r}"
end
@@ -33,7 +42,7 @@ module Lrama
end
def with_actions
"#{to_s} {#{token_code&.s_value}}"
"#{display_name} {#{token_code&.s_value}}"
end
# opt_nl: ε <-- empty_rule
@@ -55,6 +64,12 @@ module Lrama
Code::RuleAction.new(type: :rule_action, token_code: token_code, rule: self).translated_code
end
def contains_at_reference?
return false unless token_code
token_code.references.any? {|r| r.type == :at }
end
end
end
end
+66 -79
View File
@@ -1,12 +1,15 @@
# frozen_string_literal: true
module Lrama
class Grammar
class RuleBuilder
attr_accessor :lhs, :line
attr_reader :lhs_tag, :rhs, :user_code, :precedence_sym
def initialize(rule_counter, midrule_action_counter, position_in_original_rule_rhs = nil, lhs_tag: nil, skip_preprocess_references: false)
def initialize(rule_counter, midrule_action_counter, parameterizing_rule_resolver, position_in_original_rule_rhs = nil, lhs_tag: nil, skip_preprocess_references: false)
@rule_counter = rule_counter
@midrule_action_counter = midrule_action_counter
@parameterizing_rule_resolver = parameterizing_rule_resolver
@position_in_original_rule_rhs = position_in_original_rule_rhs
@skip_preprocess_references = skip_preprocess_references
@@ -19,16 +22,12 @@ module Lrama
@rules = []
@rule_builders_for_parameterizing_rules = []
@rule_builders_for_derived_rules = []
@rule_builders_for_inline_rules = []
@parameterizing_rules = []
@inline_rules = []
@midrule_action_rules = []
end
def add_rhs(rhs)
if !@line
@line = rhs.line
end
@line ||= rhs.line
flush_user_code
@@ -36,9 +35,7 @@ module Lrama
end
def user_code=(user_code)
if !@line
@line = user_code&.line
end
@line ||= user_code&.line
flush_user_code
@@ -55,18 +52,41 @@ module Lrama
freeze_rhs
end
def setup_rules(parameterizing_rule_resolver)
def setup_rules
preprocess_references unless @skip_preprocess_references
if rhs.any? { |token| parameterizing_rule_resolver.find_inline(token) }
resolve_inline(parameterizing_rule_resolver)
else
process_rhs(parameterizing_rule_resolver)
end
process_rhs
build_rules
end
def rules
@parameterizing_rules + @inline_rules + @midrule_action_rules + @rules
@parameterizing_rules + @midrule_action_rules + @rules
end
def has_inline_rules?
rhs.any? { |token| @parameterizing_rule_resolver.find_inline(token) }
end
def resolve_inline_rules
resolved_builders = [] #: Array[RuleBuilder]
rhs.each_with_index do |token, i|
if (inline_rule = @parameterizing_rule_resolver.find_inline(token))
inline_rule.rhs_list.each do |inline_rhs|
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, lhs_tag: lhs_tag)
if token.is_a?(Lexer::Token::InstantiateRule)
resolve_inline_rhs(rule_builder, inline_rhs, i, Binding.new(inline_rule.parameters, token.args))
else
resolve_inline_rhs(rule_builder, inline_rhs, i)
end
rule_builder.lhs = lhs
rule_builder.line = line
rule_builder.precedence_sym = precedence_sym
rule_builder.user_code = replace_inline_user_code(inline_rhs, i)
resolved_builders << rule_builder
end
break
end
end
resolved_builders
end
private
@@ -82,31 +102,25 @@ module Lrama
def build_rules
tokens = @replaced_rhs
if tokens
rule = Rule.new(
id: @rule_counter.increment, _lhs: lhs, _rhs: tokens, lhs_tag: lhs_tag, token_code: user_code,
position_in_original_rule_rhs: @position_in_original_rule_rhs, precedence_sym: precedence_sym, lineno: line
)
@rules = [rule]
@parameterizing_rules = @rule_builders_for_parameterizing_rules.map do |rule_builder|
rule_builder.rules
end.flatten
@midrule_action_rules = @rule_builders_for_derived_rules.map do |rule_builder|
rule_builder.rules
end.flatten
@midrule_action_rules.each do |r|
r.original_rule = rule
end
else
@inline_rules = @rule_builders_for_inline_rules.map do |rule_builder|
rule_builder.rules
end.flatten
rule = Rule.new(
id: @rule_counter.increment, _lhs: lhs, _rhs: tokens, lhs_tag: lhs_tag, token_code: user_code,
position_in_original_rule_rhs: @position_in_original_rule_rhs, precedence_sym: precedence_sym, lineno: line
)
@rules = [rule]
@parameterizing_rules = @rule_builders_for_parameterizing_rules.map do |rule_builder|
rule_builder.rules
end.flatten
@midrule_action_rules = @rule_builders_for_derived_rules.map do |rule_builder|
rule_builder.rules
end.flatten
@midrule_action_rules.each do |r|
r.original_rule = rule
end
end
# rhs is a mixture of variety type of tokens like `Ident`, `InstantiateRule`, `UserCode` and so on.
# `#process_rhs` replaces some kind of tokens to `Ident` so that all `@replaced_rhs` are `Ident` or `Char`.
def process_rhs(parameterizing_rule_resolver)
def process_rhs
return if @replaced_rhs
@replaced_rhs = []
@@ -118,26 +132,26 @@ module Lrama
when Lrama::Lexer::Token::Ident
@replaced_rhs << token
when Lrama::Lexer::Token::InstantiateRule
parameterizing_rule = parameterizing_rule_resolver.find_rule(token)
parameterizing_rule = @parameterizing_rule_resolver.find_rule(token)
raise "Unexpected token. #{token}" unless parameterizing_rule
bindings = Binding.new(parameterizing_rule, token.args)
lhs_s_value = lhs_s_value(token, bindings)
if (created_lhs = parameterizing_rule_resolver.created_lhs(lhs_s_value))
bindings = Binding.new(parameterizing_rule.parameters, token.args)
lhs_s_value = bindings.concatenated_args_str(token)
if (created_lhs = @parameterizing_rule_resolver.created_lhs(lhs_s_value))
@replaced_rhs << created_lhs
else
lhs_token = Lrama::Lexer::Token::Ident.new(s_value: lhs_s_value, location: token.location)
@replaced_rhs << lhs_token
parameterizing_rule_resolver.created_lhs_list << lhs_token
@parameterizing_rule_resolver.created_lhs_list << lhs_token
parameterizing_rule.rhs_list.each do |r|
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, lhs_tag: token.lhs_tag || parameterizing_rule.tag)
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, lhs_tag: token.lhs_tag || parameterizing_rule.tag)
rule_builder.lhs = lhs_token
r.symbols.each { |sym| rule_builder.add_rhs(bindings.resolve_symbol(sym)) }
rule_builder.line = line
rule_builder.precedence_sym = r.precedence_sym
rule_builder.user_code = r.resolve_user_code(bindings)
rule_builder.complete_input
rule_builder.setup_rules(parameterizing_rule_resolver)
rule_builder.setup_rules
@rule_builders_for_parameterizing_rules << rule_builder
end
end
@@ -147,11 +161,11 @@ module Lrama
new_token = Lrama::Lexer::Token::Ident.new(s_value: prefix + @midrule_action_counter.increment.to_s)
@replaced_rhs << new_token
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, i, lhs_tag: tag, skip_preprocess_references: true)
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, @parameterizing_rule_resolver, i, lhs_tag: tag, skip_preprocess_references: true)
rule_builder.lhs = new_token
rule_builder.user_code = token
rule_builder.complete_input
rule_builder.setup_rules(parameterizing_rule_resolver)
rule_builder.setup_rules
@rule_builders_for_derived_rules << rule_builder
else
@@ -160,39 +174,10 @@ module Lrama
end
end
def lhs_s_value(token, bindings)
s_values = token.args.map do |arg|
resolved = bindings.resolve_symbol(arg)
if resolved.is_a?(Lexer::Token::InstantiateRule)
[resolved.s_value, resolved.args.map(&:s_value)]
else
resolved.s_value
end
end
"#{token.rule_name}_#{s_values.join('_')}"
end
def resolve_inline(parameterizing_rule_resolver)
rhs.each_with_index do |token, i|
if inline_rule = parameterizing_rule_resolver.find_inline(token)
inline_rule.rhs_list.each_with_index do |inline_rhs|
rule_builder = RuleBuilder.new(@rule_counter, @midrule_action_counter, lhs_tag: lhs_tag, skip_preprocess_references: true)
resolve_inline_rhs(rule_builder, inline_rhs, i)
rule_builder.lhs = lhs
rule_builder.line = line
rule_builder.user_code = replace_inline_user_code(inline_rhs, i)
rule_builder.complete_input
rule_builder.setup_rules(parameterizing_rule_resolver)
@rule_builders_for_inline_rules << rule_builder
end
end
end
end
def resolve_inline_rhs(rule_builder, inline_rhs, index)
def resolve_inline_rhs(rule_builder, inline_rhs, index, bindings = nil)
rhs.each_with_index do |token, i|
if index == i
inline_rhs.symbols.each { |sym| rule_builder.add_rhs(sym) }
inline_rhs.symbols.each { |sym| rule_builder.add_rhs(bindings.nil? ? sym : bindings.resolve_symbol(sym)) }
else
rule_builder.add_rhs(token)
end
@@ -204,6 +189,11 @@ module Lrama
return user_code if user_code.nil?
code = user_code.s_value.gsub(/\$#{index + 1}/, inline_rhs.user_code.s_value)
user_code.references.each do |ref|
next if ref.index.nil? || ref.index <= index # nil is a case for `$$`
code = code.gsub(/\$#{ref.index}/, "$#{ref.index + (inline_rhs.symbols.count-1)}")
code = code.gsub(/@#{ref.index}/, "@#{ref.index + (inline_rhs.symbols.count-1)}")
end
Lrama::Lexer::Token::UserCode.new(s_value: code, location: user_code.location)
end
@@ -238,9 +228,6 @@ module Lrama
end
if ref.number
# TODO: When Inlining is implemented, for example, if `$1` is expanded to multiple RHS tokens,
# `$2` needs to access `$2 + n` to actually access it. So, after the Inlining implementation,
# it needs resolves from number to index.
ref.index = ref.number
end
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
# Symbol is both of nterm and term
# `number` is both for nterm and term
# `token_id` is tokentype for term, internal sequence number for nterm
+2
View File
@@ -1 +1,3 @@
# frozen_string_literal: true
require_relative "symbols/resolver"
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Symbols
@@ -42,7 +44,9 @@ module Lrama
end
def add_nterm(id:, alias_name: nil, tag: nil)
return if find_symbol_by_id(id)
if (sym = find_symbol_by_id(id))
return sym
end
@symbols = nil
nterm = Symbol.new(
@@ -53,6 +57,10 @@ module Lrama
nterm
end
def find_term_by_s_value(s_value)
terms.find { |s| s.id.s_value == s_value }
end
def find_symbol_by_s_value(s_value)
symbols.find { |s| s.id.s_value == s_value }
end
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Type
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Grammar
class Union < Struct.new(:code, :lineno, keyword_init: true)
@@ -0,0 +1,37 @@
# frozen_string_literal: true
module Lrama
class GrammarValidator
def initialize(grammar, states, logger)
@grammar = grammar
@states = states
@logger = logger
end
def valid?
conflicts_within_threshold?
end
private
def conflicts_within_threshold?
return true unless @grammar.expect
[sr_conflicts_within_threshold(@grammar.expect), rr_conflicts_within_threshold(0)].all?
end
def sr_conflicts_within_threshold(expected)
return true if expected == @states.sr_conflicts_count
@logger.error("shift/reduce conflicts: #{@states.sr_conflicts_count} found, #{expected} expected")
false
end
def rr_conflicts_within_threshold(expected)
return true if expected == @states.rr_conflicts_count
@logger.error("reduce/reduce conflicts: #{@states.rr_conflicts_count} found, #{expected} expected")
false
end
end
end
+16 -13
View File
@@ -1,19 +1,22 @@
# frozen_string_literal: true
require "strscan"
require "lrama/lexer/grammar_file"
require "lrama/lexer/location"
require "lrama/lexer/token"
require_relative "lexer/grammar_file"
require_relative "lexer/location"
require_relative "lexer/token"
module Lrama
class Lexer
attr_reader :head_line, :head_column, :line
attr_accessor :status, :end_symbol
SYMBOLS = ['%{', '%}', '%%', '{', '}', '\[', '\]', '\(', '\)', '\,', ':', '\|', ';']
SYMBOLS = ['%{', '%}', '%%', '{', '}', '\[', '\]', '\(', '\)', '\,', ':', '\|', ';'].freeze
PERCENT_TOKENS = %w(
%union
%token
%type
%nterm
%left
%right
%nonassoc
@@ -38,7 +41,8 @@ module Lrama
%rule
%no-stdlib
%inline
)
%locations
).freeze
def initialize(grammar_file)
@grammar_file = grammar_file
@@ -71,7 +75,7 @@ module Lrama
end
def lex_token
while !@scanner.eos? do
until @scanner.eos? do
case
when @scanner.scan(/\n/)
newline
@@ -126,7 +130,7 @@ module Lrama
code = ''
reset_first_position
while !@scanner.eos? do
until @scanner.eos? do
case
when @scanner.scan(/{/)
code += @scanner.matched
@@ -163,14 +167,13 @@ module Lrama
private
def lex_comment
while !@scanner.eos? do
until @scanner.eos? do
case
when @scanner.scan(/\n/)
newline
when @scanner.scan(/\*\//)
when @scanner.scan_until(/[\s\S]*?\*\//)
@scanner.matched.count("\n").times { newline }
return
else
@scanner.getch
when @scanner.scan_until(/\n/)
newline
end
end
end
+10 -1
View File
@@ -1,28 +1,37 @@
# rbs_inline: enabled
# frozen_string_literal: true
module Lrama
class Lexer
class GrammarFile
class Text < String
# @rbs () -> String
def inspect
length <= 50 ? super : "#{self[0..47]}...".inspect
end
end
attr_reader :path, :text
attr_reader :path #: String
attr_reader :text #: String
# @rbs (String path, String text) -> void
def initialize(path, text)
@path = path
@text = Text.new(text).freeze
end
# @rbs () -> String
def inspect
"<#{self.class}: @path=#{path}, @text=#{text.inspect}>"
end
# @rbs (GrammarFile other) -> bool
def ==(other)
self.class == other.class &&
self.path == other.path
end
# @rbs () -> Array[String]
def lines
@lines ||= text.split("\n")
end
+19 -1
View File
@@ -1,8 +1,16 @@
# rbs_inline: enabled
# frozen_string_literal: true
module Lrama
class Lexer
class Location
attr_reader :grammar_file, :first_line, :first_column, :last_line, :last_column
attr_reader :grammar_file #: GrammarFile
attr_reader :first_line #: Integer
attr_reader :first_column #: Integer
attr_reader :last_line #: Integer
attr_reader :last_column #: Integer
# @rbs (grammar_file: GrammarFile, first_line: Integer, first_column: Integer, last_line: Integer, last_column: Integer) -> void
def initialize(grammar_file:, first_line:, first_column:, last_line:, last_column:)
@grammar_file = grammar_file
@first_line = first_line
@@ -11,6 +19,7 @@ module Lrama
@last_column = last_column
end
# @rbs (Location other) -> bool
def ==(other)
self.class == other.class &&
self.grammar_file == other.grammar_file &&
@@ -20,6 +29,7 @@ module Lrama
self.last_column == other.last_column
end
# @rbs (Integer left, Integer right) -> Location
def partial_location(left, right)
offset = -first_column
new_first_line = -1
@@ -50,10 +60,12 @@ module Lrama
)
end
# @rbs () -> String
def to_s
"#{path} (#{first_line},#{first_column})-(#{last_line},#{last_column})"
end
# @rbs (String error_message) -> String
def generate_error_message(error_message)
<<~ERROR.chomp
#{path}:#{first_line}:#{first_column}: #{error_message}
@@ -61,6 +73,7 @@ module Lrama
ERROR
end
# @rbs () -> String
def line_with_carets
<<~TEXT
#{text}
@@ -70,22 +83,27 @@ module Lrama
private
# @rbs () -> String
def path
grammar_file.path
end
# @rbs () -> String
def blanks
(text[0...first_column] or raise "#{first_column} is invalid").gsub(/[^\t]/, ' ')
end
# @rbs () -> String
def carets
blanks + '^' * (last_column - first_column)
end
# @rbs () -> String
def text
@text ||= _text.join("\n")
end
# @rbs () -> Array[String]
def _text
@_text ||=begin
range = (first_line - 1)...last_line
+21 -7
View File
@@ -1,15 +1,21 @@
require 'lrama/lexer/token/char'
require 'lrama/lexer/token/ident'
require 'lrama/lexer/token/instantiate_rule'
require 'lrama/lexer/token/tag'
require 'lrama/lexer/token/user_code'
# rbs_inline: enabled
# frozen_string_literal: true
require_relative 'token/char'
require_relative 'token/ident'
require_relative 'token/instantiate_rule'
require_relative 'token/tag'
require_relative 'token/user_code'
module Lrama
class Lexer
class Token
attr_reader :s_value, :location
attr_accessor :alias_name, :referred
attr_reader :s_value #: String
attr_reader :location #: Location
attr_accessor :alias_name #: String
attr_accessor :referred #: bool
# @rbs (s_value: String, ?alias_name: String, ?location: Location) -> void
def initialize(s_value:, alias_name: nil, location: nil)
s_value.freeze
@s_value = s_value
@@ -17,36 +23,44 @@ module Lrama
@location = location
end
# @rbs () -> String
def to_s
"value: `#{s_value}`, location: #{location}"
end
# @rbs (String string) -> bool
def referred_by?(string)
[self.s_value, self.alias_name].compact.include?(string)
end
# @rbs (Token other) -> bool
def ==(other)
self.class == other.class && self.s_value == other.s_value
end
# @rbs () -> Integer
def first_line
location.first_line
end
alias :line :first_line
# @rbs () -> Integer
def first_column
location.first_column
end
alias :column :first_column
# @rbs () -> Integer
def last_line
location.last_line
end
# @rbs () -> Integer
def last_column
location.last_column
end
# @rbs (Lrama::Grammar::Reference ref, String message) -> bot
def invalid_ref(ref, message)
location = self.location.partial_location(ref.first_column, ref.last_column)
raise location.generate_error_message(message)
@@ -1,3 +1,6 @@
# rbs_inline: enabled
# frozen_string_literal: true
module Lrama
class Lexer
class Token
@@ -1,3 +1,6 @@
# rbs_inline: enabled
# frozen_string_literal: true
module Lrama
class Lexer
class Token
@@ -1,19 +1,26 @@
# rbs_inline: enabled
# frozen_string_literal: true
module Lrama
class Lexer
class Token
class InstantiateRule < Token
attr_reader :args, :lhs_tag
attr_reader :args #: Array[Lexer::Token]
attr_reader :lhs_tag #: Lexer::Token::Tag?
# @rbs (s_value: String, ?alias_name: String, ?location: Location, ?args: Array[Lexer::Token], ?lhs_tag: Lexer::Token::Tag?) -> void
def initialize(s_value:, alias_name: nil, location: nil, args: [], lhs_tag: nil)
super s_value: s_value, alias_name: alias_name, location: location
@args = args
@lhs_tag = lhs_tag
end
# @rbs () -> String
def rule_name
s_value
end
# @rbs () -> Integer
def args_count
args.count
end
+5 -1
View File
@@ -1,9 +1,13 @@
# rbs_inline: enabled
# frozen_string_literal: true
module Lrama
class Lexer
class Token
class Tag < Token
# Omit "<>"
# @rbs () -> String
def member
# Omit "<>"
s_value[1..-2] or raise "Unexpected Tag format (#{s_value})"
end
end
@@ -1,22 +1,27 @@
# rbs_inline: enabled
# frozen_string_literal: true
require "strscan"
module Lrama
class Lexer
class Token
class UserCode < Token
attr_accessor :tag
attr_accessor :tag #: Lexer::Token::Tag
# @rbs () -> Array[Lrama::Grammar::Reference]
def references
@references ||= _references
end
private
# @rbs () -> Array[Lrama::Grammar::Reference]
def _references
scanner = StringScanner.new(s_value)
references = []
references = [] #: Array[Grammar::Reference]
while !scanner.eos? do
until scanner.eos? do
case
when reference = scan_reference(scanner)
references << reference
@@ -30,6 +35,7 @@ module Lrama
references
end
# @rbs (StringScanner scanner) -> Lrama::Grammar::Reference?
def scan_reference(scanner)
start = scanner.pos
case
@@ -1,25 +1,21 @@
module Lrama
class Warning
attr_reader :errors, :warns
# rbs_inline: enabled
# frozen_string_literal: true
module Lrama
class Logger
# @rbs (IO out) -> void
def initialize(out = STDERR)
@out = out
@errors = []
@warns = []
end
def error(message)
@out << message << "\n"
@errors << message
end
# @rbs (String message) -> void
def warn(message)
@out << message << "\n"
@warns << message
end
def has_error?
!@errors.empty?
# @rbs (String message) -> void
def error(message)
@out << message << "\n"
end
end
end
+63 -36
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
require 'optparse'
module Lrama
@@ -16,7 +18,7 @@ module Lrama
@options.report_opts = validate_report(@report)
@options.grammar_file = argv.shift
if !@options.grammar_file
unless @options.grammar_file
abort "File should be specified\n"
end
@@ -57,26 +59,42 @@ module Lrama
o.separator ''
o.separator 'Tuning the Parser:'
o.on('-S', '--skeleton=FILE', 'specify the skeleton to use') {|v| @options.skeleton = v }
o.on('-t', 'reserved, do nothing') { }
o.on('--debug', 'display debugging outputs of internal parser') {|v| @options.debug = true }
o.on('-t', '--debug', 'display debugging outputs of internal parser') {|v| @options.debug = true }
o.on('-D', '--define=NAME[=VALUE]', Array, "similar to '%define NAME VALUE'") {|v| @options.define = v }
o.separator ''
o.separator 'Output:'
o.on('-H', '--header=[FILE]', 'also produce a header file named FILE') {|v| @options.header = true; @options.header_file = v }
o.on('-d', 'also produce a header file') { @options.header = true }
o.on('-r', '--report=THINGS', Array, 'also produce details on the automaton') {|v| @report = v }
o.on('-r', '--report=REPORTS', Array, 'also produce details on the automaton') {|v| @report = v }
o.on_tail ''
o.on_tail 'Valid Reports:'
o.on_tail " #{VALID_REPORTS.join(' ')}"
o.on_tail 'REPORTS is a list of comma-separated words that can include:'
o.on_tail ' states describe the states'
o.on_tail ' itemsets complete the core item sets with their closure'
o.on_tail ' lookaheads explicitly associate lookahead tokens to items'
o.on_tail ' solved describe shift/reduce conflicts solving'
o.on_tail ' counterexamples, cex generate conflict counterexamples'
o.on_tail ' rules list unused rules'
o.on_tail ' terms list unused terminals'
o.on_tail ' verbose report detailed internal state and analysis results'
o.on_tail ' all include all the above reports'
o.on_tail ' none disable all reports'
o.on('--report-file=FILE', 'also produce details on the automaton output to a file named FILE') {|v| @options.report_file = v }
o.on('-o', '--output=FILE', 'leave output to FILE') {|v| @options.outfile = v }
o.on('--trace=THINGS', Array, 'also output trace logs at runtime') {|v| @trace = v }
o.on('--trace=TRACES', Array, 'also output trace logs at runtime') {|v| @trace = v }
o.on_tail ''
o.on_tail 'Valid Traces:'
o.on_tail " #{VALID_TRACES.join(' ')}"
o.on('-v', 'reserved, do nothing') { }
o.on_tail 'TRACES is a list of comma-separated words that can include:'
o.on_tail ' automaton display states'
o.on_tail ' closure display states'
o.on_tail ' rules display grammar rules'
o.on_tail ' only-explicit-rules display only explicit grammar rules'
o.on_tail ' actions display grammar rules with actions'
o.on_tail ' time display generation time'
o.on_tail ' all include all the above traces'
o.on_tail ' none disable all traces'
o.on('-v', '--verbose', "same as '--report=state'") {|_v| @report << 'states' }
o.separator ''
o.separator 'Diagnostics:'
o.on('-W', '--warnings', 'report the warnings') {|v| @options.diagnostic = true }
o.separator ''
o.separator 'Error Recovery:'
o.on('-e', 'enable error recovery') {|v| @options.error_recovery = true }
@@ -89,48 +107,57 @@ module Lrama
end
end
BISON_REPORTS = %w[states itemsets lookaheads solved counterexamples cex all none]
OTHER_REPORTS = %w[verbose]
NOT_SUPPORTED_REPORTS = %w[cex none]
VALID_REPORTS = BISON_REPORTS + OTHER_REPORTS - NOT_SUPPORTED_REPORTS
ALIASED_REPORTS = { cex: :counterexamples }.freeze
VALID_REPORTS = %i[states itemsets lookaheads solved counterexamples rules terms verbose].freeze
def validate_report(report)
list = VALID_REPORTS
h = { grammar: true }
return h if report.empty?
return {} if report == ['none']
if report == ['all']
VALID_REPORTS.each { |r| h[r] = true }
return h
end
report.each do |r|
if list.include?(r)
h[r.to_sym] = true
aliased = aliased_report_option(r)
if VALID_REPORTS.include?(aliased)
h[aliased] = true
else
raise "Invalid report option \"#{r}\"."
end
end
if h[:all]
(BISON_REPORTS - NOT_SUPPORTED_REPORTS).each do |r|
h[r.to_sym] = true
end
h.delete(:all)
end
return h
end
def aliased_report_option(opt)
(ALIASED_REPORTS[opt.to_sym] || opt).to_sym
end
VALID_TRACES = %w[
none locations scan parse automaton bitsets
closure grammar rules actions resource
sets muscles tools m4-early m4 skeleton time
ielr cex all
]
locations scan parse automaton bitsets closure
grammar rules only-explicit-rules actions resource
sets muscles tools m4-early m4 skeleton time ielr cex
].freeze
NOT_SUPPORTED_TRACES = %w[
locations scan parse bitsets grammar resource
sets muscles tools m4-early m4 skeleton ielr cex
].freeze
SUPPORTED_TRACES = VALID_TRACES - NOT_SUPPORTED_TRACES
def validate_trace(trace)
list = VALID_TRACES
h = {}
return h if trace.empty? || trace == ['none']
all_traces = SUPPORTED_TRACES - %w[only-explicit-rules]
if trace == ['all']
all_traces.each { |t| h[t.gsub(/-/, '_').to_sym] = true }
return h
end
trace.each do |t|
if list.include?(t)
h[t.to_sym] = true
if SUPPORTED_TRACES.include?(t)
h[t.gsub(/-/, '_').to_sym] = true
else
raise "Invalid trace option \"#{t}\"."
end
+6 -2
View File
@@ -1,14 +1,17 @@
# frozen_string_literal: true
module Lrama
# Command line options.
class Options
attr_accessor :skeleton, :header, :header_file,
:report_file, :outfile,
:error_recovery, :grammar_file,
:trace_opts, :report_opts, :y,
:debug
:trace_opts, :report_opts,
:diagnostic, :y, :debug, :define
def initialize
@skeleton = "bison/yacc.c"
@define = {}
@header = false
@header_file = nil
@report_file = nil
@@ -17,6 +20,7 @@ module Lrama
@grammar_file = nil
@trace_opts = nil
@report_opts = nil
@diagnostic = false
@y = STDIN
@debug = false
end
+38 -69
View File
@@ -1,6 +1,8 @@
# frozen_string_literal: true
require "erb"
require "forwardable"
require "lrama/report/duration"
require_relative "report/duration"
module Lrama
class Output
@@ -63,37 +65,29 @@ module Lrama
# A part of b4_token_enums
def token_enums
str = ""
@context.yytokentype.each do |s_value, token_id, display_name|
@context.yytokentype.map do |s_value, token_id, display_name|
s = sprintf("%s = %d%s", s_value, token_id, token_id == yymaxutok ? "" : ",")
if display_name
str << sprintf(" %-30s /* %s */\n", s, display_name)
sprintf(" %-30s /* %s */\n", s, display_name)
else
str << sprintf(" %s\n", s)
sprintf(" %s\n", s)
end
end
str
end.join
end
# b4_symbol_enum
def symbol_enum
str = ""
last_sym_number = @context.yysymbol_kind_t.last[1]
@context.yysymbol_kind_t.each do |s_value, sym_number, display_name|
@context.yysymbol_kind_t.map do |s_value, sym_number, display_name|
s = sprintf("%s = %d%s", s_value, sym_number, (sym_number == last_sym_number) ? "" : ",")
if display_name
str << sprintf(" %-40s /* %s */\n", s, display_name)
sprintf(" %-40s /* %s */\n", s, display_name)
else
str << sprintf(" %s\n", s)
sprintf(" %s\n", s)
end
end
str
end.join
end
def yytranslate
@@ -132,12 +126,10 @@ module Lrama
end
def symbol_actions_for_printer
str = ""
@grammar.symbols.each do |sym|
@grammar.symbols.map do |sym|
next unless sym.printer
str << <<-STR
<<-STR
case #{sym.enum_name}: /* #{sym.comment} */
#line #{sym.printer.lineno} "#{@grammar_file_path}"
{#{sym.printer.translated_code(sym.tag)}}
@@ -145,18 +137,14 @@ module Lrama
break;
STR
end
str
end.join
end
def symbol_actions_for_destructor
str = ""
@grammar.symbols.each do |sym|
@grammar.symbols.map do |sym|
next unless sym.destructor
str << <<-STR
<<-STR
case #{sym.enum_name}: /* #{sym.comment} */
#line #{sym.destructor.lineno} "#{@grammar_file_path}"
{#{sym.destructor.translated_code(sym.tag)}}
@@ -164,9 +152,7 @@ module Lrama
break;
STR
end
str
end.join
end
# b4_user_initial_action
@@ -236,12 +222,10 @@ module Lrama
end
def symbol_actions_for_error_token
str = ""
@grammar.symbols.each do |sym|
@grammar.symbols.map do |sym|
next unless sym.error_token
str << <<-STR
<<-STR
case #{sym.enum_name}: /* #{sym.comment} */
#line #{sym.error_token.lineno} "#{@grammar_file_path}"
{#{sym.error_token.translated_code(sym.tag)}}
@@ -249,22 +233,18 @@ module Lrama
break;
STR
end
str
end.join
end
# b4_user_actions
def user_actions
str = ""
@context.states.rules.each do |rule|
action = @context.states.rules.map do |rule|
next unless rule.token_code
code = rule.token_code
spaces = " " * (code.column - 1)
str << <<-STR
<<-STR
case #{rule.id + 1}: /* #{rule.as_comment} */
#line #{code.line} "#{@grammar_file_path}"
#{spaces}{#{rule.translated_code}}
@@ -272,14 +252,12 @@ module Lrama
break;
STR
end
end.join
str << <<-STR
action + <<-STR
#line [@oline@] [@ofile@]
STR
str
end
def omit_blanks(param)
@@ -343,7 +321,7 @@ module Lrama
# b4_parse_param_use
def parse_param_use(val, loc)
str = <<-STR
str = <<-STR.dup
YY_USE (#{val});
YY_USE (#{loc});
STR
@@ -357,7 +335,8 @@ module Lrama
# b4_yylex_formals
def yylex_formals
ary = ["&yylval", "&yylloc"]
ary = ["&yylval"]
ary << "&yylloc" if @grammar.locations
if @grammar.lex_param
ary << lex_param_name
@@ -397,17 +376,9 @@ module Lrama
def int_array_to_string(ary)
last = ary.count - 1
s = ary.each_with_index.each_slice(10).map do |slice|
str = " "
slice.each do |e, i|
str << sprintf("%6d%s", e, (i == last) ? "" : ",")
end
str
end
s.join("\n")
ary.each_with_index.each_slice(10).map do |slice|
" " + slice.map { |e, i| sprintf("%6d%s", e, (i == last) ? "" : ",") }.join
end.join("\n")
end
def spec_mapped_header_file
@@ -457,26 +428,24 @@ module Lrama
end
def template_dir
File.expand_path("../../../template", __FILE__)
File.expand_path('../../template', __dir__)
end
def string_array_to_string(ary)
str = ""
result = ""
tmp = " "
ary.each do |s|
s = s.gsub('\\', '\\\\\\\\')
s = s.gsub('"', '\\"')
if (tmp + s + " \"\",").length > 75
str << tmp << "\n"
tmp = " \"#{s}\","
replaced = s.gsub('\\', '\\\\\\\\').gsub('"', '\\"')
if (tmp + replaced + " \"\",").length > 75
result = "#{result}#{tmp}\n"
tmp = " \"#{replaced}\","
else
tmp << " \"#{s}\","
tmp = "#{tmp} \"#{replaced}\","
end
end
str << tmp
result + tmp
end
def replace_special_variables(str, ofile)
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -1,2 +1,4 @@
require 'lrama/report/duration'
require 'lrama/report/profile'
# frozen_string_literal: true
require_relative 'report/duration'
require_relative 'report/profile'
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Report
module Duration
+2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class Report
module Profile
+295 -6
View File
@@ -1,14 +1,16 @@
require "lrama/state/reduce"
require "lrama/state/reduce_reduce_conflict"
require "lrama/state/resolved_conflict"
require "lrama/state/shift"
require "lrama/state/shift_reduce_conflict"
# frozen_string_literal: true
require_relative "state/reduce"
require_relative "state/reduce_reduce_conflict"
require_relative "state/resolved_conflict"
require_relative "state/shift"
require_relative "state/shift_reduce_conflict"
module Lrama
class State
attr_reader :id, :accessing_symbol, :kernels, :conflicts, :resolved_conflicts,
:default_reduction_rule, :closure, :items
attr_accessor :shifts, :reduces
attr_accessor :shifts, :reduces, :ielr_isocores, :lalr_isocore
def initialize(id, accessing_symbol, kernels)
@id = id
@@ -21,6 +23,12 @@ module Lrama
@conflicts = []
@resolved_conflicts = []
@default_reduction_rule = nil
@predecessors = []
@lalr_isocore = self
@ielr_isocores = [self]
@internal_dependencies = {}
@successor_dependencies = {}
@always_follows = {}
end
def closure=(closure)
@@ -82,6 +90,18 @@ module Lrama
@transitions ||= shifts.map {|shift| [shift, @items_to_state[shift.next_items]] }
end
def update_transition(shift, next_state)
set_items_to_state(shift.next_items, next_state)
next_state.append_predecessor(self)
clear_transitions_cache
end
def clear_transitions_cache
@nterm_transitions = nil
@term_transitions = nil
@transitions = nil
end
def selected_term_transitions
term_transitions.reject do |shift, next_state|
shift.not_selected
@@ -140,5 +160,274 @@ module Lrama
conflict.type == :reduce_reduce
end
end
def propagate_lookaheads(next_state)
next_state.kernels.map {|item|
lookahead_sets =
if item.position == 1
goto_follow_set(item.lhs)
else
kernel = kernels.find {|k| k.predecessor_item_of?(item) }
item_lookahead_set[kernel]
end
[item, lookahead_sets & next_state.lookahead_set_filters[item]]
}.to_h
end
def lookaheads_recomputed
!@item_lookahead_set.nil?
end
def compatible_lookahead?(filtered_lookahead)
!lookaheads_recomputed ||
@lalr_isocore.annotation_list.all? {|token, actions|
a = dominant_contribution(token, actions, item_lookahead_set)
b = dominant_contribution(token, actions, filtered_lookahead)
a.nil? || b.nil? || a == b
}
end
def lookahead_set_filters
kernels.map {|kernel|
[kernel,
@lalr_isocore.annotation_list.select {|token, actions|
token.term? && actions.any? {|action, contributions|
!contributions.nil? && contributions.key?(kernel) && contributions[kernel]
}
}.map {|token, _| token }
]
}.to_h
end
def dominant_contribution(token, actions, lookaheads)
a = actions.select {|action, contributions|
contributions.nil? || contributions.any? {|item, contributed| contributed && lookaheads[item].include?(token) }
}.map {|action, _| action }
return nil if a.empty?
a.reject {|action|
if action.is_a?(State::Shift)
action.not_selected
elsif action.is_a?(State::Reduce)
action.not_selected_symbols.include?(token)
end
}
end
def inadequacy_list
return @inadequacy_list if @inadequacy_list
shift_contributions = shifts.map {|shift|
[shift.next_sym, [shift]]
}.to_h
reduce_contributions = reduces.map {|reduce|
(reduce.look_ahead || []).map {|sym|
[sym, [reduce]]
}.to_h
}.reduce(Hash.new([])) {|hash, cont|
hash.merge(cont) {|_, a, b| a | b }
}
list = shift_contributions.merge(reduce_contributions) {|_, a, b| a | b }
@inadequacy_list = list.select {|token, actions| token.term? && actions.size > 1 }
end
def annotation_list
return @annotation_list if @annotation_list
@annotation_list = annotate_manifestation
@annotation_list = @items_to_state.values.map {|next_state| next_state.annotate_predecessor(self) }
.reduce(@annotation_list) {|result, annotations|
result.merge(annotations) {|_, actions_a, actions_b|
if actions_a.nil? || actions_b.nil?
actions_a || actions_b
else
actions_a.merge(actions_b) {|_, contributions_a, contributions_b|
if contributions_a.nil? || contributions_b.nil?
next contributions_a || contributions_b
end
contributions_a.merge(contributions_b) {|_, contributed_a, contributed_b|
contributed_a || contributed_b
}
}
end
}
}
end
def annotate_manifestation
inadequacy_list.transform_values {|actions|
actions.map {|action|
if action.is_a?(Shift)
[action, nil]
elsif action.is_a?(Reduce)
if action.rule.empty_rule?
[action, lhs_contributions(action.rule.lhs, inadequacy_list.key(actions))]
else
contributions = kernels.map {|kernel| [kernel, kernel.rule == action.rule && kernel.end_of_rule?] }.to_h
[action, contributions]
end
end
}.to_h
}
end
def annotate_predecessor(predecessor)
annotation_list.transform_values {|actions|
token = annotation_list.key(actions)
actions.transform_values {|inadequacy|
next nil if inadequacy.nil?
lhs_adequacy = kernels.any? {|kernel|
inadequacy[kernel] && kernel.position == 1 && predecessor.lhs_contributions(kernel.lhs, token).nil?
}
if lhs_adequacy
next nil
else
predecessor.kernels.map {|pred_k|
[pred_k, kernels.any? {|k|
inadequacy[k] && (
pred_k.predecessor_item_of?(k) && predecessor.item_lookahead_set[pred_k].include?(token) ||
k.position == 1 && predecessor.lhs_contributions(k.lhs, token)[pred_k]
)
}]
}.to_h
end
}
}
end
def lhs_contributions(sym, token)
shift, next_state = nterm_transitions.find {|sh, _| sh.next_sym == sym }
if always_follows(shift, next_state).include?(token)
nil
else
kernels.map {|kernel| [kernel, follow_kernel_items(shift, next_state, kernel) && item_lookahead_set[kernel].include?(token)] }.to_h
end
end
def follow_kernel_items(shift, next_state, kernel)
queue = [[self, shift, next_state]]
until queue.empty?
st, sh, next_st = queue.pop
return true if kernel.next_sym == sh.next_sym && kernel.symbols_after_transition.all?(&:nullable)
st.internal_dependencies(sh, next_st).each {|v| queue << v }
end
false
end
def item_lookahead_set
return @item_lookahead_set if @item_lookahead_set
kernels.map {|item|
value =
if item.lhs.accept_symbol?
[]
elsif item.position > 1
prev_items = predecessors_with_item(item)
prev_items.map {|st, i| st.item_lookahead_set[i] }.reduce([]) {|acc, syms| acc |= syms }
elsif item.position == 1
prev_state = @predecessors.find {|p| p.shifts.any? {|shift| shift.next_sym == item.lhs } }
shift, next_state = prev_state.nterm_transitions.find {|shift, _| shift.next_sym == item.lhs }
prev_state.goto_follows(shift, next_state)
end
[item, value]
}.to_h
end
def item_lookahead_set=(k)
@item_lookahead_set = k
end
def predecessors_with_item(item)
result = []
@predecessors.each do |pre|
pre.items.each do |i|
result << [pre, i] if i.predecessor_item_of?(item)
end
end
result
end
def append_predecessor(prev_state)
@predecessors << prev_state
@predecessors.uniq!
end
def goto_follow_set(nterm_token)
return [] if nterm_token.accept_symbol?
shift, next_state = @lalr_isocore.nterm_transitions.find {|sh, _| sh.next_sym == nterm_token }
@kernels
.select {|kernel| follow_kernel_items(shift, next_state, kernel) }
.map {|kernel| item_lookahead_set[kernel] }
.reduce(always_follows(shift, next_state)) {|result, terms| result |= terms }
end
def goto_follows(shift, next_state)
queue = internal_dependencies(shift, next_state) + predecessor_dependencies(shift, next_state)
terms = always_follows(shift, next_state)
until queue.empty?
st, sh, next_st = queue.pop
terms |= st.always_follows(sh, next_st)
st.internal_dependencies(sh, next_st).each {|v| queue << v }
st.predecessor_dependencies(sh, next_st).each {|v| queue << v }
end
terms
end
def always_follows(shift, next_state)
return @always_follows[[shift, next_state]] if @always_follows[[shift, next_state]]
queue = internal_dependencies(shift, next_state) + successor_dependencies(shift, next_state)
terms = []
until queue.empty?
st, sh, next_st = queue.pop
terms |= next_st.term_transitions.map {|sh, _| sh.next_sym }
st.internal_dependencies(sh, next_st).each {|v| queue << v }
st.successor_dependencies(sh, next_st).each {|v| queue << v }
end
@always_follows[[shift, next_state]] = terms
end
def internal_dependencies(shift, next_state)
return @internal_dependencies[[shift, next_state]] if @internal_dependencies[[shift, next_state]]
syms = @items.select {|i|
i.next_sym == shift.next_sym && i.symbols_after_transition.all?(&:nullable) && i.position == 0
}.map(&:lhs).uniq
@internal_dependencies[[shift, next_state]] = nterm_transitions.select {|sh, _| syms.include?(sh.next_sym) }.map {|goto| [self, *goto] }
end
def successor_dependencies(shift, next_state)
return @successor_dependencies[[shift, next_state]] if @successor_dependencies[[shift, next_state]]
@successor_dependencies[[shift, next_state]] =
next_state.nterm_transitions
.select {|next_shift, _| next_shift.next_sym.nullable }
.map {|transition| [next_state, *transition] }
end
def predecessor_dependencies(shift, next_state)
state_items = []
@kernels.select {|kernel|
kernel.next_sym == shift.next_sym && kernel.symbols_after_transition.all?(&:nullable)
}.each do |item|
queue = predecessors_with_item(item)
until queue.empty?
st, i = queue.pop
if i.position == 0
state_items << [st, i]
else
st.predecessors_with_item(i).each {|v| queue << v }
end
end
end
state_items.map {|state, item|
sh, next_st = state.nterm_transitions.find {|shi, _| shi.next_sym == item.lhs }
[state, sh, next_st]
}
end
end
end
+4 -2
View File
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class State
class Reduce
@@ -24,8 +26,8 @@ module Lrama
end
def selected_look_ahead
if @look_ahead
@look_ahead - @not_selected_symbols
if look_ahead
look_ahead - @not_selected_symbols
else
[]
end
@@ -1,3 +1,5 @@
# frozen_string_literal: true
module Lrama
class State
class ReduceReduceConflict < Struct.new(:symbols, :reduce1, :reduce2, keyword_init: true)

Some files were not shown because too many files have changed in this diff Show More