This commit is contained in:
Intege-rs
2026-08-15 08:44:02 -04:00
commit eb5172548a
102 changed files with 78046 additions and 0 deletions

1
.cargo-ok Normal file
View File

@@ -0,0 +1 @@
{"v":1}

6
.cargo_vcs_info.json Normal file
View File

@@ -0,0 +1,6 @@
{
"git": {
"sha1": "82ec1c3024f9df3c4d7a98a7814e199b1f41deaf"
},
"path_in_vcs": ""
}

247
Cargo.lock generated Normal file
View File

@@ -0,0 +1,247 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "cc"
version = "1.2.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
]
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "ruby-comp"
version = "0.4.0"
dependencies = [
"bindgen",
"cc",
"glob",
"libc",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.110"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"

62
Cargo.toml Normal file
View File

@@ -0,0 +1,62 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "ruby-comp"
version = "0.4.0"
authors = [
"Uchio Kondo <udzura@udzura.jp>",
"Hitoshi Hasumi",
"prism developers",
]
build = "build.rs"
include = [
"src/lib.rs",
"build.rs",
"examples/*.rs",
"Cargo.toml",
"vendor/*",
"lib/.keep",
]
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "mruby-compiler2 bindings for Rust"
readme = "README.md"
license-file = "LICENSE"
[lib]
name = "mruby_compiler2_sys"
path = "src/lib.rs"
[[example]]
name = "cfunc"
path = "examples/cfunc.rs"
[[example]]
name = "smoke"
path = "examples/smoke.rs"
[dependencies.libc]
version = "0.2.182"
optional = true
[build-dependencies.bindgen]
version = ">= 0"
[build-dependencies.cc]
version = ">= 1"
[build-dependencies.glob]
version = ">= 0"

32
Cargo.toml.orig generated Normal file
View File

@@ -0,0 +1,32 @@
[package]
name = "mruby-compiler2-sys"
version = "0.4.0"
edition = "2024"
authors = [
"Uchio Kondo <udzura@udzura.jp>",
"Hitoshi Hasumi",
"prism developers",
]
description = "mruby-compiler2 bindings for Rust"
license-file = "LICENSE"
include = [
"src/lib.rs",
"build.rs",
"examples/*.rs",
"Cargo.toml",
"vendor/*",
"lib/.keep",
]
[dependencies]
libc = { version = "0.2.182", optional = true }
[build-dependencies]
bindgen = ">= 0"
cc = ">= 1"
glob = ">= 0"
[features]
default = ["std"]
std = ["dep:libc"]
no-std = []

24
LICENSE Normal file
View File

@@ -0,0 +1,24 @@
mruby-compiler2-sys
Copyright (c) Uchio Kondo 2025 -
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[ MIT license: http://www.opensource.org/licenses/mit-license.php ]

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# mruby-compiler2-sys
Naive mruby-compiler2 binding for Rust
See Rust doc [mruby-compiler2-sys](https://crates.io/crates/mruby-compiler2-sys) for details.
## License
See LICENSE file.
NOTE: This crate includes code from mruby-compiler2 and prism, which are under the MIT License.

56
build.rs Normal file
View File

@@ -0,0 +1,56 @@
extern crate cc;
use glob::glob;
fn main() {
let out_dir = std::env::var("OUT_DIR").unwrap();
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-link-search={}", out_dir);
cc::Build::new()
.files(
glob("./vendor/mruby-compiler2/src/**/*.c")
.expect("cannot find c source")
.map(|x| x.unwrap()),
)
.files(
glob("./vendor/mruby-compiler2/lib/prism/src/**/*.c")
.expect("cannot find c source")
.map(|x| x.unwrap()),
)
.warnings(false)
.define("MRB_NO_PRESYM", "")
.define("MRB_INT64", "1")
.define("PRISM_XALLOCATOR", "")
.define("PRISM_BUILD_MINIMAL", "")
.define("PICORB_VM_MRUBYC", "")
.define("MRBC_ALLOC_LIBC", "")
.include("./vendor/include")
.include("./vendor/mruby-compiler2/include")
.include("./vendor/mruby-compiler2/lib/prism/include")
// .flag("-fPIC")
.flag("-c")
.compile("mrubycompiler2");
println!("cargo:rustc-link-lib=mrubycompiler2");
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
let mut builder = bindgen::Builder::default()
.header("./vendor/mruby-compiler2/include/mruby_compiler.h")
.header("./vendor/mruby-compiler2/include/mrc_codedump.h")
.clang_arg("-I./vendor/mruby-compiler2/include")
.clang_arg("-I./vendor/mruby-compiler2/lib/prism/include")
.blocklist_item("FP_NAN")
.blocklist_item("FP_INFINITE")
.blocklist_item("FP_ZERO")
.blocklist_item("FP_SUBNORMAL")
.blocklist_item("FP_NORMAL")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
if target_arch == "wasm32" {
builder = builder.blocklist_type("max_align_t");
}
let bindings = builder.generate().expect("Unable to generate bindings");
let out = std::path::PathBuf::from(out_dir).join("bindings.rs");
bindings
.write_to_file(out)
.expect("Couldn't write bindings!");
}

15
examples/cfunc.rs Normal file
View File

@@ -0,0 +1,15 @@
extern crate mruby_compiler2_sys;
use mruby_compiler2_sys::MRubyCompiler2Context;
fn main() -> Result<(), Box<dyn std::error::Error>> {
unsafe {
let mut cxt = MRubyCompiler2Context::new();
cxt.compile_to_c_function(
"puts \"Hello, mruby-compiler2!\"",
"init_test_func",
std::path::Path::new("examples/out.c"),
)?;
}
println!("Created examples/out.c");
Ok(())
}

17
examples/smoke.rs Normal file
View File

@@ -0,0 +1,17 @@
extern crate mruby_compiler2_sys;
use mruby_compiler2_sys::MRubyCompiler2Context;
fn main() -> Result<(), Box<dyn std::error::Error>> {
unsafe {
let mut cxt = MRubyCompiler2Context::new();
cxt.dump_bytecode("puts \"Hello, mruby-compiler2!\"")?;
let bin = cxt.compile("puts \"Hello, mruby-compiler2!\"")?;
let out = std::fs::File::create("examples/out.mrb")?;
std::io::Write::write_all(&mut &out, &bin)?;
println!("Compiled bytecode file: examples/out.mrb, size: {}", bin.len());
}
Ok(())
}

101
src/lib.rs Normal file
View File

@@ -0,0 +1,101 @@
use std::ptr::null_mut;
mod bindings {
#![allow(nonstandard_style)]
#![allow(unused)]
#![allow(unnecessary_transmutes)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
use bindings::{
MRC_DUMP_OK, mrc_ccontext, mrc_ccontext_free, mrc_ccontext_new, mrc_dump_irep, mrc_irep,
mrc_irep_free, mrc_load_string_cxt,
};
#[derive(Debug)]
pub struct MRubyCompiler2Error {
details: String,
}
impl MRubyCompiler2Error {
fn new(msg: &str) -> MRubyCompiler2Error {
MRubyCompiler2Error {
details: msg.to_string(),
}
}
#[allow(unused)]
fn from_error<E: std::error::Error>(msg: &str, err: E) -> MRubyCompiler2Error {
MRubyCompiler2Error {
details: format!("{}: {}", msg, err.to_string()),
}
}
}
impl std::fmt::Display for MRubyCompiler2Error {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.details)
}
}
impl std::error::Error for MRubyCompiler2Error {}
pub struct MRubyCompiler2Context {
c: *mut mrc_ccontext,
}
impl MRubyCompiler2Context {
/// Creates a new MRubyCompiler2Context
pub unsafe fn new() -> Self {
unsafe {
let ccontext = mrc_ccontext_new(null_mut());
MRubyCompiler2Context { c: ccontext }
}
}
/// Compiles the given mruby code into mruby bytecode binary
/// Returns the bytecode as a `Vec<u8>`
pub unsafe fn compile(&mut self, code: &str) -> Result<Vec<u8>, MRubyCompiler2Error> {
unsafe {
let c_code = std::ffi::CString::new(code)
.map_err(|_| MRubyCompiler2Error::new("Code includes null bytes"))?;
let mut ptr = c_code.as_ptr() as *const u8;
let irep =
mrc_load_string_cxt(self.c, &mut ptr as *mut *const u8, c_code.as_bytes().len());
if irep.is_null() {
return Err(MRubyCompiler2Error::new("Failed to compile code"));
}
// Set dummy capacity, deduced from code length
// And leak for safety rather than memory efficiency
let bin: &'static mut [u8] = Vec::with_capacity(code.len() * 2).leak();
let bin_ptr = bin.as_mut_ptr();
let mut bin_size: usize = 0;
let result = mrc_dump_irep(
self.c,
irep as *mut mrc_irep,
0,
&bin_ptr as *const *mut u8 as *mut *mut u8,
&mut bin_size as *mut usize,
);
mrc_irep_free(self.c, irep as *mut mrc_irep);
if result as u32 != MRC_DUMP_OK {
return Err(MRubyCompiler2Error::new("Failed to dump irep binary"));
}
let newvec = Vec::from_raw_parts(bin_ptr, bin_size, bin_size);
Ok(newvec)
}
}
}
impl Drop for MRubyCompiler2Context {
fn drop(&mut self) {
unsafe {
mrc_ccontext_free(self.c);
}
}
}

3
target/CACHEDIR.TAG Normal file
View File

@@ -0,0 +1,3 @@
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/

0
target/debug/.cargo-lock Normal file
View File

1
vendor/include/mrubyc.h vendored Normal file
View File

@@ -0,0 +1 @@
// Dummy inclusion header

24
vendor/mruby-compiler2/LICENSE vendored Normal file
View File

@@ -0,0 +1,24 @@
mruby-compiler2
Copyright (c) HASUMI Hitoshi 2024
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[ MIT license: http://www.opensource.org/licenses/mit-license.php ]

20
vendor/mruby-compiler2/README.md vendored Normal file
View File

@@ -0,0 +1,20 @@
# mruby-compiler2
New mruby compiler based on Prism.
## install by mrbgems
- add conf.gem line to `build_config.rb`
```ruby
MRuby::Build.new do |conf|
# ... (snip) ...
conf.gem :github => 'picoruby/mruby-compiler2'
end
```
## License
under the MIT License:
- see LICENSE file

View File

@@ -0,0 +1,87 @@
#ifndef MRC_CCONTEXT_H
#define MRC_CCONTEXT_H
#include "mrc_common.h"
#include "mrc_diagnostic.h"
#include "mrc_throw.h"
#include "mrc_pool.h"
#include <stddef.h>
MRC_BEGIN_DECL
typedef pm_node_t mrc_node;
typedef pm_parser_t mrc_parser_state;
typedef pm_constant_id_list_t mrc_constant_id_list;
typedef struct {
pm_parser_t parser;
pm_options_t options;
pm_string_t input;
bool parsed;
} pm_parse_result_t;
struct mrc_diagnostic_list;
typedef struct mrc_filename_table {
const char *filename;
uint32_t start;
} mrc_filename_table;
typedef struct mrc_ccontext {
mrb_state *mrb;
struct mrc_jmpbuf *jmp;
mrc_parser_state *p;
pm_options_t *options; // instead of mrb_sym *syms
int slen;
char *filename;
uint16_t lineno;
struct RClass *target_class;
mrc_bool capture_errors:1;
mrc_bool dump_result:1;
mrc_bool no_exec:1;
mrc_bool keep_lv:1;
mrc_bool no_optimize:1;
mrc_bool no_ext_ops:1;
#if defined(MRC_TARGET_MRUBY)
const struct RProc *upper;
#endif
// TODO
//size_t parser_nerr;
struct mrc_diagnostic_list *diagnostic_list;
// For PICOIRB
uint16_t scope_sp;
#ifndef MRC_NO_STDIO
mrc_pool *pool; // for codedump
mrc_filename_table *filename_table;
uint16_t filename_table_length;
uint16_t current_filename_index;
#endif
} mrc_ccontext; /* compiler context */
#ifdef MRC_TARGET_MRUBY
static inline int mrc_gc_arena_save(mrc_ccontext *c)
{
if (!c->mrb) return 0;
return mrb_gc_arena_save(c->mrb);
}
static inline void mrc_gc_arena_restore(mrc_ccontext *c, int ai)
{
if (!c->mrb) return;
mrb_gc_arena_restore(c->mrb, ai);
}
#else
# define mrc_gc_arena_save(c) 0;(void)ai
# define mrc_gc_arena_restore(c,ai)
#endif
mrc_ccontext *mrc_ccontext_new(mrb_state *mrb);
void mrc_ccontext_cleanup_local_variables(mrc_ccontext *c);
const char *mrc_ccontext_filename(mrc_ccontext *c, const char *s);
void mrc_ccontext_free(mrc_ccontext *c);
MRC_END_DECL
#endif // MRC_CCONTEXT_H

View File

@@ -0,0 +1,14 @@
#ifndef MRC_CDUMP_H
#define MRC_CDUMP_H
#include "mrc_ccontext.h"
#include "mrc_irep.h"
MRC_BEGIN_DECL
MRC_END_DECL
int mrc_dump_irep_cstruct(mrc_ccontext *c, const mrc_irep *irep, uint8_t flags, FILE *fp, const char *initname);
#endif // MRC_CDUMP_H

View File

@@ -0,0 +1,17 @@
#ifndef MRC_CODEDUMP_H
#define MRC_CODEDUMP_H
#include "mrc_ccontext.h"
MRC_BEGIN_DECL
#ifndef MRC_NO_STDIO
void mrc_codedump_all_file(mrc_ccontext *c, mrc_irep *irep, FILE *out);
#endif
void mrc_codedump_all(mrc_ccontext *c, mrc_irep *irep);
MRC_END_DECL
#endif // MRC_CODEDUMP_H

View File

@@ -0,0 +1,15 @@
#ifndef MRC_CODEGEN_H
#define MRC_CODEGEN_H
#include "mrc_common.h"
#include "mrc_ccontext.h"
#include "mrc_irep.h"
MRC_BEGIN_DECL
mrc_irep *mrc_generate_code(mrc_ccontext *c, mrc_node *node);
MRC_END_DECL
#endif // MRC_CODEGEN_H

View File

@@ -0,0 +1,165 @@
#ifndef MRC_COMMON_H
#define MRC_COMMON_H
#include <stdint.h>
#define MRC_STRINGIZE0(expr) #expr
#define MRC_STRINGIZE(expr) MRC_STRINGIZE0(expr)
#if defined(PICORB_VM_MRUBY)
#if !defined(MRC_TARGET_MRUBY)
#define MRC_TARGET_MRUBY
#endif
#include <mruby.h>
#endif
#if defined(PICORB_VM_MRUBYC)
#if !defined(MRC_TARGET_MRUBYC)
#define MRC_TARGET_MRUBYC
#endif
#include <mrubyc.h>
#define mrb_state void
#endif
#if !defined(MRC_TARGET_MRUBY) && !defined(PICORB_VM_MRUBYC)
/* May be building mrbc (picorbc) */
#define mrb_state void
#endif
#if !defined(PRISM_XALLOCATOR)
#define PRISM_XALLOCATOR
#endif
#include "prism.h"
#ifndef PICORUBY_VERSION
#define MRC_VERSION "unknown (standalone)"
#else
#define MRC_VERSION PICORUBY_VERSION
#endif
#define MRC_RELEASE_YEAR 2026
#define MRC_RELEASE_MONTH 1
#define MRC_RELEASE_DAY 21
#define MRC_RELEASE_DATE MRC_STRINGIZE(MRC_RELEASE_YEAR) "-" \
MRC_STRINGIZE(MRC_RELEASE_MONTH) "-" \
MRC_STRINGIZE(MRC_RELEASE_DAY)
#ifdef MRB_USE_CXX_ABI
#define MRC_USE_CXX_ABI
#endif
#ifdef __cplusplus
#ifdef MRC_USE_CXX_ABI
#define MRC_BEGIN_DECL
#define MRC_END_DECL
#else
#define MRC_BEGIN_DECL extern "C" {
#define MRC_END_DECL }
#endif
#else
/** Start declarations in C mode */
# define MRC_BEGIN_DECL
/** End declarations in C mode */
# define MRC_END_DECL
#endif
/** Declare a public mruby API function. */
#ifndef MRC_API
#if defined(MRC_BUILD_AS_DLL)
#if defined(MRC_CORE) || defined(MRC_LIB)
# define MRC_API __declspec(dllexport)
#else
# define MRC_API __declspec(dllimport)
#endif
#else
# define MRC_API extern
#endif
#endif
#if defined(__cplusplus) || (defined(__bool_true_false_are_defined) && __bool_true_false_are_defined)
typedef bool mrc_bool;
# ifndef FALSE
# define FALSE false
# endif
# ifndef TRUE
# define TRUE true
# endif
#else
# if __STDC_VERSION__ >= 199901L
typedef _Bool mrc_bool;
# else
typedef uint8_t mrc_bool;
# endif
# ifndef FALSE
# define FALSE 0
# endif
# ifndef TRUE
# define TRUE 1
# endif
#endif
#if !defined(MRC_INT32)
#define MRC_INT64 1
#endif
#if !defined(MRC_32BIT)
#define MRC_64BIT 1
#endif
#if defined(MRC_INT64)
typedef int64_t mrc_int;
typedef uint64_t mrc_uint;
#define MRC_INT_BIT 64
#define MRC_INT_MIN INT64_MIN
#define MRC_INT_MAX INT64_MAX
#define MRC_PRIo PRIo64
#define MRC_PRId PRId64
#define MRC_PRIx PRIx64
#else
typedef int32_t mrc_int;
typedef uint32_t mrc_uint;
#define MRC_INT_BIT 32
#define MRC_INT_MIN INT32_MIN
#define MRC_INT_MAX INT32_MAX
#define MRC_PRIo PRIo32
#define MRC_PRId PRId32
#define MRC_PRIx PRIx32
#endif
#ifdef MRB_NO_FLOAT
#define MRC_NO_FLOAT
#endif
#ifdef MRB_USE_FLOAT32
#define MRC_USE_FLOAT32
#endif
#ifndef MRC_NO_FLOAT
#ifdef MRC_USE_FLOAT32
typedef float mrc_float;
#else
typedef double mrc_float;
#endif
#endif
typedef uint32_t mrc_sym;
typedef uint8_t mrc_code;
/**
* \class mrb_aspec
*
* Specifies the number of arguments a function takes
*
* Example: `MRB_ARGS_REQ(2) | MRB_ARGS_OPT(1)` for a method that expects 2..3 arguments
*/
typedef uint32_t mrc_aspec;
#ifdef MRC_DEBUG
#include <assert.h>
#define mrc_assert(p) assert(p)
#define mrc_assert_int_fit(t1,n,t2,max) assert((n)>=0 && ((sizeof(n)<=sizeof(t2))||(n<=(t1)(max))))
#else
#define mrc_assert(p) ((void)0)
#define mrc_assert_int_fit(t1,n,t2,max) ((void)0)
#endif
#endif /* MRC_COMMON_H */

View File

@@ -0,0 +1,14 @@
#ifndef MRC_COMPILE_H
#define MRC_COMPILE_H
#include "mrc_ccontext.h"
#include "mrc_irep.h"
MRC_BEGIN_DECL
mrc_irep *mrc_load_file_cxt(mrc_ccontext *c, const char **filenames, uint8_t **source);
mrc_irep *mrc_load_string_cxt(mrc_ccontext *c, const uint8_t **source, size_t length);
MRC_END_DECL
#endif /* MRC_COMPILE_H */

View File

@@ -0,0 +1,78 @@
/**
** @file mruby/debug.h - mruby debug info
**
** See Copyright Notice in mruby.h
*/
#ifndef MRC_DEBUG_H
#define MRC_DEBUG_H
#include "mrc_ccontext.h"
#include "mrc_irep.h"
/**
* mruby Debugging.
*/
MRC_BEGIN_DECL
typedef enum mrc_debug_line_type {
mrc_debug_line_ary = 0,
mrc_debug_line_flat_map,
mrc_debug_line_packed_map
} mrc_debug_line_type;
typedef struct mrc_irep_debug_info_line {
uint32_t start_pos;
uint16_t line;
} mrc_irep_debug_info_line;
typedef struct mrc_irep_debug_info_file {
uint32_t start_pos;
mrc_sym filename_sym;
uint32_t line_entry_count;
mrc_debug_line_type line_type;
union {
const char *s;
void *ptr;
const uint16_t *ary;
const mrc_irep_debug_info_line *flat_map;
const uint8_t *packed_map;
} lines;
} mrc_irep_debug_info_file;
typedef struct mrc_irep_debug_info {
uint32_t pc_count;
uint16_t flen;
mrc_irep_debug_info_file **files;
} mrc_irep_debug_info;
/*
* get filename from irep's debug info and program counter
* @return returns NULL if not found
*/
const char *mrc_debug_get_filename(mrc_ccontext *c, const mrc_irep *irep, uint32_t pc);
/*
* get line from irep's debug info and program counter
* @return returns -1 if not found
*/
int32_t mrc_debug_get_line(mrc_ccontext *c, const mrc_irep *irep, uint32_t pc);
/*
* get line and filename from irep's debug info and program counter
* @return returns FALSE if not found
*/
mrc_bool mrc_debug_get_position(mrc_ccontext *c, const mrc_irep *irep, uint32_t pc, int32_t *lp, const char **fp);
const char *mrc_debug_get_filename(mrc_ccontext *c, const mrc_irep *irep, uint32_t pc);
mrc_irep_debug_info *mrc_debug_info_alloc(mrc_ccontext *c, mrc_irep *irep);
mrc_irep_debug_info_file *mrc_debug_info_append_file(
mrc_ccontext *c, mrc_irep_debug_info *info,
const char *filename, uint16_t *lines,
uint32_t start_pos, uint32_t end_pos);
void mrc_debug_info_free(mrc_ccontext *c, mrc_irep_debug_info *d);
MRC_END_DECL
#endif /* MRC_DEBUG_H */

View File

@@ -0,0 +1,30 @@
#ifndef MRC_DIAGNOSTIC_H
#define MRC_DIAGNOSTIC_H
#include "mrc_ccontext.h"
MRC_BEGIN_DECL
typedef enum {
MRC_PARSER_WARNING = 0,
MRC_PARSER_ERROR = 1,
MRC_GENERATOR_WARNING = 2,
MRC_GENERATOR_ERROR = 3,
} mrc_diagnostic_code;
typedef struct mrc_diagnostic_list {
mrc_diagnostic_code code;
char *message;
uint32_t line;
uint32_t column;
struct mrc_diagnostic_list *next;
} mrc_diagnostic_list;
struct mrc_ccontext;
void mrc_diagnostic_list_append(struct mrc_ccontext *c, const uint8_t *location_start, const char *message, mrc_diagnostic_code code);
void mrc_diagnostic_list_free(struct mrc_ccontext *c);
MRC_END_DECL
#endif // MRC_DIAGNOSTIC_H

View File

@@ -0,0 +1,154 @@
#ifndef MRC_DUMP_H
#define MRC_DUMP_H
#include "mrc_irep.h"
#include "mrc_ccontext.h"
MRC_BEGIN_DECL
#define MRC_DUMP_DEBUG_INFO 1
#define MRC_DUMP_STATIC 2
#ifndef MRC_NO_STDIO
int mrc_dump_irep_cfunc(mrc_ccontext *c, const mrc_irep *irep, uint8_t flags, FILE *fp, const char *initname);
int mrc_dump_irep_binary(mrc_ccontext *c, const mrc_irep *irep, uint8_t flags, FILE* fp);
int mrc_dump_irep(mrc_ccontext *c, const mrc_irep *irep, uint8_t flags, uint8_t **bin, size_t *bin_size);
#endif
/* dump/load error code
*
* NOTE: MRC_DUMP_GENERAL_FAILURE is caused by
* unspecified issues like malloc failed.
*/
#define MRC_DUMP_OK 0
#define MRC_DUMP_GENERAL_FAILURE (-1)
#define MRC_DUMP_WRITE_FAULT (-2)
#define MRC_DUMP_READ_FAULT (-3)
#define MRC_DUMP_INVALID_FILE_HEADER (-4)
#define MRC_DUMP_INVALID_IREP (-5)
#define MRC_DUMP_INVALID_ARGUMENT (-6)
/* null symbol length */
#define MRC_DUMP_NULL_SYM_LEN 0xFFFF
/* Rite Binary File header */
#define RITE_BINARY_IDENT "RITE"
/* Binary Format Version Major:Minor */
/* Major: Incompatible to prior versions */
/* Minor: Upper-compatible to prior versions */
#define RITE_BINARY_MAJOR_VER "03"
#define RITE_BINARY_MINOR_VER "00"
#define RITE_BINARY_FORMAT_VER RITE_BINARY_MAJOR_VER RITE_BINARY_MINOR_VER
#if defined(RITE_COMPILER_NAME)
#undef RITE_COMPILER_NAME
#endif
#define RITE_COMPILER_NAME "HSMK"
#define RITE_PARSER_NAME "Prism"
#define RITE_COMPILER_VERSION "0000"
#define RITE_VM_VER "0300"
#define RITE_BINARY_EOF "END\0"
#define RITE_SECTION_IREP_IDENT "IREP"
#define RITE_SECTION_DEBUG_IDENT "DBG\0"
#define RITE_SECTION_LV_IDENT "LVAR"
#define MRC_DUMP_DEFAULT_STR_LEN 128
#define MRC_DUMP_ALIGNMENT sizeof(uint32_t)
/* binary header */
struct rite_binary_header {
uint8_t binary_ident[4]; /* Binary Identifier */
uint8_t major_version[2]; /* Binary Format Major Version */
uint8_t minor_version[2]; /* Binary Format Minor Version */
uint8_t binary_size[4]; /* Binary Size */
uint8_t compiler_name[4]; /* Compiler name */
uint8_t compiler_version[4];
};
/* section header */
#define RITE_SECTION_HEADER \
uint8_t section_ident[4]; \
uint8_t section_size[4]
struct rite_section_header {
RITE_SECTION_HEADER;
};
struct rite_section_irep_header {
RITE_SECTION_HEADER;
uint8_t rite_version[4]; /* Rite Instruction Specification Version */
};
struct rite_section_debug_header {
RITE_SECTION_HEADER;
};
struct rite_section_lv_header {
RITE_SECTION_HEADER;
};
#define RITE_LV_NULL_MARK UINT16_MAX
struct rite_binary_footer {
RITE_SECTION_HEADER;
};
static inline size_t
mrc_uint8_to_bin(uint8_t s, uint8_t *bin)
{
*bin = s;
return sizeof(uint8_t);
}
static inline size_t
mrc_uint16_to_bin(uint16_t s, uint8_t *bin)
{
*bin++ = (s >> 8) & 0xff;
*bin = s & 0xff;
return sizeof(uint16_t);
}
static inline size_t
mrc_uint32_to_bin(uint32_t l, uint8_t *bin)
{
*bin++ = (l >> 24) & 0xff;
*bin++ = (l >> 16) & 0xff;
*bin++ = (l >> 8) & 0xff;
*bin = l & 0xff;
return sizeof(uint32_t);
}
static inline uint32_t
mrc_bin_to_uint32(const uint8_t *bin)
{
return (uint32_t)bin[0] << 24 |
(uint32_t)bin[1] << 16 |
(uint32_t)bin[2] << 8 |
(uint32_t)bin[3];
}
static inline uint16_t
mrc_bin_to_uint16(const uint8_t *bin)
{
return (uint16_t)bin[0] << 8 |
(uint16_t)bin[1];
}
static inline uint8_t
mrc_bin_to_uint8(const uint8_t *bin)
{
return (uint8_t)bin[0];
}
static inline const char*
mrc_description(void)
{
return MRC_VERSION " (" MRC_RELEASE_DATE ") Parser: " RITE_PARSER_NAME ", RITE: " RITE_BINARY_FORMAT_VER;
}
MRC_END_DECL
#endif // MRC_DUMP_H

View File

@@ -0,0 +1,45 @@
/**
** @file mruby/endian.h - detect endian-ness
**
** See Copyright Notice in mruby.h
*/
#ifndef MRC_ENDIAN_H
#define MRC_ENDIAN_H
#include <limits.h>
MRC_BEGIN_DECL
#if !defined(BYTE_ORDER) && defined(__BYTE_ORDER__)
# define BYTE_ORDER __BYTE_ORDER__
#endif
#if !defined(BIG_ENDIAN) && defined(__ORDER_BIG_ENDIAN__)
# define BIG_ENDIAN __ORDER_BIG_ENDIAN__
#endif
#if !defined(LITTLE_ENDIAN) && defined(__ORDER_LITTLE_ENDIAN__)
# define LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__
#endif
#ifdef BYTE_ORDER
# if BYTE_ORDER == BIG_ENDIAN
# define littleendian 0
# elif BYTE_ORDER == LITTLE_ENDIAN
# define littleendian 1
# endif
#endif
#ifndef littleendian
/* can't distinguish endian in compile time */
static inline int
check_little_endian(void)
{
unsigned int n = 1;
return (*(unsigned char*)&n == 1);
}
# define littleendian check_little_endian()
#endif
MRC_END_DECL
#endif /* MRC_ENDIAN_H */

View File

@@ -0,0 +1,98 @@
/**
** @file mruby/irep.h - mrc_irep structure
**
** See Copyright Notice in mruby.h
*/
#ifndef MRC_IREP_H
#define MRC_IREP_H
#include "mrc_ccontext.h"
/**
* Compiled mruby scripts.
*/
MRC_BEGIN_DECL
#define IREP_TT_NFLAG 1 /* number (non string) flag */
#define IREP_TT_SFLAG 2 /* static string flag */
typedef struct mrc_pool_value {
uint32_t tt; /* packed type and length (for string) */
union {
const char *str;
int32_t i32;
int64_t i64;
#ifndef MRC_NO_FLOAT
mrc_float f;
#endif
} u;
} mrc_pool_value;
enum mrc_catch_type {
MRC_CATCH_RESCUE = 0,
MRC_CATCH_ENSURE = 1,
};
struct mrc_irep_catch_handler {
uint8_t type; /* enum mrc_catch_type */
uint8_t begin[4]; /* The starting address to match the handler. Includes this. */
uint8_t end[4]; /* The endpoint address that matches the handler. Not Includes this. */
uint8_t target[4]; /* The address to jump to if a match is made. */
};
/* Program data array struct */
typedef struct mrc_irep {
uint16_t nlocals; /* Number of local variables */
uint16_t nregs; /* Number of register variables */
uint16_t clen; /* Number of catch handlers */
uint8_t flags;
const mrc_code *iseq;
/*
* A catch handler table is placed after the iseq entity.
* The reason it doesn't add fields to the structure is to keep the mrc_irep structure from bloating.
* The catch handler table can be obtained with `mrc_irep_catch_handler_table(irep)`.
*/
const mrc_pool_value *pool;
const mrc_sym *syms;
const struct mrc_irep * const *reps;
mrc_sym *lv; // Remove const for mrc_resolve_intern
/* debug info */
struct mrc_irep_debug_info* debug_info;
uint32_t ilen;
uint16_t plen, slen;
uint16_t rlen;
uint16_t refcnt;
} mrc_irep;
#define MRC_ISEQ_NO_FREE 1
#define MRC_IREP_NO_FREE 2
struct mrc_insn_data {
uint8_t insn;
uint32_t a;
uint16_t b;
uint16_t cc;
const mrc_code *addr;
};
#define mrc_irep_catch_handler_pack(n, v) mrc_uint32_to_bin(n, v)
#define mrc_irep_catch_handler_unpack(v) mrc_bin_to_uint32(v)
void mrc_irep_remove_lv(mrc_ccontext *c, mrc_irep *irep);
void mrc_irep_free(mrc_ccontext *c, mrc_irep *irep);
#define MRC_ASPEC_REQ(a) (((a) >> 18) & 0x1f)
#define MRC_ASPEC_OPT(a) (((a) >> 13) & 0x1f)
#define MRC_ASPEC_REST(a) (((a) >> 12) & 0x1)
#define MRC_ASPEC_POST(a) (((a) >> 7) & 0x1f)
#define MRC_ASPEC_KEY(a) (((a) >> 2) & 0x1f)
#define MRC_ASPEC_KDICT(a) (((a) >> 1) & 0x1)
#define MRC_ASPEC_BLOCK(a) ((a) & 1)
MRC_END_DECL
#endif /* MRC_IREP_H */

View File

@@ -0,0 +1,17 @@
#ifndef MRB_IREP_POOL_TYPE_H
#define MRB_IREP_POOL_TYPE_H
MRC_BEGIN_DECL
enum irep_pool_type {
IREP_TT_STR = 0, /* string (need free) */
IREP_TT_SSTR = 2, /* string (static) */
IREP_TT_INT32 = 1, /* 32bit integer */
IREP_TT_INT64 = 3, /* 64bit integer */
IREP_TT_BIGINT = 7, /* big integer (not yet supported) */
IREP_TT_FLOAT = 5, /* float (double/float) */
};
MRC_END_DECL
#endif

View File

@@ -0,0 +1,69 @@
/**
** @file mruby/opcode.h - RiteVM operation codes
**
** See Copyright Notice in mruby.h
*/
#ifndef MRUBY_OPCODE_H
#define MRUBY_OPCODE_H
enum mrb_insn {
#define OPCODE(x,_) OP_ ## x,
#include "mrc_ops.h"
#undef OPCODE
};
#define OP_L_STRICT 1
#define OP_L_CAPTURE 2
#define OP_L_METHOD OP_L_STRICT
#define OP_L_LAMBDA (OP_L_STRICT|OP_L_CAPTURE)
#define OP_L_BLOCK OP_L_CAPTURE
#define PEEK_B(pc) (*(pc))
#define PEEK_S(pc) ((pc)[0]<<8|(pc)[1])
#define PEEK_W(pc) ((pc)[0]<<16|(pc)[1]<<8|(pc)[2])
#define READ_B() PEEK_B(pc++)
#define READ_S() (pc+=2, PEEK_S(pc-2))
#define READ_W() (pc+=3, PEEK_W(pc-3))
#define FETCH_Z() /* nothing */
#define FETCH_B() do {a=READ_B();} while (0)
#define FETCH_BB() do {a=READ_B(); b=READ_B();} while (0)
#define FETCH_BBB() do {a=READ_B(); b=READ_B(); cc=READ_B();} while (0)
#define FETCH_BS() do {a=READ_B(); b=READ_S();} while (0)
#define FETCH_BSS() do {a=READ_B(); b=READ_S(); cc=READ_S();} while (0)
#define FETCH_S() do {a=READ_S();} while (0)
#define FETCH_W() do {a=READ_W();} while (0)
/* with OP_EXT1 (1st 16bit) */
#define FETCH_Z_1() FETCH_Z()
#define FETCH_B_1() FETCH_S()
#define FETCH_BB_1() do {a=READ_S(); b=READ_B();} while (0)
#define FETCH_BBB_1() do {a=READ_S(); b=READ_B(); cc=READ_B();} while (0)
#define FETCH_BS_1() do {a=READ_S(); b=READ_S();} while (0)
#define FETCH_BSS_1() do {a=READ_S(); b=READ_S();cc=READ_S();} while (0)
#define FETCH_S_1() FETCH_S()
#define FETCH_W_1() FETCH_W()
/* with OP_EXT2 (2nd 16bit) */
#define FETCH_Z_2() FETCH_Z()
#define FETCH_B_2() FETCH_B()
#define FETCH_BB_2() do {a=READ_B(); b=READ_S();} while (0)
#define FETCH_BBB_2() do {a=READ_B(); b=READ_S(); cc=READ_B();} while (0)
#define FETCH_BS_2() FETCH_BS()
#define FETCH_BSS_2() FETCH_BSS()
#define FETCH_S_2() FETCH_S()
#define FETCH_W_2() FETCH_W()
/* with OP_EXT3 (1st & 2nd 16bit) */
#define FETCH_Z_3() FETCH_Z()
#define FETCH_B_3() FETCH_B()
#define FETCH_BB_3() do {a=READ_S(); b=READ_S();} while (0)
#define FETCH_BBB_3() do {a=READ_S(); b=READ_S(); cc=READ_B();} while (0)
#define FETCH_BS_3() do {a=READ_S(); b=READ_S();} while (0)
#define FETCH_BSS_3() FETCH_BSS_1()
#define FETCH_S_3() FETCH_S()
#define FETCH_W_3() FETCH_W()
#endif /* MRUBY_OPCODE_H */

120
vendor/mruby-compiler2/include/mrc_ops.h vendored Normal file
View File

@@ -0,0 +1,120 @@
/* operand types:
+ Z: no operand
+ B: 8bit
+ BB: 8+8bit
+ BBB: 8+8+8bit
+ BS: 8+16bit
+ BSS: 8+16+16bit
+ S: 16bit
+ W: 24bit
*/
/*-----------------------------------------------------------------------
operation code operands semantics
------------------------------------------------------------------------*/
OPCODE(NOP, Z) /* no operation */
OPCODE(MOVE, BB) /* R[a] = R[b] */
OPCODE(LOADL, BB) /* R[a] = Pool[b] */
OPCODE(LOADI, BB) /* R[a] = mrb_int(b) */
OPCODE(LOADINEG, BB) /* R[a] = mrb_int(-b) */
OPCODE(LOADI__1, B) /* R[a] = mrb_int(-1) */
OPCODE(LOADI_0, B) /* R[a] = mrb_int(0) */
OPCODE(LOADI_1, B) /* R[a] = mrb_int(1) */
OPCODE(LOADI_2, B) /* R[a] = mrb_int(2) */
OPCODE(LOADI_3, B) /* R[a] = mrb_int(3) */
OPCODE(LOADI_4, B) /* R[a] = mrb_int(4) */
OPCODE(LOADI_5, B) /* R[a] = mrb_int(5) */
OPCODE(LOADI_6, B) /* R[a] = mrb_int(6) */
OPCODE(LOADI_7, B) /* R[a] = mrb_int(7) */
OPCODE(LOADI16, BS) /* R[a] = mrb_int(b) */
OPCODE(LOADI32, BSS) /* R[a] = mrb_int((b<<16)+c) */
OPCODE(LOADSYM, BB) /* R[a] = Syms[b] */
OPCODE(LOADNIL, B) /* R[a] = nil */
OPCODE(LOADSELF, B) /* R[a] = self */
OPCODE(LOADT, B) /* R[a] = true */
OPCODE(LOADF, B) /* R[a] = false */
OPCODE(GETGV, BB) /* R[a] = getglobal(Syms[b]) */
OPCODE(SETGV, BB) /* setglobal(Syms[b], R[a]) */
OPCODE(GETSV, BB) /* R[a] = Special[Syms[b]] */
OPCODE(SETSV, BB) /* Special[Syms[b]] = R[a] */
OPCODE(GETIV, BB) /* R[a] = ivget(Syms[b]) */
OPCODE(SETIV, BB) /* ivset(Syms[b],R[a]) */
OPCODE(GETCV, BB) /* R[a] = cvget(Syms[b]) */
OPCODE(SETCV, BB) /* cvset(Syms[b],R[a]) */
OPCODE(GETCONST, BB) /* R[a] = constget(Syms[b]) */
OPCODE(SETCONST, BB) /* constset(Syms[b],R[a]) */
OPCODE(GETMCNST, BB) /* R[a] = R[a]::Syms[b] */
OPCODE(SETMCNST, BB) /* R[a+1]::Syms[b] = R[a] */
OPCODE(GETUPVAR, BBB) /* R[a] = uvget(b,c) */
OPCODE(SETUPVAR, BBB) /* uvset(b,c,R[a]) */
OPCODE(GETIDX, B) /* R[a] = R[a][R[a+1]] */
OPCODE(SETIDX, B) /* R[a][R[a+1]] = R[a+2] */
OPCODE(JMP, S) /* pc+=a */
OPCODE(JMPIF, BS) /* if R[a] pc+=b */
OPCODE(JMPNOT, BS) /* if !R[a] pc+=b */
OPCODE(JMPNIL, BS) /* if R[a]==nil pc+=b */
OPCODE(JMPUW, S) /* unwind_and_jump_to(a) */
OPCODE(EXCEPT, B) /* R[a] = exc */
OPCODE(RESCUE, BB) /* R[b] = R[a].isa?(R[b]) */
OPCODE(RAISEIF, B) /* raise(R[a]) if R[a] */
OPCODE(SSEND, BBB) /* R[a] = self.send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..) (c=n|k<<4) */
OPCODE(SSENDB, BBB) /* R[a] = self.send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..,&R[a+n+2k+1]) */
OPCODE(SEND, BBB) /* R[a] = R[a].send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..) (c=n|k<<4) */
OPCODE(SENDB, BBB) /* R[a] = R[a].send(Syms[b],R[a+1]..,R[a+n+1]:R[a+n+2]..,&R[a+n+2k+1]) */
OPCODE(CALL, Z) /* self.call(*, **, &) (But overlay the current call frame; tailcall) */
OPCODE(SUPER, BB) /* R[a] = super(R[a+1],... ,R[a+b+1]) */
OPCODE(ARGARY, BS) /* R[a] = argument array (16=m5:r1:m5:d1:lv4) */
OPCODE(ENTER, W) /* arg setup according to flags (23=m5:o5:r1:m5:k5:d1:b1) */
OPCODE(KEY_P, BB) /* R[a] = kdict.key?(Syms[b]) */
OPCODE(KEYEND, Z) /* raise unless kdict.empty? */
OPCODE(KARG, BB) /* R[a] = kdict[Syms[b]]; kdict.delete(Syms[b]) */
OPCODE(RETURN, B) /* return R[a] (normal) */
OPCODE(RETURN_BLK, B) /* return R[a] (in-block return) */
OPCODE(BREAK, B) /* break R[a] */
OPCODE(BLKPUSH, BS) /* R[a] = block (16=m5:r1:m5:d1:lv4) */
OPCODE(ADD, B) /* R[a] = R[a]+R[a+1] */
OPCODE(ADDI, BB) /* R[a] = R[a]+mrb_int(b) */
OPCODE(SUB, B) /* R[a] = R[a]-R[a+1] */
OPCODE(SUBI, BB) /* R[a] = R[a]-mrb_int(b) */
OPCODE(MUL, B) /* R[a] = R[a]*R[a+1] */
OPCODE(DIV, B) /* R[a] = R[a]/R[a+1] */
OPCODE(EQ, B) /* R[a] = R[a]==R[a+1] */
OPCODE(LT, B) /* R[a] = R[a]<R[a+1] */
OPCODE(LE, B) /* R[a] = R[a]<=R[a+1] */
OPCODE(GT, B) /* R[a] = R[a]>R[a+1] */
OPCODE(GE, B) /* R[a] = R[a]>=R[a+1] */
OPCODE(ARRAY, BB) /* R[a] = ary_new(R[a],R[a+1]..R[a+b]) */
OPCODE(ARRAY2, BBB) /* R[a] = ary_new(R[b],R[b+1]..R[b+c]) */
OPCODE(ARYCAT, B) /* ary_cat(R[a],R[a+1]) */
OPCODE(ARYPUSH, BB) /* ary_push(R[a],R[a+1]..R[a+b]) */
OPCODE(ARYSPLAT, B) /* R[a] = ary_splat(R[a]) */
OPCODE(AREF, BBB) /* R[a] = R[b][c] */
OPCODE(ASET, BBB) /* R[b][c] = R[a] */
OPCODE(APOST, BBB) /* *R[a],R[a+1]..R[a+c] = R[a][b..] */
OPCODE(INTERN, B) /* R[a] = intern(R[a]) */
OPCODE(SYMBOL, BB) /* R[a] = intern(Pool[b]) */
OPCODE(STRING, BB) /* R[a] = str_dup(Pool[b]) */
OPCODE(STRCAT, B) /* str_cat(R[a],R[a+1]) */
OPCODE(HASH, BB) /* R[a] = hash_new(R[a],R[a+1]..R[a+b*2-1]) */
OPCODE(HASHADD, BB) /* hash_push(R[a],R[a+1]..R[a+b*2]) */
OPCODE(HASHCAT, B) /* R[a] = hash_cat(R[a],R[a+1]) */
OPCODE(LAMBDA, BB) /* R[a] = lambda(Irep[b],L_LAMBDA) */
OPCODE(BLOCK, BB) /* R[a] = lambda(Irep[b],L_BLOCK) */
OPCODE(METHOD, BB) /* R[a] = lambda(Irep[b],L_METHOD) */
OPCODE(RANGE_INC, B) /* R[a] = range_new(R[a],R[a+1],FALSE) */
OPCODE(RANGE_EXC, B) /* R[a] = range_new(R[a],R[a+1],TRUE) */
OPCODE(OCLASS, B) /* R[a] = ::Object */
OPCODE(CLASS, BB) /* R[a] = newclass(R[a],Syms[b],R[a+1]) */
OPCODE(MODULE, BB) /* R[a] = newmodule(R[a],Syms[b]) */
OPCODE(EXEC, BB) /* R[a] = blockexec(R[a],Irep[b]) */
OPCODE(DEF, BB) /* R[a].newmethod(Syms[b],R[a+1]); R[a] = Syms[b] */
OPCODE(ALIAS, BB) /* alias_method(target_class,Syms[a],Syms[b]) */
OPCODE(UNDEF, B) /* undef_method(target_class,Syms[a]) */
OPCODE(SCLASS, B) /* R[a] = R[a].singleton_class */
OPCODE(TCLASS, B) /* R[a] = target_class */
OPCODE(DEBUG, BBB) /* print a,b,c */
OPCODE(ERR, B) /* raise(LocalJumpError, Pool[a]) */
OPCODE(EXT1, Z) /* make 1st operand (a) 16bit */
OPCODE(EXT2, Z) /* make 2nd operand (b) 16bit */
OPCODE(EXT3, Z) /* make 1st and 2nd operands 16bit */
OPCODE(STOP, Z) /* stop VM */

View File

@@ -0,0 +1,12 @@
#ifndef MRC_PARSER_UTIL_H
#define MRC_PARSER_UTIL_H
#include "mrc_ccontext.h"
MRC_BEGIN_DECL
const char* mrc_sym_name_len(mrc_ccontext *c, mrc_sym sym, mrc_int *lenp);
MRC_END_DECL
#endif // MRC_PARSER_UTIL_H

View File

@@ -0,0 +1,23 @@
#ifndef MRC_POOL_H
#define MRC_POOL_H
#include <stddef.h>
#include "mrc_ccontext.h"
MRC_BEGIN_DECL
typedef struct mrc_pool {
struct mrc_ccontext *c;
struct mrc_pool_page *pages;
} mrc_pool;
MRC_API mrc_pool *mrc_pool_open(struct mrc_ccontext *c);
MRC_API void mrc_pool_close(mrc_pool *pool);
MRC_API void *mrc_pool_alloc(mrc_pool *pool, size_t len);
MRC_API void *mrc_pool_realloc(mrc_pool *pool, void *p, size_t oldlen, size_t newlen);
MRC_END_DECL
#endif // MRC_POOL_H

View File

@@ -0,0 +1,26 @@
#ifndef MRC_PRESYM_H
#define MRC_PRESYM_H
MRC_BEGIN_DECL
enum mrc_opsym {
#define MRC_OPSYM_2(name, lit, num) MRC_OPSYM_2__##name = num,
#define MRC_SYM_1(name, num) MRC_SYM_1__##name = num,
#define MRC_SYM_2(name, lit, num) MRC_SYM_2__##name = num,
#include "mrc_presym.inc"
#undef MRC_OPSYM_2
#undef MRC_SYM_1
#undef MRC_SYM_2
};
#define MRC_OPSYM_2(name) mrc_sym_offset(MRC_OPSYM_2__##name)
#define MRC_SYM_1(name) mrc_sym_offset(MRC_SYM_1__##name)
#define MRC_SYM_2(name) mrc_sym_offset(MRC_SYM_2__##name)
void mrc_init_presym(pm_constant_pool_t *pool);
mrc_sym mrc_sym_offset(mrc_sym sym);
MRC_END_DECL
#endif // MRC_PRESYM_H

View File

@@ -0,0 +1,33 @@
MRC_OPSYM_2(aref, [], 1)
MRC_OPSYM_2(lshift, <<, 2)
MRC_OPSYM_2(rshift, >>, 3)
MRC_OPSYM_2(mod, %, 4)
MRC_OPSYM_2(and, &, 5)
MRC_OPSYM_2(or, |, 6)
MRC_OPSYM_2(xor, ^, 7)
MRC_OPSYM_2(neg, ~, 8)
MRC_OPSYM_2(pow, **, 9)
MRC_OPSYM_2(add, +, 10)
MRC_OPSYM_2(sub, -, 11)
MRC_OPSYM_2(mul, *, 12)
MRC_OPSYM_2(div, /, 13)
MRC_OPSYM_2(lt, <, 14)
MRC_OPSYM_2(le, <=, 15)
MRC_OPSYM_2(gt, >, 16)
MRC_OPSYM_2(ge, >=, 17)
MRC_OPSYM_2(eq, ==, 18)
MRC_OPSYM_2(aset, []=, 19)
MRC_OPSYM_2(eqq, ===, 20)
MRC_OPSYM_2(tick, `, 21)
MRC_SYM_1(each, 22)
MRC_SYM_1(__case_eqq, 23)
MRC_SYM_1(StandardError, 24)
MRC_SYM_1(call, 25)
MRC_SYM_1(Kernel, 26)
MRC_SYM_1(Regexp, 27)
MRC_SYM_1(compile, 28)
MRC_SYM_1(__ENCODING__, 29)
MRC_SYM_2(nil_p, nil?,30)
MRC_SYM_2(back_ref, $+, 31)
MRC_SYM_2(defined_p, defined?, 32)

View File

@@ -0,0 +1,30 @@
#ifndef MRC_PROC_H
#define MRC_PROC_H
MRC_BEGIN_DECL
#define MRC_OBJECT_HEADER \
struct RClass *c; \
struct RBasic *gcnext; \
enum mrb_vtype tt:8; \
unsigned int gc_color:3; \
unsigned int frozen:1; \
uint32_t flags:20
struct RProc {
MRC_OBJECT_HEADER;
union {
const mrc_irep *irep;
mrb_func_t func;
mrb_sym mid;
} body;
const struct RProc *upper;
union {
struct RClass *target_class;
struct REnv *env;
} e;
};
MRC_END_DECL
#endif // MRC_PROC_H

View File

@@ -0,0 +1,57 @@
/**
** @file mruby/throw.h - mruby exception throwing handler
**
** See Copyright Notice in mruby.h
*/
#ifndef MRC_THROW_H
#define MRC_THROW_H
#if defined(MRC_USE_CXX_ABI) && !defined(__cplusplus)
# error Trying to use C++ exception handling in C code
#endif
#if defined(MRC_USE_CXX_EXCEPTION)
# if defined(__cplusplus)
#define MRC_TRY(buf) try {
#define MRC_CATCH(buf) } catch(mrc_jmpbuf *e) { if (e != (buf)) { throw e; }
#define MRC_END_EXC(buf) }
#define MRC_THROW(buf) throw(buf)
typedef void *mrc_jmpbuf_impl;
# else
# error "need to be compiled with C++ compiler"
# endif /* __cplusplus */
#else
#include <setjmp.h>
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#define MRC_SETJMP _setjmp
#define MRC_LONGJMP _longjmp
#elif defined(__MINGW64__) && defined(__GNUC__) && __GNUC__ >= 4
#define MRC_SETJMP __builtin_setjmp
#define MRC_LONGJMP __builtin_longjmp
#else
#define MRC_SETJMP setjmp
#define MRC_LONGJMP longjmp
#endif
#define MRC_TRY(buf) if (MRC_SETJMP((buf)->impl) == 0) {
#define MRC_CATCH(buf) } else {
#define MRC_END_EXC(buf) }
#define MRC_THROW(buf) MRC_LONGJMP((buf)->impl, 1);
#define mrc_jmpbuf_impl jmp_buf
#endif
struct mrc_jmpbuf {
mrc_jmpbuf_impl impl;
};
#endif /* MRC_THROW_H */

View File

@@ -0,0 +1,9 @@
#ifndef MRUBY_COMPILER_H
#define MRUBY_COMPILER_H
#include "mrc_ccontext.h"
#include "mrc_irep.h"
#include "mrc_compile.h"
#include "mrc_dump.h"
#endif // MRUBY_COMPILER_H

View File

@@ -0,0 +1,73 @@
#ifndef PRISM_CUSTOM_ALLOCATOR_H
#define PRISM_CUSTOM_ALLOCATOR_H
#if defined(MRC_TARGET_MRUBY)
#include "mruby.h"
extern mrb_state *global_mrb;
#define xmalloc(size) mrb_malloc(global_mrb, size)
#define xcalloc(nmemb,size) mrb_calloc(global_mrb, nmemb, size)
#define xrealloc(ptr,size) mrb_realloc(global_mrb, ptr, size)
#define xfree(ptr) mrb_free(global_mrb, ptr)
#define mrc_malloc(c,size) mrb_malloc(c->mrb, size)
#define mrc_calloc(c,nmemb,size) mrb_calloc(c->mrb, nmemb, size)
#define mrc_realloc(c,ptr,size) mrb_realloc(c->mrb, ptr, size)
#define mrc_free(c,ptr) mrb_free(c->mrb, ptr)
#elif defined(MRC_TARGET_MRUBYC)
#include "mrubyc.h"
#if defined(MRBC_ALLOC_LIBC)
#define xmalloc(size) malloc(size)
#define xcalloc(nmemb,size) calloc(nmemb, size)
#define xrealloc(nmemb,size) realloc(nmemb, size)
#define xfree(ptr) free(ptr)
#define mrc_malloc(c,size) malloc(size)
#define mrc_calloc(c,nmemb,size) calloc(nmemb, size)
#define mrc_realloc(c,ptr,size) realloc(ptr, size)
#define mrc_free(c,ptr) free(ptr)
#else
#define xmalloc(size) mrbc_raw_alloc(size)
#define xcalloc(nmemb,size) mrbc_raw_calloc(nmemb, size)
#define xrealloc(nmemb,size) mrc_raw_realloc(nmemb, size)
#define xfree(ptr) mrc_raw_free(ptr)
#define mrc_malloc(c,size) mrbc_raw_alloc(size)
#define mrc_calloc(c,nmemb,size) mrbc_raw_calloc(nmemb, size)
#define mrc_realloc(c,ptr,size) mrc_raw_realloc(ptr, size)
#define mrc_free(c,ptr) mrc_raw_free(ptr)
static inline void mrc_raw_free(void *ptr)
{
/* mrbc_raw_free() warns when ptr=NULL but it should be allowed in C99 */
if (ptr == NULL) return;
mrbc_raw_free(ptr);
}
static inline void*
mrc_raw_realloc(void *ptr, unsigned int size)
{
/* mrbc_raw_realloc() fails when ptr=NULL but it should be allowed in C99 */
if (ptr == NULL) {
return mrbc_raw_alloc(size);
} else {
return mrbc_raw_realloc(ptr, size);
}
}
#endif
#else
// for picorbc
#define mrc_malloc(c,size) malloc(size)
#define mrc_calloc(c,nmemb,size) calloc(nmemb, size)
#define mrc_realloc(c,ptr,size) realloc(ptr, size)
#define mrc_free(c,ptr) free(ptr)
#define xmalloc(size) malloc(size)
#define xcalloc(nmemb,size) calloc(nmemb, size)
#define xrealloc(ptr,size) realloc(ptr, size)
#define xfree(ptr) free(ptr)
#endif
#endif

View File

@@ -0,0 +1,684 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [1.4.0] - 2025-03-18
### Added
- Support `3.5` as a version option.
- Many, many compatibility fixes for the parser translation layer.
- Handle escapes in named capture names.
- The `freeze` option is added to the various `Prism::` APIs to deeply freeze the AST.
- Properly support `it` for the parser and ruby_parser translation layers.
- Track the `then` keyword on `rescue` nodes.
- Add a `multiple_statements?` flag to parentheses nodes to support desired `defined?` behavior.
### Changed
- The strings used in the AST are now frozen.
- Fixed handling escaped characters after control sequences in character literals.
- Fix reading off the end of an unterminated global variable.
- Raise a syntax error for defining `[]=` with endless method syntax.
- Increase value of `PRISM_DEPTH_MAXIMUM` to `10000`.
- Freeze `Prism::VERSION`.
- Fix up rescue modifier precedence.
## [1.3.0] - 2024-12-21
### Added
- Introduce `Prism::StringQuery`.
- Introduce `Prism::Relocation`.
- Track `do` keyword for `WhileNode` and `UntilNode`.
- Change the way the gem is built to rely on `mkmf` instead of `make`.
- Lots more documentation on node fields.
### Changed
- Properly add an error for `def @foo; end`.
- Properly add an error for `foo(**, *)`.
- Fix up regression in string parsing in `RubyParser` translation.
- Reject invalid dot method call after match expression.
- Reject invalid operator after match expression.
- Fix up %-literals delimited by newlines.
- Properly add an error for `-> { _1; -> { _1 } }`.
- Reject blocks and keywords in index writes.
## [1.2.0] - 2024-10-10
### Added
- Introduce `Prism::CodeUnitsCache`.
### Changed
- Properly handle lexing global variables that begin with `$-`.
- Properly reject invalid multi writes within parentheses.
- Fix unary `*` binding power.
- Set `contains_keywords` flag for implicit `gets` calls when `-p` is used.
- Properly reject invalid non-associative operator patterns.
- Do not warn about unused variables declared on negative lines.
## [1.1.0] - 2024-10-02
### Added
- Explicitly type each child node field in the Ruby API.
- Add the `main_script` option to the parse APIs, which controls whether or not shebangs are considered.
- Add the `partial_script` options to the parse APIs, which controls whether or not jumps that would otherwise be considered invalid are allowed. This is useful for parsing things like ERB sources, where you know it will be evaluated in a different context. Note that this functionality is replacing the previous idiom of passing in a list of scopes to indicate an `eval` context, because that behavior has changed upstream in `ruby/ruby`.
- Add `ArgumentsNode#contains_multiple_splats?`.
- Add `ArgumentsNode#contains_forwarding?`.
- Accept all valid Ruby versions for the `version` option on parse APIs.
- Accept version shorthands like `"3.3"` and `"3.4"` for the `version` option on parse APIs.
- Support a max depth to protect against malicious payloads without hitting the stack limit.
### Changed
- Fix some token incompatibilities in the `parser` translation.
- Fix up parsing tempfiles on Windows.
- Fix up handling UTF-8 characters in file paths on Windows.
- Do not warn for a `\r` at the end of a shebang on Windows.
- Properly handle erroring for parsing a directory on Windows.
- When a numbered reference is out of range, warn instead of raise.
- Allow returns in default parameter values.
- Reject many more invalid syntax patterns.
## [1.0.0] - 2024-08-28
### Added
- Add `Node#breadth_first_search`.
- Add `Node#node_id`.
- Add `ArgumentsNode#contains_splat?`.
- Passing the special value `false` for the `encoding` option tells Prism to ignore magic encoding comments.
- Expose flags on every node type (allows checking static literal and newline).
- Implement mismatched indentation warning.
- Add C API for receiving a callback when parsing shebangs with additional flags.
### Changed
- **BREAKING**: Some fields are renamed that had illogical names. The previous names all now emit deprecation warnings.
- `CaseMatchNode#consequent` was renamed to `CaseMatchNode#else_clause`
- `CaseNode#consequent` was renamed to `CaseNode#else_clause`
- `IfNode#consequent` was renamed to `IfNode#subsequent`
- `RescueNode#consequent` was renamed to `RescueNode#subsequent`
- `UnlessNode#consequent` was renamed to `UnlessNode#else_clause`
- Block exits are now allowed in loop predicates (e.g., `while _ && break do end`).
- Multi-writes are now disallowed when not at the statement level.
- Ensure that range operators are non-associative.
- (JavaScript) Correctly deserialize encoded strings.
- Properly support parsing regular expressions in extended mode.
- Use gmake on FreeBSD.
- Parsing streams now handles NUL bytes in the middle of the stream.
- Properly detect invalid returns.
## [0.30.0] - 2024-06-07
### Added
- More correctly raise mixed encoding errors.
- Implement ambiguous binary operator warning.
- Fix up regexp escapes with control and meta characters.
- Fix up support for the `it` implicit local variable.
- Heredoc identifiers now properly disallow CLRF.
- Errors added for void value expressions in begin clauses.
- Many updates to more closely match the `parser` gem in parser translation.
- Many errors added for invalid regular expressions.
### Changed
- Handle parser translation missing the `parser` gem.
- Handle ruby_parser translation missing the `ruby_parser` gem.
- Various error messages have been updated to more closely match CRuby.
- `RationalNode` now has a `numerator` and `denominator` field instead of a `numeric` field. For the Ruby API we provide a `RationalNode#numeric` method for backwards-compatibility.
## [0.29.0] - 2024-05-10
### Added
- Added `Prism::CallNode#full_message_loc`, which gives the location including the `=` if there is one.
- A warning for when `# shareable_constant_value` is not used on its own line.
- An error for invalid implicit local variable writes.
- Implicit hash patterns in array patterns are disallowed.
- We now validate that Unicode escape sequences are not surrogates.
### Changed
- All fields named `operator` have been renamed to `binary_operator` for `*OperatorWriteNode` nodes. This is to make it easier to provide C++ support. In the Ruby API, the old fields are aliased to the new fields with a deprecation warning.
- Many updated error messages to more closely match CRuby.
- We ensure keyword parameters do not end in `!` or `?`.
- Fixed some escaping in string literals with control sequences and hex escapes.
- Fix a bug with RBS types when used outside the `ruby/prism` codebase.
## [0.28.0] - 2024-05-03
### Added
- Nested hashes will now warn for duplicated keys, as in: `{ foo: 1, **{ foo: 2 } }`.
- `Prism::ReturnNode` now has a flag on it to indicate if it is redundant.
- `Prism::Location#slice_lines` and `Prism::Node#slice_lines` are now provided to slice the source code of a node including the content before the node on the same line that it starts on and the content after the node on the same line that it ends on.
- Symbols with invalid byte sequences now give errors.
- You can now pass `"3.3.1"` to the `version:` parameter on all `Prism.*` APIs.
- `Prism::Source#lines`, `Prism::Location#source_lines`, `Prism::Node#source_lines`, and `Prism::Node#script_lines` are now provided, which will all return the source code of the source as an array of strings.
- `Prism::ASCIISource` is now provided, which is a subclass of `Prism::Source` but specialized to increase performance when the source is entirely ASCII.
- Prism now provides errors when parsing Ruby 3.4+ syntax for index expressions with keywords or blocks.
- Prism now provides an error when `**nil` is used after other keyword parameters.
- Prism now provides errors when safe navigation is used in call target expressions, e.g., `foo&.bar, = 1`.
- `Prism::Node#tunnel` is now provided, which returns an array of nodes starting at the current node that contain a given line and column.
### Changed
- All translation layers now assume an eval context, which means they will not return errors for invalid jumps like `yield`.
- `Prism::Node#inspect` now uses a queue instead of recursion to avoid stack overflows.
- Prism now more closely mirrors CRuby interpolation semantics, which means you could potentially have a static literal string that directly interpolates another static literal string.
- The shipped RBI sorbet types no longer use generics.
- `Prism::ConstantPathNode#child` and `Prism::ConstantTargetNode#child` are now deprecated, replaced by two new fields on these nodes: `name` and `name_loc`.
## [0.27.0] - 2024-04-23
### Added
- Implemented `===` for each of the nodes, which will check if equality but ignore the specific ranges of locations.
### Changed
- Fix translation of `ItParametersNode` for parser translation.
- Fix translation of `dstr` for ruby_parser translation.
- Do not allow omitted hash values whose keys end with `!` or `?`.
- Split up `Prism::ParseResult` into `Prism::Result` with subclasses `Prism::ParseResult`, `Prism::LexResult`, `Prism::ParseLexResult`, and `Prism::LexCompat::Result`.
- Change reflection classes to have only a single `IntegerField` class and rename `DoubleField` to `FloatField`.
- Fall back to default `AR` and `CC` in `Makefile`.
- Use GC-able symbols for the syntax tree to avoid adding to the global symbol table.
- Fix a bug with karatsuba_multiply that would result in a stack overflow.
- Fix parser translation when looking for tokens with `srange_find`.
## [0.26.0] - 2024-04-18
### Added
- Add `Prism::Node::fields`, which returns a list of `Prism::Reflection::Field` objects representing the fields of the node class. This is useful in metaprogramming contexts.
- `Prism::Location#chop`, for removing the last byte from a location.
- The void statement warning is now implemented.
- The unreachable statement warning is now implemented.
- A syntax error has been added for block arguments on yields, e.g., `yield(&foo)`.
### Changed
- Better fidelity to `parser` when translating heredocs with interpolation.
- Fixed `RBI` and `RBS` types for `Prism::parse_*` signatures.
- Remove some incorrect warnings about unused local variables.
- More closely match CRuby error messages for global variables.
- Fix an issue with `parser` translation when line continuations are found in string literals.
## [0.25.0] - 2024-04-05
### Added
- `Prism::Translation::Ripper` is now able to mirror all of the Ripper APIs.
- `Prism::Location#leading_comments` and `Prism::Location#trailing_comments` is added.
- `Prism::Comment#slice` is added.
- Warn for writing literal values in conditional predicates.
- Check for `_POSIX_MAPPED_FILES` before using `mmap`.
- `Prism::ItParametersNode` is added, to support `-> { it }`.
- Parse integer and float literal values onto the tree.
- Warn on duplicated hash keys and duplicated when clauses.
- Ship much improved `RBI` and `RBS` types.
- Support for the `-p`, `-n`, `-a`, and `-l` command line switches.
- Warn on integer literals in flip-flops.
- Support BSD make.
- Add `Prism::WhenNode#then_keyword_loc`.
- Support custom allocation functions through the `PRISM_XALLOCATOR` define.
- Warn for certain keywrods at the end of the line.
- Provide `pm_visit_node`, a C visitor API.
- `Prism::parse_stream` is added, which works for any Ruby `IO` object.
- Provide flags for regular expression literals for their derived encoding.
- Provide flags for whether or not an interpolated string literal is frozen.
- Add `Prism::StringNode.mutable?` for when a string is explicitly mutable, to support delineating chilled strings.
- Warn for incorrect character literal syntax.
- Warn for chained comparison operators.
- Warn for `**` interpreted as an argument prefix.
- Warn for `&` interpreted as an argument prefix.
- `Prism::ShareableConstantNode` added to support ractors.
- Warn for frozen string literals found after tokens.
- Support `PRISM_BUILD_MINIMAL` to provide only the minimal necessary functionality to reduce the binary size.
- Handle CLRF inside heredocs, strings, and regular expressions.
- Mark inner strings in interpolated strings as frozen.
- Support the `-x` command line switch.
- Error messages now much more closely mirror CRuby.
- Provide syntax errors for invalid block exits (`break`, `next`, `retry`, and `yield`).
- Warn on unused local variables.
- Do not syntax error on default parameter values that only write to the parameter.
### Changed
- Many improvements to the compatibility with the `whitequark/parser` translation.
- Accept newlines before pattern terminators `)` or `]`.
- `Prism::Node#start_offset` and `Prism::Node#end_offset` are now much more efficient.
- Read files using `fread` instead of `mmap` when we're going to keep around the source through the Ruby API.
- Fix `Sexp#line_max` setting in the `seattlerb/ruby_parser` translation layer.
- Allow spaces before the encoding comment.
## [0.24.0] - 2024-02-15
### Added
- More support for `Prism::Translation::Ripper` is added.
- Support multiple versions for `Prism::Translation::Parser`.
- Improved memory usage in the FFI backend.
- Very large speed and memory improvements for creating the Ruby AST.
### Changed
- Fix location for empty symbol in hash key.
- Fix parsing a rescue modifier on the value of an assignment when the LHS is a method call with arguments and no parentheses.
## [0.23.0] - 2024-02-14
### Added
- More support for `Prism::RipperCompat` is added.
- A significantly faster offset cache for `Prism::Translation::Parser` is added for files with multibyte characters.
- `Prism::Translation::RubyParser` is added.
- `Prism::ConstantPathTarget#full_name` is added.
- `version: "3.4.0"` is added as an option that is an alias for `version: "latest"`.
- Four new APIs are added to `Prism::Location`:
- `Prism::Location#start_code_units_offset`
- `Prism::Location#end_code_units_offset`
- `Prism::Location#start_code_units_column`
- `Prism::Location#end_code_units_column`
- Invalid multibyte characters are now validated within strings, lists, and heredocs.
### Changed
- When defining `def !@`, the `name_loc` was previously only pointing to `!`, but now includes the `@`. The `name` is the same.
- `Prism::RipperCompat` has been moved to `Prism::Translation::Ripper`.
- Many of the error messages that prism produces have been changed to match the error messages that CRuby produces.
## [0.22.0] - 2024-02-07
### Added
- More support for `Prism::RipperCompat` is added.
- Support for Ruby 2.7 has been added, and the minimum Ruby requirement has been lowered to 2.7.
### Changed
- The error for an invalid source encoding has a new `:argument` level to indicate it raises an argument error.
- `BeginNode` nodes that are used when a class, singleton class, module, method definition, or block have an inline `rescue`/`ensure`/`else` now have their opening locations set to the beginning of the respective keyword.
- Improved error messages for invalid characters.
- `Prism.parse_file` and similar APIs will raise more appropriate errors when the file does not exist or cannot be mapped.
- Correctly handle the `recover` parameter for `Prism::Translation::Parser`.
## [0.21.0] - 2024-02-05
### Added
- Add the `pm_constant_pool_find` API for finding a constant.
### Changed
- Fixes for `Prism::Translation::Parser`.
- Ensure all errors flow through `parser.diagnostics.process`.
- Fix the find pattern node.
- Fix block forwarding with `NumberedParametersNode`.
- Ensure we can parse strings with invalid bytes for the encoding.
- Fix hash pairs in pattern matching.
- Properly reject operator writes on operator calls, e.g., `a.+ -= b`.
- Fix multi-byte escapes.
- Handle missing body in `begin` within the receiver of a method call.
## [0.20.0] - 2024-02-01
### Added
- String literal hash keys are now marked as frozen as static literal.
- `IndexTargetNode` now always has the `ATTRIBUTE_WRITE` flag.
- `Call*Node` nodes now have an `IGNORE_VISIBILITY` flag.
- We now support the `it` default parameter.
- Errors and warnings now have levels associated with them.
- Symbols now have correct encoding flags.
- We have now merged `parser-prism` in, which provides translation to the `whitequark/parser` AST.
- We now emit errors for invalid method definition receivers.
### Changed
- We now emit errors on invalid pinned local variables.
- When passed scopes, it is now assumed that the innermost scope is the current binding.
- We now provide better error recovery for non terminated heredocs.
- Fix for `RationalNode#value` for non-decimal integers.
- Unary symbols `!@` and `~@` now unescape to `!` and `~`, respectively.
- `frozen_string_literal: false` now works properly.
### Removed
- We've removed the `locals_body_index` field.
- We've removed the `verbose` option on the various parse APIs. Warnings are now always emitted with their associated level so that consumers can decide how to handle them.
## [0.19.0] - 2023-12-14
### Added
- `ArrayNode` now has a `contains_splat?` flag if it has a splatted element in it.
- All of the remaining encodings have been implemented.
- Allow forwarding `&` in a method that has a `...` parameter.
- Many statements that are found in non-statement positions are being properly rejected now.
- Void values are now properly checked.
- Referencing a parameter in its own default value is now properly rejected.
- `DATA`/`__END__` is now parsed as its own field on parse result (`data_loc`) as opposed to as a comment.
- Blank `*` now properly forwards into arrays.
- `ImplicitRestNode` is introduced to represent the implicit rest of a destructure.
- We now support negative start lines.
- `StringNode#heredoc?`, `InterpolatedStringNode#heredoc?`, `XStringNode#heredoc?`, and `InterpolatedXStringNode#heredoc?` are introduced.
- `NumberedParametersNode` is introduced to represent the implicit set of parameters when numbered parameters are used.
- `Prism::parse_success?` and `Prism::parse_failure?` are introduced to bypass reifying the AST.
- We now emit a warning for constant assignments in method definitions.
- We now provide flags on strings and xstrings to indicate the correct encoding.
- The hash pattern `rest` field now more accurately parses `**` and `**nil`.
- The equality operators are now properly parsed as non-associative.
### Changed
- **BREAKING**: Many fields have changed positions within their nodes. This impacts the C API and the Ruby API if you are manually creating nodes through the initializer.
- **BREAKING**: Almost all of the error messages have been updated to begin with lowercase characters to match ruby/spec.
- Unterminated strings with only plain content are now always `StringNode` as opposed to `InterpolatedStringNode`
- **BREAKING**: Call node has been split up when it is in the target position into `CallTargetNode` and `IndexTargetNode`.
## [0.18.0] - 2023-11-21
### Added
- The `ParametersNode#signature` method is added, which returns the same thing as `Method#parameters`.
- Visitor functionality has been added to the JavaScript API.
- The `Node#to_dot` API has been added to convert syntax trees to Graphviz digraphs.
- `IfNode` and `UnlessNode` now have a `then_keyword_loc` field.
- Many more encodings are now supported.
- Some new `Location` APIs have been added for dealing with characters instead of bytes, which are: `start_character_offset`, `end_character_offset`, `start_character_column`, and `end_character_column`.
- A warning has been added for when `END {}` is used within a method.
- `ConstantPathNode#full_name{,_parts}` will now raise an error if the receiver of the constant path is not itself a constant.
- The `in` keyword and the `=>` operator now respect non-associativity.
- The `..` and `...` operators now properly respect non-associativity.
### Changed
- Previously `...` in blocks was accepted, but it is now properly rejected.
- **BREAKING**: `librubyparser.*` has been renamed to `libprism.*` in the C API.
- We now properly reject floats with exponent and rational suffixes.
- We now properly reject void value expressions.
- **BREAKING**: The `--disable-static` option has been removed from the C extension.
- The rescue modifier keyword is now properly parsed in terms of precedence.
- We now properly reject defining a numbered parameter method.
- **BREAKING**: `MatchWriteNode` now has a list of `targets`, which are local variable target nodes. This is instead of `locals` which was a constant list. This is to support writing to local variables outside the current scope. It has the added benefit of providing location information for the local variable targets.
- **BREAKING**: `CaseNode` has been split into `CaseNode` and `CaseMatchNode`, the latter is used for `case ... in` expressions.
- **BREAKING**: `StringConcatNode` has been removed in favor of using `InterpolatedStringNode` as a list.
## [0.17.1] - 2023-11-03
### Changed
- Do not use constant nesting in RBI files.
## [0.17.0] - 2023-11-03
### Added
- We now properly support forwarding arguments into arrays, like `def foo(*) = [*]`.
- We now have much better documentation for the C and Ruby APIs.
- We now properly provide an error message when attempting to assign to numbered parameters from within regular expression named capture groups, as in `/(?<_1>)/ =~ ""`.
### Changed
- **BREAKING**: `KeywordParameterNode` is split into `OptionalKeywordParameterNode` and `RequiredKeywordParameterNode`. `RequiredKeywordParameterNode` has no `value` field.
- **BREAKING**: Most of the `Prism::` APIs now accept a bunch of keyword options. The options we now support are: `filepath`, `encoding`, `line`, `frozen_string_literal`, `verbose`, and `scopes`. See [the pull request](https://github.com/ruby/prism/pull/1763) for more details.
- **BREAKING**: Comments are now split into three different classes instead of a single class, and the `type` field has been removed. They are: `InlineComment`, `EmbDocComment`, and `DATAComment`.
## [0.16.0] - 2023-10-30
### Added
- `InterpolatedMatchLastLineNode#options` and `MatchLastLineNode#options` are added, which are the same methods as are exposed on `InterpolatedRegularExpressionNode` and `RegularExpressionNode`.
- The project can now be compiled with `wasi-sdk` to expose a WebAssembly interface.
- `ArgumentsNode#keyword_splat?` is added to indicate if the arguments node has a keyword splat.
- The C API `pm_prettyprint` has a much improved output which lines up closely with `Node#inspect`.
- Prism now ships with `RBS` and `RBI` type signatures (in the `/sig` and `/rbi` directories, respectively).
- `Prism::parse_comments` and `Prism::parse_file_comments` APIs are added to extract only the comments from the source code.
### Changed
- **BREAKING**: `Multi{Target,Write}Node#targets` is split up now into `lefts`, `rest`, and `rights`. This is to avoid having to scan the list in the case that there are splat nodes.
- Some bugs are fixed on `Multi{Target,Write}Node` accidentally creating additional nesting when not necessary.
- **BREAKING**: `RequiredDestructuredParameterNode` has been removed in favor of using `MultiTargetNode` in those places.
- **BREAKING**: `HashPatternNode#assocs` has been renamed to `HashPatternNode#elements`. `HashPatternNode#kwrest` has been renamed to `HashPatternNode#rest`.
## [0.15.1] - 2023-10-18
### Changed
- Fix compilation warning on assigning to bitfield.
## [0.15.0] - 2023-10-18
### Added
- `BackReferenceReadNode#name` is now provided.
- `Index{Operator,And,Or}WriteNode` are introduced, split out from `Call{Operator,And,Or}WriteNode` when the method is `[]`.
### Changed
- Ensure `PM_NODE_FLAG_COMMON_MASK` into a constant expression to fix compile errors.
- `super(&arg)` is now fixed.
- Ensure the last encoding flag on regular expressions wins.
- Fix the common whitespace calculation when embedded expressions begin on a line.
- Capture groups in regular expressions now scan the unescaped version to get the correct local variables.
- `*` and `&` are added to the local table when `...` is found in the parameters of a method definition.
## [0.14.0] - 2023-10-13
### Added
- Syntax errors are added for invalid lambda local semicolon placement.
- Lambda locals are now checked for duplicate names.
- Destructured parameters are now checked for duplicate names.
- `Constant{Read,Path,PathTarget}Node#full_name` and `Constant{Read,Path,PathTarget}Node#full_name_parts` are added to walk constant paths for you to find the full name of the constant.
- Syntax errors are added when assigning to a numbered parameter.
- `Node::type` is added, which matches the `Node#type` API.
- Magic comments are now parsed as part of the parsing process and a new field is added in the form of `ParseResult#magic_comments` to access them.
### Changed
- **BREAKING**: `Call*Node#name` methods now return symbols instead of strings.
- **BREAKING**: For loops now have their index value considered as part of the body, so depths of local variable assignments will be increased by 1.
- Tilde heredocs now split up their lines into multiple string nodes to make them easier to dedent.
## [0.13.0] - 2023-09-29
### Added
- `BEGIN {}` blocks are only allowed at the top-level, and will now provide a syntax error if they are not.
- Numbered parameters are not allowed in block parameters, and will now provide a syntax error if they are.
- Many more Ruby modules and classes are now documented. Also, many have been moved into their own files and autoloaded so that initial boot time of the gem is much faster.
- `PM_TOKEN_METHOD_NAME` is introduced, used to indicate an identifier that if definitely a method name because it has an `!` or `?` at the end.
- In the C API, arrays, assocs, and hashes now can have the `PM_NODE_FLAG_STATIC_LITERAL` flag attached if they can be compiled statically. This is used in CRuby, for example, to determine if a `duphash`/`duparray` instruction can be used as opposed to a `newhash`/`newarray`.
- `Node#type` is introduced, which returns a symbol representing the type of the node. This is useful for case comparisons when you have to compare against multiple types.
### Changed
- **BREAKING**: Everything has been renamed to `prism` instead of `yarp`. The `yp_`/`YP_` prefix in the C API has been changed to `pm_`/`PM_`. For the most part, everything should be find/replaceable.
- **BREAKING**: `BlockArgumentNode` nodes now go into the `block` field on `CallNode` nodes, in addition to the `BlockNode` nodes that used to be there. Hopefully this makes it more consistent to compile/deal with in general, but it does mean it can be a surprising breaking change.
- Escaped whitespace in `%w` lists is now properly unescaped.
- `Node#pretty_print` now respects pretty print indentation.
- `Dispatcher` was previously firing `_leave` events in the incorrect order. This has now been fixed.
- **BREAKING**: `Visitor` has now been split into `Visitor` and `Compiler`. The visitor visits nodes but doesn't return anything from the visit methods. It is suitable for taking action based on the tree, but not manipulating the tree itself. The `Compiler` visits nodes and returns the computed value up the tree. It is suitable for compiling the tree into another format. As such, `MutationVisitor` has been renamed to `MutationCompiler`.
## [0.12.0] - 2023-09-15
### Added
- `RegularExpressionNode#options` and `InterpolatedRegularExpressionNode#options` are now provided. These return integers that match up to the `Regexp#options` API.
- Greatly improved `Node#inspect` and `Node#pretty_print` APIs.
- `MatchLastLineNode` and `InterpolatedMatchLastLineNode` are introduced to represent using a regular expression as the predicate of an `if` or `unless` statement.
- `IntegerNode` now has a base flag on it.
- Heredocs that were previously `InterpolatedStringNode` and `InterpolatedXStringNode` nodes without any actual interpolation are now `StringNode` and `XStringNode`, respectively.
- `StringNode` now has a `frozen?` flag on it, which respects the `frozen_string_literal` magic comment.
- Numbered parameters are now supported, and are properly represented using `LocalVariableReadNode` nodes.
- `ImplicitNode` is introduced, which wraps implicit calls, local variable reads, or constant reads in omitted hash values.
- `YARP::Dispatcher` is introduced, which provides a way for multiple objects to listen for certain events on the AST while it is being walked. This is effectively a way to implement a more efficient visitor pattern when you have many different uses for the AST.
### Changed
- **BREAKING**: Flags fields are now marked as private, to ensure we can change their implementation under the hood. Actually querying should be through the accessor methods.
- **BREAKING**: `AliasNode` is now split into `AliasMethodNode` and `AliasGlobalVariableNode`.
- Method definitions on local variables is now correctly handled.
- Unary minus precedence has been fixed.
- Concatenating character literals with string literals is now fixed.
- Many more invalid syntaxes are now properly rejected.
- **BREAKING**: Comments now no longer include their trailing newline.
## [0.11.0] - 2023-09-08
### Added
- `Node#inspect` is much improved.
- `YARP::Pattern` is introduced, which can construct procs to match against nodes.
- `BlockLocalVariableNode` is introduced to take the place of the locations array on `BlockParametersNode`.
- `ParseResult#attach_comments!` is now provided to attach comments to locations in the tree.
- `MultiTargetNode` is introduced as the target of multi writes and for loops.
- `Node#comment_targets` is introduced to return the list of objects that can have attached comments.
### Changed
- **BREAKING**: `GlobalVariable*Node#name` now returns a symbol.
- **BREAKING**: `Constant*Node#name` now returns a symbol.
- **BREAKING**: `BlockParameterNode`, `KeywordParameterNode`, `KeywordRestParameterNode`, `RestParameterNode`, `DefNode` all have their `name` methods returning symbols now.
- **BREAKING**: `ClassNode#name` and `ModuleNode#name` now return symbols.
- **BREAKING**: `Location#end_column` is now exclusive instead of inclusive.
- `Location#slice` now returns a properly encoded string.
- `CallNode#operator_loc` is now `CallNode#call_operator_loc`.
- `CallOperatorAndWriteNode` is renamed to `CallAndWriteNode` and its structure has changed.
- `CallOperatorOrWriteNode` is renamed to `CallOrWriteNode` and its structure has changed.
## [0.10.0] - 2023-09-01
### Added
- `InstanceVariable*Node` and `ClassVariable*Node` objects now have their `name` returning a Symbol. This is because they are now part of the constant pool.
- `NumberedReferenceReadNode` now has a `number` field, which returns an Integer.
### Changed
- **BREAKING**: Various `operator_id` and `constant_id` fields have been renamed to `operator` and `name`, respectively. See [09d0a144](https://github.com/ruby/yarp/commit/09d0a144dfd519c5b5f96f0b6ee95d256e2cb1a6) for details.
- `%w`, `%W`, `%i`, `%I`, `%q`, and `%Q` literals can now span around the contents of a heredoc.
- **BREAKING**: All of the public C APIs that accept the source string now accept `const uint8_t *` as opposed to `const char *`.
## [0.9.0] - 2023-08-25
### Added
- Regular expressions can now be bound by `\n`, `\r`, and a combination of `\r\n`.
- Strings delimited by `%`, `%q`, and `%Q` can now be bound by `\n`, `\r`, and a combination of `\r\n`.
- `IntegerNode#value` now returns the value of the integer as a Ruby `Integer`.
- `FloatNode#value` now returns the value of the float as a Ruby `Float`.
- `RationalNode#value` now returns the value of the rational as a Ruby `Rational`.
- `ImaginaryNode#value` now returns the value of the imaginary as a Ruby `Complex`.
- `ClassNode#name` is now a string that returns the name of just the class, without the namespace.
- `ModuleNode#name` is now a string that returns the name of just the module, without the namespace.
- Regular expressions and strings found after a heredoc declaration but before the heredoc body are now parsed correctly.
- The serialization API now supports shared strings, which should help reduce the size of the serialized AST.
- `*Node#copy` is introduced, which returns a copy of the node with the given overrides.
- `Location#copy` is introduced, which returns a copy of the location with the given overrides.
- `DesugarVisitor` is introduced, which provides a simpler AST for use in tools that want to process fewer node types.
- `{ClassVariable,Constant,ConstantPath,GlobalVariable,InstanceVariable,LocalVariable}TargetNode` are introduced. These nodes represent the target of writes in locations where a value cannot be provided, like a multi write or a rescue reference.
- `UntilNode#closing_loc` and `WhileNode#closing_loc` are now provided.
- `Location#join` is now provided, which joins two locations together.
- `YARP::parse_lex` and `YARP::parse_lex_file` are introduced to parse and lex in one result.
### Changed
- When there is a magic encoding comment, the encoding of the first token's source string is now properly reencoded.
- Constants followed by unary `&` are now properly parsed as a call with a passed block argument.
- Escaping multi-byte characters in a string literal will now properly escape the entire character.
- `YARP.lex_compat` now has more accurate behavior when a byte-order mark is present in the file.
- **BREAKING**: `AndWriteNode`, `OrWriteNode`, and `OperatorWriteNode` have been split back up into their `0.7.0` versions.
- We now properly support spaces between the `encoding` and `=`/`:` in a magic encoding comment.
- We now properly parse `-> foo: bar do end`.
## [0.8.0] - 2023-08-18
### Added
- Some performance improvements when converting from the C AST to the Ruby AST.
- Two rust crates have been added: `yarp-sys` and `yarp`. They are as yet unpublished.
### Changed
- Escaped newlines in strings and heredocs are now handled more correctly.
- Dedenting heredocs that result in empty string nodes will now drop those string nodes from the list.
- Beginless and endless ranges in conditional expressions now properly form a flip flop node.
- `%` at the end of files no longer crashes.
- Location information has been corrected for `if/elsif` chains that have no `else`.
- `__END__` at the very end of the file was previously parsed as an identifier, but is now correct.
- **BREAKING**: Nodes that reference `&&=`, `||=`, and other writing operators have been consolidated. Previously, they were separate individual nodes. Now they are a tree with the target being the left-hand side and the value being the right-hand side with a joining `AndWriteNode`, `OrWriteNode`, or `OperatorWriteNode` in the middle. This impacts all of the nodes that match this pattern: `{ClassVariable,Constant,ConstantPath,GlobalVariable,InstanceVariable,LocalVariable}Operator{And,Or,}WriteNode`.
- **BREAKING**: `BlockParametersNode`, `ClassNode`, `DefNode`, `LambdaNode`, `ModuleNode`, `ParenthesesNode`, and `SingletonClassNode` have had their `statements` field renamed to `body` to give a hint that it might not be a `StatementsNode` (it could also be a `BeginNode`).
## [0.7.0] - 2023-08-14
### Added
- We now have an explicit `FlipFlopNode`. It has the same flags as `RangeNode`.
- We now have a syntax error when implicit and explicit blocks are passed to a method call.
- `Node#slice` is now implemented, for retrieving the slice of the source code corresponding to a node.
- We now support the `utf8-mac` encoding.
- Predicate methods have been added for nodes that have flags. For example `CallNode#safe_navigation?` and `RangeNode#exclude_end?`.
- The gem now functions on JRuby and TruffleRuby, thanks to a new FFI backend.
- Comments are now part of the serialization API.
### Changed
- Autotools has been removed from the build system, so when the gem is installed it will no longer need to go through a configure step.
- The AST for `foo = *bar` has changed to have an explicit array on the right hand side, rather than a splat node. This is more consistent with how other parsers handle this.
- **BREAKING**: `RangeNodeFlags` has been renamed to `RangeFlags`.
- Unary minus on number literals is now parsed as part of the literal, rather than a call to a unary operator. This is more consistent with how other parsers handle this.
## [0.6.0] - 2023-08-09
### Added
- 🎉 Initial release! 🎉
[unreleased]: https://github.com/ruby/prism/compare/v1.4.0...HEAD
[1.4.0]: https://github.com/ruby/prism/compare/v1.3.0...v1.4.0
[1.3.0]: https://github.com/ruby/prism/compare/v1.2.0...v1.3.0
[1.2.0]: https://github.com/ruby/prism/compare/v1.1.0...v1.2.0
[1.1.0]: https://github.com/ruby/prism/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/ruby/prism/compare/v0.30.0...v1.0.0
[0.30.0]: https://github.com/ruby/prism/compare/v0.29.0...v0.30.0
[0.29.0]: https://github.com/ruby/prism/compare/v0.28.0...v0.29.0
[0.28.0]: https://github.com/ruby/prism/compare/v0.27.0...v0.28.0
[0.27.0]: https://github.com/ruby/prism/compare/v0.26.0...v0.27.0
[0.26.0]: https://github.com/ruby/prism/compare/v0.25.0...v0.26.0
[0.25.0]: https://github.com/ruby/prism/compare/v0.24.0...v0.25.0
[0.24.0]: https://github.com/ruby/prism/compare/v0.23.0...v0.24.0
[0.23.0]: https://github.com/ruby/prism/compare/v0.22.0...v0.23.0
[0.22.0]: https://github.com/ruby/prism/compare/v0.21.0...v0.22.0
[0.21.0]: https://github.com/ruby/prism/compare/v0.20.0...v0.21.0
[0.20.0]: https://github.com/ruby/prism/compare/v0.19.0...v0.20.0
[0.19.0]: https://github.com/ruby/prism/compare/v0.18.0...v0.19.0
[0.18.0]: https://github.com/ruby/prism/compare/v0.17.1...v0.18.0
[0.17.1]: https://github.com/ruby/prism/compare/v0.17.0...v0.17.1
[0.17.0]: https://github.com/ruby/prism/compare/v0.16.0...v0.17.0
[0.16.0]: https://github.com/ruby/prism/compare/v0.15.1...v0.16.0
[0.15.1]: https://github.com/ruby/prism/compare/v0.15.0...v0.15.1
[0.15.0]: https://github.com/ruby/prism/compare/v0.14.0...v0.15.0
[0.14.0]: https://github.com/ruby/prism/compare/v0.13.0...v0.14.0
[0.13.0]: https://github.com/ruby/prism/compare/v0.12.0...v0.13.0
[0.12.0]: https://github.com/ruby/prism/compare/v0.11.0...v0.12.0
[0.11.0]: https://github.com/ruby/prism/compare/v0.10.0...v0.11.0
[0.10.0]: https://github.com/ruby/prism/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/ruby/prism/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/ruby/prism/compare/v0.7.0...v0.8.0
[0.7.0]: https://github.com/ruby/prism/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/ruby/prism/compare/d60531...v0.6.0

View File

@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic
address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at opensource@shopify.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View File

@@ -0,0 +1,58 @@
# Contributing
Thank you for your interest in contributing to prism! Below are a couple of ways that you can help out.
## Discussions
The discussions page on the GitHub repository are open. If you have a question or want to discuss the project, feel free to open a new discussion or comment on an existing one. This is the best place to ask questions about the project.
## Code
If you want to contribute code, please first open or contribute to a discussion. A lot of the project is in flux, and we want to make sure that you are contributing to the right place. Once you have a discussion going, you can open a pull request with your changes. We will review your code and get it merged in.
## Tests
We could always use more tests! One of the biggest challenges of this project is building up a big test suite. If you want to contribute tests, feel free to open a pull request. These will get merged in as soon as possible.
The `test` Rake task will not compile libraries or the C extension, and this is intentional (to make testing against an installed version easier). If you want to test your changes, please make sure you're also running either the task:
``` sh
bundle exec rake
```
or explicitly running the `compile` task:
``` sh
bundle exec rake compile test
# or to just compile the C extension ...
bundle exec rake compile:prism test
```
To test the rust bindings (with caveats about setting up your Rust environment properly first):
``` sh
bundle exec rake compile test:rust
```
## Documentation
We could always use more documentation! If you want to contribute documentation, feel free to open a pull request. These will get merged in as soon as possible. Documenting functions or methods is always useful, but we also need more guides and tutorials. If you have an idea for a guide or tutorial, feel free to open an issue and we can discuss it.
## Developing
To get `clangd` support in the editor for development, generate the compilation database. This command will
create an ignored `compile_commands.json` file at the project root, which is used by clangd to provide functionality.
You will need `bear` which can be installed on macOS with `brew install bear`.
```sh
bundle exec rake bear
```
## Debugging
Some useful rake tasks:
- `test:valgrind` runs the test suite under valgrind to look for illegal memory access or memory leaks
- `test:gdb` and `test:lldb` run the test suite under those debuggers

View File

@@ -0,0 +1,7 @@
Copyright 2022-present, Shopify Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,141 @@
<h1 align="center">Prism Ruby parser</h1>
<div align="center">
<img alt="Prism Ruby parser" height="256px" src="https://github.com/ruby/prism/blob/main/doc/images/prism.png?raw=true">
</div>
This is a parser for the Ruby programming language. It is designed to be portable, error tolerant, and maintainable. It is written in C99 and has no dependencies.
## Overview
The repository contains the infrastructure for both a shared library (libprism) and a native CRuby extension. The shared library has no bindings to CRuby itself, and so can be used by other projects. The native CRuby extension links against `ruby.h`, and so is suitable in the context of CRuby.
```
.
├── Makefile configuration to compile the shared library and native tests
├── Rakefile configuration to compile the native extension and run the Ruby tests
├── bin
│   ├── lex runs the lexer on a file or string, prints the tokens, and compares to ripper
│   ├── parse runs the parse on a file or string and prints the AST
│   └── prism a CLI for development and debugging
├── config.yml specification for tokens and nodes in the tree
├── doc documentation website
├── docs markdown documentation about the project
├── ext
│   └── prism
│   ├── extconf.rb configuration to generate the Makefile for the native extension
│   └── extension.c the native extension that interacts with libprism
├── fuzz files related to fuzz testing
├── gemfiles gemfiles used by different Ruby versions in CI
├── include
│   ├── prism header files for the shared library
│   └── prism.h main header file for the shared library
├── java Java bindings for the shared library
├── java-wasm Java WASM bindings for the shared library
├── javascript JavaScript WASM bindings for the shared library
├── lib
│   ├── prism Ruby library files
│   └── prism.rb main entrypoint for the Ruby library
├── rakelib various Rake tasks for the project
├── rbi RBI type signatures for the Ruby library
├── rust
│   ├── ruby-prism Rustified crate for the shared library
│   └── ruby-prism-sys FFI binding for Rust
├── sample
│ └── prism Sample code that uses the Ruby API for documentation purposes
├── sig RBS type signatures for the Ruby library
├── src
│   ├── util various utility files
│   └── prism.c main entrypoint for the shared library
├── templates contains ERB templates generated by templates/template.rb
│   └── template.rb generates code from the nodes and tokens configured by config.yml
└── test
└── prism
├── fixtures Ruby code used for testing
└── snapshots snapshots of generated syntax trees corresponding to fixtures
```
## Getting started
To compile the shared library, you will need:
* C99 compiler
* GNU make
* Ruby 2.7.0 or later
Once you have these dependencies, run:
```
bundle install
```
to fetch the Ruby dependencies. Finally, run:
```
bundle exec rake compile
```
to compile the shared library. It will be built in the `build` directory. To test that everything is working, run:
```
bin/parse -e "1 + 2"
```
to see the syntax tree for the expression `1 + 2`.
## Contributing
See the [CONTRIBUTING.md](CONTRIBUTING.md) file for more information. We additionally have documentation about the overall design of the project as well as various subtopics.
* [Build system](docs/build_system.md)
* [Configuration](docs/configuration.md)
* [CRuby compilation](docs/cruby_compilation.md)
* [Design](docs/design.md)
* [Encoding](docs/encoding.md)
* [Fuzzing](docs/fuzzing.md)
* [Heredocs](docs/heredocs.md)
* [JavaScript](docs/javascript.md)
* [Local variable depth](docs/local_variable_depth.md)
* [Mapping](docs/mapping.md)
* [Parser translation](docs/parser_translation.md)
* [Parsing rules](docs/parsing_rules.md)
* [Releasing](docs/releasing.md)
* [Ripper translation](docs/ripper_translation.md)
* [Ruby API](docs/ruby_api.md)
* [RubyParser translation](docs/ruby_parser_translation.md)
* [Serialization](docs/serialization.md)
* [Testing](docs/testing.md)
## Examples
Prism has been integrated into the majority of Ruby runtimes, many libraries, and some applications. Below is a list of some of the projects that use Prism:
### Runtimes
* [CRuby](https://github.com/ruby/ruby/pull/7964) (via C)
* [Garnet](https://github.com/camertron/garnet-js) (via WASM)
* [JRuby](https://github.com/jruby/jruby/pull/8103) (via Java)
* [Natalie](https://github.com/natalie-lang/natalie/pull/1213) (via C++ and Ruby)
* [Opal](https://github.com/opal/opal/pull/2642) (via Ruby and WASM)
* [TruffleRuby](https://github.com/oracle/truffleruby/issues/3117) (via Java)
### Libraries
* [dispersion](https://github.com/joeldrapper/dispersion)
* [minifyrb](https://github.com/koic/minifyrb)
* [packwerk](https://github.com/Shopify/packwerk/pull/388) (via parser translator)
* [rbi](https://github.com/Shopify/rbi)
* [rails](https://github.com/rails/rails)
* [parsing renders](https://github.com/rails/rails/pull/49438)
* [parsing rdoc](https://github.com/rails/rails/pull/50870)
* [parsing tests](https://github.com/rails/rails/pull/51006)
* [repl_type_completor](https://github.com/ruby/repl_type_completor)
* [rubocop](https://docs.rubocop.org/rubocop/configuration.html#setting-the-parser-engine) (via parser translator)
* [ruby-lsp](https://github.com/Shopify/ruby-lsp)
* [smart_todo](https://github.com/Shopify/smart_todo/pull/69)
* [sorbet-eraser](https://github.com/kddnewton/sorbet-eraser/pull/25)
* [synvert](https://github.com/xinminlabs/synvert-core-ruby)
* [typeprof](https://github.com/ruby/typeprof)
### Applications
* [gem.sh](https://github.com/marcoroth/gem.sh/pull/96)

View File

@@ -0,0 +1,383 @@
/**
* @file prism.h
*
* The main header file for the prism parser.
*/
#ifndef PRISM_H
#define PRISM_H
#include "prism/defines.h"
#include "prism/util/pm_buffer.h"
#include "prism/util/pm_char.h"
#include "prism/util/pm_integer.h"
#include "prism/util/pm_memchr.h"
#include "prism/util/pm_strncasecmp.h"
#include "prism/util/pm_strpbrk.h"
#include "prism/ast.h"
#include "prism/diagnostic.h"
#include "prism/node.h"
#include "prism/options.h"
#include "prism/pack.h"
#include "prism/parser.h"
#include "prism/prettyprint.h"
#include "prism/regexp.h"
#include "prism/static_literals.h"
#include "prism/version.h"
#include <assert.h>
#include <errno.h>
#include <locale.h>
#include <math.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef _WIN32
#include <strings.h>
#endif
/**
* The prism version and the serialization format.
*
* @returns The prism version as a constant string.
*/
PRISM_EXPORTED_FUNCTION const char * pm_version(void);
/**
* Initialize a parser with the given start and end pointers.
*
* @param parser The parser to initialize.
* @param source The source to parse.
* @param size The size of the source.
* @param options The optional options to use when parsing.
*/
PRISM_EXPORTED_FUNCTION void pm_parser_init(pm_parser_t *parser, const uint8_t *source, size_t size, const pm_options_t *options);
/**
* Register a callback that will be called whenever prism changes the encoding
* it is using to parse based on the magic comment.
*
* @param parser The parser to register the callback with.
* @param callback The callback to register.
*/
PRISM_EXPORTED_FUNCTION void pm_parser_register_encoding_changed_callback(pm_parser_t *parser, pm_encoding_changed_callback_t callback);
/**
* Free any memory associated with the given parser.
*
* @param parser The parser to free.
*/
PRISM_EXPORTED_FUNCTION void pm_parser_free(pm_parser_t *parser);
/**
* Initiate the parser with the given parser.
*
* @param parser The parser to use.
* @return The AST representing the source.
*/
PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse(pm_parser_t *parser);
/**
* This function is used in pm_parse_stream to retrieve a line of input from a
* stream. It closely mirrors that of fgets so that fgets can be used as the
* default implementation.
*/
typedef char * (pm_parse_stream_fgets_t)(char *string, int size, void *stream);
/**
* Parse a stream of Ruby source and return the tree.
*
* @param parser The parser to use.
* @param buffer The buffer to use.
* @param stream The stream to parse.
* @param stream_fgets The function to use to read from the stream.
* @param options The optional options to use when parsing.
* @return The AST representing the source.
*/
PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const pm_options_t *options);
// We optionally support serializing to a binary string. For systems that don't
// want or need this functionality, it can be turned off with the
// PRISM_EXCLUDE_SERIALIZATION define.
#ifndef PRISM_EXCLUDE_SERIALIZATION
/**
* Parse and serialize the AST represented by the source that is read out of the
* given stream into to the given buffer.
*
* @param buffer The buffer to serialize to.
* @param stream The stream to parse.
* @param stream_fgets The function to use to read from the stream.
* @param data The optional data to pass to the parser.
*/
PRISM_EXPORTED_FUNCTION void pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const char *data);
/**
* Serialize the given list of comments to the given buffer.
*
* @param parser The parser to serialize.
* @param list The list of comments to serialize.
* @param buffer The buffer to serialize to.
*/
void pm_serialize_comment_list(pm_parser_t *parser, pm_list_t *list, pm_buffer_t *buffer);
/**
* Serialize the name of the encoding to the buffer.
*
* @param encoding The encoding to serialize.
* @param buffer The buffer to serialize to.
*/
void pm_serialize_encoding(const pm_encoding_t *encoding, pm_buffer_t *buffer);
/**
* Serialize the encoding, metadata, nodes, and constant pool.
*
* @param parser The parser to serialize.
* @param node The node to serialize.
* @param buffer The buffer to serialize to.
*/
void pm_serialize_content(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer);
/**
* Serialize the AST represented by the given node to the given buffer.
*
* @param parser The parser to serialize.
* @param node The node to serialize.
* @param buffer The buffer to serialize to.
*/
PRISM_EXPORTED_FUNCTION void pm_serialize(pm_parser_t *parser, pm_node_t *node, pm_buffer_t *buffer);
/**
* Parse the given source to the AST and dump the AST to the given buffer.
*
* @param buffer The buffer to serialize to.
* @param source The source to parse.
* @param size The size of the source.
* @param data The optional data to pass to the parser.
*/
PRISM_EXPORTED_FUNCTION void pm_serialize_parse(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data);
/**
* Parse and serialize the comments in the given source to the given buffer.
*
* @param buffer The buffer to serialize to.
* @param source The source to parse.
* @param size The size of the source.
* @param data The optional data to pass to the parser.
*/
PRISM_EXPORTED_FUNCTION void pm_serialize_parse_comments(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data);
/**
* Lex the given source and serialize to the given buffer.
*
* @param source The source to lex.
* @param size The size of the source.
* @param buffer The buffer to serialize to.
* @param data The optional data to pass to the lexer.
*/
PRISM_EXPORTED_FUNCTION void pm_serialize_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data);
/**
* Parse and serialize both the AST and the tokens represented by the given
* source to the given buffer.
*
* @param buffer The buffer to serialize to.
* @param source The source to parse.
* @param size The size of the source.
* @param data The optional data to pass to the parser.
*/
PRISM_EXPORTED_FUNCTION void pm_serialize_parse_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data);
#endif
/**
* Parse the source and return true if it parses without errors or warnings.
*
* @param source The source to parse.
* @param size The size of the source.
* @param data The optional data to pass to the parser.
* @return True if the source parses without errors or warnings.
*/
PRISM_EXPORTED_FUNCTION bool pm_parse_success_p(const uint8_t *source, size_t size, const char *data);
/**
* Returns a string representation of the given token type.
*
* @param token_type The token type to convert to a string.
* @return A string representation of the given token type.
*/
PRISM_EXPORTED_FUNCTION const char * pm_token_type_name(pm_token_type_t token_type);
/**
* Returns the human name of the given token type.
*
* @param token_type The token type to convert to a human name.
* @return The human name of the given token type.
*/
const char * pm_token_type_human(pm_token_type_t token_type);
// We optionally support dumping to JSON. For systems that don't want or need
// this functionality, it can be turned off with the PRISM_EXCLUDE_JSON define.
#ifndef PRISM_EXCLUDE_JSON
/**
* Dump JSON to the given buffer.
*
* @param buffer The buffer to serialize to.
* @param parser The parser that parsed the node.
* @param node The node to serialize.
*/
PRISM_EXPORTED_FUNCTION void pm_dump_json(pm_buffer_t *buffer, const pm_parser_t *parser, const pm_node_t *node);
#endif
/**
* Represents the results of a slice query.
*/
typedef enum {
/** Returned if the encoding given to a slice query was invalid. */
PM_STRING_QUERY_ERROR = -1,
/** Returned if the result of the slice query is false. */
PM_STRING_QUERY_FALSE,
/** Returned if the result of the slice query is true. */
PM_STRING_QUERY_TRUE
} pm_string_query_t;
/**
* Check that the slice is a valid local variable name.
*
* @param source The source to check.
* @param length The length of the source.
* @param encoding_name The name of the encoding of the source.
* @return PM_STRING_QUERY_TRUE if the query is true, PM_STRING_QUERY_FALSE if
* the query is false, and PM_STRING_QUERY_ERROR if the encoding was invalid.
*/
PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_local(const uint8_t *source, size_t length, const char *encoding_name);
/**
* Check that the slice is a valid constant name.
*
* @param source The source to check.
* @param length The length of the source.
* @param encoding_name The name of the encoding of the source.
* @return PM_STRING_QUERY_TRUE if the query is true, PM_STRING_QUERY_FALSE if
* the query is false, and PM_STRING_QUERY_ERROR if the encoding was invalid.
*/
PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_constant(const uint8_t *source, size_t length, const char *encoding_name);
/**
* Check that the slice is a valid method name.
*
* @param source The source to check.
* @param length The length of the source.
* @param encoding_name The name of the encoding of the source.
* @return PM_STRING_QUERY_TRUE if the query is true, PM_STRING_QUERY_FALSE if
* the query is false, and PM_STRING_QUERY_ERROR if the encoding was invalid.
*/
PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint8_t *source, size_t length, const char *encoding_name);
/**
* @mainpage
*
* Prism is a parser for the Ruby programming language. It is designed to be
* portable, error tolerant, and maintainable. It is written in C99 and has no
* dependencies. It is currently being integrated into
* [CRuby](https://github.com/ruby/ruby),
* [JRuby](https://github.com/jruby/jruby),
* [TruffleRuby](https://github.com/oracle/truffleruby),
* [Sorbet](https://github.com/sorbet/sorbet), and
* [Syntax Tree](https://github.com/ruby-syntax-tree/syntax_tree).
*
* @section getting-started Getting started
*
* If you're vendoring this project and compiling it statically then as long as
* you have a C99 compiler you will be fine. If you're linking against it as
* shared library, then you should compile with `-fvisibility=hidden` and
* `-DPRISM_EXPORT_SYMBOLS` to tell prism to make only its public interface
* visible.
*
* @section parsing Parsing
*
* In order to parse Ruby code, the structures and functions that you're going
* to want to use and be aware of are:
*
* * `pm_parser_t` - the main parser structure
* * `pm_parser_init` - initialize a parser
* * `pm_parse` - parse and return the root node
* * `pm_node_destroy` - deallocate the root node returned by `pm_parse`
* * `pm_parser_free` - free the internal memory of the parser
*
* Putting all of this together would look something like:
*
* ```c
* void parse(const uint8_t *source, size_t length) {
* pm_parser_t parser;
* pm_parser_init(&parser, source, length, NULL);
*
* pm_node_t *root = pm_parse(&parser);
* printf("PARSED!\n");
*
* pm_node_destroy(&parser, root);
* pm_parser_free(&parser);
* }
* ```
*
* All of the nodes "inherit" from `pm_node_t` by embedding those structures as
* their first member. This means you can downcast and upcast any node in the
* tree to a `pm_node_t`.
*
* @section serializing Serializing
*
* Prism provides the ability to serialize the AST and its related metadata into
* a binary format. This format is designed to be portable to different
* languages and runtimes so that you only need to make one FFI call in order to
* parse Ruby code. The structures and functions that you're going to want to
* use and be aware of are:
*
* * `pm_buffer_t` - a small buffer object that will hold the serialized AST
* * `pm_buffer_free` - free the memory associated with the buffer
* * `pm_serialize` - serialize the AST into a buffer
* * `pm_serialize_parse` - parse and serialize the AST into a buffer
*
* Putting all of this together would look something like:
*
* ```c
* void serialize(const uint8_t *source, size_t length) {
* pm_buffer_t buffer = { 0 };
*
* pm_serialize_parse(&buffer, source, length, NULL);
* printf("SERIALIZED!\n");
*
* pm_buffer_free(&buffer);
* }
* ```
*
* @section inspecting Inspecting
*
* Prism provides the ability to inspect the AST by pretty-printing nodes. You
* can do this with the `pm_prettyprint` function, which you would use like:
*
* ```c
* void prettyprint(const uint8_t *source, size_t length) {
* pm_parser_t parser;
* pm_parser_init(&parser, source, length, NULL);
*
* pm_node_t *root = pm_parse(&parser);
* pm_buffer_t buffer = { 0 };
*
* pm_prettyprint(&buffer, &parser, root);
* printf("%*.s\n", (int) buffer.length, buffer.value);
*
* pm_buffer_free(&buffer);
* pm_node_destroy(&parser, root);
* pm_parser_free(&parser);
* }
* ```
*/
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,260 @@
/**
* @file defines.h
*
* Macro definitions used throughout the prism library.
*
* This file should be included first by any *.h or *.c in prism for consistency
* and to ensure that the macros are defined before they are used.
*/
#ifndef PRISM_DEFINES_H
#define PRISM_DEFINES_H
#include <ctype.h>
#include <limits.h>
#include <math.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/**
* We want to be able to use the PRI* macros for printing out integers, but on
* some platforms they aren't included unless this is already defined.
*/
#define __STDC_FORMAT_MACROS
// Include sys/types.h before inttypes.h to work around issue with
// certain versions of GCC and newlib which causes omission of PRIx64
#include <sys/types.h>
#include <inttypes.h>
/**
* When we are parsing using recursive descent, we want to protect against
* malicious payloads that could attempt to crash our parser. We do this by
* specifying a maximum depth to which we are allowed to recurse.
*/
#ifndef PRISM_DEPTH_MAXIMUM
#define PRISM_DEPTH_MAXIMUM 10000
#endif
/**
* By default, we compile with -fvisibility=hidden. When this is enabled, we
* need to mark certain functions as being publically-visible. This macro does
* that in a compiler-agnostic way.
*/
#ifndef PRISM_EXPORTED_FUNCTION
# ifdef PRISM_EXPORT_SYMBOLS
# ifdef _WIN32
# define PRISM_EXPORTED_FUNCTION __declspec(dllexport) extern
# else
# define PRISM_EXPORTED_FUNCTION __attribute__((__visibility__("default"))) extern
# endif
# else
# define PRISM_EXPORTED_FUNCTION
# endif
#endif
/**
* Certain compilers support specifying that a function accepts variadic
* parameters that look like printf format strings to provide a better developer
* experience when someone is using the function. This macro does that in a
* compiler-agnostic way.
*/
#if defined(__GNUC__)
# if defined(__MINGW_PRINTF_FORMAT)
# define PRISM_ATTRIBUTE_FORMAT(string_index, argument_index) __attribute__((format(__MINGW_PRINTF_FORMAT, string_index, argument_index)))
# else
# define PRISM_ATTRIBUTE_FORMAT(string_index, argument_index) __attribute__((format(printf, string_index, argument_index)))
# endif
#elif defined(__clang__)
# define PRISM_ATTRIBUTE_FORMAT(string_index, argument_index) __attribute__((__format__(__printf__, string_index, argument_index)))
#else
# define PRISM_ATTRIBUTE_FORMAT(string_index, argument_index)
#endif
/**
* GCC will warn if you specify a function or parameter that is unused at
* runtime. This macro allows you to mark a function or parameter as unused in a
* compiler-agnostic way.
*/
#if defined(__GNUC__)
# define PRISM_ATTRIBUTE_UNUSED __attribute__((unused))
#else
# define PRISM_ATTRIBUTE_UNUSED
#endif
/**
* Old Visual Studio versions do not support the inline keyword, so we need to
* define it to be __inline.
*/
#if defined(_MSC_VER) && !defined(inline)
# define inline __inline
#endif
/**
* Old Visual Studio versions before 2015 do not implement sprintf, but instead
* implement _snprintf. We standard that here.
*/
#if !defined(snprintf) && defined(_MSC_VER) && (_MSC_VER < 1900)
# define snprintf _snprintf
#endif
/**
* A simple utility macro to concatenate two tokens together, necessary when one
* of the tokens is itself a macro.
*/
#define PM_CONCATENATE(left, right) left ## right
/**
* We want to be able to use static assertions, but they weren't standardized
* until C11. As such, we polyfill it here by making a hacky typedef that will
* fail to compile due to a negative array size if the condition is false.
*/
#if defined(_Static_assert)
# define PM_STATIC_ASSERT(line, condition, message) _Static_assert(condition, message)
#else
# define PM_STATIC_ASSERT(line, condition, message) typedef char PM_CONCATENATE(static_assert_, line)[(condition) ? 1 : -1]
#endif
/**
* In general, libc for embedded systems does not support memory-mapped files.
* If the target platform is POSIX or Windows, we can map a file in memory and
* read it in a more efficient manner.
*/
#ifdef _WIN32
# define PRISM_HAS_MMAP
#else
# include <unistd.h>
# ifdef _POSIX_MAPPED_FILES
# define PRISM_HAS_MMAP
# endif
#endif
/**
* If PRISM_HAS_NO_FILESYSTEM is defined, then we want to exclude all filesystem
* related code from the library. All filesystem related code should be guarded
* by PRISM_HAS_FILESYSTEM.
*/
#ifndef PRISM_HAS_NO_FILESYSTEM
# define PRISM_HAS_FILESYSTEM
#endif
/**
* isinf on POSIX systems it accepts a float, a double, or a long double.
* But mingw didn't provide an isinf macro, only an isinf function that only
* accepts floats, so we need to use _finite instead.
*/
#ifdef __MINGW64__
#include <float.h>
#define PRISM_ISINF(x) (!_finite(x))
#else
#define PRISM_ISINF(x) isinf(x)
#endif
/**
* If you build prism with a custom allocator, configure it with
* "-D PRISM_XALLOCATOR" to use your own allocator that defines xmalloc,
* xrealloc, xcalloc, and xfree.
*
* For example, your `prism_xallocator.h` file could look like this:
*
* ```
* #ifndef PRISM_XALLOCATOR_H
* #define PRISM_XALLOCATOR_H
* #define xmalloc my_malloc
* #define xrealloc my_realloc
* #define xcalloc my_calloc
* #define xfree my_free
* #endif
* ```
*/
#ifdef PRISM_XALLOCATOR
#include "prism_xallocator.h"
#else
#ifndef xmalloc
/**
* The malloc function that should be used. This can be overridden with
* the PRISM_XALLOCATOR define.
*/
#define xmalloc malloc
#endif
#ifndef xrealloc
/**
* The realloc function that should be used. This can be overridden with
* the PRISM_XALLOCATOR define.
*/
#define xrealloc realloc
#endif
#ifndef xcalloc
/**
* The calloc function that should be used. This can be overridden with
* the PRISM_XALLOCATOR define.
*/
#define xcalloc calloc
#endif
#ifndef xfree
/**
* The free function that should be used. This can be overridden with the
* PRISM_XALLOCATOR define.
*/
#define xfree free
#endif
#endif
/**
* If PRISM_BUILD_MINIMAL is defined, then we're going to define every possible
* switch that will turn off certain features of prism.
*/
#ifdef PRISM_BUILD_MINIMAL
/** Exclude the serialization API. */
#define PRISM_EXCLUDE_SERIALIZATION
/** Exclude the JSON serialization API. */
#define PRISM_EXCLUDE_JSON
/** Exclude the Array#pack parser API. */
#define PRISM_EXCLUDE_PACK
/** Exclude the prettyprint API. */
#define PRISM_EXCLUDE_PRETTYPRINT
/** Exclude the full set of encodings, using the minimal only. */
#define PRISM_ENCODING_EXCLUDE_FULL
#endif
/**
* Support PRISM_LIKELY and PRISM_UNLIKELY to help the compiler optimize its
* branch predication.
*/
#if defined(__GNUC__) || defined(__clang__)
/** The compiler should predicate that this branch will be taken. */
#define PRISM_LIKELY(x) __builtin_expect(!!(x), 1)
/** The compiler should predicate that this branch will not be taken. */
#define PRISM_UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
/** Void because this platform does not support branch prediction hints. */
#define PRISM_LIKELY(x) (x)
/** Void because this platform does not support branch prediction hints. */
#define PRISM_UNLIKELY(x) (x)
#endif
/**
* We use -Wimplicit-fallthrough to guard potentially unintended fall-through between cases of a switch.
* Use PRISM_FALLTHROUGH to explicitly annotate cases where the fallthrough is intentional.
*/
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L // C23 or later
#define PRISM_FALLTHROUGH [[fallthrough]];
#elif defined(__GNUC__) || defined(__clang__)
#define PRISM_FALLTHROUGH __attribute__((fallthrough));
#elif defined(_MSC_VER)
#define PRISM_FALLTHROUGH __fallthrough;
#else
#define PRISM_FALLTHROUGH
#endif
#endif

View File

@@ -0,0 +1,451 @@
/*----------------------------------------------------------------------------*/
/* This file is generated by the templates/template.rb script and should not */
/* be modified manually. See */
/* templates/include/prism/diagnostic.h.erb */
/* if you are looking to modify the */
/* template */
/*----------------------------------------------------------------------------*/
/**
* @file diagnostic.h
*
* A list of diagnostics generated during parsing.
*/
#ifndef PRISM_DIAGNOSTIC_H
#define PRISM_DIAGNOSTIC_H
#include "prism/ast.h"
#include "prism/defines.h"
#include "prism/util/pm_list.h"
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
/**
* The diagnostic IDs of all of the diagnostics, used to communicate the types
* of errors between the parser and the user.
*/
typedef enum {
// These are the error diagnostics.
PM_ERR_ALIAS_ARGUMENT,
PM_ERR_ALIAS_ARGUMENT_NUMBERED_REFERENCE,
PM_ERR_AMPAMPEQ_MULTI_ASSIGN,
PM_ERR_ARGUMENT_AFTER_BLOCK,
PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES,
PM_ERR_ARGUMENT_BARE_HASH,
PM_ERR_ARGUMENT_BLOCK_FORWARDING,
PM_ERR_ARGUMENT_BLOCK_MULTI,
PM_ERR_ARGUMENT_CONFLICT_AMPERSAND,
PM_ERR_ARGUMENT_CONFLICT_STAR,
PM_ERR_ARGUMENT_CONFLICT_STAR_STAR,
PM_ERR_ARGUMENT_FORMAL_CLASS,
PM_ERR_ARGUMENT_FORMAL_CONSTANT,
PM_ERR_ARGUMENT_FORMAL_GLOBAL,
PM_ERR_ARGUMENT_FORMAL_IVAR,
PM_ERR_ARGUMENT_FORWARDING_UNBOUND,
PM_ERR_ARGUMENT_NO_FORWARDING_AMPERSAND,
PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES,
PM_ERR_ARGUMENT_NO_FORWARDING_STAR,
PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR,
PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT,
PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT,
PM_ERR_ARGUMENT_TERM_PAREN,
PM_ERR_ARGUMENT_UNEXPECTED_BLOCK,
PM_ERR_ARRAY_ELEMENT,
PM_ERR_ARRAY_EXPRESSION,
PM_ERR_ARRAY_EXPRESSION_AFTER_STAR,
PM_ERR_ARRAY_SEPARATOR,
PM_ERR_ARRAY_TERM,
PM_ERR_BEGIN_LONELY_ELSE,
PM_ERR_BEGIN_TERM,
PM_ERR_BEGIN_UPCASE_BRACE,
PM_ERR_BEGIN_UPCASE_TERM,
PM_ERR_BEGIN_UPCASE_TOPLEVEL,
PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE,
PM_ERR_BLOCK_PARAM_PIPE_TERM,
PM_ERR_BLOCK_TERM_BRACE,
PM_ERR_BLOCK_TERM_END,
PM_ERR_CANNOT_PARSE_EXPRESSION,
PM_ERR_CANNOT_PARSE_STRING_PART,
PM_ERR_CASE_EXPRESSION_AFTER_CASE,
PM_ERR_CASE_EXPRESSION_AFTER_WHEN,
PM_ERR_CASE_MATCH_MISSING_PREDICATE,
PM_ERR_CASE_MISSING_CONDITIONS,
PM_ERR_CASE_TERM,
PM_ERR_CLASS_IN_METHOD,
PM_ERR_CLASS_NAME,
PM_ERR_CLASS_SUPERCLASS,
PM_ERR_CLASS_TERM,
PM_ERR_CLASS_UNEXPECTED_END,
PM_ERR_CLASS_VARIABLE_BARE,
PM_ERR_CONDITIONAL_ELSIF_PREDICATE,
PM_ERR_CONDITIONAL_IF_PREDICATE,
PM_ERR_CONDITIONAL_PREDICATE_TERM,
PM_ERR_CONDITIONAL_TERM,
PM_ERR_CONDITIONAL_TERM_ELSE,
PM_ERR_CONDITIONAL_UNLESS_PREDICATE,
PM_ERR_CONDITIONAL_UNTIL_PREDICATE,
PM_ERR_CONDITIONAL_WHILE_PREDICATE,
PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT,
PM_ERR_DEF_ENDLESS,
PM_ERR_DEF_ENDLESS_SETTER,
PM_ERR_DEF_NAME,
PM_ERR_DEF_PARAMS_TERM,
PM_ERR_DEF_PARAMS_TERM_PAREN,
PM_ERR_DEF_RECEIVER,
PM_ERR_DEF_RECEIVER_TERM,
PM_ERR_DEF_TERM,
PM_ERR_DEFINED_EXPRESSION,
PM_ERR_EMBDOC_TERM,
PM_ERR_EMBEXPR_END,
PM_ERR_EMBVAR_INVALID,
PM_ERR_END_UPCASE_BRACE,
PM_ERR_END_UPCASE_TERM,
PM_ERR_ESCAPE_INVALID_CONTROL,
PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT,
PM_ERR_ESCAPE_INVALID_HEXADECIMAL,
PM_ERR_ESCAPE_INVALID_META,
PM_ERR_ESCAPE_INVALID_META_REPEAT,
PM_ERR_ESCAPE_INVALID_UNICODE,
PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS,
PM_ERR_ESCAPE_INVALID_UNICODE_LIST,
PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL,
PM_ERR_ESCAPE_INVALID_UNICODE_LONG,
PM_ERR_ESCAPE_INVALID_UNICODE_SHORT,
PM_ERR_ESCAPE_INVALID_UNICODE_TERM,
PM_ERR_EXPECT_ARGUMENT,
PM_ERR_EXPECT_EOL_AFTER_STATEMENT,
PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ,
PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA,
PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL,
PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS,
PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN,
PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR,
PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ,
PM_ERR_EXPECT_EXPRESSION_AFTER_QUESTION,
PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT,
PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH,
PM_ERR_EXPECT_EXPRESSION_AFTER_STAR,
PM_ERR_EXPECT_FOR_DELIMITER,
PM_ERR_EXPECT_IDENT_REQ_PARAMETER,
PM_ERR_EXPECT_IN_DELIMITER,
PM_ERR_EXPECT_LPAREN_REQ_PARAMETER,
PM_ERR_EXPECT_MESSAGE,
PM_ERR_EXPECT_RBRACKET,
PM_ERR_EXPECT_RPAREN,
PM_ERR_EXPECT_RPAREN_AFTER_MULTI,
PM_ERR_EXPECT_RPAREN_REQ_PARAMETER,
PM_ERR_EXPECT_SINGLETON_CLASS_DELIMITER,
PM_ERR_EXPECT_STRING_CONTENT,
PM_ERR_EXPECT_WHEN_DELIMITER,
PM_ERR_EXPRESSION_BARE_HASH,
PM_ERR_EXPRESSION_NOT_WRITABLE,
PM_ERR_EXPRESSION_NOT_WRITABLE_ENCODING,
PM_ERR_EXPRESSION_NOT_WRITABLE_FALSE,
PM_ERR_EXPRESSION_NOT_WRITABLE_FILE,
PM_ERR_EXPRESSION_NOT_WRITABLE_LINE,
PM_ERR_EXPRESSION_NOT_WRITABLE_NIL,
PM_ERR_EXPRESSION_NOT_WRITABLE_NUMBERED,
PM_ERR_EXPRESSION_NOT_WRITABLE_SELF,
PM_ERR_EXPRESSION_NOT_WRITABLE_TRUE,
PM_ERR_FLOAT_PARSE,
PM_ERR_FOR_COLLECTION,
PM_ERR_FOR_IN,
PM_ERR_FOR_INDEX,
PM_ERR_FOR_TERM,
PM_ERR_GLOBAL_VARIABLE_BARE,
PM_ERR_HASH_EXPRESSION_AFTER_LABEL,
PM_ERR_HASH_KEY,
PM_ERR_HASH_ROCKET,
PM_ERR_HASH_TERM,
PM_ERR_HASH_VALUE,
PM_ERR_HEREDOC_IDENTIFIER,
PM_ERR_HEREDOC_TERM,
PM_ERR_INCOMPLETE_QUESTION_MARK,
PM_ERR_INCOMPLETE_VARIABLE_CLASS,
PM_ERR_INCOMPLETE_VARIABLE_CLASS_3_3,
PM_ERR_INCOMPLETE_VARIABLE_INSTANCE,
PM_ERR_INCOMPLETE_VARIABLE_INSTANCE_3_3,
PM_ERR_INSTANCE_VARIABLE_BARE,
PM_ERR_INVALID_BLOCK_EXIT,
PM_ERR_INVALID_CHARACTER,
PM_ERR_INVALID_COMMA,
PM_ERR_INVALID_ENCODING_MAGIC_COMMENT,
PM_ERR_INVALID_ESCAPE_CHARACTER,
PM_ERR_INVALID_FLOAT_EXPONENT,
PM_ERR_INVALID_LOCAL_VARIABLE_READ,
PM_ERR_INVALID_LOCAL_VARIABLE_WRITE,
PM_ERR_INVALID_MULTIBYTE_CHAR,
PM_ERR_INVALID_MULTIBYTE_CHARACTER,
PM_ERR_INVALID_MULTIBYTE_ESCAPE,
PM_ERR_INVALID_NUMBER_BINARY,
PM_ERR_INVALID_NUMBER_DECIMAL,
PM_ERR_INVALID_NUMBER_FRACTION,
PM_ERR_INVALID_NUMBER_HEXADECIMAL,
PM_ERR_INVALID_NUMBER_OCTAL,
PM_ERR_INVALID_NUMBER_UNDERSCORE_INNER,
PM_ERR_INVALID_NUMBER_UNDERSCORE_TRAILING,
PM_ERR_INVALID_PERCENT,
PM_ERR_INVALID_PERCENT_EOF,
PM_ERR_INVALID_PRINTABLE_CHARACTER,
PM_ERR_INVALID_RETRY_AFTER_ELSE,
PM_ERR_INVALID_RETRY_AFTER_ENSURE,
PM_ERR_INVALID_RETRY_WITHOUT_RESCUE,
PM_ERR_INVALID_SYMBOL,
PM_ERR_INVALID_VARIABLE_GLOBAL,
PM_ERR_INVALID_VARIABLE_GLOBAL_3_3,
PM_ERR_INVALID_YIELD,
PM_ERR_IT_NOT_ALLOWED_NUMBERED,
PM_ERR_IT_NOT_ALLOWED_ORDINARY,
PM_ERR_LAMBDA_OPEN,
PM_ERR_LAMBDA_TERM_BRACE,
PM_ERR_LAMBDA_TERM_END,
PM_ERR_LIST_I_LOWER_ELEMENT,
PM_ERR_LIST_I_LOWER_TERM,
PM_ERR_LIST_I_UPPER_ELEMENT,
PM_ERR_LIST_I_UPPER_TERM,
PM_ERR_LIST_W_LOWER_ELEMENT,
PM_ERR_LIST_W_LOWER_TERM,
PM_ERR_LIST_W_UPPER_ELEMENT,
PM_ERR_LIST_W_UPPER_TERM,
PM_ERR_MALLOC_FAILED,
PM_ERR_MIXED_ENCODING,
PM_ERR_MODULE_IN_METHOD,
PM_ERR_MODULE_NAME,
PM_ERR_MODULE_TERM,
PM_ERR_MULTI_ASSIGN_MULTI_SPLATS,
PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST,
PM_ERR_NESTING_TOO_DEEP,
PM_ERR_NO_LOCAL_VARIABLE,
PM_ERR_NON_ASSOCIATIVE_OPERATOR,
PM_ERR_NOT_EXPRESSION,
PM_ERR_NUMBER_LITERAL_UNDERSCORE,
PM_ERR_NUMBERED_PARAMETER_INNER_BLOCK,
PM_ERR_NUMBERED_PARAMETER_IT,
PM_ERR_NUMBERED_PARAMETER_ORDINARY,
PM_ERR_NUMBERED_PARAMETER_OUTER_BLOCK,
PM_ERR_OPERATOR_MULTI_ASSIGN,
PM_ERR_OPERATOR_WRITE_ARGUMENTS,
PM_ERR_OPERATOR_WRITE_BLOCK,
PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI,
PM_ERR_PARAMETER_BLOCK_MULTI,
PM_ERR_PARAMETER_CIRCULAR,
PM_ERR_PARAMETER_FORWARDING_AFTER_REST,
PM_ERR_PARAMETER_METHOD_NAME,
PM_ERR_PARAMETER_NAME_DUPLICATED,
PM_ERR_PARAMETER_NO_DEFAULT,
PM_ERR_PARAMETER_NO_DEFAULT_KW,
PM_ERR_PARAMETER_NUMBERED_RESERVED,
PM_ERR_PARAMETER_ORDER,
PM_ERR_PARAMETER_SPLAT_MULTI,
PM_ERR_PARAMETER_STAR,
PM_ERR_PARAMETER_UNEXPECTED_FWD,
PM_ERR_PARAMETER_UNEXPECTED_NO_KW,
PM_ERR_PARAMETER_WILD_LOOSE_COMMA,
PM_ERR_PATTERN_ARRAY_MULTIPLE_RESTS,
PM_ERR_PATTERN_CAPTURE_DUPLICATE,
PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET,
PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA,
PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET,
PM_ERR_PATTERN_EXPRESSION_AFTER_IN,
PM_ERR_PATTERN_EXPRESSION_AFTER_KEY,
PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN,
PM_ERR_PATTERN_EXPRESSION_AFTER_PIN,
PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE,
PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE,
PM_ERR_PATTERN_EXPRESSION_AFTER_REST,
PM_ERR_PATTERN_FIND_MISSING_INNER,
PM_ERR_PATTERN_HASH_IMPLICIT,
PM_ERR_PATTERN_HASH_KEY,
PM_ERR_PATTERN_HASH_KEY_DUPLICATE,
PM_ERR_PATTERN_HASH_KEY_INTERPOLATED,
PM_ERR_PATTERN_HASH_KEY_LABEL,
PM_ERR_PATTERN_HASH_KEY_LOCALS,
PM_ERR_PATTERN_IDENT_AFTER_HROCKET,
PM_ERR_PATTERN_LABEL_AFTER_COMMA,
PM_ERR_PATTERN_REST,
PM_ERR_PATTERN_TERM_BRACE,
PM_ERR_PATTERN_TERM_BRACKET,
PM_ERR_PATTERN_TERM_PAREN,
PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN,
PM_ERR_REGEXP_ENCODING_OPTION_MISMATCH,
PM_ERR_REGEXP_INCOMPAT_CHAR_ENCODING,
PM_ERR_REGEXP_INVALID_UNICODE_RANGE,
PM_ERR_REGEXP_NON_ESCAPED_MBC,
PM_ERR_REGEXP_PARSE_ERROR,
PM_ERR_REGEXP_TERM,
PM_ERR_REGEXP_UNKNOWN_OPTIONS,
PM_ERR_REGEXP_UTF8_CHAR_NON_UTF8_REGEXP,
PM_ERR_RESCUE_EXPRESSION,
PM_ERR_RESCUE_MODIFIER_VALUE,
PM_ERR_RESCUE_TERM,
PM_ERR_RESCUE_VARIABLE,
PM_ERR_RETURN_INVALID,
PM_ERR_SCRIPT_NOT_FOUND,
PM_ERR_SINGLETON_FOR_LITERALS,
PM_ERR_STATEMENT_ALIAS,
PM_ERR_STATEMENT_POSTEXE_END,
PM_ERR_STATEMENT_PREEXE_BEGIN,
PM_ERR_STATEMENT_UNDEF,
PM_ERR_STRING_CONCATENATION,
PM_ERR_STRING_INTERPOLATED_TERM,
PM_ERR_STRING_LITERAL_EOF,
PM_ERR_STRING_LITERAL_TERM,
PM_ERR_SYMBOL_INVALID,
PM_ERR_SYMBOL_TERM_DYNAMIC,
PM_ERR_SYMBOL_TERM_INTERPOLATED,
PM_ERR_TERNARY_COLON,
PM_ERR_TERNARY_EXPRESSION_FALSE,
PM_ERR_TERNARY_EXPRESSION_TRUE,
PM_ERR_UNARY_DISALLOWED,
PM_ERR_UNARY_RECEIVER,
PM_ERR_UNDEF_ARGUMENT,
PM_ERR_UNEXPECTED_BLOCK_ARGUMENT,
PM_ERR_UNEXPECTED_INDEX_BLOCK,
PM_ERR_UNEXPECTED_INDEX_KEYWORDS,
PM_ERR_UNEXPECTED_LABEL,
PM_ERR_UNEXPECTED_MULTI_WRITE,
PM_ERR_UNEXPECTED_RANGE_OPERATOR,
PM_ERR_UNEXPECTED_SAFE_NAVIGATION,
PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT,
PM_ERR_UNEXPECTED_TOKEN_IGNORE,
PM_ERR_UNTIL_TERM,
PM_ERR_VOID_EXPRESSION,
PM_ERR_WHILE_TERM,
PM_ERR_WRITE_TARGET_IN_METHOD,
PM_ERR_WRITE_TARGET_READONLY,
PM_ERR_WRITE_TARGET_UNEXPECTED,
PM_ERR_XSTRING_TERM,
// These are the warning diagnostics.
PM_WARN_AMBIGUOUS_BINARY_OPERATOR,
PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS,
PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS,
PM_WARN_AMBIGUOUS_PREFIX_AMPERSAND,
PM_WARN_AMBIGUOUS_PREFIX_STAR,
PM_WARN_AMBIGUOUS_PREFIX_STAR_STAR,
PM_WARN_AMBIGUOUS_SLASH,
PM_WARN_COMPARISON_AFTER_COMPARISON,
PM_WARN_DOT_DOT_DOT_EOL,
PM_WARN_EQUAL_IN_CONDITIONAL,
PM_WARN_EQUAL_IN_CONDITIONAL_3_3,
PM_WARN_END_IN_METHOD,
PM_WARN_DUPLICATED_HASH_KEY,
PM_WARN_DUPLICATED_WHEN_CLAUSE,
PM_WARN_FLOAT_OUT_OF_RANGE,
PM_WARN_IGNORED_FROZEN_STRING_LITERAL,
PM_WARN_INDENTATION_MISMATCH,
PM_WARN_INTEGER_IN_FLIP_FLOP,
PM_WARN_INVALID_CHARACTER,
PM_WARN_INVALID_MAGIC_COMMENT_VALUE,
PM_WARN_INVALID_NUMBERED_REFERENCE,
PM_WARN_KEYWORD_EOL,
PM_WARN_LITERAL_IN_CONDITION_DEFAULT,
PM_WARN_LITERAL_IN_CONDITION_VERBOSE,
PM_WARN_SHAREABLE_CONSTANT_VALUE_LINE,
PM_WARN_SHEBANG_CARRIAGE_RETURN,
PM_WARN_UNEXPECTED_CARRIAGE_RETURN,
PM_WARN_UNREACHABLE_STATEMENT,
PM_WARN_UNUSED_LOCAL_VARIABLE,
PM_WARN_VOID_STATEMENT,
} pm_diagnostic_id_t;
/**
* This struct represents a diagnostic generated during parsing.
*
* @extends pm_list_node_t
*/
typedef struct {
/** The embedded base node. */
pm_list_node_t node;
/** The location of the diagnostic in the source. */
pm_location_t location;
/** The ID of the diagnostic. */
pm_diagnostic_id_t diag_id;
/** The message associated with the diagnostic. */
const char *message;
/**
* Whether or not the memory related to the message of this diagnostic is
* owned by this diagnostic. If it is, it needs to be freed when the
* diagnostic is freed.
*/
bool owned;
/**
* The level of the diagnostic, see `pm_error_level_t` and
* `pm_warning_level_t` for possible values.
*/
uint8_t level;
} pm_diagnostic_t;
/**
* The levels of errors generated during parsing.
*/
typedef enum {
/** For errors that should raise a syntax error. */
PM_ERROR_LEVEL_SYNTAX = 0,
/** For errors that should raise an argument error. */
PM_ERROR_LEVEL_ARGUMENT = 1,
/** For errors that should raise a load error. */
PM_ERROR_LEVEL_LOAD = 2
} pm_error_level_t;
/**
* The levels of warnings generated during parsing.
*/
typedef enum {
/** For warnings which should be emitted if $VERBOSE != nil. */
PM_WARNING_LEVEL_DEFAULT = 0,
/** For warnings which should be emitted if $VERBOSE == true. */
PM_WARNING_LEVEL_VERBOSE = 1
} pm_warning_level_t;
/**
* Get the human-readable name of the given diagnostic ID.
*
* @param diag_id The diagnostic ID.
* @return The human-readable name of the diagnostic ID.
*/
const char * pm_diagnostic_id_human(pm_diagnostic_id_t diag_id);
/**
* Append a diagnostic to the given list of diagnostics that is using shared
* memory for its message.
*
* @param list The list to append to.
* @param start The start of the diagnostic.
* @param end The end of the diagnostic.
* @param diag_id The diagnostic ID.
* @return Whether the diagnostic was successfully appended.
*/
bool pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id);
/**
* Append a diagnostic to the given list of diagnostics that is using a format
* string for its message.
*
* @param list The list to append to.
* @param start The start of the diagnostic.
* @param end The end of the diagnostic.
* @param diag_id The diagnostic ID.
* @param ... The arguments to the format string for the message.
* @return Whether the diagnostic was successfully appended.
*/
bool pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id, ...);
/**
* Deallocate the internal state of the given diagnostic list.
*
* @param list The list to deallocate.
*/
void pm_diagnostic_list_free(pm_list_t *list);
#endif

View File

@@ -0,0 +1,283 @@
/**
* @file encoding.h
*
* The encoding interface and implementations used by the parser.
*/
#ifndef PRISM_ENCODING_H
#define PRISM_ENCODING_H
#include "prism/defines.h"
#include "prism/util/pm_strncasecmp.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/**
* This struct defines the functions necessary to implement the encoding
* interface so we can determine how many bytes the subsequent character takes.
* Each callback should return the number of bytes, or 0 if the next bytes are
* invalid for the encoding and type.
*/
typedef struct {
/**
* Return the number of bytes that the next character takes if it is valid
* in the encoding. Does not read more than n bytes. It is assumed that n is
* at least 1.
*/
size_t (*char_width)(const uint8_t *b, ptrdiff_t n);
/**
* Return the number of bytes that the next character takes if it is valid
* in the encoding and is alphabetical. Does not read more than n bytes. It
* is assumed that n is at least 1.
*/
size_t (*alpha_char)(const uint8_t *b, ptrdiff_t n);
/**
* Return the number of bytes that the next character takes if it is valid
* in the encoding and is alphanumeric. Does not read more than n bytes. It
* is assumed that n is at least 1.
*/
size_t (*alnum_char)(const uint8_t *b, ptrdiff_t n);
/**
* Return true if the next character is valid in the encoding and is an
* uppercase character. Does not read more than n bytes. It is assumed that
* n is at least 1.
*/
bool (*isupper_char)(const uint8_t *b, ptrdiff_t n);
/**
* The name of the encoding. This should correspond to a value that can be
* passed to Encoding.find in Ruby.
*/
const char *name;
/**
* Return true if the encoding is a multibyte encoding.
*/
bool multibyte;
} pm_encoding_t;
/**
* All of the lookup tables use the first bit of each embedded byte to indicate
* whether the codepoint is alphabetical.
*/
#define PRISM_ENCODING_ALPHABETIC_BIT 1 << 0
/**
* All of the lookup tables use the second bit of each embedded byte to indicate
* whether the codepoint is alphanumeric.
*/
#define PRISM_ENCODING_ALPHANUMERIC_BIT 1 << 1
/**
* All of the lookup tables use the third bit of each embedded byte to indicate
* whether the codepoint is uppercase.
*/
#define PRISM_ENCODING_UPPERCASE_BIT 1 << 2
/**
* Return the size of the next character in the UTF-8 encoding.
*
* @param b The bytes to read.
* @param n The number of bytes that can be read.
* @returns The number of bytes that the next character takes if it is valid in
* the encoding, or 0 if it is not.
*/
size_t pm_encoding_utf_8_char_width(const uint8_t *b, ptrdiff_t n);
/**
* Return the size of the next character in the UTF-8 encoding if it is an
* alphabetical character.
*
* @param b The bytes to read.
* @param n The number of bytes that can be read.
* @returns The number of bytes that the next character takes if it is valid in
* the encoding, or 0 if it is not.
*/
size_t pm_encoding_utf_8_alpha_char(const uint8_t *b, ptrdiff_t n);
/**
* Return the size of the next character in the UTF-8 encoding if it is an
* alphanumeric character.
*
* @param b The bytes to read.
* @param n The number of bytes that can be read.
* @returns The number of bytes that the next character takes if it is valid in
* the encoding, or 0 if it is not.
*/
size_t pm_encoding_utf_8_alnum_char(const uint8_t *b, ptrdiff_t n);
/**
* Return true if the next character in the UTF-8 encoding if it is an uppercase
* character.
*
* @param b The bytes to read.
* @param n The number of bytes that can be read.
* @returns True if the next character is valid in the encoding and is an
* uppercase character, or false if it is not.
*/
bool pm_encoding_utf_8_isupper_char(const uint8_t *b, ptrdiff_t n);
/**
* This lookup table is referenced in both the UTF-8 encoding file and the
* parser directly in order to speed up the default encoding processing. It is
* used to indicate whether a character is alphabetical, alphanumeric, or
* uppercase in unicode mappings.
*/
extern const uint8_t pm_encoding_unicode_table[256];
/**
* These are all of the encodings that prism supports.
*/
typedef enum {
PM_ENCODING_UTF_8 = 0,
PM_ENCODING_US_ASCII,
PM_ENCODING_ASCII_8BIT,
PM_ENCODING_EUC_JP,
PM_ENCODING_WINDOWS_31J,
// We optionally support excluding the full set of encodings to only support the
// minimum necessary to process Ruby code without encoding comments.
#ifndef PRISM_ENCODING_EXCLUDE_FULL
PM_ENCODING_BIG5,
PM_ENCODING_BIG5_HKSCS,
PM_ENCODING_BIG5_UAO,
PM_ENCODING_CESU_8,
PM_ENCODING_CP51932,
PM_ENCODING_CP850,
PM_ENCODING_CP852,
PM_ENCODING_CP855,
PM_ENCODING_CP949,
PM_ENCODING_CP950,
PM_ENCODING_CP951,
PM_ENCODING_EMACS_MULE,
PM_ENCODING_EUC_JP_MS,
PM_ENCODING_EUC_JIS_2004,
PM_ENCODING_EUC_KR,
PM_ENCODING_EUC_TW,
PM_ENCODING_GB12345,
PM_ENCODING_GB18030,
PM_ENCODING_GB1988,
PM_ENCODING_GB2312,
PM_ENCODING_GBK,
PM_ENCODING_IBM437,
PM_ENCODING_IBM720,
PM_ENCODING_IBM737,
PM_ENCODING_IBM775,
PM_ENCODING_IBM852,
PM_ENCODING_IBM855,
PM_ENCODING_IBM857,
PM_ENCODING_IBM860,
PM_ENCODING_IBM861,
PM_ENCODING_IBM862,
PM_ENCODING_IBM863,
PM_ENCODING_IBM864,
PM_ENCODING_IBM865,
PM_ENCODING_IBM866,
PM_ENCODING_IBM869,
PM_ENCODING_ISO_8859_1,
PM_ENCODING_ISO_8859_2,
PM_ENCODING_ISO_8859_3,
PM_ENCODING_ISO_8859_4,
PM_ENCODING_ISO_8859_5,
PM_ENCODING_ISO_8859_6,
PM_ENCODING_ISO_8859_7,
PM_ENCODING_ISO_8859_8,
PM_ENCODING_ISO_8859_9,
PM_ENCODING_ISO_8859_10,
PM_ENCODING_ISO_8859_11,
PM_ENCODING_ISO_8859_13,
PM_ENCODING_ISO_8859_14,
PM_ENCODING_ISO_8859_15,
PM_ENCODING_ISO_8859_16,
PM_ENCODING_KOI8_R,
PM_ENCODING_KOI8_U,
PM_ENCODING_MAC_CENT_EURO,
PM_ENCODING_MAC_CROATIAN,
PM_ENCODING_MAC_CYRILLIC,
PM_ENCODING_MAC_GREEK,
PM_ENCODING_MAC_ICELAND,
PM_ENCODING_MAC_JAPANESE,
PM_ENCODING_MAC_ROMAN,
PM_ENCODING_MAC_ROMANIA,
PM_ENCODING_MAC_THAI,
PM_ENCODING_MAC_TURKISH,
PM_ENCODING_MAC_UKRAINE,
PM_ENCODING_SHIFT_JIS,
PM_ENCODING_SJIS_DOCOMO,
PM_ENCODING_SJIS_KDDI,
PM_ENCODING_SJIS_SOFTBANK,
PM_ENCODING_STATELESS_ISO_2022_JP,
PM_ENCODING_STATELESS_ISO_2022_JP_KDDI,
PM_ENCODING_TIS_620,
PM_ENCODING_UTF8_MAC,
PM_ENCODING_UTF8_DOCOMO,
PM_ENCODING_UTF8_KDDI,
PM_ENCODING_UTF8_SOFTBANK,
PM_ENCODING_WINDOWS_1250,
PM_ENCODING_WINDOWS_1251,
PM_ENCODING_WINDOWS_1252,
PM_ENCODING_WINDOWS_1253,
PM_ENCODING_WINDOWS_1254,
PM_ENCODING_WINDOWS_1255,
PM_ENCODING_WINDOWS_1256,
PM_ENCODING_WINDOWS_1257,
PM_ENCODING_WINDOWS_1258,
PM_ENCODING_WINDOWS_874,
#endif
PM_ENCODING_MAXIMUM
} pm_encoding_type_t;
/**
* This is the table of all of the encodings that prism supports.
*/
extern const pm_encoding_t pm_encodings[PM_ENCODING_MAXIMUM];
/**
* This is the default UTF-8 encoding. We need a reference to it to quickly
* create parsers.
*/
#define PM_ENCODING_UTF_8_ENTRY (&pm_encodings[PM_ENCODING_UTF_8])
/**
* This is the US-ASCII encoding. We need a reference to it to be able to
* compare against it when a string is being created because it could possibly
* need to fall back to ASCII-8BIT.
*/
#define PM_ENCODING_US_ASCII_ENTRY (&pm_encodings[PM_ENCODING_US_ASCII])
/**
* This is the ASCII-8BIT encoding. We need a reference to it so that pm_strpbrk
* can compare against it because invalid multibyte characters are not a thing
* in this encoding. It is also needed for handling Regexp encoding flags.
*/
#define PM_ENCODING_ASCII_8BIT_ENTRY (&pm_encodings[PM_ENCODING_ASCII_8BIT])
/**
* This is the EUC-JP encoding. We need a reference to it to quickly process
* regular expression modifiers.
*/
#define PM_ENCODING_EUC_JP_ENTRY (&pm_encodings[PM_ENCODING_EUC_JP])
/**
* This is the Windows-31J encoding. We need a reference to it to quickly
* process regular expression modifiers.
*/
#define PM_ENCODING_WINDOWS_31J_ENTRY (&pm_encodings[PM_ENCODING_WINDOWS_31J])
/**
* Parse the given name of an encoding and return a pointer to the corresponding
* encoding struct if one can be found, otherwise return NULL.
*
* @param start A pointer to the first byte of the name.
* @param end A pointer to the last byte of the name.
* @returns A pointer to the encoding struct if one is found, otherwise NULL.
*/
const pm_encoding_t * pm_encoding_find(const uint8_t *start, const uint8_t *end);
#endif

View File

@@ -0,0 +1,129 @@
/**
* @file node.h
*
* Functions related to nodes in the AST.
*/
#ifndef PRISM_NODE_H
#define PRISM_NODE_H
#include "prism/defines.h"
#include "prism/parser.h"
#include "prism/util/pm_buffer.h"
/**
* Loop through each node in the node list, writing each node to the given
* pm_node_t pointer.
*/
#define PM_NODE_LIST_FOREACH(list, index, node) \
for (size_t index = 0; index < (list)->size && ((node) = (list)->nodes[index]); index++)
/**
* Append a new node onto the end of the node list.
*
* @param list The list to append to.
* @param node The node to append.
*/
void pm_node_list_append(pm_node_list_t *list, pm_node_t *node);
/**
* Prepend a new node onto the beginning of the node list.
*
* @param list The list to prepend to.
* @param node The node to prepend.
*/
void pm_node_list_prepend(pm_node_list_t *list, pm_node_t *node);
/**
* Concatenate the given node list onto the end of the other node list.
*
* @param list The list to concatenate onto.
* @param other The list to concatenate.
*/
void pm_node_list_concat(pm_node_list_t *list, pm_node_list_t *other);
/**
* Free the internal memory associated with the given node list.
*
* @param list The list to free.
*/
void pm_node_list_free(pm_node_list_t *list);
/**
* Deallocate a node and all of its children.
*
* @param parser The parser that owns the node.
* @param node The node to deallocate.
*/
PRISM_EXPORTED_FUNCTION void pm_node_destroy(pm_parser_t *parser, struct pm_node *node);
/**
* Returns a string representation of the given node type.
*
* @param node_type The node type to convert to a string.
* @return A string representation of the given node type.
*/
PRISM_EXPORTED_FUNCTION const char * pm_node_type_to_str(pm_node_type_t node_type);
/**
* Visit each of the nodes in this subtree using the given visitor callback. The
* callback function will be called for each node in the subtree. If it returns
* false, then that node's children will not be visited. If it returns true,
* then the children will be visited. The data parameter is treated as an opaque
* pointer and is passed to the visitor callback for consumers to use as they
* see fit.
*
* As an example:
*
* ```c
* #include "prism.h"
*
* bool visit(const pm_node_t *node, void *data) {
* size_t *indent = (size_t *) data;
* for (size_t i = 0; i < *indent * 2; i++) putc(' ', stdout);
* printf("%s\n", pm_node_type_to_str(node->type));
*
* size_t next_indent = *indent + 1;
* size_t *next_data = &next_indent;
* pm_visit_child_nodes(node, visit, next_data);
*
* return false;
* }
*
* int main(void) {
* const char *source = "1 + 2; 3 + 4";
* size_t size = strlen(source);
*
* pm_parser_t parser;
* pm_options_t options = { 0 };
* pm_parser_init(&parser, (const uint8_t *) source, size, &options);
*
* size_t indent = 0;
* pm_node_t *node = pm_parse(&parser);
*
* size_t *data = &indent;
* pm_visit_node(node, visit, data);
*
* pm_node_destroy(&parser, node);
* pm_parser_free(&parser);
* return EXIT_SUCCESS;
* }
* ```
*
* @param node The root node to start visiting from.
* @param visitor The callback to call for each node in the subtree.
* @param data An opaque pointer that is passed to the visitor callback.
*/
PRISM_EXPORTED_FUNCTION void pm_visit_node(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data);
/**
* Visit the children of the given node with the given callback. This is the
* default behavior for walking the tree that is called from pm_visit_node if
* the callback returns true.
*
* @param node The node to visit the children of.
* @param visitor The callback to call for each child node.
* @param data An opaque pointer that is passed to the visitor callback.
*/
PRISM_EXPORTED_FUNCTION void pm_visit_child_nodes(const pm_node_t *node, bool (*visitor)(const pm_node_t *node, void *data), void *data);
#endif

View File

@@ -0,0 +1,442 @@
/**
* @file options.h
*
* The options that can be passed to parsing.
*/
#ifndef PRISM_OPTIONS_H
#define PRISM_OPTIONS_H
#include "prism/defines.h"
#include "prism/util/pm_char.h"
#include "prism/util/pm_string.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/**
* String literals should be made frozen.
*/
#define PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED ((int8_t) -1)
/**
* String literals may be frozen or mutable depending on the implementation
* default.
*/
#define PM_OPTIONS_FROZEN_STRING_LITERAL_UNSET ((int8_t) 0)
/**
* String literals should be made mutable.
*/
#define PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED ((int8_t) 1)
/**
* A scope of locals surrounding the code that is being parsed.
*/
typedef struct pm_options_scope {
/** The number of locals in the scope. */
size_t locals_count;
/** The names of the locals in the scope. */
pm_string_t *locals;
/** Flags for the set of forwarding parameters in this scope. */
uint8_t forwarding;
} pm_options_scope_t;
/** The default value for parameters. */
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_NONE = 0x0;
/** When the scope is fowarding with the * parameter. */
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS = 0x1;
/** When the scope is fowarding with the ** parameter. */
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS = 0x2;
/** When the scope is fowarding with the & parameter. */
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_BLOCK = 0x4;
/** When the scope is fowarding with the ... parameter. */
static const uint8_t PM_OPTIONS_SCOPE_FORWARDING_ALL = 0x8;
// Forward declaration needed by the callback typedef.
struct pm_options;
/**
* The callback called when additional switches are found in a shebang comment
* that need to be processed by the runtime.
*
* @param options The options struct that may be updated by this callback.
* Certain fields will be checked for changes, specifically encoding,
* command_line, and frozen_string_literal.
* @param source The source of the shebang comment.
* @param length The length of the source.
* @param shebang_callback_data Any additional data that should be passed along
* to the callback.
*/
typedef void (*pm_options_shebang_callback_t)(struct pm_options *options, const uint8_t *source, size_t length, void *shebang_callback_data);
/**
* The version of Ruby syntax that we should be parsing with. This is used to
* allow consumers to specify which behavior they want in case they need to
* parse in the same way as a specific version of CRuby would have.
*/
typedef enum {
/** The current version of prism. */
PM_OPTIONS_VERSION_LATEST = 0,
/** The vendored version of prism in CRuby 3.3.x. */
PM_OPTIONS_VERSION_CRUBY_3_3 = 1,
/** The vendored version of prism in CRuby 3.4.x. */
PM_OPTIONS_VERSION_CRUBY_3_4 = 2
} pm_options_version_t;
/**
* The options that can be passed to the parser.
*/
typedef struct pm_options {
/**
* The callback to call when additional switches are found in a shebang
* comment.
*/
pm_options_shebang_callback_t shebang_callback;
/**
* Any additional data that should be passed along to the shebang callback
* if one was set.
*/
void *shebang_callback_data;
/** The name of the file that is currently being parsed. */
pm_string_t filepath;
/**
* The line within the file that the parse starts on. This value is
* 1-indexed.
*/
int32_t line;
/**
* The name of the encoding that the source file is in. Note that this must
* correspond to a name that can be found with Encoding.find in Ruby.
*/
pm_string_t encoding;
/**
* The number of scopes surrounding the code that is being parsed.
*/
size_t scopes_count;
/**
* The scopes surrounding the code that is being parsed. For most parses
* this will be NULL, but for evals it will be the locals that are in scope
* surrounding the eval. Scopes are ordered from the outermost scope to the
* innermost one.
*/
pm_options_scope_t *scopes;
/**
* The version of prism that we should be parsing with. This is used to
* allow consumers to specify which behavior they want in case they need to
* parse exactly as a specific version of CRuby.
*/
pm_options_version_t version;
/** A bitset of the various options that were set on the command line. */
uint8_t command_line;
/**
* Whether or not the frozen string literal option has been set.
* May be:
* - PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED
* - PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED
* - PM_OPTIONS_FROZEN_STRING_LITERAL_UNSET
*/
int8_t frozen_string_literal;
/**
* Whether or not the encoding magic comments should be respected. This is a
* niche use-case where you want to parse a file with a specific encoding
* but ignore any encoding magic comments at the top of the file.
*/
bool encoding_locked;
/**
* When the file being parsed is the main script, the shebang will be
* considered for command-line flags (or for implicit -x). The caller needs
* to pass this information to the parser so that it can behave correctly.
*/
bool main_script;
/**
* When the file being parsed is considered a "partial" script, jumps will
* not be marked as errors if they are not contained within loops/blocks.
* This is used in the case that you're parsing a script that you know will
* be embedded inside another script later, but you do not have that context
* yet. For example, when parsing an ERB template that will be evaluated
* inside another script.
*/
bool partial_script;
/**
* Whether or not the parser should freeze the nodes that it creates. This
* makes it possible to have a deeply frozen AST that is safe to share
* between concurrency primitives.
*/
bool freeze;
} pm_options_t;
/**
* A bit representing whether or not the command line -a option was set. -a
* splits the input line $_ into $F.
*/
static const uint8_t PM_OPTIONS_COMMAND_LINE_A = 0x1;
/**
* A bit representing whether or not the command line -e option was set. -e
* allow the user to specify a script to be executed. This is necessary for
* prism to know because certain warnings are not generated when -e is used.
*/
static const uint8_t PM_OPTIONS_COMMAND_LINE_E = 0x2;
/**
* A bit representing whether or not the command line -l option was set. -l
* chomps the input line by default.
*/
static const uint8_t PM_OPTIONS_COMMAND_LINE_L = 0x4;
/**
* A bit representing whether or not the command line -n option was set. -n
* wraps the script in a while gets loop.
*/
static const uint8_t PM_OPTIONS_COMMAND_LINE_N = 0x8;
/**
* A bit representing whether or not the command line -p option was set. -p
* prints the value of $_ at the end of each loop.
*/
static const uint8_t PM_OPTIONS_COMMAND_LINE_P = 0x10;
/**
* A bit representing whether or not the command line -x option was set. -x
* searches the input file for a shebang that matches the current Ruby engine.
*/
static const uint8_t PM_OPTIONS_COMMAND_LINE_X = 0x20;
/**
* Set the shebang callback option on the given options struct.
*
* @param options The options struct to set the shebang callback on.
* @param shebang_callback The shebang callback to set.
* @param shebang_callback_data Any additional data that should be passed along
* to the callback.
*/
PRISM_EXPORTED_FUNCTION void pm_options_shebang_callback_set(pm_options_t *options, pm_options_shebang_callback_t shebang_callback, void *shebang_callback_data);
/**
* Set the filepath option on the given options struct.
*
* @param options The options struct to set the filepath on.
* @param filepath The filepath to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_filepath_set(pm_options_t *options, const char *filepath);
/**
* Set the line option on the given options struct.
*
* @param options The options struct to set the line on.
* @param line The line to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_line_set(pm_options_t *options, int32_t line);
/**
* Set the encoding option on the given options struct.
*
* @param options The options struct to set the encoding on.
* @param encoding The encoding to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_encoding_set(pm_options_t *options, const char *encoding);
/**
* Set the encoding_locked option on the given options struct.
*
* @param options The options struct to set the encoding_locked value on.
* @param encoding_locked The encoding_locked value to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_encoding_locked_set(pm_options_t *options, bool encoding_locked);
/**
* Set the frozen string literal option on the given options struct.
*
* @param options The options struct to set the frozen string literal value on.
* @param frozen_string_literal The frozen string literal value to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_frozen_string_literal_set(pm_options_t *options, bool frozen_string_literal);
/**
* Sets the command line option on the given options struct.
*
* @param options The options struct to set the command line option on.
* @param command_line The command_line value to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_command_line_set(pm_options_t *options, uint8_t command_line);
/**
* Set the version option on the given options struct by parsing the given
* string. If the string contains an invalid option, this returns false.
* Otherwise, it returns true.
*
* @param options The options struct to set the version on.
* @param version The version to set.
* @param length The length of the version string.
* @return Whether or not the version was parsed successfully.
*/
PRISM_EXPORTED_FUNCTION bool pm_options_version_set(pm_options_t *options, const char *version, size_t length);
/**
* Set the main script option on the given options struct.
*
* @param options The options struct to set the main script value on.
* @param main_script The main script value to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_main_script_set(pm_options_t *options, bool main_script);
/**
* Set the partial script option on the given options struct.
*
* @param options The options struct to set the partial script value on.
* @param partial_script The partial script value to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_partial_script_set(pm_options_t *options, bool partial_script);
/**
* Set the freeze option on the given options struct.
*
* @param options The options struct to set the freeze value on.
* @param freeze The freeze value to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_freeze_set(pm_options_t *options, bool freeze);
/**
* Allocate and zero out the scopes array on the given options struct.
*
* @param options The options struct to initialize the scopes array on.
* @param scopes_count The number of scopes to allocate.
* @return Whether or not the scopes array was initialized successfully.
*/
PRISM_EXPORTED_FUNCTION bool pm_options_scopes_init(pm_options_t *options, size_t scopes_count);
/**
* Return a pointer to the scope at the given index within the given options.
*
* @param options The options struct to get the scope from.
* @param index The index of the scope to get.
* @return A pointer to the scope at the given index.
*/
PRISM_EXPORTED_FUNCTION const pm_options_scope_t * pm_options_scope_get(const pm_options_t *options, size_t index);
/**
* Create a new options scope struct. This will hold a set of locals that are in
* scope surrounding the code that is being parsed.
*
* @param scope The scope struct to initialize.
* @param locals_count The number of locals to allocate.
* @return Whether or not the scope was initialized successfully.
*/
PRISM_EXPORTED_FUNCTION bool pm_options_scope_init(pm_options_scope_t *scope, size_t locals_count);
/**
* Return a pointer to the local at the given index within the given scope.
*
* @param scope The scope struct to get the local from.
* @param index The index of the local to get.
* @return A pointer to the local at the given index.
*/
PRISM_EXPORTED_FUNCTION const pm_string_t * pm_options_scope_local_get(const pm_options_scope_t *scope, size_t index);
/**
* Set the forwarding option on the given scope struct.
*
* @param scope The scope struct to set the forwarding on.
* @param forwarding The forwarding value to set.
*/
PRISM_EXPORTED_FUNCTION void pm_options_scope_forwarding_set(pm_options_scope_t *scope, uint8_t forwarding);
/**
* Free the internal memory associated with the options.
*
* @param options The options struct whose internal memory should be freed.
*/
PRISM_EXPORTED_FUNCTION void pm_options_free(pm_options_t *options);
/**
* Deserialize an options struct from the given binary string. This is used to
* pass options to the parser from an FFI call so that consumers of the library
* from an FFI perspective don't have to worry about the structure of our
* options structs. Since the source of these calls will be from Ruby
* implementation internals we assume it is from a trusted source.
*
* `data` is assumed to be a valid pointer pointing to well-formed data. The
* layout of this data should be the same every time, and is described below:
*
* | # bytes | field |
* | ------- | -------------------------- |
* | `4` | the length of the filepath |
* | ... | the filepath bytes |
* | `4` | the line number |
* | `4` | the length the encoding |
* | ... | the encoding bytes |
* | `1` | frozen string literal |
* | `1` | -p command line option |
* | `1` | -n command line option |
* | `1` | -l command line option |
* | `1` | -a command line option |
* | `1` | the version |
* | `1` | encoding locked |
* | `1` | main script |
* | `1` | partial script |
* | `1` | freeze |
* | `4` | the number of scopes |
* | ... | the scopes |
*
* The version field is an enum, so it should be one of the following values:
*
* | value | version |
* | ----- | ------------------------- |
* | `0` | use the latest version of prism |
* | `1` | use the version of prism that is vendored in CRuby 3.3.0 |
*
* Each scope is laid out as follows:
*
* | # bytes | field |
* | ------- | -------------------------- |
* | `4` | the number of locals |
* | `1` | the forwarding flags |
* | ... | the locals |
*
* Each local is laid out as follows:
*
* | # bytes | field |
* | ------- | -------------------------- |
* | `4` | the length of the local |
* | ... | the local bytes |
*
* Some additional things to note about this layout:
*
* * The filepath can have a length of 0, in which case we'll consider it an
* empty string.
* * The line number should be 0-indexed.
* * The encoding can have a length of 0, in which case we'll use the default
* encoding (UTF-8). If it's not 0, it should correspond to a name of an
* encoding that can be passed to `Encoding.find` in Ruby.
* * The frozen string literal, encoding locked, main script, and partial script
* fields are booleans, so their values should be either 0 or 1.
* * The number of scopes can be 0.
*
* @param options The options struct to deserialize into.
* @param data The binary string to deserialize from.
*/
void pm_options_read(pm_options_t *options, const char *data);
#endif

View File

@@ -0,0 +1,163 @@
/**
* @file pack.h
*
* A pack template string parser.
*/
#ifndef PRISM_PACK_H
#define PRISM_PACK_H
#include "prism/defines.h"
// We optionally support parsing String#pack templates. For systems that don't
// want or need this functionality, it can be turned off with the
// PRISM_EXCLUDE_PACK define.
#ifdef PRISM_EXCLUDE_PACK
void pm_pack_parse(void);
#else
#include <stdint.h>
#include <stdlib.h>
/** The version of the pack template language that we are parsing. */
typedef enum pm_pack_version {
PM_PACK_VERSION_3_2_0
} pm_pack_version;
/** The type of pack template we are parsing. */
typedef enum pm_pack_variant {
PM_PACK_VARIANT_PACK,
PM_PACK_VARIANT_UNPACK
} pm_pack_variant;
/** A directive within the pack template. */
typedef enum pm_pack_type {
PM_PACK_SPACE,
PM_PACK_COMMENT,
PM_PACK_INTEGER,
PM_PACK_UTF8,
PM_PACK_BER,
PM_PACK_FLOAT,
PM_PACK_STRING_SPACE_PADDED,
PM_PACK_STRING_NULL_PADDED,
PM_PACK_STRING_NULL_TERMINATED,
PM_PACK_STRING_MSB,
PM_PACK_STRING_LSB,
PM_PACK_STRING_HEX_HIGH,
PM_PACK_STRING_HEX_LOW,
PM_PACK_STRING_UU,
PM_PACK_STRING_MIME,
PM_PACK_STRING_BASE64,
PM_PACK_STRING_FIXED,
PM_PACK_STRING_POINTER,
PM_PACK_MOVE,
PM_PACK_BACK,
PM_PACK_NULL,
PM_PACK_END
} pm_pack_type;
/** The signness of a pack directive. */
typedef enum pm_pack_signed {
PM_PACK_UNSIGNED,
PM_PACK_SIGNED,
PM_PACK_SIGNED_NA
} pm_pack_signed;
/** The endianness of a pack directive. */
typedef enum pm_pack_endian {
PM_PACK_AGNOSTIC_ENDIAN,
PM_PACK_LITTLE_ENDIAN, // aka 'VAX', or 'V'
PM_PACK_BIG_ENDIAN, // aka 'network', or 'N'
PM_PACK_NATIVE_ENDIAN,
PM_PACK_ENDIAN_NA
} pm_pack_endian;
/** The size of an integer pack directive. */
typedef enum pm_pack_size {
PM_PACK_SIZE_SHORT,
PM_PACK_SIZE_INT,
PM_PACK_SIZE_LONG,
PM_PACK_SIZE_LONG_LONG,
PM_PACK_SIZE_8,
PM_PACK_SIZE_16,
PM_PACK_SIZE_32,
PM_PACK_SIZE_64,
PM_PACK_SIZE_P,
PM_PACK_SIZE_NA
} pm_pack_size;
/** The type of length of a pack directive. */
typedef enum pm_pack_length_type {
PM_PACK_LENGTH_FIXED,
PM_PACK_LENGTH_MAX,
PM_PACK_LENGTH_RELATIVE, // special case for unpack @*
PM_PACK_LENGTH_NA
} pm_pack_length_type;
/** The type of encoding for a pack template string. */
typedef enum pm_pack_encoding {
PM_PACK_ENCODING_START,
PM_PACK_ENCODING_ASCII_8BIT,
PM_PACK_ENCODING_US_ASCII,
PM_PACK_ENCODING_UTF_8
} pm_pack_encoding;
/** The result of parsing a pack template. */
typedef enum pm_pack_result {
PM_PACK_OK,
PM_PACK_ERROR_UNSUPPORTED_DIRECTIVE,
PM_PACK_ERROR_UNKNOWN_DIRECTIVE,
PM_PACK_ERROR_LENGTH_TOO_BIG,
PM_PACK_ERROR_BANG_NOT_ALLOWED,
PM_PACK_ERROR_DOUBLE_ENDIAN
} pm_pack_result;
/**
* Parse a single directive from a pack or unpack format string.
*
* @param variant (in) pack or unpack
* @param format (in, out) the start of the next directive to parse on calling,
* and advanced beyond the parsed directive on return, or as much of it as
* was consumed until an error was encountered
* @param format_end (in) the end of the format string
* @param type (out) the type of the directive
* @param signed_type (out) whether the value is signed
* @param endian (out) the endianness of the value
* @param size (out) the size of the value
* @param length_type (out) what kind of length is specified
* @param length (out) the length of the directive
* @param encoding (in, out) takes the current encoding of the string which
* would result from parsing the whole format string, and returns a possibly
* changed directive - the encoding should be `PM_PACK_ENCODING_START` when
* pm_pack_parse is called for the first directive in a format string
*
* @return `PM_PACK_OK` on success or `PM_PACK_ERROR_*` on error
* @note Consult Ruby documentation for the meaning of directives.
*/
PRISM_EXPORTED_FUNCTION pm_pack_result
pm_pack_parse(
pm_pack_variant variant,
const char **format,
const char *format_end,
pm_pack_type *type,
pm_pack_signed *signed_type,
pm_pack_endian *endian,
pm_pack_size *size,
pm_pack_length_type *length_type,
uint64_t *length,
pm_pack_encoding *encoding
);
/**
* Prism abstracts sizes away from the native system - this converts an abstract
* size to a native size.
*
* @param size The abstract size to convert.
* @return The native size.
*/
PRISM_EXPORTED_FUNCTION size_t pm_size_to_native(pm_pack_size size);
#endif
#endif

View File

@@ -0,0 +1,933 @@
/**
* @file parser.h
*
* The parser used to parse Ruby source.
*/
#ifndef PRISM_PARSER_H
#define PRISM_PARSER_H
#include "prism/defines.h"
#include "prism/ast.h"
#include "prism/encoding.h"
#include "prism/options.h"
#include "prism/static_literals.h"
#include "prism/util/pm_constant_pool.h"
#include "prism/util/pm_list.h"
#include "prism/util/pm_newline_list.h"
#include "prism/util/pm_string.h"
#include <stdbool.h>
/**
* This enum provides various bits that represent different kinds of states that
* the lexer can track. This is used to determine which kind of token to return
* based on the context of the parser.
*/
typedef enum {
PM_LEX_STATE_BIT_BEG,
PM_LEX_STATE_BIT_END,
PM_LEX_STATE_BIT_ENDARG,
PM_LEX_STATE_BIT_ENDFN,
PM_LEX_STATE_BIT_ARG,
PM_LEX_STATE_BIT_CMDARG,
PM_LEX_STATE_BIT_MID,
PM_LEX_STATE_BIT_FNAME,
PM_LEX_STATE_BIT_DOT,
PM_LEX_STATE_BIT_CLASS,
PM_LEX_STATE_BIT_LABEL,
PM_LEX_STATE_BIT_LABELED,
PM_LEX_STATE_BIT_FITEM
} pm_lex_state_bit_t;
/**
* This enum combines the various bits from the above enum into individual
* values that represent the various states of the lexer.
*/
typedef enum {
PM_LEX_STATE_NONE = 0,
PM_LEX_STATE_BEG = (1 << PM_LEX_STATE_BIT_BEG),
PM_LEX_STATE_END = (1 << PM_LEX_STATE_BIT_END),
PM_LEX_STATE_ENDARG = (1 << PM_LEX_STATE_BIT_ENDARG),
PM_LEX_STATE_ENDFN = (1 << PM_LEX_STATE_BIT_ENDFN),
PM_LEX_STATE_ARG = (1 << PM_LEX_STATE_BIT_ARG),
PM_LEX_STATE_CMDARG = (1 << PM_LEX_STATE_BIT_CMDARG),
PM_LEX_STATE_MID = (1 << PM_LEX_STATE_BIT_MID),
PM_LEX_STATE_FNAME = (1 << PM_LEX_STATE_BIT_FNAME),
PM_LEX_STATE_DOT = (1 << PM_LEX_STATE_BIT_DOT),
PM_LEX_STATE_CLASS = (1 << PM_LEX_STATE_BIT_CLASS),
PM_LEX_STATE_LABEL = (1 << PM_LEX_STATE_BIT_LABEL),
PM_LEX_STATE_LABELED = (1 << PM_LEX_STATE_BIT_LABELED),
PM_LEX_STATE_FITEM = (1 << PM_LEX_STATE_BIT_FITEM),
PM_LEX_STATE_BEG_ANY = PM_LEX_STATE_BEG | PM_LEX_STATE_MID | PM_LEX_STATE_CLASS,
PM_LEX_STATE_ARG_ANY = PM_LEX_STATE_ARG | PM_LEX_STATE_CMDARG,
PM_LEX_STATE_END_ANY = PM_LEX_STATE_END | PM_LEX_STATE_ENDARG | PM_LEX_STATE_ENDFN
} pm_lex_state_t;
/**
* The type of quote that a heredoc uses.
*/
typedef enum {
PM_HEREDOC_QUOTE_NONE,
PM_HEREDOC_QUOTE_SINGLE = '\'',
PM_HEREDOC_QUOTE_DOUBLE = '"',
PM_HEREDOC_QUOTE_BACKTICK = '`',
} pm_heredoc_quote_t;
/**
* The type of indentation that a heredoc uses.
*/
typedef enum {
PM_HEREDOC_INDENT_NONE,
PM_HEREDOC_INDENT_DASH,
PM_HEREDOC_INDENT_TILDE,
} pm_heredoc_indent_t;
/**
* All of the information necessary to store to lexing a heredoc.
*/
typedef struct {
/** A pointer to the start of the heredoc identifier. */
const uint8_t *ident_start;
/** The length of the heredoc identifier. */
size_t ident_length;
/** The type of quote that the heredoc uses. */
pm_heredoc_quote_t quote;
/** The type of indentation that the heredoc uses. */
pm_heredoc_indent_t indent;
} pm_heredoc_lex_mode_t;
/**
* When lexing Ruby source, the lexer has a small amount of state to tell which
* kind of token it is currently lexing. For example, when we find the start of
* a string, the first token that we return is a TOKEN_STRING_BEGIN token. After
* that the lexer is now in the PM_LEX_STRING mode, and will return tokens that
* are found as part of a string.
*/
typedef struct pm_lex_mode {
/** The type of this lex mode. */
enum {
/** This state is used when any given token is being lexed. */
PM_LEX_DEFAULT,
/**
* This state is used when we're lexing as normal but inside an embedded
* expression of a string.
*/
PM_LEX_EMBEXPR,
/**
* This state is used when we're lexing a variable that is embedded
* directly inside of a string with the # shorthand.
*/
PM_LEX_EMBVAR,
/** This state is used when you are inside the content of a heredoc. */
PM_LEX_HEREDOC,
/**
* This state is used when we are lexing a list of tokens, as in a %w
* word list literal or a %i symbol list literal.
*/
PM_LEX_LIST,
/**
* This state is used when a regular expression has been begun and we
* are looking for the terminator.
*/
PM_LEX_REGEXP,
/**
* This state is used when we are lexing a string or a string-like
* token, as in string content with either quote or an xstring.
*/
PM_LEX_STRING
} mode;
/** The data associated with this type of lex mode. */
union {
struct {
/** This keeps track of the nesting level of the list. */
size_t nesting;
/** Whether or not interpolation is allowed in this list. */
bool interpolation;
/**
* When lexing a list, it takes into account balancing the
* terminator if the terminator is one of (), [], {}, or <>.
*/
uint8_t incrementor;
/** This is the terminator of the list literal. */
uint8_t terminator;
/**
* This is the character set that should be used to delimit the
* tokens within the list.
*/
uint8_t breakpoints[11];
} list;
struct {
/**
* This keeps track of the nesting level of the regular expression.
*/
size_t nesting;
/**
* When lexing a regular expression, it takes into account balancing
* the terminator if the terminator is one of (), [], {}, or <>.
*/
uint8_t incrementor;
/** This is the terminator of the regular expression. */
uint8_t terminator;
/**
* This is the character set that should be used to delimit the
* tokens within the regular expression.
*/
uint8_t breakpoints[7];
} regexp;
struct {
/** This keeps track of the nesting level of the string. */
size_t nesting;
/** Whether or not interpolation is allowed in this string. */
bool interpolation;
/**
* Whether or not at the end of the string we should allow a :,
* which would indicate this was a dynamic symbol instead of a
* string.
*/
bool label_allowed;
/**
* When lexing a string, it takes into account balancing the
* terminator if the terminator is one of (), [], {}, or <>.
*/
uint8_t incrementor;
/**
* This is the terminator of the string. It is typically either a
* single or double quote.
*/
uint8_t terminator;
/**
* This is the character set that should be used to delimit the
* tokens within the string.
*/
uint8_t breakpoints[7];
} string;
struct {
/**
* All of the data necessary to lex a heredoc.
*/
pm_heredoc_lex_mode_t base;
/**
* This is the pointer to the character where lexing should resume
* once the heredoc has been completely processed.
*/
const uint8_t *next_start;
/**
* This is used to track the amount of common whitespace on each
* line so that we know how much to dedent each line in the case of
* a tilde heredoc.
*/
size_t *common_whitespace;
/** True if the previous token ended with a line continuation. */
bool line_continuation;
} heredoc;
} as;
/** The previous lex state so that it knows how to pop. */
struct pm_lex_mode *prev;
} pm_lex_mode_t;
/**
* We pre-allocate a certain number of lex states in order to avoid having to
* call malloc too many times while parsing. You really shouldn't need more than
* this because you only really nest deeply when doing string interpolation.
*/
#define PM_LEX_STACK_SIZE 4
/**
* The parser used to parse Ruby source.
*/
typedef struct pm_parser pm_parser_t;
/**
* While parsing, we keep track of a stack of contexts. This is helpful for
* error recovery so that we can pop back to a previous context when we hit a
* token that is understood by a parent context but not by the current context.
*/
typedef enum {
/** a null context, used for returning a value from a function */
PM_CONTEXT_NONE = 0,
/** a begin statement */
PM_CONTEXT_BEGIN,
/** an ensure statement with an explicit begin */
PM_CONTEXT_BEGIN_ENSURE,
/** a rescue else statement with an explicit begin */
PM_CONTEXT_BEGIN_ELSE,
/** a rescue statement with an explicit begin */
PM_CONTEXT_BEGIN_RESCUE,
/** expressions in block arguments using braces */
PM_CONTEXT_BLOCK_BRACES,
/** expressions in block arguments using do..end */
PM_CONTEXT_BLOCK_KEYWORDS,
/** an ensure statement within a do..end block */
PM_CONTEXT_BLOCK_ENSURE,
/** a rescue else statement within a do..end block */
PM_CONTEXT_BLOCK_ELSE,
/** a rescue statement within a do..end block */
PM_CONTEXT_BLOCK_RESCUE,
/** a case when statements */
PM_CONTEXT_CASE_WHEN,
/** a case in statements */
PM_CONTEXT_CASE_IN,
/** a class declaration */
PM_CONTEXT_CLASS,
/** an ensure statement within a class statement */
PM_CONTEXT_CLASS_ENSURE,
/** a rescue else statement within a class statement */
PM_CONTEXT_CLASS_ELSE,
/** a rescue statement within a class statement */
PM_CONTEXT_CLASS_RESCUE,
/** a method definition */
PM_CONTEXT_DEF,
/** an ensure statement within a method definition */
PM_CONTEXT_DEF_ENSURE,
/** a rescue else statement within a method definition */
PM_CONTEXT_DEF_ELSE,
/** a rescue statement within a method definition */
PM_CONTEXT_DEF_RESCUE,
/** a method definition's parameters */
PM_CONTEXT_DEF_PARAMS,
/** a defined? expression */
PM_CONTEXT_DEFINED,
/** a method definition's default parameter */
PM_CONTEXT_DEFAULT_PARAMS,
/** an else clause */
PM_CONTEXT_ELSE,
/** an elsif clause */
PM_CONTEXT_ELSIF,
/** an interpolated expression */
PM_CONTEXT_EMBEXPR,
/** a for loop */
PM_CONTEXT_FOR,
/** a for loop's index */
PM_CONTEXT_FOR_INDEX,
/** an if statement */
PM_CONTEXT_IF,
/** a lambda expression with braces */
PM_CONTEXT_LAMBDA_BRACES,
/** a lambda expression with do..end */
PM_CONTEXT_LAMBDA_DO_END,
/** an ensure statement within a lambda expression */
PM_CONTEXT_LAMBDA_ENSURE,
/** a rescue else statement within a lambda expression */
PM_CONTEXT_LAMBDA_ELSE,
/** a rescue statement within a lambda expression */
PM_CONTEXT_LAMBDA_RESCUE,
/** the predicate clause of a loop statement */
PM_CONTEXT_LOOP_PREDICATE,
/** the top level context */
PM_CONTEXT_MAIN,
/** a module declaration */
PM_CONTEXT_MODULE,
/** an ensure statement within a module statement */
PM_CONTEXT_MODULE_ENSURE,
/** a rescue else statement within a module statement */
PM_CONTEXT_MODULE_ELSE,
/** a rescue statement within a module statement */
PM_CONTEXT_MODULE_RESCUE,
/** a multiple target expression */
PM_CONTEXT_MULTI_TARGET,
/** a parenthesized expression */
PM_CONTEXT_PARENS,
/** an END block */
PM_CONTEXT_POSTEXE,
/** a predicate inside an if/elsif/unless statement */
PM_CONTEXT_PREDICATE,
/** a BEGIN block */
PM_CONTEXT_PREEXE,
/** a modifier rescue clause */
PM_CONTEXT_RESCUE_MODIFIER,
/** a singleton class definition */
PM_CONTEXT_SCLASS,
/** an ensure statement with a singleton class */
PM_CONTEXT_SCLASS_ENSURE,
/** a rescue else statement with a singleton class */
PM_CONTEXT_SCLASS_ELSE,
/** a rescue statement with a singleton class */
PM_CONTEXT_SCLASS_RESCUE,
/** a ternary expression */
PM_CONTEXT_TERNARY,
/** an unless statement */
PM_CONTEXT_UNLESS,
/** an until statement */
PM_CONTEXT_UNTIL,
/** a while statement */
PM_CONTEXT_WHILE,
} pm_context_t;
/** This is a node in a linked list of contexts. */
typedef struct pm_context_node {
/** The context that this node represents. */
pm_context_t context;
/** A pointer to the previous context in the linked list. */
struct pm_context_node *prev;
} pm_context_node_t;
/** This is the type of a comment that we've found while parsing. */
typedef enum {
PM_COMMENT_INLINE,
PM_COMMENT_EMBDOC
} pm_comment_type_t;
/**
* This is a node in the linked list of comments that we've found while parsing.
*
* @extends pm_list_node_t
*/
typedef struct pm_comment {
/** The embedded base node. */
pm_list_node_t node;
/** The location of the comment in the source. */
pm_location_t location;
/** The type of comment that we've found. */
pm_comment_type_t type;
} pm_comment_t;
/**
* This is a node in the linked list of magic comments that we've found while
* parsing.
*
* @extends pm_list_node_t
*/
typedef struct {
/** The embedded base node. */
pm_list_node_t node;
/** A pointer to the start of the key in the source. */
const uint8_t *key_start;
/** A pointer to the start of the value in the source. */
const uint8_t *value_start;
/** The length of the key in the source. */
uint32_t key_length;
/** The length of the value in the source. */
uint32_t value_length;
} pm_magic_comment_t;
/**
* When the encoding that is being used to parse the source is changed by prism,
* we provide the ability here to call out to a user-defined function.
*/
typedef void (*pm_encoding_changed_callback_t)(pm_parser_t *parser);
/**
* When you are lexing through a file, the lexer needs all of the information
* that the parser additionally provides (for example, the local table). So if
* you want to properly lex Ruby, you need to actually lex it in the context of
* the parser. In order to provide this functionality, we optionally allow a
* struct to be attached to the parser that calls back out to a user-provided
* callback when each token is lexed.
*/
typedef struct {
/**
* This opaque pointer is used to provide whatever information the user
* deemed necessary to the callback. In our case we use it to pass the array
* that the tokens get appended into.
*/
void *data;
/**
* This is the callback that is called when a token is lexed. It is passed
* the opaque data pointer, the parser, and the token that was lexed.
*/
void (*callback)(void *data, pm_parser_t *parser, pm_token_t *token);
} pm_lex_callback_t;
/** The type of shareable constant value that can be set. */
typedef uint8_t pm_shareable_constant_value_t;
static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_NONE = 0x0;
static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_LITERAL = PM_SHAREABLE_CONSTANT_NODE_FLAGS_LITERAL;
static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_EVERYTHING = PM_SHAREABLE_CONSTANT_NODE_FLAGS_EXPERIMENTAL_EVERYTHING;
static const pm_shareable_constant_value_t PM_SCOPE_SHAREABLE_CONSTANT_EXPERIMENTAL_COPY = PM_SHAREABLE_CONSTANT_NODE_FLAGS_EXPERIMENTAL_COPY;
/**
* This tracks an individual local variable in a certain lexical context, as
* well as the number of times is it read.
*/
typedef struct {
/** The name of the local variable. */
pm_constant_id_t name;
/** The location of the local variable in the source. */
pm_location_t location;
/** The index of the local variable in the local table. */
uint32_t index;
/** The number of times the local variable is read. */
uint32_t reads;
/** The hash of the local variable. */
uint32_t hash;
} pm_local_t;
/**
* This is a set of local variables in a certain lexical context (method, class,
* module, etc.). We need to track how many times these variables are read in
* order to warn if they only get written.
*/
typedef struct pm_locals {
/** The number of local variables in the set. */
uint32_t size;
/** The capacity of the local variables set. */
uint32_t capacity;
/** The nullable allocated memory for the local variables in the set. */
pm_local_t *locals;
} pm_locals_t;
/** The flags about scope parameters that can be set. */
typedef uint8_t pm_scope_parameters_t;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_NONE = 0x0;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_FORWARDING_POSITIONALS = 0x1;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_FORWARDING_KEYWORDS = 0x2;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_FORWARDING_BLOCK = 0x4;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_FORWARDING_ALL = 0x8;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_IMPLICIT_DISALLOWED = 0x10;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_NUMBERED_INNER = 0x20;
static const pm_scope_parameters_t PM_SCOPE_PARAMETERS_NUMBERED_FOUND = 0x40;
/**
* This struct represents a node in a linked list of scopes. Some scopes can see
* into their parent scopes, while others cannot.
*/
typedef struct pm_scope {
/** A pointer to the previous scope in the linked list. */
struct pm_scope *previous;
/** The IDs of the locals in the given scope. */
pm_locals_t locals;
/**
* This is a list of the implicit parameters contained within the block.
* These will be processed after the block is parsed to determine the kind
* of parameters node that should be used and to check if any errors need to
* be added.
*/
pm_node_list_t implicit_parameters;
/**
* This is a bitfield that indicates the parameters that are being used in
* this scope. It is a combination of the PM_SCOPE_PARAMETERS_* constants.
* There are three different kinds of parameters that can be used in a
* scope:
*
* - Ordinary parameters (e.g., def foo(bar); end)
* - Numbered parameters (e.g., def foo; _1; end)
* - The it parameter (e.g., def foo; it; end)
*
* If ordinary parameters are being used, then certain parameters can be
* forwarded to another method/structure. Those are indicated by four
* additional bits in the params field. For example, some combinations of:
*
* - def foo(*); end
* - def foo(**); end
* - def foo(&); end
* - def foo(...); end
*/
pm_scope_parameters_t parameters;
/**
* The current state of constant shareability for this scope. This is
* changed by magic shareable_constant_value comments.
*/
pm_shareable_constant_value_t shareable_constant;
/**
* A boolean indicating whether or not this scope can see into its parent.
* If closed is true, then the scope cannot see into its parent.
*/
bool closed;
} pm_scope_t;
/**
* A struct that represents a stack of boolean values.
*/
typedef uint32_t pm_state_stack_t;
/**
* This struct represents the overall parser. It contains a reference to the
* source file, as well as pointers that indicate where in the source it's
* currently parsing. It also contains the most recent and current token that
* it's considering.
*/
struct pm_parser {
/**
* The next node identifier that will be assigned. This is a unique
* identifier used to track nodes such that the syntax tree can be dropped
* but the node can be found through another parse.
*/
uint32_t node_id;
/** The current state of the lexer. */
pm_lex_state_t lex_state;
/** Tracks the current nesting of (), [], and {}. */
int enclosure_nesting;
/**
* Used to temporarily track the nesting of enclosures to determine if a {
* is the beginning of a lambda following the parameters of a lambda.
*/
int lambda_enclosure_nesting;
/**
* Used to track the nesting of braces to ensure we get the correct value
* when we are interpolating blocks with braces.
*/
int brace_nesting;
/**
* The stack used to determine if a do keyword belongs to the predicate of a
* while, until, or for loop.
*/
pm_state_stack_t do_loop_stack;
/**
* The stack used to determine if a do keyword belongs to the beginning of a
* block.
*/
pm_state_stack_t accepts_block_stack;
/** A stack of lex modes. */
struct {
/** The current mode of the lexer. */
pm_lex_mode_t *current;
/** The stack of lexer modes. */
pm_lex_mode_t stack[PM_LEX_STACK_SIZE];
/** The current index into the lexer mode stack. */
size_t index;
} lex_modes;
/** The pointer to the start of the source. */
const uint8_t *start;
/** The pointer to the end of the source. */
const uint8_t *end;
/** The previous token we were considering. */
pm_token_t previous;
/** The current token we're considering. */
pm_token_t current;
/**
* This is a special field set on the parser when we need the parser to jump
* to a specific location when lexing the next token, as opposed to just
* using the end of the previous token. Normally this is NULL.
*/
const uint8_t *next_start;
/**
* This field indicates the end of a heredoc whose identifier was found on
* the current line. If another heredoc is found on the same line, then this
* will be moved forward to the end of that heredoc. If no heredocs are
* found on a line then this is NULL.
*/
const uint8_t *heredoc_end;
/** The list of comments that have been found while parsing. */
pm_list_t comment_list;
/** The list of magic comments that have been found while parsing. */
pm_list_t magic_comment_list;
/**
* An optional location that represents the location of the __END__ marker
* and the rest of the content of the file. This content is loaded into the
* DATA constant when the file being parsed is the main file being executed.
*/
pm_location_t data_loc;
/** The list of warnings that have been found while parsing. */
pm_list_t warning_list;
/** The list of errors that have been found while parsing. */
pm_list_t error_list;
/** The current local scope. */
pm_scope_t *current_scope;
/** The current parsing context. */
pm_context_node_t *current_context;
/**
* The hash keys for the hash that is currently being parsed. This is not
* usually necessary because it can pass it down the various call chains,
* but in the event that you're parsing a hash that is being directly
* pushed into another hash with **, we need to share the hash keys so that
* we can warn for the nested hash as well.
*/
pm_static_literals_t *current_hash_keys;
/**
* The encoding functions for the current file is attached to the parser as
* it's parsing so that it can change with a magic comment.
*/
const pm_encoding_t *encoding;
/**
* When the encoding that is being used to parse the source is changed by
* prism, we provide the ability here to call out to a user-defined
* function.
*/
pm_encoding_changed_callback_t encoding_changed_callback;
/**
* This pointer indicates where a comment must start if it is to be
* considered an encoding comment.
*/
const uint8_t *encoding_comment_start;
/**
* This is an optional callback that can be attached to the parser that will
* be called whenever a new token is lexed by the parser.
*/
pm_lex_callback_t *lex_callback;
/**
* This is the path of the file being parsed. We use the filepath when
* constructing SourceFileNodes.
*/
pm_string_t filepath;
/**
* This constant pool keeps all of the constants defined throughout the file
* so that we can reference them later.
*/
pm_constant_pool_t constant_pool;
/** This is the list of newline offsets in the source file. */
pm_newline_list_t newline_list;
/**
* We want to add a flag to integer nodes that indicates their base. We only
* want to parse these once, but we don't have space on the token itself to
* communicate this information. So we store it here and pass it through
* when we find tokens that we need it for.
*/
pm_node_flags_t integer_base;
/**
* This string is used to pass information from the lexer to the parser. It
* is particularly necessary because of escape sequences.
*/
pm_string_t current_string;
/**
* The line number at the start of the parse. This will be used to offset
* the line numbers of all of the locations.
*/
int32_t start_line;
/**
* When a string-like expression is being lexed, any byte or escape sequence
* that resolves to a value whose top bit is set (i.e., >= 0x80) will
* explicitly set the encoding to the same encoding as the source.
* Alternatively, if a unicode escape sequence is used (e.g., \\u{80}) that
* resolves to a value whose top bit is set, then the encoding will be
* explicitly set to UTF-8.
*
* The _next_ time this happens, if the encoding that is about to become the
* explicitly set encoding does not match the previously set explicit
* encoding, a mixed encoding error will be emitted.
*
* When the expression is finished being lexed, the explicit encoding
* controls the encoding of the expression. For the most part this means
* that the expression will either be encoded in the source encoding or
* UTF-8. This holds for all encodings except US-ASCII. If the source is
* US-ASCII and an explicit encoding was set that was _not_ UTF-8, then the
* expression will be encoded as ASCII-8BIT.
*
* Note that if the expression is a list, different elements within the same
* list can have different encodings, so this will get reset between each
* element. Furthermore all of this only applies to lists that support
* interpolation, because otherwise escapes that could change the encoding
* are ignored.
*
* At first glance, it may make more sense for this to live on the lexer
* mode, but we need it here to communicate back to the parser for character
* literals that do not push a new lexer mode.
*/
const pm_encoding_t *explicit_encoding;
/**
* When parsing block exits (e.g., break, next, redo), we need to validate
* that they are in correct contexts. For the most part we can do this by
* looking at our parent contexts. However, modifier while and until
* expressions can change that context to make block exits valid. In these
* cases, we need to keep track of the block exits and then validate them
* after the expression has been parsed.
*
* We use a pointer here because we don't want to keep a whole list attached
* since this will only be used in the context of begin/end expressions.
*/
pm_node_list_t *current_block_exits;
/** The version of prism that we should use to parse. */
pm_options_version_t version;
/** The command line flags given from the options. */
uint8_t command_line;
/**
* Whether or not we have found a frozen_string_literal magic comment with
* a true or false value.
* May be:
* - PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED
* - PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED
* - PM_OPTIONS_FROZEN_STRING_LITERAL_UNSET
*/
int8_t frozen_string_literal;
/**
* Whether or not we are parsing an eval string. This impacts whether or not
* we should evaluate if block exits/yields are valid.
*/
bool parsing_eval;
/**
* Whether or not we are parsing a "partial" script, which is a script that
* will be evaluated in the context of another script, so we should not
* check jumps (next/break/etc.) for validity.
*/
bool partial_script;
/** Whether or not we're at the beginning of a command. */
bool command_start;
/** Whether or not we're currently recovering from a syntax error. */
bool recovering;
/**
* This is very specialized behavior for when you want to parse in a context
* that does not respect encoding comments. Its main use case is translating
* into the whitequark/parser AST which re-encodes source files in UTF-8
* before they are parsed and ignores encoding comments.
*/
bool encoding_locked;
/**
* Whether or not the encoding has been changed by a magic comment. We use
* this to provide a fast path for the lexer instead of going through the
* function pointer.
*/
bool encoding_changed;
/**
* This flag indicates that we are currently parsing a pattern matching
* expression and impacts that calculation of newlines.
*/
bool pattern_matching_newlines;
/** This flag indicates that we are currently parsing a keyword argument. */
bool in_keyword_arg;
/**
* Whether or not the parser has seen a token that has semantic meaning
* (i.e., a token that is not a comment or whitespace).
*/
bool semantic_token_seen;
/**
* True if the current regular expression being lexed contains only ASCII
* characters.
*/
bool current_regular_expression_ascii_only;
/**
* By default, Ruby always warns about mismatched indentation. This can be
* toggled with a magic comment.
*/
bool warn_mismatched_indentation;
};
#endif

View File

@@ -0,0 +1,34 @@
/**
* @file prettyprint.h
*
* An AST node pretty-printer.
*/
#ifndef PRISM_PRETTYPRINT_H
#define PRISM_PRETTYPRINT_H
#include "prism/defines.h"
#ifdef PRISM_EXCLUDE_PRETTYPRINT
void pm_prettyprint(void);
#else
#include <stdio.h>
#include "prism/ast.h"
#include "prism/parser.h"
#include "prism/util/pm_buffer.h"
/**
* Pretty-prints the AST represented by the given node to the given buffer.
*
* @param output_buffer The buffer to write the pretty-printed AST to.
* @param parser The parser that parsed the AST.
* @param node The root node of the AST to pretty-print.
*/
PRISM_EXPORTED_FUNCTION void pm_prettyprint(pm_buffer_t *output_buffer, const pm_parser_t *parser, const pm_node_t *node);
#endif
#endif

View File

@@ -0,0 +1,43 @@
/**
* @file regexp.h
*
* A regular expression parser.
*/
#ifndef PRISM_REGEXP_H
#define PRISM_REGEXP_H
#include "prism/defines.h"
#include "prism/parser.h"
#include "prism/encoding.h"
#include "prism/util/pm_memchr.h"
#include "prism/util/pm_string.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
/**
* This callback is called when a named capture group is found.
*/
typedef void (*pm_regexp_name_callback_t)(const pm_string_t *name, void *data);
/**
* This callback is called when a parse error is found.
*/
typedef void (*pm_regexp_error_callback_t)(const uint8_t *start, const uint8_t *end, const char *message, void *data);
/**
* Parse a regular expression.
*
* @param parser The parser that is currently being used.
* @param source The source code to parse.
* @param size The size of the source code.
* @param extended_mode Whether to parse the regular expression in extended mode.
* @param name_callback The optional callback to call when a named capture group is found.
* @param name_data The optional data to pass to the name callback.
* @param error_callback The callback to call when a parse error is found.
* @param error_data The data to pass to the error callback.
*/
PRISM_EXPORTED_FUNCTION void pm_regexp_parse(pm_parser_t *parser, const uint8_t *source, size_t size, bool extended_mode, pm_regexp_name_callback_t name_callback, void *name_data, pm_regexp_error_callback_t error_callback, void *error_data);
#endif

View File

@@ -0,0 +1,121 @@
/**
* @file static_literals.h
*
* A set of static literal nodes that can be checked for duplicates.
*/
#ifndef PRISM_STATIC_LITERALS_H
#define PRISM_STATIC_LITERALS_H
#include "prism/defines.h"
#include "prism/ast.h"
#include "prism/util/pm_newline_list.h"
#include <assert.h>
#include <stdbool.h>
/**
* An internal hash table for a set of nodes.
*/
typedef struct {
/** The array of nodes in the hash table. */
pm_node_t **nodes;
/** The size of the hash table. */
uint32_t size;
/** The space that has been allocated in the hash table. */
uint32_t capacity;
} pm_node_hash_t;
/**
* Certain sets of nodes (hash keys and when clauses) check for duplicate nodes
* to alert the user of potential issues. To do this, we keep a set of the nodes
* that have been seen so far, and compare whenever we find a new node.
*
* We bucket the nodes based on their type to minimize the number of comparisons
* that need to be performed.
*/
typedef struct {
/**
* This is the set of IntegerNode and SourceLineNode instances.
*/
pm_node_hash_t integer_nodes;
/**
* This is the set of FloatNode instances.
*/
pm_node_hash_t float_nodes;
/**
* This is the set of RationalNode and ImaginaryNode instances.
*/
pm_node_hash_t number_nodes;
/**
* This is the set of StringNode and SourceFileNode instances.
*/
pm_node_hash_t string_nodes;
/**
* This is the set of RegularExpressionNode instances.
*/
pm_node_hash_t regexp_nodes;
/**
* This is the set of SymbolNode instances.
*/
pm_node_hash_t symbol_nodes;
/**
* A pointer to the last TrueNode instance that was inserted, or NULL.
*/
pm_node_t *true_node;
/**
* A pointer to the last FalseNode instance that was inserted, or NULL.
*/
pm_node_t *false_node;
/**
* A pointer to the last NilNode instance that was inserted, or NULL.
*/
pm_node_t *nil_node;
/**
* A pointer to the last SourceEncodingNode instance that was inserted, or
* NULL.
*/
pm_node_t *source_encoding_node;
} pm_static_literals_t;
/**
* Add a node to the set of static literals.
*
* @param newline_list The list of newline offsets to use to calculate lines.
* @param start_line The line number that the parser starts on.
* @param literals The set of static literals to add the node to.
* @param node The node to add to the set.
* @param replace Whether to replace the previous node if one already exists.
* @return A pointer to the node that is being overwritten, if there is one.
*/
pm_node_t * pm_static_literals_add(const pm_newline_list_t *newline_list, int32_t start_line, pm_static_literals_t *literals, pm_node_t *node, bool replace);
/**
* Free the internal memory associated with the given static literals set.
*
* @param literals The set of static literals to free.
*/
void pm_static_literals_free(pm_static_literals_t *literals);
/**
* Create a string-based representation of the given static literal.
*
* @param buffer The buffer to write the string to.
* @param newline_list The list of newline offsets to use to calculate lines.
* @param start_line The line number that the parser starts on.
* @param encoding_name The name of the encoding of the source being parsed.
* @param node The node to create a string representation of.
*/
void pm_static_literal_inspect(pm_buffer_t *buffer, const pm_newline_list_t *newline_list, int32_t start_line, const char *encoding_name, const pm_node_t *node);
#endif

View File

@@ -0,0 +1,228 @@
/**
* @file pm_buffer.h
*
* A wrapper around a contiguous block of allocated memory.
*/
#ifndef PRISM_BUFFER_H
#define PRISM_BUFFER_H
#include "prism/defines.h"
#include "prism/util/pm_char.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/**
* A pm_buffer_t is a simple memory buffer that stores data in a contiguous
* block of memory.
*/
typedef struct {
/** The length of the buffer in bytes. */
size_t length;
/** The capacity of the buffer in bytes that has been allocated. */
size_t capacity;
/** A pointer to the start of the buffer. */
char *value;
} pm_buffer_t;
/**
* Return the size of the pm_buffer_t struct.
*
* @returns The size of the pm_buffer_t struct.
*/
PRISM_EXPORTED_FUNCTION size_t pm_buffer_sizeof(void);
/**
* Initialize a pm_buffer_t with the given capacity.
*
* @param buffer The buffer to initialize.
* @param capacity The capacity of the buffer.
* @returns True if the buffer was initialized successfully, false otherwise.
*/
bool pm_buffer_init_capacity(pm_buffer_t *buffer, size_t capacity);
/**
* Initialize a pm_buffer_t with its default values.
*
* @param buffer The buffer to initialize.
* @returns True if the buffer was initialized successfully, false otherwise.
*/
PRISM_EXPORTED_FUNCTION bool pm_buffer_init(pm_buffer_t *buffer);
/**
* Return the value of the buffer.
*
* @param buffer The buffer to get the value of.
* @returns The value of the buffer.
*/
PRISM_EXPORTED_FUNCTION char * pm_buffer_value(const pm_buffer_t *buffer);
/**
* Return the length of the buffer.
*
* @param buffer The buffer to get the length of.
* @returns The length of the buffer.
*/
PRISM_EXPORTED_FUNCTION size_t pm_buffer_length(const pm_buffer_t *buffer);
/**
* Append the given amount of space as zeroes to the buffer.
*
* @param buffer The buffer to append to.
* @param length The amount of space to append and zero.
*/
void pm_buffer_append_zeroes(pm_buffer_t *buffer, size_t length);
/**
* Append a formatted string to the buffer.
*
* @param buffer The buffer to append to.
* @param format The format string to append.
* @param ... The arguments to the format string.
*/
void pm_buffer_append_format(pm_buffer_t *buffer, const char *format, ...) PRISM_ATTRIBUTE_FORMAT(2, 3);
/**
* Append a string to the buffer.
*
* @param buffer The buffer to append to.
* @param value The string to append.
* @param length The length of the string to append.
*/
void pm_buffer_append_string(pm_buffer_t *buffer, const char *value, size_t length);
/**
* Append a list of bytes to the buffer.
*
* @param buffer The buffer to append to.
* @param value The bytes to append.
* @param length The length of the bytes to append.
*/
void pm_buffer_append_bytes(pm_buffer_t *buffer, const uint8_t *value, size_t length);
/**
* Append a single byte to the buffer.
*
* @param buffer The buffer to append to.
* @param value The byte to append.
*/
void pm_buffer_append_byte(pm_buffer_t *buffer, uint8_t value);
/**
* Append a 32-bit unsigned integer to the buffer as a variable-length integer.
*
* @param buffer The buffer to append to.
* @param value The integer to append.
*/
void pm_buffer_append_varuint(pm_buffer_t *buffer, uint32_t value);
/**
* Append a 32-bit signed integer to the buffer as a variable-length integer.
*
* @param buffer The buffer to append to.
* @param value The integer to append.
*/
void pm_buffer_append_varsint(pm_buffer_t *buffer, int32_t value);
/**
* Append a double to the buffer.
*
* @param buffer The buffer to append to.
* @param value The double to append.
*/
void pm_buffer_append_double(pm_buffer_t *buffer, double value);
/**
* Append a unicode codepoint to the buffer.
*
* @param buffer The buffer to append to.
* @param value The character to append.
* @returns True if the codepoint was valid and appended successfully, false
* otherwise.
*/
bool pm_buffer_append_unicode_codepoint(pm_buffer_t *buffer, uint32_t value);
/**
* The different types of escaping that can be performed by the buffer when
* appending a slice of Ruby source code.
*/
typedef enum {
PM_BUFFER_ESCAPING_RUBY,
PM_BUFFER_ESCAPING_JSON
} pm_buffer_escaping_t;
/**
* Append a slice of source code to the buffer.
*
* @param buffer The buffer to append to.
* @param source The source code to append.
* @param length The length of the source code to append.
* @param escaping The type of escaping to perform.
*/
void pm_buffer_append_source(pm_buffer_t *buffer, const uint8_t *source, size_t length, pm_buffer_escaping_t escaping);
/**
* Prepend the given string to the buffer.
*
* @param buffer The buffer to prepend to.
* @param value The string to prepend.
* @param length The length of the string to prepend.
*/
void pm_buffer_prepend_string(pm_buffer_t *buffer, const char *value, size_t length);
/**
* Concatenate one buffer onto another.
*
* @param destination The buffer to concatenate onto.
* @param source The buffer to concatenate.
*/
void pm_buffer_concat(pm_buffer_t *destination, const pm_buffer_t *source);
/**
* Clear the buffer by reducing its size to 0. This does not free the allocated
* memory, but it does allow the buffer to be reused.
*
* @param buffer The buffer to clear.
*/
void pm_buffer_clear(pm_buffer_t *buffer);
/**
* Strip the whitespace from the end of the buffer.
*
* @param buffer The buffer to strip.
*/
void pm_buffer_rstrip(pm_buffer_t *buffer);
/**
* Checks if the buffer includes the given value.
*
* @param buffer The buffer to check.
* @param value The value to check for.
* @returns The index of the first occurrence of the value in the buffer, or
* SIZE_MAX if the value is not found.
*/
size_t pm_buffer_index(const pm_buffer_t *buffer, char value);
/**
* Insert the given string into the buffer at the given index.
*
* @param buffer The buffer to insert into.
* @param index The index to insert at.
* @param value The string to insert.
* @param length The length of the string to insert.
*/
void pm_buffer_insert(pm_buffer_t *buffer, size_t index, const char *value, size_t length);
/**
* Free the memory associated with the buffer.
*
* @param buffer The buffer to free.
*/
PRISM_EXPORTED_FUNCTION void pm_buffer_free(pm_buffer_t *buffer);
#endif

View File

@@ -0,0 +1,204 @@
/**
* @file pm_char.h
*
* Functions for working with characters and strings.
*/
#ifndef PRISM_CHAR_H
#define PRISM_CHAR_H
#include "prism/defines.h"
#include "prism/util/pm_newline_list.h"
#include <stdbool.h>
#include <stddef.h>
/**
* Returns the number of characters at the start of the string that are
* whitespace. Disallows searching past the given maximum number of characters.
*
* @param string The string to search.
* @param length The maximum number of characters to search.
* @return The number of characters at the start of the string that are
* whitespace.
*/
size_t pm_strspn_whitespace(const uint8_t *string, ptrdiff_t length);
/**
* Returns the number of characters at the start of the string that are
* whitespace while also tracking the location of each newline. Disallows
* searching past the given maximum number of characters.
*
* @param string The string to search.
* @param length The maximum number of characters to search.
* @param newline_list The list of newlines to populate.
* @return The number of characters at the start of the string that are
* whitespace.
*/
size_t pm_strspn_whitespace_newlines(const uint8_t *string, ptrdiff_t length, pm_newline_list_t *newline_list);
/**
* Returns the number of characters at the start of the string that are inline
* whitespace. Disallows searching past the given maximum number of characters.
*
* @param string The string to search.
* @param length The maximum number of characters to search.
* @return The number of characters at the start of the string that are inline
* whitespace.
*/
size_t pm_strspn_inline_whitespace(const uint8_t *string, ptrdiff_t length);
/**
* Returns the number of characters at the start of the string that are decimal
* digits. Disallows searching past the given maximum number of characters.
*
* @param string The string to search.
* @param length The maximum number of characters to search.
* @return The number of characters at the start of the string that are decimal
* digits.
*/
size_t pm_strspn_decimal_digit(const uint8_t *string, ptrdiff_t length);
/**
* Returns the number of characters at the start of the string that are
* hexadecimal digits. Disallows searching past the given maximum number of
* characters.
*
* @param string The string to search.
* @param length The maximum number of characters to search.
* @return The number of characters at the start of the string that are
* hexadecimal digits.
*/
size_t pm_strspn_hexadecimal_digit(const uint8_t *string, ptrdiff_t length);
/**
* Returns the number of characters at the start of the string that are octal
* digits or underscores. Disallows searching past the given maximum number of
* characters.
*
* If multiple underscores are found in a row or if an underscore is
* found at the end of the number, then the invalid pointer is set to the index
* of the first invalid underscore.
*
* @param string The string to search.
* @param length The maximum number of characters to search.
* @param invalid The pointer to set to the index of the first invalid
* underscore.
* @return The number of characters at the start of the string that are octal
* digits or underscores.
*/
size_t pm_strspn_octal_number(const uint8_t *string, ptrdiff_t length, const uint8_t **invalid);
/**
* Returns the number of characters at the start of the string that are decimal
* digits or underscores. Disallows searching past the given maximum number of
* characters.
*
* If multiple underscores are found in a row or if an underscore is
* found at the end of the number, then the invalid pointer is set to the index
* of the first invalid underscore.
*
* @param string The string to search.
* @param length The maximum number of characters to search.
* @param invalid The pointer to set to the index of the first invalid
* underscore.
* @return The number of characters at the start of the string that are decimal
* digits or underscores.
*/
size_t pm_strspn_decimal_number(const uint8_t *string, ptrdiff_t length, const uint8_t **invalid);
/**
* Returns the number of characters at the start of the string that are
* hexadecimal digits or underscores. Disallows searching past the given maximum
* number of characters.
*
* If multiple underscores are found in a row or if an underscore is
* found at the end of the number, then the invalid pointer is set to the index
* of the first invalid underscore.
*
* @param string The string to search.
* @param length The maximum number of characters to search.
* @param invalid The pointer to set to the index of the first invalid
* underscore.
* @return The number of characters at the start of the string that are
* hexadecimal digits or underscores.
*/
size_t pm_strspn_hexadecimal_number(const uint8_t *string, ptrdiff_t length, const uint8_t **invalid);
/**
* Returns the number of characters at the start of the string that are regexp
* options. Disallows searching past the given maximum number of characters.
*
* @param string The string to search.
* @param length The maximum number of characters to search.
* @return The number of characters at the start of the string that are regexp
* options.
*/
size_t pm_strspn_regexp_option(const uint8_t *string, ptrdiff_t length);
/**
* Returns the number of characters at the start of the string that are binary
* digits or underscores. Disallows searching past the given maximum number of
* characters.
*
* If multiple underscores are found in a row or if an underscore is
* found at the end of the number, then the invalid pointer is set to the index
* of the first invalid underscore.
*
* @param string The string to search.
* @param length The maximum number of characters to search.
* @param invalid The pointer to set to the index of the first invalid
* underscore.
* @return The number of characters at the start of the string that are binary
* digits or underscores.
*/
size_t pm_strspn_binary_number(const uint8_t *string, ptrdiff_t length, const uint8_t **invalid);
/**
* Returns true if the given character is a whitespace character.
*
* @param b The character to check.
* @return True if the given character is a whitespace character.
*/
bool pm_char_is_whitespace(const uint8_t b);
/**
* Returns true if the given character is an inline whitespace character.
*
* @param b The character to check.
* @return True if the given character is an inline whitespace character.
*/
bool pm_char_is_inline_whitespace(const uint8_t b);
/**
* Returns true if the given character is a binary digit.
*
* @param b The character to check.
* @return True if the given character is a binary digit.
*/
bool pm_char_is_binary_digit(const uint8_t b);
/**
* Returns true if the given character is an octal digit.
*
* @param b The character to check.
* @return True if the given character is an octal digit.
*/
bool pm_char_is_octal_digit(const uint8_t b);
/**
* Returns true if the given character is a decimal digit.
*
* @param b The character to check.
* @return True if the given character is a decimal digit.
*/
bool pm_char_is_decimal_digit(const uint8_t b);
/**
* Returns true if the given character is a hexadecimal digit.
*
* @param b The character to check.
* @return True if the given character is a hexadecimal digit.
*/
bool pm_char_is_hexadecimal_digit(const uint8_t b);
#endif

View File

@@ -0,0 +1,218 @@
/**
* @file pm_constant_pool.h
*
* A data structure that stores a set of strings.
*
* Each string is assigned a unique id, which can be used to compare strings for
* equality. This comparison ends up being much faster than strcmp, since it
* only requires a single integer comparison.
*/
#ifndef PRISM_CONSTANT_POOL_H
#define PRISM_CONSTANT_POOL_H
#include "prism/defines.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
/**
* When we allocate constants into the pool, we reserve 0 to mean that the slot
* is not yet filled. This constant is reused in other places to indicate the
* lack of a constant id.
*/
#define PM_CONSTANT_ID_UNSET 0
/**
* A constant id is a unique identifier for a constant in the constant pool.
*/
typedef uint32_t pm_constant_id_t;
/**
* A list of constant IDs. Usually used to represent a set of locals.
*/
typedef struct {
/** The number of constant ids in the list. */
size_t size;
/** The number of constant ids that have been allocated in the list. */
size_t capacity;
/** The constant ids in the list. */
pm_constant_id_t *ids;
} pm_constant_id_list_t;
/**
* Initialize a list of constant ids.
*
* @param list The list to initialize.
*/
void pm_constant_id_list_init(pm_constant_id_list_t *list);
/**
* Initialize a list of constant ids with a given capacity.
*
* @param list The list to initialize.
* @param capacity The initial capacity of the list.
*/
void pm_constant_id_list_init_capacity(pm_constant_id_list_t *list, size_t capacity);
/**
* Append a constant id to a list of constant ids. Returns false if any
* potential reallocations fail.
*
* @param list The list to append to.
* @param id The id to append.
* @return Whether the append succeeded.
*/
bool pm_constant_id_list_append(pm_constant_id_list_t *list, pm_constant_id_t id);
/**
* Insert a constant id into a list of constant ids at the specified index.
*
* @param list The list to insert into.
* @param index The index at which to insert.
* @param id The id to insert.
*/
void pm_constant_id_list_insert(pm_constant_id_list_t *list, size_t index, pm_constant_id_t id);
/**
* Checks if the current constant id list includes the given constant id.
*
* @param list The list to check.
* @param id The id to check for.
* @return Whether the list includes the given id.
*/
bool pm_constant_id_list_includes(pm_constant_id_list_t *list, pm_constant_id_t id);
/**
* Free the memory associated with a list of constant ids.
*
* @param list The list to free.
*/
void pm_constant_id_list_free(pm_constant_id_list_t *list);
/**
* The type of bucket in the constant pool hash map. This determines how the
* bucket should be freed.
*/
typedef unsigned int pm_constant_pool_bucket_type_t;
/** By default, each constant is a slice of the source. */
static const pm_constant_pool_bucket_type_t PM_CONSTANT_POOL_BUCKET_DEFAULT = 0;
/** An owned constant is one for which memory has been allocated. */
static const pm_constant_pool_bucket_type_t PM_CONSTANT_POOL_BUCKET_OWNED = 1;
/** A constant constant is known at compile time. */
static const pm_constant_pool_bucket_type_t PM_CONSTANT_POOL_BUCKET_CONSTANT = 2;
/** A bucket in the hash map. */
typedef struct {
/** The incremental ID used for indexing back into the pool. */
unsigned int id: 30;
/** The type of the bucket, which determines how to free it. */
pm_constant_pool_bucket_type_t type: 2;
/** The hash of the bucket. */
uint32_t hash;
} pm_constant_pool_bucket_t;
/** A constant in the pool which effectively stores a string. */
typedef struct {
/** A pointer to the start of the string. */
const uint8_t *start;
/** The length of the string. */
size_t length;
} pm_constant_t;
/** The overall constant pool, which stores constants found while parsing. */
typedef struct {
/** The buckets in the hash map. */
pm_constant_pool_bucket_t *buckets;
/** The constants that are stored in the buckets. */
pm_constant_t *constants;
/** The number of buckets in the hash map. */
uint32_t size;
/** The number of buckets that have been allocated in the hash map. */
uint32_t capacity;
} pm_constant_pool_t;
/**
* Initialize a new constant pool with a given capacity.
*
* @param pool The pool to initialize.
* @param capacity The initial capacity of the pool.
* @return Whether the initialization succeeded.
*/
bool pm_constant_pool_init(pm_constant_pool_t *pool, uint32_t capacity);
/**
* Return a pointer to the constant indicated by the given constant id.
*
* @param pool The pool to get the constant from.
* @param constant_id The id of the constant to get.
* @return A pointer to the constant.
*/
pm_constant_t * pm_constant_pool_id_to_constant(const pm_constant_pool_t *pool, pm_constant_id_t constant_id);
/**
* Find a constant in a constant pool. Returns the id of the constant, or 0 if
* the constant is not found.
*
* @param pool The pool to find the constant in.
* @param start A pointer to the start of the constant.
* @param length The length of the constant.
* @return The id of the constant.
*/
pm_constant_id_t pm_constant_pool_find(const pm_constant_pool_t *pool, const uint8_t *start, size_t length);
/**
* Insert a constant into a constant pool that is a slice of a source string.
* Returns the id of the constant, or 0 if any potential calls to resize fail.
*
* @param pool The pool to insert the constant into.
* @param start A pointer to the start of the constant.
* @param length The length of the constant.
* @return The id of the constant.
*/
pm_constant_id_t pm_constant_pool_insert_shared(pm_constant_pool_t *pool, const uint8_t *start, size_t length);
/**
* Insert a constant into a constant pool from memory that is now owned by the
* constant pool. Returns the id of the constant, or 0 if any potential calls to
* resize fail.
*
* @param pool The pool to insert the constant into.
* @param start A pointer to the start of the constant.
* @param length The length of the constant.
* @return The id of the constant.
*/
pm_constant_id_t pm_constant_pool_insert_owned(pm_constant_pool_t *pool, uint8_t *start, size_t length);
/**
* Insert a constant into a constant pool from memory that is constant. Returns
* the id of the constant, or 0 if any potential calls to resize fail.
*
* @param pool The pool to insert the constant into.
* @param start A pointer to the start of the constant.
* @param length The length of the constant.
* @return The id of the constant.
*/
pm_constant_id_t pm_constant_pool_insert_constant(pm_constant_pool_t *pool, const uint8_t *start, size_t length);
/**
* Free the memory associated with a constant pool.
*
* @param pool The pool to free.
*/
void pm_constant_pool_free(pm_constant_pool_t *pool);
#endif

View File

@@ -0,0 +1,126 @@
/**
* @file pm_integer.h
*
* This module provides functions for working with arbitrary-sized integers.
*/
#ifndef PRISM_NUMBER_H
#define PRISM_NUMBER_H
#include "prism/defines.h"
#include "prism/util/pm_buffer.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/**
* A structure represents an arbitrary-sized integer.
*/
typedef struct {
/**
* The number of allocated values. length is set to 0 if the integer fits
* into uint32_t.
*/
size_t length;
/**
* List of 32-bit integers. Set to NULL if the integer fits into uint32_t.
*/
uint32_t *values;
/**
* Embedded value for small integer. This value is set to 0 if the value
* does not fit into uint32_t.
*/
uint32_t value;
/**
* Whether or not the integer is negative. It is stored this way so that a
* zeroed pm_integer_t is always positive zero.
*/
bool negative;
} pm_integer_t;
/**
* An enum controlling the base of an integer. It is expected that the base is
* already known before parsing the integer, even though it could be derived
* from the string itself.
*/
typedef enum {
/** The default decimal base, with no prefix. Leading 0s will be ignored. */
PM_INTEGER_BASE_DEFAULT,
/** The binary base, indicated by a 0b or 0B prefix. */
PM_INTEGER_BASE_BINARY,
/** The octal base, indicated by a 0, 0o, or 0O prefix. */
PM_INTEGER_BASE_OCTAL,
/** The decimal base, indicated by a 0d, 0D, or empty prefix. */
PM_INTEGER_BASE_DECIMAL,
/** The hexadecimal base, indicated by a 0x or 0X prefix. */
PM_INTEGER_BASE_HEXADECIMAL,
/**
* An unknown base, in which case pm_integer_parse will derive it based on
* the content of the string. This is less efficient and does more
* comparisons, so if callers know the base ahead of time, they should use
* that instead.
*/
PM_INTEGER_BASE_UNKNOWN
} pm_integer_base_t;
/**
* Parse an integer from a string. This assumes that the format of the integer
* has already been validated, as internal validation checks are not performed
* here.
*
* @param integer The integer to parse into.
* @param base The base of the integer.
* @param start The start of the string.
* @param end The end of the string.
*/
void pm_integer_parse(pm_integer_t *integer, pm_integer_base_t base, const uint8_t *start, const uint8_t *end);
/**
* Compare two integers. This function returns -1 if the left integer is less
* than the right integer, 0 if they are equal, and 1 if the left integer is
* greater than the right integer.
*
* @param left The left integer to compare.
* @param right The right integer to compare.
* @return The result of the comparison.
*/
int pm_integer_compare(const pm_integer_t *left, const pm_integer_t *right);
/**
* Reduce a ratio of integers to its simplest form.
*
* If either the numerator or denominator do not fit into a 32-bit integer, then
* this function is a no-op. In the future, we may consider reducing even the
* larger numbers, but for now we're going to keep it simple.
*
* @param numerator The numerator of the ratio.
* @param denominator The denominator of the ratio.
*/
void pm_integers_reduce(pm_integer_t *numerator, pm_integer_t *denominator);
/**
* Convert an integer to a decimal string.
*
* @param buffer The buffer to append the string to.
* @param integer The integer to convert to a string.
*/
PRISM_EXPORTED_FUNCTION void pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer);
/**
* Free the internal memory of an integer. This memory will only be allocated if
* the integer exceeds the size of a single node in the linked list.
*
* @param integer The integer to free.
*/
PRISM_EXPORTED_FUNCTION void pm_integer_free(pm_integer_t *integer);
#endif

View File

@@ -0,0 +1,97 @@
/**
* @file pm_list.h
*
* An abstract linked list.
*/
#ifndef PRISM_LIST_H
#define PRISM_LIST_H
#include "prism/defines.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
/**
* This struct represents an abstract linked list that provides common
* functionality. It is meant to be used any time a linked list is necessary to
* store data.
*
* The linked list itself operates off a set of pointers. Because the pointers
* are not necessarily sequential, they can be of any size. We use this fact to
* allow the consumer of this linked list to extend the node struct to include
* any data they want. This is done by using the pm_list_node_t as the first
* member of the struct.
*
* For example, if we want to store a list of integers, we can do the following:
*
* ```c
* typedef struct {
* pm_list_node_t node;
* int value;
* } pm_int_node_t;
*
* pm_list_t list = { 0 };
* pm_int_node_t *node = xmalloc(sizeof(pm_int_node_t));
* node->value = 5;
*
* pm_list_append(&list, &node->node);
* ```
*
* The pm_list_t struct is used to represent the overall linked list. It
* contains a pointer to the head and tail of the list. This allows for easy
* iteration and appending of new nodes.
*/
typedef struct pm_list_node {
/** A pointer to the next node in the list. */
struct pm_list_node *next;
} pm_list_node_t;
/**
* This represents the overall linked list. It keeps a pointer to the head and
* tail so that iteration is easy and pushing new nodes is easy.
*/
typedef struct {
/** The size of the list. */
size_t size;
/** A pointer to the head of the list. */
pm_list_node_t *head;
/** A pointer to the tail of the list. */
pm_list_node_t *tail;
} pm_list_t;
/**
* Returns true if the given list is empty.
*
* @param list The list to check.
* @return True if the given list is empty, otherwise false.
*/
PRISM_EXPORTED_FUNCTION bool pm_list_empty_p(pm_list_t *list);
/**
* Returns the size of the list.
*
* @param list The list to check.
* @return The size of the list.
*/
PRISM_EXPORTED_FUNCTION size_t pm_list_size(pm_list_t *list);
/**
* Append a node to the given list.
*
* @param list The list to append to.
* @param node The node to append.
*/
void pm_list_append(pm_list_t *list, pm_list_node_t *node);
/**
* Deallocate the internal state of the given list.
*
* @param list The list to free.
*/
PRISM_EXPORTED_FUNCTION void pm_list_free(pm_list_t *list);
#endif

View File

@@ -0,0 +1,29 @@
/**
* @file pm_memchr.h
*
* A custom memchr implementation.
*/
#ifndef PRISM_MEMCHR_H
#define PRISM_MEMCHR_H
#include "prism/defines.h"
#include "prism/encoding.h"
#include <stddef.h>
/**
* We need to roll our own memchr to handle cases where the encoding changes and
* we need to search for a character in a buffer that could be the trailing byte
* of a multibyte character.
*
* @param source The source string.
* @param character The character to search for.
* @param number The maximum number of bytes to search.
* @param encoding_changed Whether the encoding changed.
* @param encoding A pointer to the encoding.
* @return A pointer to the first occurrence of the character in the source
* string, or NULL if no such character exists.
*/
void * pm_memchr(const void *source, int character, size_t number, bool encoding_changed, const pm_encoding_t *encoding);
#endif

View File

@@ -0,0 +1,113 @@
/**
* @file pm_newline_list.h
*
* A list of byte offsets of newlines in a string.
*
* When compiling the syntax tree, it's necessary to know the line and column
* of many nodes. This is necessary to support things like error messages,
* tracepoints, etc.
*
* It's possible that we could store the start line, start column, end line, and
* end column on every node in addition to the offsets that we already store,
* but that would be quite a lot of memory overhead.
*/
#ifndef PRISM_NEWLINE_LIST_H
#define PRISM_NEWLINE_LIST_H
#include "prism/defines.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
/**
* A list of offsets of newlines in a string. The offsets are assumed to be
* sorted/inserted in ascending order.
*/
typedef struct {
/** A pointer to the start of the source string. */
const uint8_t *start;
/** The number of offsets in the list. */
size_t size;
/** The capacity of the list that has been allocated. */
size_t capacity;
/** The list of offsets. */
size_t *offsets;
} pm_newline_list_t;
/**
* A line and column in a string.
*/
typedef struct {
/** The line number. */
int32_t line;
/** The column number. */
uint32_t column;
} pm_line_column_t;
/**
* Initialize a new newline list with the given capacity. Returns true if the
* allocation of the offsets succeeds, otherwise returns false.
*
* @param list The list to initialize.
* @param start A pointer to the start of the source string.
* @param capacity The initial capacity of the list.
* @return True if the allocation of the offsets succeeds, otherwise false.
*/
bool pm_newline_list_init(pm_newline_list_t *list, const uint8_t *start, size_t capacity);
/**
* Clear out the newlines that have been appended to the list.
*
* @param list The list to clear.
*/
void
pm_newline_list_clear(pm_newline_list_t *list);
/**
* Append a new offset to the newline list. Returns true if the reallocation of
* the offsets succeeds (if one was necessary), otherwise returns false.
*
* @param list The list to append to.
* @param cursor A pointer to the offset to append.
* @return True if the reallocation of the offsets succeeds (if one was
* necessary), otherwise false.
*/
bool pm_newline_list_append(pm_newline_list_t *list, const uint8_t *cursor);
/**
* Returns the line of the given offset. If the offset is not in the list, the
* line of the closest offset less than the given offset is returned.
*
* @param list The list to search.
* @param cursor A pointer to the offset to search for.
* @param start_line The line to start counting from.
* @return The line of the given offset.
*/
int32_t pm_newline_list_line(const pm_newline_list_t *list, const uint8_t *cursor, int32_t start_line);
/**
* Returns the line and column of the given offset. If the offset is not in the
* list, the line and column of the closest offset less than the given offset
* are returned.
*
* @param list The list to search.
* @param cursor A pointer to the offset to search for.
* @param start_line The line to start counting from.
* @return The line and column of the given offset.
*/
pm_line_column_t pm_newline_list_line_column(const pm_newline_list_t *list, const uint8_t *cursor, int32_t start_line);
/**
* Free the internal memory allocated for the newline list.
*
* @param list The list to free.
*/
void pm_newline_list_free(pm_newline_list_t *list);
#endif

View File

@@ -0,0 +1,190 @@
/**
* @file pm_string.h
*
* A generic string type that can have various ownership semantics.
*/
#ifndef PRISM_STRING_H
#define PRISM_STRING_H
#include "prism/defines.h"
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
// The following headers are necessary to read files using demand paging.
#ifdef _WIN32
#include <windows.h>
#elif defined(_POSIX_MAPPED_FILES)
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#elif defined(PRISM_HAS_FILESYSTEM)
#include <fcntl.h>
#include <sys/stat.h>
#endif
/**
* A generic string type that can have various ownership semantics.
*/
typedef struct {
/** A pointer to the start of the string. */
const uint8_t *source;
/** The length of the string in bytes of memory. */
size_t length;
/** The type of the string. This field determines how the string should be freed. */
enum {
/** This string is a constant string, and should not be freed. */
PM_STRING_CONSTANT,
/** This is a slice of another string, and should not be freed. */
PM_STRING_SHARED,
/** This string owns its memory, and should be freed using `pm_string_free`. */
PM_STRING_OWNED,
#ifdef PRISM_HAS_MMAP
/** This string is a memory-mapped file, and should be freed using `pm_string_free`. */
PM_STRING_MAPPED
#endif
} type;
} pm_string_t;
/**
* Returns the size of the pm_string_t struct. This is necessary to allocate the
* correct amount of memory in the FFI backend.
*
* @return The size of the pm_string_t struct.
*/
PRISM_EXPORTED_FUNCTION size_t pm_string_sizeof(void);
/**
* Defines an empty string. This is useful for initializing a string that will
* be filled in later.
*/
#define PM_STRING_EMPTY ((pm_string_t) { .type = PM_STRING_CONSTANT, .source = NULL, .length = 0 })
/**
* Initialize a shared string that is based on initial input.
*
* @param string The string to initialize.
* @param start The start of the string.
* @param end The end of the string.
*/
void pm_string_shared_init(pm_string_t *string, const uint8_t *start, const uint8_t *end);
/**
* Initialize an owned string that is responsible for freeing allocated memory.
*
* @param string The string to initialize.
* @param source The source of the string.
* @param length The length of the string.
*/
void pm_string_owned_init(pm_string_t *string, uint8_t *source, size_t length);
/**
* Initialize a constant string that doesn't own its memory source.
*
* @param string The string to initialize.
* @param source The source of the string.
* @param length The length of the string.
*/
void pm_string_constant_init(pm_string_t *string, const char *source, size_t length);
/**
* Represents the result of calling pm_string_mapped_init or
* pm_string_file_init. We need this additional information because there is
* not a platform-agnostic way to indicate that the file that was attempted to
* be opened was a directory.
*/
typedef enum {
/** Indicates that the string was successfully initialized. */
PM_STRING_INIT_SUCCESS = 0,
/**
* Indicates a generic error from a string_*_init function, where the type
* of error should be read from `errno` or `GetLastError()`.
*/
PM_STRING_INIT_ERROR_GENERIC = 1,
/**
* Indicates that the file that was attempted to be opened was a directory.
*/
PM_STRING_INIT_ERROR_DIRECTORY = 2
} pm_string_init_result_t;
/**
* Read the file indicated by the filepath parameter into source and load its
* contents and size into the given `pm_string_t`. The given `pm_string_t`
* should be freed using `pm_string_free` when it is no longer used.
*
* We want to use demand paging as much as possible in order to avoid having to
* read the entire file into memory (which could be detrimental to performance
* for large files). This means that if we're on windows we'll use
* `MapViewOfFile`, on POSIX systems that have access to `mmap` we'll use
* `mmap`, and on other POSIX systems we'll use `read`.
*
* @param string The string to initialize.
* @param filepath The filepath to read.
* @return The success of the read, indicated by the value of the enum.
*/
PRISM_EXPORTED_FUNCTION pm_string_init_result_t pm_string_mapped_init(pm_string_t *string, const char *filepath);
/**
* Read the file indicated by the filepath parameter into source and load its
* contents and size into the given `pm_string_t`. The given `pm_string_t`
* should be freed using `pm_string_free` when it is no longer used.
*
* @param string The string to initialize.
* @param filepath The filepath to read.
* @return The success of the read, indicated by the value of the enum.
*/
PRISM_EXPORTED_FUNCTION pm_string_init_result_t pm_string_file_init(pm_string_t *string, const char *filepath);
/**
* Ensure the string is owned. If it is not, then reinitialize it as owned and
* copy over the previous source.
*
* @param string The string to ensure is owned.
*/
void pm_string_ensure_owned(pm_string_t *string);
/**
* Compare the underlying lengths and bytes of two strings. Returns 0 if the
* strings are equal, a negative number if the left string is less than the
* right string, and a positive number if the left string is greater than the
* right string.
*
* @param left The left string to compare.
* @param right The right string to compare.
* @return The comparison result.
*/
int pm_string_compare(const pm_string_t *left, const pm_string_t *right);
/**
* Returns the length associated with the string.
*
* @param string The string to get the length of.
* @return The length of the string.
*/
PRISM_EXPORTED_FUNCTION size_t pm_string_length(const pm_string_t *string);
/**
* Returns the start pointer associated with the string.
*
* @param string The string to get the start pointer of.
* @return The start pointer of the string.
*/
PRISM_EXPORTED_FUNCTION const uint8_t * pm_string_source(const pm_string_t *string);
/**
* Free the associated memory of the given string.
*
* @param string The string to free.
*/
PRISM_EXPORTED_FUNCTION void pm_string_free(pm_string_t *string);
#endif

View File

@@ -0,0 +1,32 @@
/**
* @file pm_strncasecmp.h
*
* A custom strncasecmp implementation.
*/
#ifndef PRISM_STRNCASECMP_H
#define PRISM_STRNCASECMP_H
#include "prism/defines.h"
#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
/**
* Compare two strings, ignoring case, up to the given length. Returns 0 if the
* strings are equal, a negative number if string1 is less than string2, or a
* positive number if string1 is greater than string2.
*
* Note that this is effectively our own implementation of strncasecmp, but it's
* not available on all of the platforms we want to support so we're rolling it
* here.
*
* @param string1 The first string to compare.
* @param string2 The second string to compare
* @param length The maximum number of characters to compare.
* @return 0 if the strings are equal, a negative number if string1 is less than
* string2, or a positive number if string1 is greater than string2.
*/
int pm_strncasecmp(const uint8_t *string1, const uint8_t *string2, size_t length);
#endif

View File

@@ -0,0 +1,46 @@
/**
* @file pm_strpbrk.h
*
* A custom strpbrk implementation.
*/
#ifndef PRISM_STRPBRK_H
#define PRISM_STRPBRK_H
#include "prism/defines.h"
#include "prism/diagnostic.h"
#include "prism/parser.h"
#include <stddef.h>
#include <string.h>
/**
* Here we have rolled our own version of strpbrk. The standard library strpbrk
* has undefined behavior when the source string is not null-terminated. We want
* to support strings that are not null-terminated because pm_parse does not
* have the contract that the string is null-terminated. (This is desirable
* because it means the extension can call pm_parse with the result of a call to
* mmap).
*
* The standard library strpbrk also does not support passing a maximum length
* to search. We want to support this for the reason mentioned above, but we
* also don't want it to stop on null bytes. Ruby actually allows null bytes
* within strings, comments, regular expressions, etc. So we need to be able to
* skip past them.
*
* Finally, we want to support encodings wherein the charset could contain
* characters that are trailing bytes of multi-byte characters. For example, in
* Shift-JIS, the backslash character can be a trailing byte. In that case we
* need to take a slower path and iterate one multi-byte character at a time.
*
* @param parser The parser.
* @param source The source to search.
* @param charset The charset to search for.
* @param length The maximum number of bytes to search.
* @param validate Whether to validate that the source string is valid in the
* current encoding of the parser.
* @return A pointer to the first character in the source string that is in the
* charset, or NULL if no such character exists.
*/
const uint8_t * pm_strpbrk(pm_parser_t *parser, const uint8_t *source, const uint8_t *charset, ptrdiff_t length, bool validate);
#endif

View File

@@ -0,0 +1,29 @@
/**
* @file version.h
*
* The version of the Prism library.
*/
#ifndef PRISM_VERSION_H
#define PRISM_VERSION_H
/**
* The major version of the Prism library as an int.
*/
#define PRISM_VERSION_MAJOR 1
/**
* The minor version of the Prism library as an int.
*/
#define PRISM_VERSION_MINOR 4
/**
* The patch version of the Prism library as an int.
*/
#define PRISM_VERSION_PATCH 0
/**
* The version of the Prism library as a constant string.
*/
#define PRISM_VERSION "1.4.0"
#endif

View File

@@ -0,0 +1,170 @@
# frozen_string_literal: true
Gem::Specification.new do |spec|
spec.name = "prism"
spec.version = "1.4.0"
spec.authors = ["Shopify"]
spec.email = ["ruby@shopify.com"]
spec.summary = "Prism Ruby parser"
spec.homepage = "https://github.com/ruby/prism"
spec.license = "MIT"
spec.required_ruby_version = ">= 2.7.0"
spec.require_paths = ["lib"]
spec.files = [
"BSDmakefile",
"CHANGELOG.md",
"CODE_OF_CONDUCT.md",
"CONTRIBUTING.md",
"LICENSE.md",
"Makefile",
"README.md",
"config.yml",
"docs/build_system.md",
"docs/configuration.md",
"docs/cruby_compilation.md",
"docs/design.md",
"docs/encoding.md",
"docs/fuzzing.md",
"docs/heredocs.md",
"docs/javascript.md",
"docs/local_variable_depth.md",
"docs/mapping.md",
"docs/parser_translation.md",
"docs/parsing_rules.md",
"docs/releasing.md",
"docs/relocation.md",
"docs/ripper_translation.md",
"docs/ruby_api.md",
"docs/ruby_parser_translation.md",
"docs/serialization.md",
"docs/testing.md",
"ext/prism/api_node.c",
"ext/prism/api_pack.c",
"ext/prism/extension.c",
"ext/prism/extension.h",
"include/prism.h",
"include/prism/ast.h",
"include/prism/defines.h",
"include/prism/diagnostic.h",
"include/prism/encoding.h",
"include/prism/node.h",
"include/prism/options.h",
"include/prism/pack.h",
"include/prism/parser.h",
"include/prism/prettyprint.h",
"include/prism/regexp.h",
"include/prism/static_literals.h",
"include/prism/util/pm_buffer.h",
"include/prism/util/pm_char.h",
"include/prism/util/pm_constant_pool.h",
"include/prism/util/pm_integer.h",
"include/prism/util/pm_list.h",
"include/prism/util/pm_memchr.h",
"include/prism/util/pm_newline_list.h",
"include/prism/util/pm_strncasecmp.h",
"include/prism/util/pm_string.h",
"include/prism/util/pm_strpbrk.h",
"include/prism/version.h",
"lib/prism.rb",
"lib/prism/compiler.rb",
"lib/prism/desugar_compiler.rb",
"lib/prism/dispatcher.rb",
"lib/prism/dot_visitor.rb",
"lib/prism/dsl.rb",
"lib/prism/ffi.rb",
"lib/prism/inspect_visitor.rb",
"lib/prism/lex_compat.rb",
"lib/prism/mutation_compiler.rb",
"lib/prism/node_ext.rb",
"lib/prism/node.rb",
"lib/prism/pack.rb",
"lib/prism/parse_result.rb",
"lib/prism/parse_result/comments.rb",
"lib/prism/parse_result/errors.rb",
"lib/prism/parse_result/newlines.rb",
"lib/prism/pattern.rb",
"lib/prism/polyfill/append_as_bytes.rb",
"lib/prism/polyfill/byteindex.rb",
"lib/prism/polyfill/unpack1.rb",
"lib/prism/reflection.rb",
"lib/prism/relocation.rb",
"lib/prism/serialize.rb",
"lib/prism/string_query.rb",
"lib/prism/translation.rb",
"lib/prism/translation/parser.rb",
"lib/prism/translation/parser33.rb",
"lib/prism/translation/parser34.rb",
"lib/prism/translation/parser35.rb",
"lib/prism/translation/parser/builder.rb",
"lib/prism/translation/parser/compiler.rb",
"lib/prism/translation/parser/lexer.rb",
"lib/prism/translation/ripper.rb",
"lib/prism/translation/ripper/sexp.rb",
"lib/prism/translation/ripper/shim.rb",
"lib/prism/translation/ruby_parser.rb",
"lib/prism/visitor.rb",
"prism.gemspec",
"rbi/prism.rbi",
"rbi/prism/compiler.rbi",
"rbi/prism/dsl.rbi",
"rbi/prism/inspect_visitor.rbi",
"rbi/prism/node_ext.rbi",
"rbi/prism/node.rbi",
"rbi/prism/parse_result.rbi",
"rbi/prism/reflection.rbi",
"rbi/prism/string_query.rbi",
"rbi/prism/translation/parser.rbi",
"rbi/prism/translation/parser33.rbi",
"rbi/prism/translation/parser34.rbi",
"rbi/prism/translation/parser35.rbi",
"rbi/prism/translation/ripper.rbi",
"rbi/prism/visitor.rbi",
"sig/prism.rbs",
"sig/prism/compiler.rbs",
"sig/prism/dispatcher.rbs",
"sig/prism/dot_visitor.rbs",
"sig/prism/dsl.rbs",
"sig/prism/inspect_visitor.rbs",
"sig/prism/lex_compat.rbs",
"sig/prism/mutation_compiler.rbs",
"sig/prism/node_ext.rbs",
"sig/prism/node.rbs",
"sig/prism/pack.rbs",
"sig/prism/parse_result.rbs",
"sig/prism/pattern.rbs",
"sig/prism/reflection.rbs",
"sig/prism/relocation.rbs",
"sig/prism/serialize.rbs",
"sig/prism/string_query.rbs",
"sig/prism/visitor.rbs",
"src/diagnostic.c",
"src/encoding.c",
"src/node.c",
"src/options.c",
"src/pack.c",
"src/prettyprint.c",
"src/prism.c",
"src/regexp.c",
"src/serialize.c",
"src/static_literals.c",
"src/token_type.c",
"src/util/pm_buffer.c",
"src/util/pm_char.c",
"src/util/pm_constant_pool.c",
"src/util/pm_integer.c",
"src/util/pm_list.c",
"src/util/pm_memchr.c",
"src/util/pm_newline_list.c",
"src/util/pm_string.c",
"src/util/pm_strncasecmp.c",
"src/util/pm_strpbrk.c"
]
spec.extensions = ["ext/prism/extconf.rb"]
spec.metadata["allowed_push_host"] = "https://rubygems.org"
spec.metadata["source_code_uri"] = "https://github.com/ruby/prism"
spec.metadata["changelog_uri"] = "https://github.com/ruby/prism/blob/main/CHANGELOG.md"
end

View File

@@ -0,0 +1,842 @@
/*----------------------------------------------------------------------------*/
/* This file is generated by the templates/template.rb script and should not */
/* be modified manually. See */
/* templates/src/diagnostic.c.erb */
/* if you are looking to modify the */
/* template */
/*----------------------------------------------------------------------------*/
#include "prism/diagnostic.h"
#define PM_DIAGNOSTIC_ID_MAX 319
/** This struct holds the data for each diagnostic. */
typedef struct {
/** The message associated with the diagnostic. */
const char* message;
/** The level associated with the diagnostic. */
uint8_t level;
} pm_diagnostic_data_t;
/**
* ## Message composition
*
* When composing an error message, use sentence fragments.
*
* Try describing the property of the code that caused the error, rather than
* the rule that is being violated. It may help to use a fragment that completes
* a sentence beginning, "the parser encountered (a) ...". If appropriate, add a
* description of the rule violation (or other helpful context) after a
* semicolon.
*
* For example:, instead of "control escape sequence cannot be doubled", prefer:
*
* > "invalid control escape sequence; control cannot be repeated"
*
* In some cases, where the failure is more general or syntax expectations are
* violated, it may make more sense to use a fragment that completes a sentence
* beginning, "the parser ...".
*
* For example:
*
* > "expected an expression after `(`"
* > "cannot parse the expression"
*
* ## Message style guide
*
* - Use articles like "a", "an", and "the" when appropriate.
* - e.g., prefer "cannot parse the expression" to "cannot parse expression".
* - Use the common name for tokens and nodes.
* - e.g., prefer "keyword splat" to "assoc splat"
* - e.g., prefer "embedded document" to "embdoc"
* - Do not capitalize the initial word of the message.
* - Use back ticks around token literals
* - e.g., "Expected a `=>` between the hash key and value"
* - Do not use `.` or other punctuation at the end of the message.
* - Do not use contractions like "can't". Prefer "cannot" to "can not".
* - For tokens that can have multiple meanings, reference the token and its meaning.
* - e.g., "`*` splat argument" is clearer and more complete than "splat argument" or "`*` argument"
*
* ## Error names (PM_ERR_*)
*
* - When appropriate, prefer node name to token name.
* - e.g., prefer "SPLAT" to "STAR" in the context of argument parsing.
* - Prefer token name to common name.
* - e.g., prefer "STAR" to "ASTERISK".
* - Try to order the words in the name from more general to more specific,
* - e.g., "INVALID_NUMBER_DECIMAL" is better than "DECIMAL_INVALID_NUMBER".
* - When in doubt, look for similar patterns and name them so that they are grouped when lexically
* sorted. See PM_ERR_ARGUMENT_NO_FORWARDING_* for an example.
*
* ## Level
*
* For errors, they are:
*
* * `PM_ERROR_LEVEL_SYNTAX` - Errors that should raise SyntaxError.
* * `PM_ERROR_LEVEL_ARGUMENT` - Errors that should raise ArgumentError.
* * `PM_ERROR_LEVEL_LOAD` - Errors that should raise LoadError.
*
* For warnings, they are:
*
* * `PM_WARNING_LEVEL_DEFAULT` - Warnings that appear for `ruby -c -e 'code'`.
* * `PM_WARNING_LEVEL_VERBOSE` - Warnings that appear with `-w`, as in `ruby -w -c -e 'code'`.
*/
static const pm_diagnostic_data_t diagnostic_messages[PM_DIAGNOSTIC_ID_MAX] = {
// Special error that can be replaced
[PM_ERR_CANNOT_PARSE_EXPRESSION] = { "cannot parse the expression", PM_ERROR_LEVEL_SYNTAX },
// Errors that should raise argument errors
[PM_ERR_INVALID_ENCODING_MAGIC_COMMENT] = { "unknown or invalid encoding in the magic comment", PM_ERROR_LEVEL_ARGUMENT },
// Errors that should raise load errors
[PM_ERR_SCRIPT_NOT_FOUND] = { "no Ruby script found in input", PM_ERROR_LEVEL_LOAD },
// Errors that should raise syntax errors
[PM_ERR_ALIAS_ARGUMENT] = { "invalid argument being passed to `alias`; expected a bare word, symbol, constant, or global variable", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ALIAS_ARGUMENT_NUMBERED_REFERENCE] = { "invalid argument being passed to `alias`; can't make alias for the number variables", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_AMPAMPEQ_MULTI_ASSIGN] = { "unexpected `&&=` in a multiple assignment", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_AFTER_BLOCK] = { "unexpected argument after a block argument", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES] = { "unexpected argument after `...`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_BARE_HASH] = { "unexpected bare hash argument", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_BLOCK_MULTI] = { "both block arg and actual block given; only one block is allowed", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_CONFLICT_AMPERSAND] = { "unexpected `&`; anonymous block parameter is also used within block", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_CONFLICT_STAR] = { "unexpected `*`; anonymous rest parameter is also used within block", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_CONFLICT_STAR_STAR] = { "unexpected `**`; anonymous keyword rest parameter is also used within block", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_FORMAL_CLASS] = { "invalid formal argument; formal argument cannot be a class variable", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_FORMAL_CONSTANT] = { "invalid formal argument; formal argument cannot be a constant", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_FORMAL_GLOBAL] = { "invalid formal argument; formal argument cannot be a global variable", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_FORMAL_IVAR] = { "invalid formal argument; formal argument cannot be an instance variable", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_FORWARDING_UNBOUND] = { "unexpected `...` in an non-parenthesized call", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_NO_FORWARDING_AMPERSAND] = { "unexpected `&`; no anonymous block parameter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES] = { "unexpected ... when the parent method is not forwarding", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_NO_FORWARDING_STAR] = { "unexpected `*`; no anonymous rest parameter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR] = { "unexpected `**`; no anonymous keyword rest parameter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT] = { "unexpected `*` splat argument after a `**` keyword splat argument", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT] = { "unexpected `*` splat argument after a `*` splat argument", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_TERM_PAREN] = { "unexpected %s; expected a `)` to close the arguments", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARGUMENT_UNEXPECTED_BLOCK] = { "unexpected '{' after a method call without parenthesis", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARRAY_ELEMENT] = { "expected an element for the array", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARRAY_EXPRESSION] = { "expected an expression for the array element", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARRAY_EXPRESSION_AFTER_STAR] = { "expected an expression after `*` in the array", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARRAY_SEPARATOR] = { "unexpected %s; expected a `,` separator for the array elements", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ARRAY_TERM] = { "unexpected %s; expected a `]` to close the array", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BEGIN_LONELY_ELSE] = { "unexpected `else` in `begin` block; else without rescue is useless", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BEGIN_TERM] = { "expected an `end` to close the `begin` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BEGIN_UPCASE_BRACE] = { "expected a `{` after `BEGIN`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BEGIN_UPCASE_TERM] = { "expected a `}` to close the `BEGIN` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BEGIN_UPCASE_TOPLEVEL] = { "BEGIN is permitted only at toplevel", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE] = { "expected a local variable name in the block parameters", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BLOCK_PARAM_PIPE_TERM] = { "expected the block parameters to end with `|`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BLOCK_TERM_BRACE] = { "expected a block beginning with `{` to end with `}`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_BLOCK_TERM_END] = { "expected a block beginning with `do` to end with `end`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CANNOT_PARSE_STRING_PART] = { "cannot parse the string part", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CASE_EXPRESSION_AFTER_CASE] = { "expected an expression after `case`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CASE_EXPRESSION_AFTER_WHEN] = { "expected an expression after `when`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CASE_MATCH_MISSING_PREDICATE] = { "expected a predicate for a case matching statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CASE_MISSING_CONDITIONS] = { "expected a `when` or `in` clause after `case`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CASE_TERM] = { "expected an `end` to close the `case` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CLASS_IN_METHOD] = { "unexpected class definition in method body", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CLASS_NAME] = { "unexpected constant path after `class`; class/module name must be CONSTANT", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CLASS_SUPERCLASS] = { "expected a superclass after `<`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CLASS_TERM] = { "expected an `end` to close the `class` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CLASS_UNEXPECTED_END] = { "unexpected `end`, expecting ';' or '\\n'", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CLASS_VARIABLE_BARE] = { "'@@' without identifiers is not allowed as a class variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CONDITIONAL_ELSIF_PREDICATE] = { "expected a predicate expression for the `elsif` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CONDITIONAL_IF_PREDICATE] = { "expected a predicate expression for the `if` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CONDITIONAL_PREDICATE_TERM] = { "expected `then` or `;` or '\\n'", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CONDITIONAL_TERM] = { "expected an `end` to close the conditional clause", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CONDITIONAL_TERM_ELSE] = { "expected an `end` to close the `else` clause", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CONDITIONAL_UNLESS_PREDICATE] = { "expected a predicate expression for the `unless` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CONDITIONAL_UNTIL_PREDICATE] = { "expected a predicate expression for the `until` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CONDITIONAL_WHILE_PREDICATE] = { "expected a predicate expression for the `while` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT] = { "expected a constant after the `::` operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_ENDLESS] = { "could not parse the endless method body", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_ENDLESS_SETTER] = { "invalid method name; a setter method cannot be defined in an endless method definition", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_NAME] = { "unexpected %s; expected a method name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_PARAMS_TERM] = { "expected a delimiter to close the parameters", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_PARAMS_TERM_PAREN] = { "unexpected %s; expected a `)` to close the parameters", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_RECEIVER] = { "expected a receiver for the method definition", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_RECEIVER_TERM] = { "expected a `.` or `::` after the receiver in a method definition", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEF_TERM] = { "expected an `end` to close the `def` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_DEFINED_EXPRESSION] = { "expected an expression after `defined?`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EMBDOC_TERM] = { "embedded document meets end of file", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EMBEXPR_END] = { "expected a `}` to close the embedded expression", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EMBVAR_INVALID] = { "invalid embedded variable", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_END_UPCASE_BRACE] = { "expected a `{` after `END`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_END_UPCASE_TERM] = { "expected a `}` to close the `END` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_CONTROL] = { "Invalid escape character syntax", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT] = { "invalid control escape sequence; control cannot be repeated", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_HEXADECIMAL] = { "invalid hex escape sequence", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_META] = { "Invalid escape character syntax", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_META_REPEAT] = { "invalid meta escape sequence; meta cannot be repeated", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_UNICODE] = { "invalid Unicode escape sequence", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS] = { "invalid Unicode escape sequence; Unicode cannot be combined with control or meta flags", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_UNICODE_LIST] = { "invalid Unicode list: %.*s", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL] = { "invalid Unicode escape sequence; Multiple codepoints at single character literal are disallowed", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_UNICODE_LONG] = { "invalid Unicode escape sequence; maximum length is 6 digits", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_UNICODE_SHORT] = { "too short escape sequence: %.*s", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_ESCAPE_INVALID_UNICODE_TERM] = { "unterminated Unicode escape", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_ARGUMENT] = { "unexpected %s; expected an argument", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EOL_AFTER_STATEMENT] = { "unexpected %s, expecting end-of-input", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ] = { "expected an expression after `&&=`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ] = { "expected an expression after `||=`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA] = { "expected an expression after `,`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL] = { "expected an expression after `=`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS] = { "expected an expression after `<<`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN] = { "expected an expression after `(`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR] = { "unexpected %s; expected an expression after the operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT] = { "expected an expression after `*` splat in an argument", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH] = { "expected an expression after `**` in a hash", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_EXPRESSION_AFTER_STAR] = { "expected an expression after `*`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_FOR_DELIMITER] = { "unexpected %s; expected a 'do', newline, or ';' after the 'for' loop collection", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_IDENT_REQ_PARAMETER] = { "expected an identifier for the required parameter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_IN_DELIMITER] = { "expected a delimiter after the patterns of an `in` clause", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_LPAREN_REQ_PARAMETER] = { "expected a `(` to start a required parameter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_MESSAGE] = { "unexpected %s; expecting a message to send to the receiver", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_RBRACKET] = { "expected a matching `]`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_RPAREN] = { "expected a matching `)`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_RPAREN_AFTER_MULTI] = { "expected a `)` after multiple assignment", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_RPAREN_REQ_PARAMETER] = { "expected a `)` to end a required parameter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_SINGLETON_CLASS_DELIMITER] = { "unexpected %s; expected a newline or a ';' after the singleton class", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_STRING_CONTENT] = { "expected string content after opening string delimiter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPECT_WHEN_DELIMITER] = { "expected a delimiter after the predicates of a `when` clause", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPRESSION_BARE_HASH] = { "unexpected bare hash in expression", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPRESSION_NOT_WRITABLE] = { "unexpected '='; target cannot be written", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPRESSION_NOT_WRITABLE_ENCODING] = { "Can't assign to __ENCODING__", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPRESSION_NOT_WRITABLE_FALSE] = { "Can't assign to false", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPRESSION_NOT_WRITABLE_FILE] = { "Can't assign to __FILE__", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPRESSION_NOT_WRITABLE_LINE] = { "Can't assign to __LINE__", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPRESSION_NOT_WRITABLE_NIL] = { "Can't assign to nil", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPRESSION_NOT_WRITABLE_NUMBERED] = { "Can't assign to numbered parameter %.2s", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPRESSION_NOT_WRITABLE_SELF] = { "Can't change the value of self", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_EXPRESSION_NOT_WRITABLE_TRUE] = { "Can't assign to true", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_FLOAT_PARSE] = { "could not parse the float '%.*s'", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_FOR_COLLECTION] = { "expected a collection after the `in` in a `for` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_FOR_INDEX] = { "expected an index after `for`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_FOR_IN] = { "expected an `in` after the index in a `for` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_FOR_TERM] = { "expected an `end` to close the `for` loop", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_GLOBAL_VARIABLE_BARE] = { "'$' without identifiers is not allowed as a global variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_HASH_EXPRESSION_AFTER_LABEL] = { "expected an expression after the label in a hash", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_HASH_KEY] = { "unexpected %s, expecting '}' or a key in the hash literal", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_HASH_ROCKET] = { "expected a `=>` between the hash key and value", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_HASH_TERM] = { "expected a `}` to close the hash literal", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_HASH_VALUE] = { "unexpected %s; expected a value in the hash literal", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_HEREDOC_IDENTIFIER] = { "unterminated here document identifier", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_HEREDOC_TERM] = { "unterminated heredoc; can't find string \"%.*s\" anywhere before EOF", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INCOMPLETE_QUESTION_MARK] = { "incomplete expression at `?`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INCOMPLETE_VARIABLE_CLASS_3_3] = { "`%.*s' is not allowed as a class variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INCOMPLETE_VARIABLE_CLASS] = { "'%.*s' is not allowed as a class variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INCOMPLETE_VARIABLE_INSTANCE_3_3] = { "`%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INCOMPLETE_VARIABLE_INSTANCE] = { "'%.*s' is not allowed as an instance variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INSTANCE_VARIABLE_BARE] = { "'@' without identifiers is not allowed as an instance variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_BLOCK_EXIT] = { "Invalid %s", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_COMMA] = { "invalid comma", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_ESCAPE_CHARACTER] = { "Invalid escape character syntax", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_FLOAT_EXPONENT] = { "invalid exponent", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_LOCAL_VARIABLE_READ] = { "identifier %.*s is not valid to get", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_LOCAL_VARIABLE_WRITE] = { "identifier %.*s is not valid to set", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_NUMBER_BINARY] = { "invalid binary number; numeric literal without digits", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_NUMBER_DECIMAL] = { "invalid decimal number; numeric literal without digits", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_NUMBER_FRACTION] = { "unexpected fraction part after numeric literal", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_NUMBER_HEXADECIMAL] = { "invalid hexadecimal number; numeric literal without digits", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_NUMBER_OCTAL] = { "invalid octal number; numeric literal without digits", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_NUMBER_UNDERSCORE_INNER] = { "invalid underscore placement in number", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_NUMBER_UNDERSCORE_TRAILING] = { "trailing '_' in number", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_CHARACTER] = { "Invalid char '\\x%02X' in expression", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_MULTIBYTE_CHAR] = { "invalid multibyte char (%s)", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_MULTIBYTE_CHARACTER] = { "invalid multibyte character 0x%X", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_MULTIBYTE_ESCAPE] = { "invalid multibyte escape: /%.*s/", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_PRINTABLE_CHARACTER] = { "invalid character `%c`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_PERCENT] = { "unknown type of %string", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_PERCENT_EOF] = { "unterminated quoted string meets end of file", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_RETRY_AFTER_ELSE] = { "Invalid retry after else", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_RETRY_AFTER_ENSURE] = { "Invalid retry after ensure", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_RETRY_WITHOUT_RESCUE] = { "Invalid retry without rescue", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_SYMBOL] = { "invalid symbol", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_VARIABLE_GLOBAL_3_3] = { "`%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_VARIABLE_GLOBAL] = { "'%.*s' is not allowed as a global variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_INVALID_YIELD] = { "Invalid yield", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_IT_NOT_ALLOWED_NUMBERED] = { "'it' is not allowed when a numbered parameter is already used", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_IT_NOT_ALLOWED_ORDINARY] = { "'it' is not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LAMBDA_OPEN] = { "expected a `do` keyword or a `{` to open the lambda block", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LAMBDA_TERM_BRACE] = { "expected a lambda block beginning with `{` to end with `}`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LAMBDA_TERM_END] = { "expected a lambda block beginning with `do` to end with `end`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LIST_I_LOWER_ELEMENT] = { "expected a symbol in a `%i` list", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LIST_I_LOWER_TERM] = { "unterminated list; expected a closing delimiter for the `%i`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LIST_I_UPPER_ELEMENT] = { "expected a symbol in a `%I` list", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LIST_I_UPPER_TERM] = { "unterminated list; expected a closing delimiter for the `%I`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LIST_W_LOWER_ELEMENT] = { "expected a string in a `%w` list", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LIST_W_LOWER_TERM] = { "unterminated list; expected a closing delimiter for the `%w`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LIST_W_UPPER_ELEMENT] = { "expected a string in a `%W` list", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_LIST_W_UPPER_TERM] = { "unterminated list; expected a closing delimiter for the `%W`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_MALLOC_FAILED] = { "failed to allocate memory", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_MIXED_ENCODING] = { "UTF-8 mixed within %s source", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_MODULE_IN_METHOD] = { "unexpected module definition in method body", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_MODULE_NAME] = { "unexpected constant path after `module`; class/module name must be CONSTANT", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_MODULE_TERM] = { "expected an `end` to close the `module` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_MULTI_ASSIGN_MULTI_SPLATS] = { "multiple splats in multiple assignment", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST] = { "unexpected '%.*s' resulting in multiple splats in multiple assignment", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NESTING_TOO_DEEP] = { "nesting too deep", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NO_LOCAL_VARIABLE] = { "%.*s: no such local variable", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NON_ASSOCIATIVE_OPERATOR] = { "unexpected %s; %s is a non-associative operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NOT_EXPRESSION] = { "expected an expression after `not`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBER_LITERAL_UNDERSCORE] = { "number literal ending with a `_`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBERED_PARAMETER_INNER_BLOCK] = { "numbered parameter is already used in inner block", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBERED_PARAMETER_IT] = { "numbered parameters are not allowed when 'it' is already used", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBERED_PARAMETER_ORDINARY] = { "numbered parameters are not allowed when an ordinary parameter is defined", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_NUMBERED_PARAMETER_OUTER_BLOCK] = { "numbered parameter is already used in outer block", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_OPERATOR_MULTI_ASSIGN] = { "unexpected operator for a multiple assignment", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_OPERATOR_WRITE_ARGUMENTS] = { "unexpected operator after a call with arguments", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_OPERATOR_WRITE_BLOCK] = { "unexpected operator after a call with a block", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI] = { "unexpected multiple `**` splat parameters", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_BLOCK_MULTI] = { "multiple block parameters; only one block is allowed", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_CIRCULAR] = { "circular argument reference - %.*s", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_FORWARDING_AFTER_REST] = { "... after rest argument", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_METHOD_NAME] = { "unexpected name for a parameter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_NAME_DUPLICATED] = { "duplicated argument name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_NO_DEFAULT] = { "expected a default value for the parameter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_NO_DEFAULT_KW] = { "expected a default value for the keyword parameter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_NUMBERED_RESERVED] = { "%.2s is reserved for numbered parameters", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_ORDER] = { "unexpected parameter order", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_SPLAT_MULTI] = { "unexpected multiple `*` splat parameters", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_STAR] = { "unexpected parameter `*`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_UNEXPECTED_FWD] = { "unexpected `...` in parameters", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_WILD_LOOSE_COMMA] = { "unexpected `,` in parameters", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PARAMETER_UNEXPECTED_NO_KW] = { "unexpected **nil; no keywords marker disallowed after keywords", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_ARRAY_MULTIPLE_RESTS] = { "unexpected multiple '*' rest patterns in an array pattern", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_CAPTURE_DUPLICATE] = { "duplicated variable name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET] = { "expected a pattern expression after the `[` operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA] = { "expected a pattern expression after `,`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET] = { "expected a pattern expression after `=>`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_EXPRESSION_AFTER_IN] = { "expected a pattern expression after the `in` keyword", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_EXPRESSION_AFTER_KEY] = { "expected a pattern expression after the key", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN] = { "expected a pattern expression after the `(` operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_EXPRESSION_AFTER_PIN] = { "expected a pattern expression after the `^` pin operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE] = { "expected a pattern expression after the `|` operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE] = { "expected a pattern expression after the range operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_EXPRESSION_AFTER_REST] = { "unexpected pattern expression after the `**` expression", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_FIND_MISSING_INNER] = { "find patterns need at least one required inner pattern", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_HASH_IMPLICIT] = { "unexpected implicit hash in pattern; use '{' to delineate", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_HASH_KEY] = { "unexpected %s; expected a key in the hash pattern", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_HASH_KEY_DUPLICATE] = { "duplicated key name", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_HASH_KEY_INTERPOLATED] = { "symbol literal with interpolation is not allowed", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_HASH_KEY_LABEL] = { "expected a label as the key in the hash pattern", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_HASH_KEY_LOCALS] = { "key must be valid as local variables", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_IDENT_AFTER_HROCKET] = { "expected an identifier after the `=>` operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_LABEL_AFTER_COMMA] = { "expected a label after the `,` in the hash pattern", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_REST] = { "unexpected rest pattern", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_TERM_BRACE] = { "expected a `}` to close the pattern expression", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_TERM_BRACKET] = { "expected a `]` to close the pattern expression", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PATTERN_TERM_PAREN] = { "expected a `)` to close the pattern expression", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN] = { "unexpected `||=` in a multiple assignment", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_REGEXP_ENCODING_OPTION_MISMATCH] = { "regexp encoding option '%c' differs from source encoding '%s'", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_REGEXP_INCOMPAT_CHAR_ENCODING] = { "incompatible character encoding: /%.*s/", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_REGEXP_NON_ESCAPED_MBC] = { "/.../n has a non escaped non ASCII character in non ASCII-8BIT script: /%.*s/", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_REGEXP_INVALID_UNICODE_RANGE] = { "invalid Unicode range: /%.*s/", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_REGEXP_PARSE_ERROR] = { "%s", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_REGEXP_UNKNOWN_OPTIONS] = { "unknown regexp %s - %.*s", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_REGEXP_TERM] = { "unterminated regexp meets end of file; expected a closing delimiter", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_REGEXP_UTF8_CHAR_NON_UTF8_REGEXP] = { "UTF-8 character in non UTF-8 regexp: /%s/", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_RESCUE_EXPRESSION] = { "expected a rescued expression", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_RESCUE_MODIFIER_VALUE] = { "expected a value after the `rescue` modifier", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_RESCUE_TERM] = { "expected a closing delimiter for the `rescue` clause", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_RESCUE_VARIABLE] = { "expected an exception variable after `=>` in a rescue statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_RETURN_INVALID] = { "Invalid return in class/module body", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_SINGLETON_FOR_LITERALS] = { "cannot define singleton method for literals", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_STATEMENT_ALIAS] = { "unexpected an `alias` at a non-statement position", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_STATEMENT_POSTEXE_END] = { "unexpected an `END` at a non-statement position", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_STATEMENT_PREEXE_BEGIN] = { "unexpected a `BEGIN` at a non-statement position", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_STATEMENT_UNDEF] = { "unexpected an `undef` at a non-statement position", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_STRING_CONCATENATION] = { "expected a string for concatenation", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_STRING_INTERPOLATED_TERM] = { "unterminated string; expected a closing delimiter for the interpolated string", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_STRING_LITERAL_EOF] = { "unterminated string meets end of file", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_STRING_LITERAL_TERM] = { "unexpected %s, expected a string literal terminator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_SYMBOL_INVALID] = { "invalid symbol", PM_ERROR_LEVEL_SYNTAX }, // TODO expected symbol? prism.c ~9719
[PM_ERR_SYMBOL_TERM_DYNAMIC] = { "unterminated quoted string; expected a closing delimiter for the dynamic symbol", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_SYMBOL_TERM_INTERPOLATED] = { "unterminated symbol; expected a closing delimiter for the interpolated symbol", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_TERNARY_COLON] = { "expected a `:` after the true expression of a ternary operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_TERNARY_EXPRESSION_FALSE] = { "expected an expression after `:` in the ternary operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_TERNARY_EXPRESSION_TRUE] = { "expected an expression after `?` in the ternary operator", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNARY_RECEIVER] = { "unexpected %s, expected a receiver for unary `%c`", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNARY_DISALLOWED] = { "unexpected %s; unary calls are not allowed in this context", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNDEF_ARGUMENT] = { "invalid argument being passed to `undef`; expected a bare word, constant, or symbol argument", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNEXPECTED_BLOCK_ARGUMENT] = { "block argument should not be given", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNEXPECTED_INDEX_BLOCK] = { "unexpected block arg given in index assignment; blocks are not allowed in index assignment expressions", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNEXPECTED_INDEX_KEYWORDS] = { "unexpected keyword arg given in index assignment; keywords are not allowed in index assignment expressions", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNEXPECTED_LABEL] = { "unexpected label", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNEXPECTED_MULTI_WRITE] = { "unexpected multiple assignment; multiple assignment is not allowed in this context", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNEXPECTED_RANGE_OPERATOR] = { "unexpected range operator; .. and ... are non-associative and cannot be chained", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNEXPECTED_SAFE_NAVIGATION] = { "&. inside multiple assignment destination", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT] = { "unexpected %s, assuming it is closing the parent %s", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNEXPECTED_TOKEN_IGNORE] = { "unexpected %s, ignoring it", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_UNTIL_TERM] = { "expected an `end` to close the `until` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_VOID_EXPRESSION] = { "unexpected void value expression", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_WHILE_TERM] = { "expected an `end` to close the `while` statement", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_WRITE_TARGET_IN_METHOD] = { "dynamic constant assignment", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_WRITE_TARGET_READONLY] = { "Can't set variable %.*s", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_WRITE_TARGET_UNEXPECTED] = { "unexpected write target", PM_ERROR_LEVEL_SYNTAX },
[PM_ERR_XSTRING_TERM] = { "expected a closing delimiter for the `%x` or backtick string", PM_ERROR_LEVEL_SYNTAX },
// Warnings
[PM_WARN_AMBIGUOUS_BINARY_OPERATOR] = { "'%s' after local variable or literal is interpreted as binary operator even though it seems like %s", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS] = { "ambiguous first argument; put parentheses or a space even after `-` operator", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS] = { "ambiguous first argument; put parentheses or a space even after `+` operator", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_AMBIGUOUS_PREFIX_AMPERSAND] = { "ambiguous `&` has been interpreted as an argument prefix", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_AMBIGUOUS_PREFIX_STAR] = { "ambiguous `*` has been interpreted as an argument prefix", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_AMBIGUOUS_PREFIX_STAR_STAR] = { "ambiguous `**` has been interpreted as an argument prefix", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_AMBIGUOUS_SLASH] = { "ambiguous `/`; wrap regexp in parentheses or add a space after `/` operator", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_COMPARISON_AFTER_COMPARISON] = { "comparison '%.*s' after comparison", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_DOT_DOT_DOT_EOL] = { "... at EOL, should be parenthesized?", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_DUPLICATED_HASH_KEY] = { "key %.*s is duplicated and overwritten on line %" PRIi32, PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_DUPLICATED_WHEN_CLAUSE] = { "'when' clause on line %" PRIi32 " duplicates 'when' clause on line %" PRIi32 " and is ignored", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_EQUAL_IN_CONDITIONAL_3_3] = { "found `= literal' in conditional, should be ==", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_EQUAL_IN_CONDITIONAL] = { "found '= literal' in conditional, should be ==", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_END_IN_METHOD] = { "END in method; use at_exit", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_FLOAT_OUT_OF_RANGE] = { "Float %.*s%s out of range", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_IGNORED_FROZEN_STRING_LITERAL] = { "'frozen_string_literal' is ignored after any tokens", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_INDENTATION_MISMATCH] = { "mismatched indentations at '%.*s' with '%.*s' at %" PRIi32, PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_INTEGER_IN_FLIP_FLOP] = { "integer literal in flip-flop", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_INVALID_CHARACTER] = { "invalid character syntax; use %s%s%s", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_INVALID_MAGIC_COMMENT_VALUE] = { "invalid value for %.*s: %.*s", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_INVALID_NUMBERED_REFERENCE] = { "'%.*s' is too big for a number variable, always nil", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_KEYWORD_EOL] = { "`%.*s` at the end of line without an expression", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_LITERAL_IN_CONDITION_DEFAULT] = { "%sliteral in %s", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_LITERAL_IN_CONDITION_VERBOSE] = { "%sliteral in %s", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_SHAREABLE_CONSTANT_VALUE_LINE] = { "'shareable_constant_value' is ignored unless in comment-only line", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_SHEBANG_CARRIAGE_RETURN] = { "shebang line ending with \\r may cause problems", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_UNEXPECTED_CARRIAGE_RETURN] = { "encountered \\r in middle of line, treated as a mere space", PM_WARNING_LEVEL_DEFAULT },
[PM_WARN_UNREACHABLE_STATEMENT] = { "statement not reached", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_UNUSED_LOCAL_VARIABLE] = { "assigned but unused variable - %.*s", PM_WARNING_LEVEL_VERBOSE },
[PM_WARN_VOID_STATEMENT] = { "possibly useless use of %.*s in void context", PM_WARNING_LEVEL_VERBOSE }
};
/**
* Get the human-readable name of the given diagnostic ID.
*/
const char *
pm_diagnostic_id_human(pm_diagnostic_id_t diag_id) {
switch (diag_id) {
case PM_ERR_ALIAS_ARGUMENT: return "alias_argument";
case PM_ERR_ALIAS_ARGUMENT_NUMBERED_REFERENCE: return "alias_argument_numbered_reference";
case PM_ERR_AMPAMPEQ_MULTI_ASSIGN: return "ampampeq_multi_assign";
case PM_ERR_ARGUMENT_AFTER_BLOCK: return "argument_after_block";
case PM_ERR_ARGUMENT_AFTER_FORWARDING_ELLIPSES: return "argument_after_forwarding_ellipses";
case PM_ERR_ARGUMENT_BARE_HASH: return "argument_bare_hash";
case PM_ERR_ARGUMENT_BLOCK_FORWARDING: return "argument_block_forwarding";
case PM_ERR_ARGUMENT_BLOCK_MULTI: return "argument_block_multi";
case PM_ERR_ARGUMENT_CONFLICT_AMPERSAND: return "argument_conflict_ampersand";
case PM_ERR_ARGUMENT_CONFLICT_STAR: return "argument_conflict_star";
case PM_ERR_ARGUMENT_CONFLICT_STAR_STAR: return "argument_conflict_star_star";
case PM_ERR_ARGUMENT_FORMAL_CLASS: return "argument_formal_class";
case PM_ERR_ARGUMENT_FORMAL_CONSTANT: return "argument_formal_constant";
case PM_ERR_ARGUMENT_FORMAL_GLOBAL: return "argument_formal_global";
case PM_ERR_ARGUMENT_FORMAL_IVAR: return "argument_formal_ivar";
case PM_ERR_ARGUMENT_FORWARDING_UNBOUND: return "argument_forwarding_unbound";
case PM_ERR_ARGUMENT_NO_FORWARDING_AMPERSAND: return "argument_no_forwarding_ampersand";
case PM_ERR_ARGUMENT_NO_FORWARDING_ELLIPSES: return "argument_no_forwarding_ellipses";
case PM_ERR_ARGUMENT_NO_FORWARDING_STAR: return "argument_no_forwarding_star";
case PM_ERR_ARGUMENT_NO_FORWARDING_STAR_STAR: return "argument_no_forwarding_star_star";
case PM_ERR_ARGUMENT_SPLAT_AFTER_ASSOC_SPLAT: return "argument_splat_after_assoc_splat";
case PM_ERR_ARGUMENT_SPLAT_AFTER_SPLAT: return "argument_splat_after_splat";
case PM_ERR_ARGUMENT_TERM_PAREN: return "argument_term_paren";
case PM_ERR_ARGUMENT_UNEXPECTED_BLOCK: return "argument_unexpected_block";
case PM_ERR_ARRAY_ELEMENT: return "array_element";
case PM_ERR_ARRAY_EXPRESSION: return "array_expression";
case PM_ERR_ARRAY_EXPRESSION_AFTER_STAR: return "array_expression_after_star";
case PM_ERR_ARRAY_SEPARATOR: return "array_separator";
case PM_ERR_ARRAY_TERM: return "array_term";
case PM_ERR_BEGIN_LONELY_ELSE: return "begin_lonely_else";
case PM_ERR_BEGIN_TERM: return "begin_term";
case PM_ERR_BEGIN_UPCASE_BRACE: return "begin_upcase_brace";
case PM_ERR_BEGIN_UPCASE_TERM: return "begin_upcase_term";
case PM_ERR_BEGIN_UPCASE_TOPLEVEL: return "begin_upcase_toplevel";
case PM_ERR_BLOCK_PARAM_LOCAL_VARIABLE: return "block_param_local_variable";
case PM_ERR_BLOCK_PARAM_PIPE_TERM: return "block_param_pipe_term";
case PM_ERR_BLOCK_TERM_BRACE: return "block_term_brace";
case PM_ERR_BLOCK_TERM_END: return "block_term_end";
case PM_ERR_CANNOT_PARSE_EXPRESSION: return "cannot_parse_expression";
case PM_ERR_CANNOT_PARSE_STRING_PART: return "cannot_parse_string_part";
case PM_ERR_CASE_EXPRESSION_AFTER_CASE: return "case_expression_after_case";
case PM_ERR_CASE_EXPRESSION_AFTER_WHEN: return "case_expression_after_when";
case PM_ERR_CASE_MATCH_MISSING_PREDICATE: return "case_match_missing_predicate";
case PM_ERR_CASE_MISSING_CONDITIONS: return "case_missing_conditions";
case PM_ERR_CASE_TERM: return "case_term";
case PM_ERR_CLASS_IN_METHOD: return "class_in_method";
case PM_ERR_CLASS_NAME: return "class_name";
case PM_ERR_CLASS_SUPERCLASS: return "class_superclass";
case PM_ERR_CLASS_TERM: return "class_term";
case PM_ERR_CLASS_UNEXPECTED_END: return "class_unexpected_end";
case PM_ERR_CLASS_VARIABLE_BARE: return "class_variable_bare";
case PM_ERR_CONDITIONAL_ELSIF_PREDICATE: return "conditional_elsif_predicate";
case PM_ERR_CONDITIONAL_IF_PREDICATE: return "conditional_if_predicate";
case PM_ERR_CONDITIONAL_PREDICATE_TERM: return "conditional_predicate_term";
case PM_ERR_CONDITIONAL_TERM: return "conditional_term";
case PM_ERR_CONDITIONAL_TERM_ELSE: return "conditional_term_else";
case PM_ERR_CONDITIONAL_UNLESS_PREDICATE: return "conditional_unless_predicate";
case PM_ERR_CONDITIONAL_UNTIL_PREDICATE: return "conditional_until_predicate";
case PM_ERR_CONDITIONAL_WHILE_PREDICATE: return "conditional_while_predicate";
case PM_ERR_CONSTANT_PATH_COLON_COLON_CONSTANT: return "constant_path_colon_colon_constant";
case PM_ERR_DEF_ENDLESS: return "def_endless";
case PM_ERR_DEF_ENDLESS_SETTER: return "def_endless_setter";
case PM_ERR_DEF_NAME: return "def_name";
case PM_ERR_DEF_PARAMS_TERM: return "def_params_term";
case PM_ERR_DEF_PARAMS_TERM_PAREN: return "def_params_term_paren";
case PM_ERR_DEF_RECEIVER: return "def_receiver";
case PM_ERR_DEF_RECEIVER_TERM: return "def_receiver_term";
case PM_ERR_DEF_TERM: return "def_term";
case PM_ERR_DEFINED_EXPRESSION: return "defined_expression";
case PM_ERR_EMBDOC_TERM: return "embdoc_term";
case PM_ERR_EMBEXPR_END: return "embexpr_end";
case PM_ERR_EMBVAR_INVALID: return "embvar_invalid";
case PM_ERR_END_UPCASE_BRACE: return "end_upcase_brace";
case PM_ERR_END_UPCASE_TERM: return "end_upcase_term";
case PM_ERR_ESCAPE_INVALID_CONTROL: return "escape_invalid_control";
case PM_ERR_ESCAPE_INVALID_CONTROL_REPEAT: return "escape_invalid_control_repeat";
case PM_ERR_ESCAPE_INVALID_HEXADECIMAL: return "escape_invalid_hexadecimal";
case PM_ERR_ESCAPE_INVALID_META: return "escape_invalid_meta";
case PM_ERR_ESCAPE_INVALID_META_REPEAT: return "escape_invalid_meta_repeat";
case PM_ERR_ESCAPE_INVALID_UNICODE: return "escape_invalid_unicode";
case PM_ERR_ESCAPE_INVALID_UNICODE_CM_FLAGS: return "escape_invalid_unicode_cm_flags";
case PM_ERR_ESCAPE_INVALID_UNICODE_LIST: return "escape_invalid_unicode_list";
case PM_ERR_ESCAPE_INVALID_UNICODE_LITERAL: return "escape_invalid_unicode_literal";
case PM_ERR_ESCAPE_INVALID_UNICODE_LONG: return "escape_invalid_unicode_long";
case PM_ERR_ESCAPE_INVALID_UNICODE_SHORT: return "escape_invalid_unicode_short";
case PM_ERR_ESCAPE_INVALID_UNICODE_TERM: return "escape_invalid_unicode_term";
case PM_ERR_EXPECT_ARGUMENT: return "expect_argument";
case PM_ERR_EXPECT_EOL_AFTER_STATEMENT: return "expect_eol_after_statement";
case PM_ERR_EXPECT_EXPRESSION_AFTER_AMPAMPEQ: return "expect_expression_after_ampampeq";
case PM_ERR_EXPECT_EXPRESSION_AFTER_COMMA: return "expect_expression_after_comma";
case PM_ERR_EXPECT_EXPRESSION_AFTER_EQUAL: return "expect_expression_after_equal";
case PM_ERR_EXPECT_EXPRESSION_AFTER_LESS_LESS: return "expect_expression_after_less_less";
case PM_ERR_EXPECT_EXPRESSION_AFTER_LPAREN: return "expect_expression_after_lparen";
case PM_ERR_EXPECT_EXPRESSION_AFTER_OPERATOR: return "expect_expression_after_operator";
case PM_ERR_EXPECT_EXPRESSION_AFTER_PIPEPIPEEQ: return "expect_expression_after_pipepipeeq";
case PM_ERR_EXPECT_EXPRESSION_AFTER_QUESTION: return "expect_expression_after_question";
case PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT: return "expect_expression_after_splat";
case PM_ERR_EXPECT_EXPRESSION_AFTER_SPLAT_HASH: return "expect_expression_after_splat_hash";
case PM_ERR_EXPECT_EXPRESSION_AFTER_STAR: return "expect_expression_after_star";
case PM_ERR_EXPECT_FOR_DELIMITER: return "expect_for_delimiter";
case PM_ERR_EXPECT_IDENT_REQ_PARAMETER: return "expect_ident_req_parameter";
case PM_ERR_EXPECT_IN_DELIMITER: return "expect_in_delimiter";
case PM_ERR_EXPECT_LPAREN_REQ_PARAMETER: return "expect_lparen_req_parameter";
case PM_ERR_EXPECT_MESSAGE: return "expect_message";
case PM_ERR_EXPECT_RBRACKET: return "expect_rbracket";
case PM_ERR_EXPECT_RPAREN: return "expect_rparen";
case PM_ERR_EXPECT_RPAREN_AFTER_MULTI: return "expect_rparen_after_multi";
case PM_ERR_EXPECT_RPAREN_REQ_PARAMETER: return "expect_rparen_req_parameter";
case PM_ERR_EXPECT_SINGLETON_CLASS_DELIMITER: return "expect_singleton_class_delimiter";
case PM_ERR_EXPECT_STRING_CONTENT: return "expect_string_content";
case PM_ERR_EXPECT_WHEN_DELIMITER: return "expect_when_delimiter";
case PM_ERR_EXPRESSION_BARE_HASH: return "expression_bare_hash";
case PM_ERR_EXPRESSION_NOT_WRITABLE: return "expression_not_writable";
case PM_ERR_EXPRESSION_NOT_WRITABLE_ENCODING: return "expression_not_writable_encoding";
case PM_ERR_EXPRESSION_NOT_WRITABLE_FALSE: return "expression_not_writable_false";
case PM_ERR_EXPRESSION_NOT_WRITABLE_FILE: return "expression_not_writable_file";
case PM_ERR_EXPRESSION_NOT_WRITABLE_LINE: return "expression_not_writable_line";
case PM_ERR_EXPRESSION_NOT_WRITABLE_NIL: return "expression_not_writable_nil";
case PM_ERR_EXPRESSION_NOT_WRITABLE_NUMBERED: return "expression_not_writable_numbered";
case PM_ERR_EXPRESSION_NOT_WRITABLE_SELF: return "expression_not_writable_self";
case PM_ERR_EXPRESSION_NOT_WRITABLE_TRUE: return "expression_not_writable_true";
case PM_ERR_FLOAT_PARSE: return "float_parse";
case PM_ERR_FOR_COLLECTION: return "for_collection";
case PM_ERR_FOR_IN: return "for_in";
case PM_ERR_FOR_INDEX: return "for_index";
case PM_ERR_FOR_TERM: return "for_term";
case PM_ERR_GLOBAL_VARIABLE_BARE: return "global_variable_bare";
case PM_ERR_HASH_EXPRESSION_AFTER_LABEL: return "hash_expression_after_label";
case PM_ERR_HASH_KEY: return "hash_key";
case PM_ERR_HASH_ROCKET: return "hash_rocket";
case PM_ERR_HASH_TERM: return "hash_term";
case PM_ERR_HASH_VALUE: return "hash_value";
case PM_ERR_HEREDOC_IDENTIFIER: return "heredoc_identifier";
case PM_ERR_HEREDOC_TERM: return "heredoc_term";
case PM_ERR_INCOMPLETE_QUESTION_MARK: return "incomplete_question_mark";
case PM_ERR_INCOMPLETE_VARIABLE_CLASS: return "incomplete_variable_class";
case PM_ERR_INCOMPLETE_VARIABLE_CLASS_3_3: return "incomplete_variable_class_3_3";
case PM_ERR_INCOMPLETE_VARIABLE_INSTANCE: return "incomplete_variable_instance";
case PM_ERR_INCOMPLETE_VARIABLE_INSTANCE_3_3: return "incomplete_variable_instance_3_3";
case PM_ERR_INSTANCE_VARIABLE_BARE: return "instance_variable_bare";
case PM_ERR_INVALID_BLOCK_EXIT: return "invalid_block_exit";
case PM_ERR_INVALID_CHARACTER: return "invalid_character";
case PM_ERR_INVALID_COMMA: return "invalid_comma";
case PM_ERR_INVALID_ENCODING_MAGIC_COMMENT: return "invalid_encoding_magic_comment";
case PM_ERR_INVALID_ESCAPE_CHARACTER: return "invalid_escape_character";
case PM_ERR_INVALID_FLOAT_EXPONENT: return "invalid_float_exponent";
case PM_ERR_INVALID_LOCAL_VARIABLE_READ: return "invalid_local_variable_read";
case PM_ERR_INVALID_LOCAL_VARIABLE_WRITE: return "invalid_local_variable_write";
case PM_ERR_INVALID_MULTIBYTE_CHAR: return "invalid_multibyte_char";
case PM_ERR_INVALID_MULTIBYTE_CHARACTER: return "invalid_multibyte_character";
case PM_ERR_INVALID_MULTIBYTE_ESCAPE: return "invalid_multibyte_escape";
case PM_ERR_INVALID_NUMBER_BINARY: return "invalid_number_binary";
case PM_ERR_INVALID_NUMBER_DECIMAL: return "invalid_number_decimal";
case PM_ERR_INVALID_NUMBER_FRACTION: return "invalid_number_fraction";
case PM_ERR_INVALID_NUMBER_HEXADECIMAL: return "invalid_number_hexadecimal";
case PM_ERR_INVALID_NUMBER_OCTAL: return "invalid_number_octal";
case PM_ERR_INVALID_NUMBER_UNDERSCORE_INNER: return "invalid_number_underscore_inner";
case PM_ERR_INVALID_NUMBER_UNDERSCORE_TRAILING: return "invalid_number_underscore_trailing";
case PM_ERR_INVALID_PERCENT: return "invalid_percent";
case PM_ERR_INVALID_PERCENT_EOF: return "invalid_percent_eof";
case PM_ERR_INVALID_PRINTABLE_CHARACTER: return "invalid_printable_character";
case PM_ERR_INVALID_RETRY_AFTER_ELSE: return "invalid_retry_after_else";
case PM_ERR_INVALID_RETRY_AFTER_ENSURE: return "invalid_retry_after_ensure";
case PM_ERR_INVALID_RETRY_WITHOUT_RESCUE: return "invalid_retry_without_rescue";
case PM_ERR_INVALID_SYMBOL: return "invalid_symbol";
case PM_ERR_INVALID_VARIABLE_GLOBAL: return "invalid_variable_global";
case PM_ERR_INVALID_VARIABLE_GLOBAL_3_3: return "invalid_variable_global_3_3";
case PM_ERR_INVALID_YIELD: return "invalid_yield";
case PM_ERR_IT_NOT_ALLOWED_NUMBERED: return "it_not_allowed_numbered";
case PM_ERR_IT_NOT_ALLOWED_ORDINARY: return "it_not_allowed_ordinary";
case PM_ERR_LAMBDA_OPEN: return "lambda_open";
case PM_ERR_LAMBDA_TERM_BRACE: return "lambda_term_brace";
case PM_ERR_LAMBDA_TERM_END: return "lambda_term_end";
case PM_ERR_LIST_I_LOWER_ELEMENT: return "list_i_lower_element";
case PM_ERR_LIST_I_LOWER_TERM: return "list_i_lower_term";
case PM_ERR_LIST_I_UPPER_ELEMENT: return "list_i_upper_element";
case PM_ERR_LIST_I_UPPER_TERM: return "list_i_upper_term";
case PM_ERR_LIST_W_LOWER_ELEMENT: return "list_w_lower_element";
case PM_ERR_LIST_W_LOWER_TERM: return "list_w_lower_term";
case PM_ERR_LIST_W_UPPER_ELEMENT: return "list_w_upper_element";
case PM_ERR_LIST_W_UPPER_TERM: return "list_w_upper_term";
case PM_ERR_MALLOC_FAILED: return "malloc_failed";
case PM_ERR_MIXED_ENCODING: return "mixed_encoding";
case PM_ERR_MODULE_IN_METHOD: return "module_in_method";
case PM_ERR_MODULE_NAME: return "module_name";
case PM_ERR_MODULE_TERM: return "module_term";
case PM_ERR_MULTI_ASSIGN_MULTI_SPLATS: return "multi_assign_multi_splats";
case PM_ERR_MULTI_ASSIGN_UNEXPECTED_REST: return "multi_assign_unexpected_rest";
case PM_ERR_NESTING_TOO_DEEP: return "nesting_too_deep";
case PM_ERR_NO_LOCAL_VARIABLE: return "no_local_variable";
case PM_ERR_NON_ASSOCIATIVE_OPERATOR: return "non_associative_operator";
case PM_ERR_NOT_EXPRESSION: return "not_expression";
case PM_ERR_NUMBER_LITERAL_UNDERSCORE: return "number_literal_underscore";
case PM_ERR_NUMBERED_PARAMETER_INNER_BLOCK: return "numbered_parameter_inner_block";
case PM_ERR_NUMBERED_PARAMETER_IT: return "numbered_parameter_it";
case PM_ERR_NUMBERED_PARAMETER_ORDINARY: return "numbered_parameter_ordinary";
case PM_ERR_NUMBERED_PARAMETER_OUTER_BLOCK: return "numbered_parameter_outer_block";
case PM_ERR_OPERATOR_MULTI_ASSIGN: return "operator_multi_assign";
case PM_ERR_OPERATOR_WRITE_ARGUMENTS: return "operator_write_arguments";
case PM_ERR_OPERATOR_WRITE_BLOCK: return "operator_write_block";
case PM_ERR_PARAMETER_ASSOC_SPLAT_MULTI: return "parameter_assoc_splat_multi";
case PM_ERR_PARAMETER_BLOCK_MULTI: return "parameter_block_multi";
case PM_ERR_PARAMETER_CIRCULAR: return "parameter_circular";
case PM_ERR_PARAMETER_FORWARDING_AFTER_REST: return "parameter_forwarding_after_rest";
case PM_ERR_PARAMETER_METHOD_NAME: return "parameter_method_name";
case PM_ERR_PARAMETER_NAME_DUPLICATED: return "parameter_name_duplicated";
case PM_ERR_PARAMETER_NO_DEFAULT: return "parameter_no_default";
case PM_ERR_PARAMETER_NO_DEFAULT_KW: return "parameter_no_default_kw";
case PM_ERR_PARAMETER_NUMBERED_RESERVED: return "parameter_numbered_reserved";
case PM_ERR_PARAMETER_ORDER: return "parameter_order";
case PM_ERR_PARAMETER_SPLAT_MULTI: return "parameter_splat_multi";
case PM_ERR_PARAMETER_STAR: return "parameter_star";
case PM_ERR_PARAMETER_UNEXPECTED_FWD: return "parameter_unexpected_fwd";
case PM_ERR_PARAMETER_UNEXPECTED_NO_KW: return "parameter_unexpected_no_kw";
case PM_ERR_PARAMETER_WILD_LOOSE_COMMA: return "parameter_wild_loose_comma";
case PM_ERR_PATTERN_ARRAY_MULTIPLE_RESTS: return "pattern_array_multiple_rests";
case PM_ERR_PATTERN_CAPTURE_DUPLICATE: return "pattern_capture_duplicate";
case PM_ERR_PATTERN_EXPRESSION_AFTER_BRACKET: return "pattern_expression_after_bracket";
case PM_ERR_PATTERN_EXPRESSION_AFTER_COMMA: return "pattern_expression_after_comma";
case PM_ERR_PATTERN_EXPRESSION_AFTER_HROCKET: return "pattern_expression_after_hrocket";
case PM_ERR_PATTERN_EXPRESSION_AFTER_IN: return "pattern_expression_after_in";
case PM_ERR_PATTERN_EXPRESSION_AFTER_KEY: return "pattern_expression_after_key";
case PM_ERR_PATTERN_EXPRESSION_AFTER_PAREN: return "pattern_expression_after_paren";
case PM_ERR_PATTERN_EXPRESSION_AFTER_PIN: return "pattern_expression_after_pin";
case PM_ERR_PATTERN_EXPRESSION_AFTER_PIPE: return "pattern_expression_after_pipe";
case PM_ERR_PATTERN_EXPRESSION_AFTER_RANGE: return "pattern_expression_after_range";
case PM_ERR_PATTERN_EXPRESSION_AFTER_REST: return "pattern_expression_after_rest";
case PM_ERR_PATTERN_FIND_MISSING_INNER: return "pattern_find_missing_inner";
case PM_ERR_PATTERN_HASH_IMPLICIT: return "pattern_hash_implicit";
case PM_ERR_PATTERN_HASH_KEY: return "pattern_hash_key";
case PM_ERR_PATTERN_HASH_KEY_DUPLICATE: return "pattern_hash_key_duplicate";
case PM_ERR_PATTERN_HASH_KEY_INTERPOLATED: return "pattern_hash_key_interpolated";
case PM_ERR_PATTERN_HASH_KEY_LABEL: return "pattern_hash_key_label";
case PM_ERR_PATTERN_HASH_KEY_LOCALS: return "pattern_hash_key_locals";
case PM_ERR_PATTERN_IDENT_AFTER_HROCKET: return "pattern_ident_after_hrocket";
case PM_ERR_PATTERN_LABEL_AFTER_COMMA: return "pattern_label_after_comma";
case PM_ERR_PATTERN_REST: return "pattern_rest";
case PM_ERR_PATTERN_TERM_BRACE: return "pattern_term_brace";
case PM_ERR_PATTERN_TERM_BRACKET: return "pattern_term_bracket";
case PM_ERR_PATTERN_TERM_PAREN: return "pattern_term_paren";
case PM_ERR_PIPEPIPEEQ_MULTI_ASSIGN: return "pipepipeeq_multi_assign";
case PM_ERR_REGEXP_ENCODING_OPTION_MISMATCH: return "regexp_encoding_option_mismatch";
case PM_ERR_REGEXP_INCOMPAT_CHAR_ENCODING: return "regexp_incompat_char_encoding";
case PM_ERR_REGEXP_INVALID_UNICODE_RANGE: return "regexp_invalid_unicode_range";
case PM_ERR_REGEXP_NON_ESCAPED_MBC: return "regexp_non_escaped_mbc";
case PM_ERR_REGEXP_PARSE_ERROR: return "regexp_parse_error";
case PM_ERR_REGEXP_TERM: return "regexp_term";
case PM_ERR_REGEXP_UNKNOWN_OPTIONS: return "regexp_unknown_options";
case PM_ERR_REGEXP_UTF8_CHAR_NON_UTF8_REGEXP: return "regexp_utf8_char_non_utf8_regexp";
case PM_ERR_RESCUE_EXPRESSION: return "rescue_expression";
case PM_ERR_RESCUE_MODIFIER_VALUE: return "rescue_modifier_value";
case PM_ERR_RESCUE_TERM: return "rescue_term";
case PM_ERR_RESCUE_VARIABLE: return "rescue_variable";
case PM_ERR_RETURN_INVALID: return "return_invalid";
case PM_ERR_SCRIPT_NOT_FOUND: return "script_not_found";
case PM_ERR_SINGLETON_FOR_LITERALS: return "singleton_for_literals";
case PM_ERR_STATEMENT_ALIAS: return "statement_alias";
case PM_ERR_STATEMENT_POSTEXE_END: return "statement_postexe_end";
case PM_ERR_STATEMENT_PREEXE_BEGIN: return "statement_preexe_begin";
case PM_ERR_STATEMENT_UNDEF: return "statement_undef";
case PM_ERR_STRING_CONCATENATION: return "string_concatenation";
case PM_ERR_STRING_INTERPOLATED_TERM: return "string_interpolated_term";
case PM_ERR_STRING_LITERAL_EOF: return "string_literal_eof";
case PM_ERR_STRING_LITERAL_TERM: return "string_literal_term";
case PM_ERR_SYMBOL_INVALID: return "symbol_invalid";
case PM_ERR_SYMBOL_TERM_DYNAMIC: return "symbol_term_dynamic";
case PM_ERR_SYMBOL_TERM_INTERPOLATED: return "symbol_term_interpolated";
case PM_ERR_TERNARY_COLON: return "ternary_colon";
case PM_ERR_TERNARY_EXPRESSION_FALSE: return "ternary_expression_false";
case PM_ERR_TERNARY_EXPRESSION_TRUE: return "ternary_expression_true";
case PM_ERR_UNARY_DISALLOWED: return "unary_disallowed";
case PM_ERR_UNARY_RECEIVER: return "unary_receiver";
case PM_ERR_UNDEF_ARGUMENT: return "undef_argument";
case PM_ERR_UNEXPECTED_BLOCK_ARGUMENT: return "unexpected_block_argument";
case PM_ERR_UNEXPECTED_INDEX_BLOCK: return "unexpected_index_block";
case PM_ERR_UNEXPECTED_INDEX_KEYWORDS: return "unexpected_index_keywords";
case PM_ERR_UNEXPECTED_LABEL: return "unexpected_label";
case PM_ERR_UNEXPECTED_MULTI_WRITE: return "unexpected_multi_write";
case PM_ERR_UNEXPECTED_RANGE_OPERATOR: return "unexpected_range_operator";
case PM_ERR_UNEXPECTED_SAFE_NAVIGATION: return "unexpected_safe_navigation";
case PM_ERR_UNEXPECTED_TOKEN_CLOSE_CONTEXT: return "unexpected_token_close_context";
case PM_ERR_UNEXPECTED_TOKEN_IGNORE: return "unexpected_token_ignore";
case PM_ERR_UNTIL_TERM: return "until_term";
case PM_ERR_VOID_EXPRESSION: return "void_expression";
case PM_ERR_WHILE_TERM: return "while_term";
case PM_ERR_WRITE_TARGET_IN_METHOD: return "write_target_in_method";
case PM_ERR_WRITE_TARGET_READONLY: return "write_target_readonly";
case PM_ERR_WRITE_TARGET_UNEXPECTED: return "write_target_unexpected";
case PM_ERR_XSTRING_TERM: return "xstring_term";
case PM_WARN_AMBIGUOUS_BINARY_OPERATOR: return "ambiguous_binary_operator";
case PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_MINUS: return "ambiguous_first_argument_minus";
case PM_WARN_AMBIGUOUS_FIRST_ARGUMENT_PLUS: return "ambiguous_first_argument_plus";
case PM_WARN_AMBIGUOUS_PREFIX_AMPERSAND: return "ambiguous_prefix_ampersand";
case PM_WARN_AMBIGUOUS_PREFIX_STAR: return "ambiguous_prefix_star";
case PM_WARN_AMBIGUOUS_PREFIX_STAR_STAR: return "ambiguous_prefix_star_star";
case PM_WARN_AMBIGUOUS_SLASH: return "ambiguous_slash";
case PM_WARN_COMPARISON_AFTER_COMPARISON: return "comparison_after_comparison";
case PM_WARN_DOT_DOT_DOT_EOL: return "dot_dot_dot_eol";
case PM_WARN_EQUAL_IN_CONDITIONAL: return "equal_in_conditional";
case PM_WARN_EQUAL_IN_CONDITIONAL_3_3: return "equal_in_conditional_3_3";
case PM_WARN_END_IN_METHOD: return "end_in_method";
case PM_WARN_DUPLICATED_HASH_KEY: return "duplicated_hash_key";
case PM_WARN_DUPLICATED_WHEN_CLAUSE: return "duplicated_when_clause";
case PM_WARN_FLOAT_OUT_OF_RANGE: return "float_out_of_range";
case PM_WARN_IGNORED_FROZEN_STRING_LITERAL: return "ignored_frozen_string_literal";
case PM_WARN_INDENTATION_MISMATCH: return "indentation_mismatch";
case PM_WARN_INTEGER_IN_FLIP_FLOP: return "integer_in_flip_flop";
case PM_WARN_INVALID_CHARACTER: return "invalid_character";
case PM_WARN_INVALID_MAGIC_COMMENT_VALUE: return "invalid_magic_comment_value";
case PM_WARN_INVALID_NUMBERED_REFERENCE: return "invalid_numbered_reference";
case PM_WARN_KEYWORD_EOL: return "keyword_eol";
case PM_WARN_LITERAL_IN_CONDITION_DEFAULT: return "literal_in_condition_default";
case PM_WARN_LITERAL_IN_CONDITION_VERBOSE: return "literal_in_condition_verbose";
case PM_WARN_SHAREABLE_CONSTANT_VALUE_LINE: return "shareable_constant_value_line";
case PM_WARN_SHEBANG_CARRIAGE_RETURN: return "shebang_carriage_return";
case PM_WARN_UNEXPECTED_CARRIAGE_RETURN: return "unexpected_carriage_return";
case PM_WARN_UNREACHABLE_STATEMENT: return "unreachable_statement";
case PM_WARN_UNUSED_LOCAL_VARIABLE: return "unused_local_variable";
case PM_WARN_VOID_STATEMENT: return "void_statement";
}
assert(false && "unreachable");
return "";
}
static inline const char *
pm_diagnostic_message(pm_diagnostic_id_t diag_id) {
assert(diag_id < PM_DIAGNOSTIC_ID_MAX);
const char *message = diagnostic_messages[diag_id].message;
assert(message);
return message;
}
static inline uint8_t
pm_diagnostic_level(pm_diagnostic_id_t diag_id) {
assert(diag_id < PM_DIAGNOSTIC_ID_MAX);
return (uint8_t) diagnostic_messages[diag_id].level;
}
/**
* Append an error to the given list of diagnostic.
*/
bool
pm_diagnostic_list_append(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id) {
pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(1, sizeof(pm_diagnostic_t));
if (diagnostic == NULL) return false;
*diagnostic = (pm_diagnostic_t) {
.location = { start, end },
.diag_id = diag_id,
.message = pm_diagnostic_message(diag_id),
.owned = false,
.level = pm_diagnostic_level(diag_id)
};
pm_list_append(list, (pm_list_node_t *) diagnostic);
return true;
}
/**
* Append a diagnostic to the given list of diagnostics that is using a format
* string for its message.
*/
bool
pm_diagnostic_list_append_format(pm_list_t *list, const uint8_t *start, const uint8_t *end, pm_diagnostic_id_t diag_id, ...) {
va_list arguments;
va_start(arguments, diag_id);
const char *format = pm_diagnostic_message(diag_id);
int result = vsnprintf(NULL, 0, format, arguments);
va_end(arguments);
if (result < 0) {
return false;
}
pm_diagnostic_t *diagnostic = (pm_diagnostic_t *) xcalloc(1, sizeof(pm_diagnostic_t));
if (diagnostic == NULL) {
return false;
}
size_t length = (size_t) (result + 1);
char *message = (char *) xmalloc(length);
if (message == NULL) {
xfree(diagnostic);
return false;
}
va_start(arguments, diag_id);
vsnprintf(message, length, format, arguments);
va_end(arguments);
*diagnostic = (pm_diagnostic_t) {
.location = { start, end },
.diag_id = diag_id,
.message = message,
.owned = true,
.level = pm_diagnostic_level(diag_id)
};
pm_list_append(list, (pm_list_node_t *) diagnostic);
return true;
}
/**
* Deallocate the internal state of the given diagnostic list.
*/
void
pm_diagnostic_list_free(pm_list_t *list) {
pm_diagnostic_t *node = (pm_diagnostic_t *) list->head;
while (node != NULL) {
pm_diagnostic_t *next = (pm_diagnostic_t *) node->node.next;
if (node->owned) xfree((void *) node->message);
xfree(node);
node = next;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,328 @@
#include "prism/options.h"
/**
* Set the shebang callback option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_shebang_callback_set(pm_options_t *options, pm_options_shebang_callback_t shebang_callback, void *shebang_callback_data) {
options->shebang_callback = shebang_callback;
options->shebang_callback_data = shebang_callback_data;
}
/**
* Set the filepath option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_filepath_set(pm_options_t *options, const char *filepath) {
pm_string_constant_init(&options->filepath, filepath, strlen(filepath));
}
/**
* Set the encoding option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_encoding_set(pm_options_t *options, const char *encoding) {
pm_string_constant_init(&options->encoding, encoding, strlen(encoding));
}
/**
* Set the encoding_locked option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_encoding_locked_set(pm_options_t *options, bool encoding_locked) {
options->encoding_locked = encoding_locked;
}
/**
* Set the line option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_line_set(pm_options_t *options, int32_t line) {
options->line = line;
}
/**
* Set the frozen string literal option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_frozen_string_literal_set(pm_options_t *options, bool frozen_string_literal) {
options->frozen_string_literal = frozen_string_literal ? PM_OPTIONS_FROZEN_STRING_LITERAL_ENABLED : PM_OPTIONS_FROZEN_STRING_LITERAL_DISABLED;
}
/**
* Sets the command line option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_command_line_set(pm_options_t *options, uint8_t command_line) {
options->command_line = command_line;
}
/**
* Checks if the given slice represents a number.
*/
static inline bool
is_number(const char *string, size_t length) {
return pm_strspn_decimal_digit((const uint8_t *) string, (ptrdiff_t) length) == length;
}
/**
* Set the version option on the given options struct by parsing the given
* string. If the string contains an invalid option, this returns false.
* Otherwise, it returns true.
*/
PRISM_EXPORTED_FUNCTION bool
pm_options_version_set(pm_options_t *options, const char *version, size_t length) {
if (version == NULL) {
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}
if (length == 3) {
if (strncmp(version, "3.3", 3) == 0) {
options->version = PM_OPTIONS_VERSION_CRUBY_3_3;
return true;
}
if (strncmp(version, "3.4", 3) == 0) {
options->version = PM_OPTIONS_VERSION_CRUBY_3_4;
return true;
}
if (strncmp(version, "3.5", 3) == 0) {
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}
return false;
}
if (length >= 4) {
if (strncmp(version, "3.3.", 4) == 0 && is_number(version + 4, length - 4)) {
options->version = PM_OPTIONS_VERSION_CRUBY_3_3;
return true;
}
if (strncmp(version, "3.4.", 4) == 0 && is_number(version + 4, length - 4)) {
options->version = PM_OPTIONS_VERSION_CRUBY_3_4;
return true;
}
if (strncmp(version, "3.5.", 4) == 0 && is_number(version + 4, length - 4)) {
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}
}
if (length >= 6) {
if (strncmp(version, "latest", 7) == 0) { // 7 to compare the \0 as well
options->version = PM_OPTIONS_VERSION_LATEST;
return true;
}
}
return false;
}
/**
* Set the main script option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_main_script_set(pm_options_t *options, bool main_script) {
options->main_script = main_script;
}
/**
* Set the partial script option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_partial_script_set(pm_options_t *options, bool partial_script) {
options->partial_script = partial_script;
}
/**
* Set the freeze option on the given options struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_freeze_set(pm_options_t *options, bool freeze) {
options->freeze = freeze;
}
// For some reason, GCC analyzer thinks we're leaking allocated scopes and
// locals here, even though we definitely aren't. This is a false positive.
// Ideally we wouldn't need to suppress this.
#if defined(__GNUC__) && (__GNUC__ >= 10)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak"
#endif
/**
* Allocate and zero out the scopes array on the given options struct.
*/
PRISM_EXPORTED_FUNCTION bool
pm_options_scopes_init(pm_options_t *options, size_t scopes_count) {
options->scopes_count = scopes_count;
options->scopes = xcalloc(scopes_count, sizeof(pm_options_scope_t));
return options->scopes != NULL;
}
/**
* Return a pointer to the scope at the given index within the given options.
*/
PRISM_EXPORTED_FUNCTION const pm_options_scope_t *
pm_options_scope_get(const pm_options_t *options, size_t index) {
return &options->scopes[index];
}
/**
* Create a new options scope struct. This will hold a set of locals that are in
* scope surrounding the code that is being parsed.
*/
PRISM_EXPORTED_FUNCTION bool
pm_options_scope_init(pm_options_scope_t *scope, size_t locals_count) {
scope->locals_count = locals_count;
scope->locals = xcalloc(locals_count, sizeof(pm_string_t));
scope->forwarding = PM_OPTIONS_SCOPE_FORWARDING_NONE;
return scope->locals != NULL;
}
/**
* Return a pointer to the local at the given index within the given scope.
*/
PRISM_EXPORTED_FUNCTION const pm_string_t *
pm_options_scope_local_get(const pm_options_scope_t *scope, size_t index) {
return &scope->locals[index];
}
/**
* Set the forwarding option on the given scope struct.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_scope_forwarding_set(pm_options_scope_t *scope, uint8_t forwarding) {
scope->forwarding = forwarding;
}
/**
* Free the internal memory associated with the options.
*/
PRISM_EXPORTED_FUNCTION void
pm_options_free(pm_options_t *options) {
pm_string_free(&options->filepath);
pm_string_free(&options->encoding);
for (size_t scope_index = 0; scope_index < options->scopes_count; scope_index++) {
pm_options_scope_t *scope = &options->scopes[scope_index];
for (size_t local_index = 0; local_index < scope->locals_count; local_index++) {
pm_string_free(&scope->locals[local_index]);
}
xfree(scope->locals);
}
xfree(options->scopes);
}
/**
* Read a 32-bit unsigned integer from a pointer. This function is used to read
* the options that are passed into the parser from the Ruby implementation. It
* handles aligned and unaligned reads.
*/
static uint32_t
pm_options_read_u32(const char *data) {
if (((uintptr_t) data) % sizeof(uint32_t) == 0) {
return *((uint32_t *) data);
} else {
uint32_t value;
memcpy(&value, data, sizeof(uint32_t));
return value;
}
}
/**
* Read a 32-bit signed integer from a pointer. This function is used to read
* the options that are passed into the parser from the Ruby implementation. It
* handles aligned and unaligned reads.
*/
static int32_t
pm_options_read_s32(const char *data) {
if (((uintptr_t) data) % sizeof(int32_t) == 0) {
return *((int32_t *) data);
} else {
int32_t value;
memcpy(&value, data, sizeof(int32_t));
return value;
}
}
/**
* Deserialize an options struct from the given binary string. This is used to
* pass options to the parser from an FFI call so that consumers of the library
* from an FFI perspective don't have to worry about the structure of our
* options structs. Since the source of these calls will be from Ruby
* implementation internals we assume it is from a trusted source.
*/
void
pm_options_read(pm_options_t *options, const char *data) {
options->line = 1; // default
if (data == NULL) return;
uint32_t filepath_length = pm_options_read_u32(data);
data += 4;
if (filepath_length > 0) {
pm_string_constant_init(&options->filepath, data, filepath_length);
data += filepath_length;
}
options->line = pm_options_read_s32(data);
data += 4;
uint32_t encoding_length = pm_options_read_u32(data);
data += 4;
if (encoding_length > 0) {
pm_string_constant_init(&options->encoding, data, encoding_length);
data += encoding_length;
}
options->frozen_string_literal = (int8_t) *data++;
options->command_line = (uint8_t) *data++;
options->version = (pm_options_version_t) *data++;
options->encoding_locked = ((uint8_t) *data++) > 0;
options->main_script = ((uint8_t) *data++) > 0;
options->partial_script = ((uint8_t) *data++) > 0;
options->freeze = ((uint8_t) *data++) > 0;
uint32_t scopes_count = pm_options_read_u32(data);
data += 4;
if (scopes_count > 0) {
if (!pm_options_scopes_init(options, scopes_count)) return;
for (size_t scope_index = 0; scope_index < scopes_count; scope_index++) {
uint32_t locals_count = pm_options_read_u32(data);
data += 4;
pm_options_scope_t *scope = &options->scopes[scope_index];
if (!pm_options_scope_init(scope, locals_count)) {
pm_options_free(options);
return;
}
uint8_t forwarding = (uint8_t) *data++;
pm_options_scope_forwarding_set(&options->scopes[scope_index], forwarding);
for (size_t local_index = 0; local_index < locals_count; local_index++) {
uint32_t local_length = pm_options_read_u32(data);
data += 4;
pm_string_constant_init(&scope->locals[local_index], data, local_length);
data += local_length;
}
}
}
}
#if defined(__GNUC__) && (__GNUC__ >= 10)
#pragma GCC diagnostic pop
#endif

View File

@@ -0,0 +1,509 @@
#include "prism/pack.h"
// We optionally support parsing String#pack templates. For systems that don't
// want or need this functionality, it can be turned off with the
// PRISM_EXCLUDE_PACK define.
#ifdef PRISM_EXCLUDE_PACK
void pm_pack_parse(void) {}
#else
#include <stdbool.h>
#include <errno.h>
static uintmax_t
strtoumaxc(const char **format) {
uintmax_t value = 0;
while (**format >= '0' && **format <= '9') {
if (value > UINTMAX_MAX / 10) {
errno = ERANGE;
}
value = value * 10 + ((uintmax_t) (**format - '0'));
(*format)++;
}
return value;
}
PRISM_EXPORTED_FUNCTION pm_pack_result
pm_pack_parse(
pm_pack_variant variant,
const char **format,
const char *format_end,
pm_pack_type *type,
pm_pack_signed *signed_type,
pm_pack_endian *endian,
pm_pack_size *size,
pm_pack_length_type *length_type,
uint64_t *length,
pm_pack_encoding *encoding
) {
if (*encoding == PM_PACK_ENCODING_START) {
*encoding = PM_PACK_ENCODING_US_ASCII;
}
if (*format == format_end) {
*type = PM_PACK_END;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
*length_type = PM_PACK_LENGTH_NA;
return PM_PACK_OK;
}
*length_type = PM_PACK_LENGTH_FIXED;
*length = 1;
bool length_changed_allowed = true;
char directive = **format;
(*format)++;
switch (directive) {
case ' ':
case '\t':
case '\n':
case '\v':
case '\f':
case '\r':
*type = PM_PACK_SPACE;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
*length_type = PM_PACK_LENGTH_NA;
*length = 0;
return PM_PACK_OK;
case '#':
while ((*format < format_end) && (**format != '\n')) {
(*format)++;
}
*type = PM_PACK_COMMENT;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
*length_type = PM_PACK_LENGTH_NA;
*length = 0;
return PM_PACK_OK;
case 'C':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_UNSIGNED;
*endian = PM_PACK_AGNOSTIC_ENDIAN;
*size = PM_PACK_SIZE_8;
break;
case 'S':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_UNSIGNED;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_16;
break;
case 'L':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_UNSIGNED;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_32;
break;
case 'Q':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_UNSIGNED;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_64;
break;
case 'J':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_UNSIGNED;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_P;
break;
case 'c':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_SIGNED;
*endian = PM_PACK_AGNOSTIC_ENDIAN;
*size = PM_PACK_SIZE_8;
break;
case 's':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_SIGNED;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_16;
break;
case 'l':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_SIGNED;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_32;
break;
case 'q':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_SIGNED;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_64;
break;
case 'j':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_SIGNED;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_P;
break;
case 'I':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_UNSIGNED;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_INT;
break;
case 'i':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_SIGNED;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_INT;
break;
case 'n':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_UNSIGNED;
*endian = PM_PACK_BIG_ENDIAN;
*size = PM_PACK_SIZE_16;
length_changed_allowed = false;
break;
case 'N':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_UNSIGNED;
*endian = PM_PACK_BIG_ENDIAN;
*size = PM_PACK_SIZE_32;
length_changed_allowed = false;
break;
case 'v':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_UNSIGNED;
*endian = PM_PACK_LITTLE_ENDIAN;
*size = PM_PACK_SIZE_16;
length_changed_allowed = false;
break;
case 'V':
*type = PM_PACK_INTEGER;
*signed_type = PM_PACK_UNSIGNED;
*endian = PM_PACK_LITTLE_ENDIAN;
*size = PM_PACK_SIZE_32;
length_changed_allowed = false;
break;
case 'U':
*type = PM_PACK_UTF8;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'w':
*type = PM_PACK_BER;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'D':
case 'd':
*type = PM_PACK_FLOAT;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_64;
break;
case 'F':
case 'f':
*type = PM_PACK_FLOAT;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_NATIVE_ENDIAN;
*size = PM_PACK_SIZE_32;
break;
case 'E':
*type = PM_PACK_FLOAT;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_LITTLE_ENDIAN;
*size = PM_PACK_SIZE_64;
break;
case 'e':
*type = PM_PACK_FLOAT;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_LITTLE_ENDIAN;
*size = PM_PACK_SIZE_32;
break;
case 'G':
*type = PM_PACK_FLOAT;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_BIG_ENDIAN;
*size = PM_PACK_SIZE_64;
break;
case 'g':
*type = PM_PACK_FLOAT;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_BIG_ENDIAN;
*size = PM_PACK_SIZE_32;
break;
case 'A':
*type = PM_PACK_STRING_SPACE_PADDED;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'a':
*type = PM_PACK_STRING_NULL_PADDED;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'Z':
*type = PM_PACK_STRING_NULL_TERMINATED;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'B':
*type = PM_PACK_STRING_MSB;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'b':
*type = PM_PACK_STRING_LSB;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'H':
*type = PM_PACK_STRING_HEX_HIGH;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'h':
*type = PM_PACK_STRING_HEX_LOW;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'u':
*type = PM_PACK_STRING_UU;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'M':
*type = PM_PACK_STRING_MIME;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'm':
*type = PM_PACK_STRING_BASE64;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'P':
*type = PM_PACK_STRING_FIXED;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'p':
*type = PM_PACK_STRING_POINTER;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case '@':
*type = PM_PACK_MOVE;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'X':
*type = PM_PACK_BACK;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case 'x':
*type = PM_PACK_NULL;
*signed_type = PM_PACK_SIGNED_NA;
*endian = PM_PACK_ENDIAN_NA;
*size = PM_PACK_SIZE_NA;
break;
case '%':
return PM_PACK_ERROR_UNSUPPORTED_DIRECTIVE;
default:
return PM_PACK_ERROR_UNKNOWN_DIRECTIVE;
}
bool explicit_endian = false;
while (*format < format_end) {
switch (**format) {
case '_':
case '!':
(*format)++;
if (*type != PM_PACK_INTEGER || !length_changed_allowed) {
return PM_PACK_ERROR_BANG_NOT_ALLOWED;
}
switch (*size) {
case PM_PACK_SIZE_SHORT:
case PM_PACK_SIZE_INT:
case PM_PACK_SIZE_LONG:
case PM_PACK_SIZE_LONG_LONG:
break;
case PM_PACK_SIZE_16:
*size = PM_PACK_SIZE_SHORT;
break;
case PM_PACK_SIZE_32:
*size = PM_PACK_SIZE_LONG;
break;
case PM_PACK_SIZE_64:
*size = PM_PACK_SIZE_LONG_LONG;
break;
case PM_PACK_SIZE_P:
break;
default:
return PM_PACK_ERROR_BANG_NOT_ALLOWED;
}
break;
case '<':
(*format)++;
if (explicit_endian) {
return PM_PACK_ERROR_DOUBLE_ENDIAN;
}
*endian = PM_PACK_LITTLE_ENDIAN;
explicit_endian = true;
break;
case '>':
(*format)++;
if (explicit_endian) {
return PM_PACK_ERROR_DOUBLE_ENDIAN;
}
*endian = PM_PACK_BIG_ENDIAN;
explicit_endian = true;
break;
default:
goto exit_modifier_loop;
}
}
exit_modifier_loop:
if (variant == PM_PACK_VARIANT_UNPACK && *type == PM_PACK_MOVE) {
*length = 0;
}
if (*format < format_end) {
if (**format == '*') {
switch (*type) {
case PM_PACK_NULL:
case PM_PACK_BACK:
switch (variant) {
case PM_PACK_VARIANT_PACK:
*length_type = PM_PACK_LENGTH_FIXED;
break;
case PM_PACK_VARIANT_UNPACK:
*length_type = PM_PACK_LENGTH_MAX;
break;
}
*length = 0;
break;
case PM_PACK_MOVE:
switch (variant) {
case PM_PACK_VARIANT_PACK:
*length_type = PM_PACK_LENGTH_FIXED;
break;
case PM_PACK_VARIANT_UNPACK:
*length_type = PM_PACK_LENGTH_RELATIVE;
break;
}
*length = 0;
break;
case PM_PACK_STRING_UU:
*length_type = PM_PACK_LENGTH_FIXED;
*length = 0;
break;
case PM_PACK_STRING_FIXED:
switch (variant) {
case PM_PACK_VARIANT_PACK:
*length_type = PM_PACK_LENGTH_FIXED;
*length = 1;
break;
case PM_PACK_VARIANT_UNPACK:
*length_type = PM_PACK_LENGTH_MAX;
*length = 0;
break;
}
break;
case PM_PACK_STRING_MIME:
case PM_PACK_STRING_BASE64:
*length_type = PM_PACK_LENGTH_FIXED;
*length = 1;
break;
default:
*length_type = PM_PACK_LENGTH_MAX;
*length = 0;
break;
}
(*format)++;
} else if (**format >= '0' && **format <= '9') {
errno = 0;
*length_type = PM_PACK_LENGTH_FIXED;
#if UINTMAX_MAX < UINT64_MAX
#error "prism's design assumes uintmax_t is at least as large as uint64_t"
#endif
uintmax_t length_max = strtoumaxc(format);
if (errno || length_max > UINT64_MAX) {
return PM_PACK_ERROR_LENGTH_TOO_BIG;
}
*length = (uint64_t) length_max;
}
}
switch (*type) {
case PM_PACK_UTF8:
/* if encoding is US-ASCII, upgrade to UTF-8 */
if (*encoding == PM_PACK_ENCODING_US_ASCII) {
*encoding = PM_PACK_ENCODING_UTF_8;
}
break;
case PM_PACK_STRING_MIME:
case PM_PACK_STRING_BASE64:
case PM_PACK_STRING_UU:
/* keep US-ASCII (do nothing) */
break;
default:
/* fall back to BINARY */
*encoding = PM_PACK_ENCODING_ASCII_8BIT;
break;
}
return PM_PACK_OK;
}
PRISM_EXPORTED_FUNCTION size_t
pm_size_to_native(pm_pack_size size) {
switch (size) {
case PM_PACK_SIZE_SHORT:
return sizeof(short);
case PM_PACK_SIZE_INT:
return sizeof(int);
case PM_PACK_SIZE_LONG:
return sizeof(long);
case PM_PACK_SIZE_LONG_LONG:
return sizeof(long long);
case PM_PACK_SIZE_8:
return 1;
case PM_PACK_SIZE_16:
return 2;
case PM_PACK_SIZE_32:
return 4;
case PM_PACK_SIZE_64:
return 8;
case PM_PACK_SIZE_P:
return sizeof(void *);
default:
return 0;
}
}
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,790 @@
#include "prism/regexp.h"
#define PM_REGEXP_PARSE_DEPTH_MAX 4096
/**
* This is the parser that is going to handle parsing regular expressions.
*/
typedef struct {
/** The parser that is currently being used. */
pm_parser_t *parser;
/** A pointer to the start of the source that we are parsing. */
const uint8_t *start;
/** A pointer to the current position in the source. */
const uint8_t *cursor;
/** A pointer to the end of the source that we are parsing. */
const uint8_t *end;
/**
* Whether or not the regular expression currently being parsed is in
* extended mode, wherein whitespace is ignored and comments are allowed.
*/
bool extended_mode;
/** Whether the encoding has changed from the default. */
bool encoding_changed;
/** The encoding of the source. */
const pm_encoding_t *encoding;
/** The callback to call when a named capture group is found. */
pm_regexp_name_callback_t name_callback;
/** The data to pass to the name callback. */
void *name_data;
/** The callback to call when a parse error is found. */
pm_regexp_error_callback_t error_callback;
/** The data to pass to the error callback. */
void *error_data;
} pm_regexp_parser_t;
/**
* Append an error to the parser.
*/
static inline void
pm_regexp_parse_error(pm_regexp_parser_t *parser, const uint8_t *start, const uint8_t *end, const char *message) {
parser->error_callback(start, end, message, parser->error_data);
}
/**
* This appends a new string to the list of named captures. This function
* assumes the caller has already checked the validity of the name callback.
*/
static void
pm_regexp_parser_named_capture(pm_regexp_parser_t *parser, const uint8_t *start, const uint8_t *end) {
pm_string_t string;
pm_string_shared_init(&string, start, end);
parser->name_callback(&string, parser->name_data);
pm_string_free(&string);
}
/**
* Returns true if the next character is the end of the source.
*/
static inline bool
pm_regexp_char_is_eof(pm_regexp_parser_t *parser) {
return parser->cursor >= parser->end;
}
/**
* Optionally accept a char and consume it if it exists.
*/
static inline bool
pm_regexp_char_accept(pm_regexp_parser_t *parser, uint8_t value) {
if (!pm_regexp_char_is_eof(parser) && *parser->cursor == value) {
parser->cursor++;
return true;
}
return false;
}
/**
* Expect a character to be present and consume it.
*/
static inline bool
pm_regexp_char_expect(pm_regexp_parser_t *parser, uint8_t value) {
if (!pm_regexp_char_is_eof(parser) && *parser->cursor == value) {
parser->cursor++;
return true;
}
return false;
}
/**
* This advances the current token to the next instance of the given character.
*/
static bool
pm_regexp_char_find(pm_regexp_parser_t *parser, uint8_t value) {
if (pm_regexp_char_is_eof(parser)) {
return false;
}
const uint8_t *end = (const uint8_t *) pm_memchr(parser->cursor, value, (size_t) (parser->end - parser->cursor), parser->encoding_changed, parser->encoding);
if (end == NULL) {
return false;
}
parser->cursor = end + 1;
return true;
}
/**
* Range quantifiers are a special class of quantifiers that look like
*
* * {digit}
* * {digit,}
* * {digit,digit}
* * {,digit}
*
* Unfortunately, if there are any spaces in between, then this just becomes a
* regular character match expression and we have to backtrack. So when this
* function first starts running, we'll create a "save" point and then attempt
* to parse the quantifier. If it fails, we'll restore the save point and
* return.
*
* The properly track everything, we're going to build a little state machine.
* It looks something like the following:
*
* +-------+ +---------+ ------------+
* ---- lbrace ---> | start | ---- digit ---> | minimum | |
* +-------+ +---------+ <--- digit -+
* | | |
* +-------+ | | rbrace
* | comma | <----- comma +---- comma -------+ |
* +-------+ V V
* | +---------+ +---------+
* +-- digit --> | maximum | -- rbrace --> || final ||
* +---------+ +---------+
* | ^
* +- digit -+
*
* Note that by the time we've hit this function, the lbrace has already been
* consumed so we're in the start state.
*/
static bool
pm_regexp_parse_range_quantifier(pm_regexp_parser_t *parser) {
const uint8_t *savepoint = parser->cursor;
enum {
PM_REGEXP_RANGE_QUANTIFIER_STATE_START,
PM_REGEXP_RANGE_QUANTIFIER_STATE_MINIMUM,
PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM,
PM_REGEXP_RANGE_QUANTIFIER_STATE_COMMA
} state = PM_REGEXP_RANGE_QUANTIFIER_STATE_START;
while (1) {
if (parser->cursor >= parser->end) {
parser->cursor = savepoint;
return true;
}
switch (state) {
case PM_REGEXP_RANGE_QUANTIFIER_STATE_START:
switch (*parser->cursor) {
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
parser->cursor++;
state = PM_REGEXP_RANGE_QUANTIFIER_STATE_MINIMUM;
break;
case ',':
parser->cursor++;
state = PM_REGEXP_RANGE_QUANTIFIER_STATE_COMMA;
break;
default:
parser->cursor = savepoint;
return true;
}
break;
case PM_REGEXP_RANGE_QUANTIFIER_STATE_MINIMUM:
switch (*parser->cursor) {
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
parser->cursor++;
break;
case ',':
parser->cursor++;
state = PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM;
break;
case '}':
parser->cursor++;
return true;
default:
parser->cursor = savepoint;
return true;
}
break;
case PM_REGEXP_RANGE_QUANTIFIER_STATE_COMMA:
switch (*parser->cursor) {
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
parser->cursor++;
state = PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM;
break;
default:
parser->cursor = savepoint;
return true;
}
break;
case PM_REGEXP_RANGE_QUANTIFIER_STATE_MAXIMUM:
switch (*parser->cursor) {
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
parser->cursor++;
break;
case '}':
parser->cursor++;
return true;
default:
parser->cursor = savepoint;
return true;
}
break;
}
}
return true;
}
/**
* quantifier : star-quantifier
* | plus-quantifier
* | optional-quantifier
* | range-quantifier
* | <empty>
* ;
*/
static bool
pm_regexp_parse_quantifier(pm_regexp_parser_t *parser) {
while (!pm_regexp_char_is_eof(parser)) {
switch (*parser->cursor) {
case '*':
case '+':
case '?':
parser->cursor++;
break;
case '{':
parser->cursor++;
if (!pm_regexp_parse_range_quantifier(parser)) return false;
break;
default:
// In this case there is no quantifier.
return true;
}
}
return true;
}
/**
* match-posix-class : '[' '[' ':' '^'? CHAR+ ':' ']' ']'
* ;
*/
static bool
pm_regexp_parse_posix_class(pm_regexp_parser_t *parser) {
if (!pm_regexp_char_expect(parser, ':')) {
return false;
}
pm_regexp_char_accept(parser, '^');
return (
pm_regexp_char_find(parser, ':') &&
pm_regexp_char_expect(parser, ']') &&
pm_regexp_char_expect(parser, ']')
);
}
// Forward declaration because character sets can be nested.
static bool
pm_regexp_parse_lbracket(pm_regexp_parser_t *parser, uint16_t depth);
/**
* match-char-set : '[' '^'? (match-range | match-char)* ']'
* ;
*/
static bool
pm_regexp_parse_character_set(pm_regexp_parser_t *parser, uint16_t depth) {
pm_regexp_char_accept(parser, '^');
while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ']') {
switch (*parser->cursor++) {
case '[':
pm_regexp_parse_lbracket(parser, (uint16_t) (depth + 1));
break;
case '\\':
if (!pm_regexp_char_is_eof(parser)) {
parser->cursor++;
}
break;
default:
// do nothing, we've already advanced the cursor
break;
}
}
return pm_regexp_char_expect(parser, ']');
}
/**
* A left bracket can either mean a POSIX class or a character set.
*/
static bool
pm_regexp_parse_lbracket(pm_regexp_parser_t *parser, uint16_t depth) {
if (depth >= PM_REGEXP_PARSE_DEPTH_MAX) {
pm_regexp_parse_error(parser, parser->start, parser->end, "parse depth limit over");
return false;
}
if ((parser->cursor < parser->end) && parser->cursor[0] == ']') {
parser->cursor++;
pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "empty char-class");
return true;
}
const uint8_t *reset = parser->cursor;
if ((parser->cursor + 2 < parser->end) && parser->cursor[0] == '[' && parser->cursor[1] == ':') {
parser->cursor++;
if (pm_regexp_parse_posix_class(parser)) return true;
parser->cursor = reset;
}
return pm_regexp_parse_character_set(parser, depth);
}
// Forward declaration here since parsing groups needs to go back up the grammar
// to parse expressions within them.
static bool
pm_regexp_parse_expression(pm_regexp_parser_t *parser, uint16_t depth);
/**
* These are the states of the options that are configurable on the regular
* expression (or from within a group).
*/
typedef enum {
PM_REGEXP_OPTION_STATE_INVALID,
PM_REGEXP_OPTION_STATE_TOGGLEABLE,
PM_REGEXP_OPTION_STATE_ADDABLE,
PM_REGEXP_OPTION_STATE_ADDED,
PM_REGEXP_OPTION_STATE_REMOVED
} pm_regexp_option_state_t;
// These are the options that are configurable on the regular expression (or
// from within a group).
#define PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM 'a'
#define PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM 'x'
#define PRISM_REGEXP_OPTION_STATE_SLOTS (PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM + 1)
/**
* This is the set of options that are configurable on the regular expression.
*/
typedef struct {
/** The current state of each option. */
uint8_t values[PRISM_REGEXP_OPTION_STATE_SLOTS];
} pm_regexp_options_t;
/**
* Initialize a new set of options to their default values.
*/
static void
pm_regexp_options_init(pm_regexp_options_t *options) {
memset(options, PM_REGEXP_OPTION_STATE_INVALID, sizeof(uint8_t) * PRISM_REGEXP_OPTION_STATE_SLOTS);
options->values['i' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_TOGGLEABLE;
options->values['m' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_TOGGLEABLE;
options->values['x' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_TOGGLEABLE;
options->values['d' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_ADDABLE;
options->values['a' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_ADDABLE;
options->values['u' - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM] = PM_REGEXP_OPTION_STATE_ADDABLE;
}
/**
* Attempt to add the given option to the set of options. Returns true if it was
* added, false if it was already present.
*/
static bool
pm_regexp_options_add(pm_regexp_options_t *options, uint8_t key) {
if (key >= PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM && key <= PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM) {
key = (uint8_t) (key - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM);
switch (options->values[key]) {
case PM_REGEXP_OPTION_STATE_INVALID:
case PM_REGEXP_OPTION_STATE_REMOVED:
return false;
case PM_REGEXP_OPTION_STATE_TOGGLEABLE:
case PM_REGEXP_OPTION_STATE_ADDABLE:
options->values[key] = PM_REGEXP_OPTION_STATE_ADDED;
return true;
case PM_REGEXP_OPTION_STATE_ADDED:
return true;
}
}
return false;
}
/**
* Attempt to remove the given option from the set of options. Returns true if
* it was removed, false if it was already absent.
*/
static bool
pm_regexp_options_remove(pm_regexp_options_t *options, uint8_t key) {
if (key >= PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM && key <= PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM) {
key = (uint8_t) (key - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM);
switch (options->values[key]) {
case PM_REGEXP_OPTION_STATE_INVALID:
case PM_REGEXP_OPTION_STATE_ADDABLE:
return false;
case PM_REGEXP_OPTION_STATE_TOGGLEABLE:
case PM_REGEXP_OPTION_STATE_ADDED:
case PM_REGEXP_OPTION_STATE_REMOVED:
options->values[key] = PM_REGEXP_OPTION_STATE_REMOVED;
return true;
}
}
return false;
}
/**
* True if the given key is set in the options.
*/
static uint8_t
pm_regexp_options_state(pm_regexp_options_t *options, uint8_t key) {
if (key >= PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM && key <= PRISM_REGEXP_OPTION_STATE_SLOT_MAXIMUM) {
key = (uint8_t) (key - PRISM_REGEXP_OPTION_STATE_SLOT_MINIMUM);
return options->values[key];
}
return false;
}
/**
* Groups can have quite a few different patterns for syntax. They basically
* just wrap a set of expressions, but they can potentially have options after a
* question mark. If there _isn't_ a question mark, then it's just a set of
* expressions. If there _is_, then here are the options:
*
* * (?#...) - inline comments
* * (?:subexp) - non-capturing group
* * (?=subexp) - positive lookahead
* * (?!subexp) - negative lookahead
* * (?>subexp) - atomic group
* * (?~subexp) - absence operator
* * (?<=subexp) - positive lookbehind
* * (?<!subexp) - negative lookbehind
* * (?<name>subexp) - named capturing group
* * (?'name'subexp) - named capturing group
* * (?(cond)yes-subexp) - conditional expression
* * (?(cond)yes-subexp|no-subexp) - conditional expression
* * (?imxdau-imx) - turn on and off configuration
* * (?imxdau-imx:subexp) - turn on and off configuration for an expression
*/
static bool
pm_regexp_parse_group(pm_regexp_parser_t *parser, uint16_t depth) {
const uint8_t *group_start = parser->cursor;
pm_regexp_options_t options;
pm_regexp_options_init(&options);
// First, parse any options for the group.
if (pm_regexp_char_accept(parser, '?')) {
if (pm_regexp_char_is_eof(parser)) {
pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern in group");
return false;
}
switch (*parser->cursor) {
case '#': { // inline comments
parser->cursor++;
if (pm_regexp_char_is_eof(parser)) {
pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern in group");
return false;
}
if (parser->encoding_changed && parser->encoding->multibyte) {
bool escaped = false;
// Here we're going to take a slow path and iterate through
// each multibyte character to find the close paren. We do
// this because \ can be a trailing byte in some encodings.
while (parser->cursor < parser->end) {
if (!escaped && *parser->cursor == ')') {
parser->cursor++;
return true;
}
size_t width = parser->encoding->char_width(parser->cursor, (ptrdiff_t) (parser->end - parser->cursor));
if (width == 0) return false;
escaped = (width == 1) && (*parser->cursor == '\\');
parser->cursor += width;
}
return false;
} else {
// Here we can take the fast path and use memchr to find the
// next ) because we are safe checking backward for \ since
// it cannot be a trailing character.
bool found = pm_regexp_char_find(parser, ')');
while (found && (parser->start <= parser->cursor - 2) && (*(parser->cursor - 2) == '\\')) {
found = pm_regexp_char_find(parser, ')');
}
return found;
}
}
case ':': // non-capturing group
case '=': // positive lookahead
case '!': // negative lookahead
case '>': // atomic group
case '~': // absence operator
parser->cursor++;
break;
case '<':
parser->cursor++;
if (pm_regexp_char_is_eof(parser)) {
pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern with unmatched parenthesis");
return false;
}
switch (*parser->cursor) {
case '=': // positive lookbehind
case '!': // negative lookbehind
parser->cursor++;
break;
default: { // named capture group
const uint8_t *start = parser->cursor;
if (!pm_regexp_char_find(parser, '>')) {
return false;
}
if (parser->cursor - start == 1) {
pm_regexp_parse_error(parser, start, parser->cursor, "group name is empty");
}
if (parser->name_callback != NULL) {
pm_regexp_parser_named_capture(parser, start, parser->cursor - 1);
}
break;
}
}
break;
case '\'': { // named capture group
const uint8_t *start = ++parser->cursor;
if (!pm_regexp_char_find(parser, '\'')) {
return false;
}
if (parser->name_callback != NULL) {
pm_regexp_parser_named_capture(parser, start, parser->cursor - 1);
}
break;
}
case '(': // conditional expression
if (!pm_regexp_char_find(parser, ')')) {
return false;
}
break;
case 'i': case 'm': case 'x': case 'd': case 'a': case 'u': // options
while (!pm_regexp_char_is_eof(parser) && *parser->cursor != '-' && *parser->cursor != ':' && *parser->cursor != ')') {
if (!pm_regexp_options_add(&options, *parser->cursor)) {
return false;
}
parser->cursor++;
}
if (pm_regexp_char_is_eof(parser)) {
return false;
}
// If we are at the end of the group of options and there is no
// subexpression, then we are going to be setting the options
// for the parent group. In this case we are safe to return now.
if (*parser->cursor == ')') {
if (pm_regexp_options_state(&options, 'x') == PM_REGEXP_OPTION_STATE_ADDED) {
parser->extended_mode = true;
}
parser->cursor++;
return true;
}
// If we hit a -, then we're done parsing options.
if (*parser->cursor != '-') break;
PRISM_FALLTHROUGH
case '-':
parser->cursor++;
while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ':' && *parser->cursor != ')') {
if (!pm_regexp_options_remove(&options, *parser->cursor)) {
return false;
}
parser->cursor++;
}
if (pm_regexp_char_is_eof(parser)) {
return false;
}
// If we are at the end of the group of options and there is no
// subexpression, then we are going to be setting the options
// for the parent group. In this case we are safe to return now.
if (*parser->cursor == ')') {
switch (pm_regexp_options_state(&options, 'x')) {
case PM_REGEXP_OPTION_STATE_ADDED:
parser->extended_mode = true;
break;
case PM_REGEXP_OPTION_STATE_REMOVED:
parser->extended_mode = false;
break;
}
parser->cursor++;
return true;
}
break;
default:
parser->cursor++;
pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "undefined group option");
break;
}
}
bool extended_mode = parser->extended_mode;
switch (pm_regexp_options_state(&options, 'x')) {
case PM_REGEXP_OPTION_STATE_ADDED:
parser->extended_mode = true;
break;
case PM_REGEXP_OPTION_STATE_REMOVED:
parser->extended_mode = false;
break;
}
// Now, parse the expressions within this group.
while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ')') {
if (!pm_regexp_parse_expression(parser, (uint16_t) (depth + 1))) {
parser->extended_mode = extended_mode;
return false;
}
pm_regexp_char_accept(parser, '|');
}
// Finally, make sure we have a closing parenthesis.
parser->extended_mode = extended_mode;
if (pm_regexp_char_expect(parser, ')')) return true;
pm_regexp_parse_error(parser, group_start, parser->cursor, "end pattern with unmatched parenthesis");
return false;
}
/**
* item : anchor
* | match-posix-class
* | match-char-set
* | match-char-class
* | match-char-prop
* | match-char
* | match-any
* | group
* | quantified
* ;
*/
static bool
pm_regexp_parse_item(pm_regexp_parser_t *parser, uint16_t depth) {
switch (*parser->cursor) {
case '^':
case '$':
parser->cursor++;
return pm_regexp_parse_quantifier(parser);
case '\\':
parser->cursor++;
if (!pm_regexp_char_is_eof(parser)) {
parser->cursor++;
}
return pm_regexp_parse_quantifier(parser);
case '(':
parser->cursor++;
return pm_regexp_parse_group(parser, depth) && pm_regexp_parse_quantifier(parser);
case '[':
parser->cursor++;
return pm_regexp_parse_lbracket(parser, depth) && pm_regexp_parse_quantifier(parser);
case '*':
case '?':
case '+':
parser->cursor++;
pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "target of repeat operator is not specified");
return true;
case ')':
parser->cursor++;
pm_regexp_parse_error(parser, parser->cursor - 1, parser->cursor, "unmatched close parenthesis");
return true;
case '#':
if (parser->extended_mode) {
if (!pm_regexp_char_find(parser, '\n')) parser->cursor = parser->end;
return true;
}
PRISM_FALLTHROUGH
default: {
size_t width;
if (!parser->encoding_changed) {
width = pm_encoding_utf_8_char_width(parser->cursor, (ptrdiff_t) (parser->end - parser->cursor));
} else {
width = parser->encoding->char_width(parser->cursor, (ptrdiff_t) (parser->end - parser->cursor));
}
if (width == 0) return false; // TODO: add appropriate error
parser->cursor += width;
return pm_regexp_parse_quantifier(parser);
}
}
}
/**
* expression : item+
* ;
*/
static bool
pm_regexp_parse_expression(pm_regexp_parser_t *parser, uint16_t depth) {
if (depth >= PM_REGEXP_PARSE_DEPTH_MAX) {
pm_regexp_parse_error(parser, parser->start, parser->end, "parse depth limit over");
return false;
}
if (!pm_regexp_parse_item(parser, depth)) {
return false;
}
while (!pm_regexp_char_is_eof(parser) && *parser->cursor != ')' && *parser->cursor != '|') {
if (!pm_regexp_parse_item(parser, depth)) {
return false;
}
}
return true;
}
/**
* pattern : EOF
* | expression EOF
* | expression '|' pattern
* ;
*/
static bool
pm_regexp_parse_pattern(pm_regexp_parser_t *parser) {
do {
if (pm_regexp_char_is_eof(parser)) return true;
if (!pm_regexp_parse_expression(parser, 0)) return false;
} while (pm_regexp_char_accept(parser, '|'));
return pm_regexp_char_is_eof(parser);
}
/**
* Parse a regular expression and extract the names of all of the named capture
* groups.
*/
PRISM_EXPORTED_FUNCTION void
pm_regexp_parse(pm_parser_t *parser, const uint8_t *source, size_t size, bool extended_mode, pm_regexp_name_callback_t name_callback, void *name_data, pm_regexp_error_callback_t error_callback, void *error_data) {
pm_regexp_parse_pattern(&(pm_regexp_parser_t) {
.parser = parser,
.start = source,
.cursor = source,
.end = source + size,
.extended_mode = extended_mode,
.encoding_changed = parser->encoding_changed,
.encoding = parser->encoding,
.name_callback = name_callback,
.name_data = name_data,
.error_callback = error_callback,
.error_data = error_data
});
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,617 @@
#include "prism/static_literals.h"
/**
* A small struct used for passing around a subset of the information that is
* stored on the parser. We use this to avoid having static literals explicitly
* depend on the parser struct.
*/
typedef struct {
/** The list of newline offsets to use to calculate line numbers. */
const pm_newline_list_t *newline_list;
/** The line number that the parser starts on. */
int32_t start_line;
/** The name of the encoding that the parser is using. */
const char *encoding_name;
} pm_static_literals_metadata_t;
static inline uint32_t
murmur_scramble(uint32_t value) {
value *= 0xcc9e2d51;
value = (value << 15) | (value >> 17);
value *= 0x1b873593;
return value;
}
/**
* Murmur hash (https://en.wikipedia.org/wiki/MurmurHash) is a non-cryptographic
* general-purpose hash function. It is fast, which is what we care about in
* this case.
*/
static uint32_t
murmur_hash(const uint8_t *key, size_t length) {
uint32_t hash = 0x9747b28c;
uint32_t segment;
for (size_t index = length >> 2; index; index--) {
memcpy(&segment, key, sizeof(uint32_t));
key += sizeof(uint32_t);
hash ^= murmur_scramble(segment);
hash = (hash << 13) | (hash >> 19);
hash = hash * 5 + 0xe6546b64;
}
segment = 0;
for (size_t index = length & 3; index; index--) {
segment <<= 8;
segment |= key[index - 1];
}
hash ^= murmur_scramble(segment);
hash ^= (uint32_t) length;
hash ^= hash >> 16;
hash *= 0x85ebca6b;
hash ^= hash >> 13;
hash *= 0xc2b2ae35;
hash ^= hash >> 16;
return hash;
}
/**
* Hash the value of an integer and return it.
*/
static uint32_t
integer_hash(const pm_integer_t *integer) {
uint32_t hash;
if (integer->values) {
hash = murmur_hash((const uint8_t *) integer->values, sizeof(uint32_t) * integer->length);
} else {
hash = murmur_hash((const uint8_t *) &integer->value, sizeof(uint32_t));
}
if (integer->negative) {
hash ^= murmur_scramble((uint32_t) 1);
}
return hash;
}
/**
* Return the hash of the given node. It is important that nodes that have
* equivalent static literal values have the same hash. This is because we use
* these hashes to look for duplicates.
*/
static uint32_t
node_hash(const pm_static_literals_metadata_t *metadata, const pm_node_t *node) {
switch (PM_NODE_TYPE(node)) {
case PM_INTEGER_NODE: {
// Integers hash their value.
const pm_integer_node_t *cast = (const pm_integer_node_t *) node;
return integer_hash(&cast->value);
}
case PM_SOURCE_LINE_NODE: {
// Source lines hash their line number.
const pm_line_column_t line_column = pm_newline_list_line_column(metadata->newline_list, node->location.start, metadata->start_line);
const int32_t *value = &line_column.line;
return murmur_hash((const uint8_t *) value, sizeof(int32_t));
}
case PM_FLOAT_NODE: {
// Floats hash their value.
const double *value = &((const pm_float_node_t *) node)->value;
return murmur_hash((const uint8_t *) value, sizeof(double));
}
case PM_RATIONAL_NODE: {
// Rationals hash their numerator and denominator.
const pm_rational_node_t *cast = (const pm_rational_node_t *) node;
return integer_hash(&cast->numerator) ^ integer_hash(&cast->denominator) ^ murmur_scramble((uint32_t) cast->base.type);
}
case PM_IMAGINARY_NODE: {
// Imaginaries hash their numeric value. Because their numeric value
// is stored as a subnode, we hash that node and then mix in the
// fact that this is an imaginary node.
const pm_node_t *numeric = ((const pm_imaginary_node_t *) node)->numeric;
return node_hash(metadata, numeric) ^ murmur_scramble((uint32_t) node->type);
}
case PM_STRING_NODE: {
// Strings hash their value and mix in their flags so that different
// encodings are not considered equal.
const pm_string_t *value = &((const pm_string_node_t *) node)->unescaped;
pm_node_flags_t flags = node->flags;
flags &= (PM_STRING_FLAGS_FORCED_BINARY_ENCODING | PM_STRING_FLAGS_FORCED_UTF8_ENCODING);
return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) flags);
}
case PM_SOURCE_FILE_NODE: {
// Source files hash their value and mix in their flags so that
// different encodings are not considered equal.
const pm_string_t *value = &((const pm_source_file_node_t *) node)->filepath;
return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t));
}
case PM_REGULAR_EXPRESSION_NODE: {
// Regular expressions hash their value and mix in their flags so
// that different encodings are not considered equal.
const pm_string_t *value = &((const pm_regular_expression_node_t *) node)->unescaped;
return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) node->flags);
}
case PM_SYMBOL_NODE: {
// Symbols hash their value and mix in their flags so that different
// encodings are not considered equal.
const pm_string_t *value = &((const pm_symbol_node_t *) node)->unescaped;
return murmur_hash(pm_string_source(value), pm_string_length(value) * sizeof(uint8_t)) ^ murmur_scramble((uint32_t) node->flags);
}
default:
assert(false && "unreachable");
return 0;
}
}
/**
* Insert a node into the node hash. It accepts the hash that should hold the
* new node, the parser that generated the node, the node to insert, and a
* comparison function. The comparison function is used for collision detection,
* and must be able to compare all node types that will be stored in this hash.
*/
static pm_node_t *
pm_node_hash_insert(pm_node_hash_t *hash, const pm_static_literals_metadata_t *metadata, pm_node_t *node, bool replace, int (*compare)(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right)) {
// If we are out of space, we need to resize the hash. This will cause all
// of the nodes to be rehashed and reinserted into the new hash.
if (hash->size * 2 >= hash->capacity) {
// First, allocate space for the new node list.
uint32_t new_capacity = hash->capacity == 0 ? 4 : hash->capacity * 2;
pm_node_t **new_nodes = xcalloc(new_capacity, sizeof(pm_node_t *));
if (new_nodes == NULL) return NULL;
// It turns out to be more efficient to mask the hash value than to use
// the modulo operator. Because our capacities are always powers of two,
// we can use a bitwise AND to get the same result as the modulo
// operator.
uint32_t mask = new_capacity - 1;
// Now, rehash all of the nodes into the new list.
for (uint32_t index = 0; index < hash->capacity; index++) {
pm_node_t *node = hash->nodes[index];
if (node != NULL) {
uint32_t index = node_hash(metadata, node) & mask;
new_nodes[index] = node;
}
}
// Finally, free the old node list and update the hash.
xfree(hash->nodes);
hash->nodes = new_nodes;
hash->capacity = new_capacity;
}
// Now, insert the node into the hash.
uint32_t mask = hash->capacity - 1;
uint32_t index = node_hash(metadata, node) & mask;
// We use linear probing to resolve collisions. This means that if the
// current index is occupied, we will move to the next index and try again.
// We are guaranteed that this will eventually find an empty slot because we
// resize the hash when it gets too full.
while (hash->nodes[index] != NULL) {
if (compare(metadata, hash->nodes[index], node) == 0) break;
index = (index + 1) & mask;
}
// If the current index is occupied, we need to return the node that was
// already in the hash. Otherwise, we can just increment the size and insert
// the new node.
pm_node_t *result = hash->nodes[index];
if (result == NULL) {
hash->size++;
hash->nodes[index] = node;
} else if (replace) {
hash->nodes[index] = node;
}
return result;
}
/**
* Free the internal memory associated with the given node hash.
*/
static void
pm_node_hash_free(pm_node_hash_t *hash) {
if (hash->capacity > 0) xfree(hash->nodes);
}
/**
* Compare two values that can be compared with a simple numeric comparison.
*/
#define PM_NUMERIC_COMPARISON(left, right) ((left < right) ? -1 : (left > right) ? 1 : 0)
/**
* Return the integer value of the given node as an int64_t.
*/
static int64_t
pm_int64_value(const pm_static_literals_metadata_t *metadata, const pm_node_t *node) {
switch (PM_NODE_TYPE(node)) {
case PM_INTEGER_NODE: {
const pm_integer_t *integer = &((const pm_integer_node_t *) node)->value;
if (integer->values) return integer->negative ? INT64_MIN : INT64_MAX;
int64_t value = (int64_t) integer->value;
return integer->negative ? -value : value;
}
case PM_SOURCE_LINE_NODE:
return (int64_t) pm_newline_list_line_column(metadata->newline_list, node->location.start, metadata->start_line).line;
default:
assert(false && "unreachable");
return 0;
}
}
/**
* A comparison function for comparing two IntegerNode or SourceLineNode
* instances.
*/
static int
pm_compare_integer_nodes(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
if (PM_NODE_TYPE_P(left, PM_SOURCE_LINE_NODE) || PM_NODE_TYPE_P(right, PM_SOURCE_LINE_NODE)) {
int64_t left_value = pm_int64_value(metadata, left);
int64_t right_value = pm_int64_value(metadata, right);
return PM_NUMERIC_COMPARISON(left_value, right_value);
}
const pm_integer_t *left_integer = &((const pm_integer_node_t *) left)->value;
const pm_integer_t *right_integer = &((const pm_integer_node_t *) right)->value;
return pm_integer_compare(left_integer, right_integer);
}
/**
* A comparison function for comparing two FloatNode instances.
*/
static int
pm_compare_float_nodes(PRISM_ATTRIBUTE_UNUSED const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
const double left_value = ((const pm_float_node_t *) left)->value;
const double right_value = ((const pm_float_node_t *) right)->value;
return PM_NUMERIC_COMPARISON(left_value, right_value);
}
/**
* A comparison function for comparing two nodes that have attached numbers.
*/
static int
pm_compare_number_nodes(const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
if (PM_NODE_TYPE(left) != PM_NODE_TYPE(right)) {
return PM_NUMERIC_COMPARISON(PM_NODE_TYPE(left), PM_NODE_TYPE(right));
}
switch (PM_NODE_TYPE(left)) {
case PM_IMAGINARY_NODE:
return pm_compare_number_nodes(metadata, ((const pm_imaginary_node_t *) left)->numeric, ((const pm_imaginary_node_t *) right)->numeric);
case PM_RATIONAL_NODE: {
const pm_rational_node_t *left_rational = (const pm_rational_node_t *) left;
const pm_rational_node_t *right_rational = (const pm_rational_node_t *) right;
int result = pm_integer_compare(&left_rational->denominator, &right_rational->denominator);
if (result != 0) return result;
return pm_integer_compare(&left_rational->numerator, &right_rational->numerator);
}
case PM_INTEGER_NODE:
return pm_compare_integer_nodes(metadata, left, right);
case PM_FLOAT_NODE:
return pm_compare_float_nodes(metadata, left, right);
default:
assert(false && "unreachable");
return 0;
}
}
/**
* Return a pointer to the string value of the given node.
*/
static const pm_string_t *
pm_string_value(const pm_node_t *node) {
switch (PM_NODE_TYPE(node)) {
case PM_STRING_NODE:
return &((const pm_string_node_t *) node)->unescaped;
case PM_SOURCE_FILE_NODE:
return &((const pm_source_file_node_t *) node)->filepath;
case PM_SYMBOL_NODE:
return &((const pm_symbol_node_t *) node)->unescaped;
default:
assert(false && "unreachable");
return NULL;
}
}
/**
* A comparison function for comparing two nodes that have attached strings.
*/
static int
pm_compare_string_nodes(PRISM_ATTRIBUTE_UNUSED const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
const pm_string_t *left_string = pm_string_value(left);
const pm_string_t *right_string = pm_string_value(right);
return pm_string_compare(left_string, right_string);
}
/**
* A comparison function for comparing two RegularExpressionNode instances.
*/
static int
pm_compare_regular_expression_nodes(PRISM_ATTRIBUTE_UNUSED const pm_static_literals_metadata_t *metadata, const pm_node_t *left, const pm_node_t *right) {
const pm_regular_expression_node_t *left_regexp = (const pm_regular_expression_node_t *) left;
const pm_regular_expression_node_t *right_regexp = (const pm_regular_expression_node_t *) right;
int result = pm_string_compare(&left_regexp->unescaped, &right_regexp->unescaped);
if (result != 0) return result;
return PM_NUMERIC_COMPARISON(left_regexp->base.flags, right_regexp->base.flags);
}
#undef PM_NUMERIC_COMPARISON
/**
* Add a node to the set of static literals.
*/
pm_node_t *
pm_static_literals_add(const pm_newline_list_t *newline_list, int32_t start_line, pm_static_literals_t *literals, pm_node_t *node, bool replace) {
switch (PM_NODE_TYPE(node)) {
case PM_INTEGER_NODE:
case PM_SOURCE_LINE_NODE:
return pm_node_hash_insert(
&literals->integer_nodes,
&(pm_static_literals_metadata_t) {
.newline_list = newline_list,
.start_line = start_line,
.encoding_name = NULL
},
node,
replace,
pm_compare_integer_nodes
);
case PM_FLOAT_NODE:
return pm_node_hash_insert(
&literals->float_nodes,
&(pm_static_literals_metadata_t) {
.newline_list = newline_list,
.start_line = start_line,
.encoding_name = NULL
},
node,
replace,
pm_compare_float_nodes
);
case PM_RATIONAL_NODE:
case PM_IMAGINARY_NODE:
return pm_node_hash_insert(
&literals->number_nodes,
&(pm_static_literals_metadata_t) {
.newline_list = newline_list,
.start_line = start_line,
.encoding_name = NULL
},
node,
replace,
pm_compare_number_nodes
);
case PM_STRING_NODE:
case PM_SOURCE_FILE_NODE:
return pm_node_hash_insert(
&literals->string_nodes,
&(pm_static_literals_metadata_t) {
.newline_list = newline_list,
.start_line = start_line,
.encoding_name = NULL
},
node,
replace,
pm_compare_string_nodes
);
case PM_REGULAR_EXPRESSION_NODE:
return pm_node_hash_insert(
&literals->regexp_nodes,
&(pm_static_literals_metadata_t) {
.newline_list = newline_list,
.start_line = start_line,
.encoding_name = NULL
},
node,
replace,
pm_compare_regular_expression_nodes
);
case PM_SYMBOL_NODE:
return pm_node_hash_insert(
&literals->symbol_nodes,
&(pm_static_literals_metadata_t) {
.newline_list = newline_list,
.start_line = start_line,
.encoding_name = NULL
},
node,
replace,
pm_compare_string_nodes
);
case PM_TRUE_NODE: {
pm_node_t *duplicated = literals->true_node;
if ((duplicated == NULL) || replace) literals->true_node = node;
return duplicated;
}
case PM_FALSE_NODE: {
pm_node_t *duplicated = literals->false_node;
if ((duplicated == NULL) || replace) literals->false_node = node;
return duplicated;
}
case PM_NIL_NODE: {
pm_node_t *duplicated = literals->nil_node;
if ((duplicated == NULL) || replace) literals->nil_node = node;
return duplicated;
}
case PM_SOURCE_ENCODING_NODE: {
pm_node_t *duplicated = literals->source_encoding_node;
if ((duplicated == NULL) || replace) literals->source_encoding_node = node;
return duplicated;
}
default:
return NULL;
}
}
/**
* Free the internal memory associated with the given static literals set.
*/
void
pm_static_literals_free(pm_static_literals_t *literals) {
pm_node_hash_free(&literals->integer_nodes);
pm_node_hash_free(&literals->float_nodes);
pm_node_hash_free(&literals->number_nodes);
pm_node_hash_free(&literals->string_nodes);
pm_node_hash_free(&literals->regexp_nodes);
pm_node_hash_free(&literals->symbol_nodes);
}
/**
* A helper to determine if the given node is a static literal that is positive.
* This is used for formatting imaginary nodes.
*/
static bool
pm_static_literal_positive_p(const pm_node_t *node) {
switch (PM_NODE_TYPE(node)) {
case PM_FLOAT_NODE:
return ((const pm_float_node_t *) node)->value > 0;
case PM_INTEGER_NODE:
return !((const pm_integer_node_t *) node)->value.negative;
case PM_RATIONAL_NODE:
return !((const pm_rational_node_t *) node)->numerator.negative;
case PM_IMAGINARY_NODE:
return pm_static_literal_positive_p(((const pm_imaginary_node_t *) node)->numeric);
default:
assert(false && "unreachable");
return false;
}
}
/**
* Create a string-based representation of the given static literal.
*/
static inline void
pm_static_literal_inspect_node(pm_buffer_t *buffer, const pm_static_literals_metadata_t *metadata, const pm_node_t *node) {
switch (PM_NODE_TYPE(node)) {
case PM_FALSE_NODE:
pm_buffer_append_string(buffer, "false", 5);
break;
case PM_FLOAT_NODE: {
const double value = ((const pm_float_node_t *) node)->value;
if (PRISM_ISINF(value)) {
if (*node->location.start == '-') {
pm_buffer_append_byte(buffer, '-');
}
pm_buffer_append_string(buffer, "Infinity", 8);
} else if (value == 0.0) {
if (*node->location.start == '-') {
pm_buffer_append_byte(buffer, '-');
}
pm_buffer_append_string(buffer, "0.0", 3);
} else {
pm_buffer_append_format(buffer, "%g", value);
// %g will not insert a .0 for 1e100 (we'll get back 1e+100). So
// we check for the decimal point and add it in here if it's not
// present.
if (pm_buffer_index(buffer, '.') == SIZE_MAX) {
size_t exponent_index = pm_buffer_index(buffer, 'e');
size_t index = exponent_index == SIZE_MAX ? pm_buffer_length(buffer) : exponent_index;
pm_buffer_insert(buffer, index, ".0", 2);
}
}
break;
}
case PM_IMAGINARY_NODE: {
const pm_node_t *numeric = ((const pm_imaginary_node_t *) node)->numeric;
pm_buffer_append_string(buffer, "(0", 2);
if (pm_static_literal_positive_p(numeric)) pm_buffer_append_byte(buffer, '+');
pm_static_literal_inspect_node(buffer, metadata, numeric);
if (PM_NODE_TYPE_P(numeric, PM_RATIONAL_NODE)) {
pm_buffer_append_byte(buffer, '*');
}
pm_buffer_append_string(buffer, "i)", 2);
break;
}
case PM_INTEGER_NODE:
pm_integer_string(buffer, &((const pm_integer_node_t *) node)->value);
break;
case PM_NIL_NODE:
pm_buffer_append_string(buffer, "nil", 3);
break;
case PM_RATIONAL_NODE: {
const pm_rational_node_t *rational = (const pm_rational_node_t *) node;
pm_buffer_append_byte(buffer, '(');
pm_integer_string(buffer, &rational->numerator);
pm_buffer_append_byte(buffer, '/');
pm_integer_string(buffer, &rational->denominator);
pm_buffer_append_byte(buffer, ')');
break;
}
case PM_REGULAR_EXPRESSION_NODE: {
const pm_string_t *unescaped = &((const pm_regular_expression_node_t *) node)->unescaped;
pm_buffer_append_byte(buffer, '/');
pm_buffer_append_source(buffer, pm_string_source(unescaped), pm_string_length(unescaped), PM_BUFFER_ESCAPING_RUBY);
pm_buffer_append_byte(buffer, '/');
if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_MULTI_LINE)) pm_buffer_append_string(buffer, "m", 1);
if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_IGNORE_CASE)) pm_buffer_append_string(buffer, "i", 1);
if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_EXTENDED)) pm_buffer_append_string(buffer, "x", 1);
if (PM_NODE_FLAG_P(node, PM_REGULAR_EXPRESSION_FLAGS_ASCII_8BIT)) pm_buffer_append_string(buffer, "n", 1);
break;
}
case PM_SOURCE_ENCODING_NODE:
pm_buffer_append_format(buffer, "#<Encoding:%s>", metadata->encoding_name);
break;
case PM_SOURCE_FILE_NODE: {
const pm_string_t *filepath = &((const pm_source_file_node_t *) node)->filepath;
pm_buffer_append_byte(buffer, '"');
pm_buffer_append_source(buffer, pm_string_source(filepath), pm_string_length(filepath), PM_BUFFER_ESCAPING_RUBY);
pm_buffer_append_byte(buffer, '"');
break;
}
case PM_SOURCE_LINE_NODE:
pm_buffer_append_format(buffer, "%d", pm_newline_list_line_column(metadata->newline_list, node->location.start, metadata->start_line).line);
break;
case PM_STRING_NODE: {
const pm_string_t *unescaped = &((const pm_string_node_t *) node)->unescaped;
pm_buffer_append_byte(buffer, '"');
pm_buffer_append_source(buffer, pm_string_source(unescaped), pm_string_length(unescaped), PM_BUFFER_ESCAPING_RUBY);
pm_buffer_append_byte(buffer, '"');
break;
}
case PM_SYMBOL_NODE: {
const pm_string_t *unescaped = &((const pm_symbol_node_t *) node)->unescaped;
pm_buffer_append_byte(buffer, ':');
pm_buffer_append_source(buffer, pm_string_source(unescaped), pm_string_length(unescaped), PM_BUFFER_ESCAPING_RUBY);
break;
}
case PM_TRUE_NODE:
pm_buffer_append_string(buffer, "true", 4);
break;
default:
assert(false && "unreachable");
break;
}
}
/**
* Create a string-based representation of the given static literal.
*/
void
pm_static_literal_inspect(pm_buffer_t *buffer, const pm_newline_list_t *newline_list, int32_t start_line, const char *encoding_name, const pm_node_t *node) {
pm_static_literal_inspect_node(
buffer,
&(pm_static_literals_metadata_t) {
.newline_list = newline_list,
.start_line = start_line,
.encoding_name = encoding_name
},
node
);
}

View File

@@ -0,0 +1,701 @@
/*----------------------------------------------------------------------------*/
/* This file is generated by the templates/template.rb script and should not */
/* be modified manually. See */
/* templates/src/token_type.c.erb */
/* if you are looking to modify the */
/* template */
/*----------------------------------------------------------------------------*/
#include <string.h>
#include "prism/ast.h"
/**
* Returns a string representation of the given token type.
*/
PRISM_EXPORTED_FUNCTION const char *
pm_token_type_name(pm_token_type_t token_type) {
switch (token_type) {
case PM_TOKEN_EOF:
return "EOF";
case PM_TOKEN_MISSING:
return "MISSING";
case PM_TOKEN_NOT_PROVIDED:
return "NOT_PROVIDED";
case PM_TOKEN_AMPERSAND:
return "AMPERSAND";
case PM_TOKEN_AMPERSAND_AMPERSAND:
return "AMPERSAND_AMPERSAND";
case PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL:
return "AMPERSAND_AMPERSAND_EQUAL";
case PM_TOKEN_AMPERSAND_DOT:
return "AMPERSAND_DOT";
case PM_TOKEN_AMPERSAND_EQUAL:
return "AMPERSAND_EQUAL";
case PM_TOKEN_BACKTICK:
return "BACKTICK";
case PM_TOKEN_BACK_REFERENCE:
return "BACK_REFERENCE";
case PM_TOKEN_BANG:
return "BANG";
case PM_TOKEN_BANG_EQUAL:
return "BANG_EQUAL";
case PM_TOKEN_BANG_TILDE:
return "BANG_TILDE";
case PM_TOKEN_BRACE_LEFT:
return "BRACE_LEFT";
case PM_TOKEN_BRACE_RIGHT:
return "BRACE_RIGHT";
case PM_TOKEN_BRACKET_LEFT:
return "BRACKET_LEFT";
case PM_TOKEN_BRACKET_LEFT_ARRAY:
return "BRACKET_LEFT_ARRAY";
case PM_TOKEN_BRACKET_LEFT_RIGHT:
return "BRACKET_LEFT_RIGHT";
case PM_TOKEN_BRACKET_LEFT_RIGHT_EQUAL:
return "BRACKET_LEFT_RIGHT_EQUAL";
case PM_TOKEN_BRACKET_RIGHT:
return "BRACKET_RIGHT";
case PM_TOKEN_CARET:
return "CARET";
case PM_TOKEN_CARET_EQUAL:
return "CARET_EQUAL";
case PM_TOKEN_CHARACTER_LITERAL:
return "CHARACTER_LITERAL";
case PM_TOKEN_CLASS_VARIABLE:
return "CLASS_VARIABLE";
case PM_TOKEN_COLON:
return "COLON";
case PM_TOKEN_COLON_COLON:
return "COLON_COLON";
case PM_TOKEN_COMMA:
return "COMMA";
case PM_TOKEN_COMMENT:
return "COMMENT";
case PM_TOKEN_CONSTANT:
return "CONSTANT";
case PM_TOKEN_DOT:
return "DOT";
case PM_TOKEN_DOT_DOT:
return "DOT_DOT";
case PM_TOKEN_DOT_DOT_DOT:
return "DOT_DOT_DOT";
case PM_TOKEN_EMBDOC_BEGIN:
return "EMBDOC_BEGIN";
case PM_TOKEN_EMBDOC_END:
return "EMBDOC_END";
case PM_TOKEN_EMBDOC_LINE:
return "EMBDOC_LINE";
case PM_TOKEN_EMBEXPR_BEGIN:
return "EMBEXPR_BEGIN";
case PM_TOKEN_EMBEXPR_END:
return "EMBEXPR_END";
case PM_TOKEN_EMBVAR:
return "EMBVAR";
case PM_TOKEN_EQUAL:
return "EQUAL";
case PM_TOKEN_EQUAL_EQUAL:
return "EQUAL_EQUAL";
case PM_TOKEN_EQUAL_EQUAL_EQUAL:
return "EQUAL_EQUAL_EQUAL";
case PM_TOKEN_EQUAL_GREATER:
return "EQUAL_GREATER";
case PM_TOKEN_EQUAL_TILDE:
return "EQUAL_TILDE";
case PM_TOKEN_FLOAT:
return "FLOAT";
case PM_TOKEN_FLOAT_IMAGINARY:
return "FLOAT_IMAGINARY";
case PM_TOKEN_FLOAT_RATIONAL:
return "FLOAT_RATIONAL";
case PM_TOKEN_FLOAT_RATIONAL_IMAGINARY:
return "FLOAT_RATIONAL_IMAGINARY";
case PM_TOKEN_GLOBAL_VARIABLE:
return "GLOBAL_VARIABLE";
case PM_TOKEN_GREATER:
return "GREATER";
case PM_TOKEN_GREATER_EQUAL:
return "GREATER_EQUAL";
case PM_TOKEN_GREATER_GREATER:
return "GREATER_GREATER";
case PM_TOKEN_GREATER_GREATER_EQUAL:
return "GREATER_GREATER_EQUAL";
case PM_TOKEN_HEREDOC_END:
return "HEREDOC_END";
case PM_TOKEN_HEREDOC_START:
return "HEREDOC_START";
case PM_TOKEN_IDENTIFIER:
return "IDENTIFIER";
case PM_TOKEN_IGNORED_NEWLINE:
return "IGNORED_NEWLINE";
case PM_TOKEN_INSTANCE_VARIABLE:
return "INSTANCE_VARIABLE";
case PM_TOKEN_INTEGER:
return "INTEGER";
case PM_TOKEN_INTEGER_IMAGINARY:
return "INTEGER_IMAGINARY";
case PM_TOKEN_INTEGER_RATIONAL:
return "INTEGER_RATIONAL";
case PM_TOKEN_INTEGER_RATIONAL_IMAGINARY:
return "INTEGER_RATIONAL_IMAGINARY";
case PM_TOKEN_KEYWORD_ALIAS:
return "KEYWORD_ALIAS";
case PM_TOKEN_KEYWORD_AND:
return "KEYWORD_AND";
case PM_TOKEN_KEYWORD_BEGIN:
return "KEYWORD_BEGIN";
case PM_TOKEN_KEYWORD_BEGIN_UPCASE:
return "KEYWORD_BEGIN_UPCASE";
case PM_TOKEN_KEYWORD_BREAK:
return "KEYWORD_BREAK";
case PM_TOKEN_KEYWORD_CASE:
return "KEYWORD_CASE";
case PM_TOKEN_KEYWORD_CLASS:
return "KEYWORD_CLASS";
case PM_TOKEN_KEYWORD_DEF:
return "KEYWORD_DEF";
case PM_TOKEN_KEYWORD_DEFINED:
return "KEYWORD_DEFINED";
case PM_TOKEN_KEYWORD_DO:
return "KEYWORD_DO";
case PM_TOKEN_KEYWORD_DO_LOOP:
return "KEYWORD_DO_LOOP";
case PM_TOKEN_KEYWORD_ELSE:
return "KEYWORD_ELSE";
case PM_TOKEN_KEYWORD_ELSIF:
return "KEYWORD_ELSIF";
case PM_TOKEN_KEYWORD_END:
return "KEYWORD_END";
case PM_TOKEN_KEYWORD_END_UPCASE:
return "KEYWORD_END_UPCASE";
case PM_TOKEN_KEYWORD_ENSURE:
return "KEYWORD_ENSURE";
case PM_TOKEN_KEYWORD_FALSE:
return "KEYWORD_FALSE";
case PM_TOKEN_KEYWORD_FOR:
return "KEYWORD_FOR";
case PM_TOKEN_KEYWORD_IF:
return "KEYWORD_IF";
case PM_TOKEN_KEYWORD_IF_MODIFIER:
return "KEYWORD_IF_MODIFIER";
case PM_TOKEN_KEYWORD_IN:
return "KEYWORD_IN";
case PM_TOKEN_KEYWORD_MODULE:
return "KEYWORD_MODULE";
case PM_TOKEN_KEYWORD_NEXT:
return "KEYWORD_NEXT";
case PM_TOKEN_KEYWORD_NIL:
return "KEYWORD_NIL";
case PM_TOKEN_KEYWORD_NOT:
return "KEYWORD_NOT";
case PM_TOKEN_KEYWORD_OR:
return "KEYWORD_OR";
case PM_TOKEN_KEYWORD_REDO:
return "KEYWORD_REDO";
case PM_TOKEN_KEYWORD_RESCUE:
return "KEYWORD_RESCUE";
case PM_TOKEN_KEYWORD_RESCUE_MODIFIER:
return "KEYWORD_RESCUE_MODIFIER";
case PM_TOKEN_KEYWORD_RETRY:
return "KEYWORD_RETRY";
case PM_TOKEN_KEYWORD_RETURN:
return "KEYWORD_RETURN";
case PM_TOKEN_KEYWORD_SELF:
return "KEYWORD_SELF";
case PM_TOKEN_KEYWORD_SUPER:
return "KEYWORD_SUPER";
case PM_TOKEN_KEYWORD_THEN:
return "KEYWORD_THEN";
case PM_TOKEN_KEYWORD_TRUE:
return "KEYWORD_TRUE";
case PM_TOKEN_KEYWORD_UNDEF:
return "KEYWORD_UNDEF";
case PM_TOKEN_KEYWORD_UNLESS:
return "KEYWORD_UNLESS";
case PM_TOKEN_KEYWORD_UNLESS_MODIFIER:
return "KEYWORD_UNLESS_MODIFIER";
case PM_TOKEN_KEYWORD_UNTIL:
return "KEYWORD_UNTIL";
case PM_TOKEN_KEYWORD_UNTIL_MODIFIER:
return "KEYWORD_UNTIL_MODIFIER";
case PM_TOKEN_KEYWORD_WHEN:
return "KEYWORD_WHEN";
case PM_TOKEN_KEYWORD_WHILE:
return "KEYWORD_WHILE";
case PM_TOKEN_KEYWORD_WHILE_MODIFIER:
return "KEYWORD_WHILE_MODIFIER";
case PM_TOKEN_KEYWORD_YIELD:
return "KEYWORD_YIELD";
case PM_TOKEN_KEYWORD___ENCODING__:
return "KEYWORD___ENCODING__";
case PM_TOKEN_KEYWORD___FILE__:
return "KEYWORD___FILE__";
case PM_TOKEN_KEYWORD___LINE__:
return "KEYWORD___LINE__";
case PM_TOKEN_LABEL:
return "LABEL";
case PM_TOKEN_LABEL_END:
return "LABEL_END";
case PM_TOKEN_LAMBDA_BEGIN:
return "LAMBDA_BEGIN";
case PM_TOKEN_LESS:
return "LESS";
case PM_TOKEN_LESS_EQUAL:
return "LESS_EQUAL";
case PM_TOKEN_LESS_EQUAL_GREATER:
return "LESS_EQUAL_GREATER";
case PM_TOKEN_LESS_LESS:
return "LESS_LESS";
case PM_TOKEN_LESS_LESS_EQUAL:
return "LESS_LESS_EQUAL";
case PM_TOKEN_METHOD_NAME:
return "METHOD_NAME";
case PM_TOKEN_MINUS:
return "MINUS";
case PM_TOKEN_MINUS_EQUAL:
return "MINUS_EQUAL";
case PM_TOKEN_MINUS_GREATER:
return "MINUS_GREATER";
case PM_TOKEN_NEWLINE:
return "NEWLINE";
case PM_TOKEN_NUMBERED_REFERENCE:
return "NUMBERED_REFERENCE";
case PM_TOKEN_PARENTHESIS_LEFT:
return "PARENTHESIS_LEFT";
case PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES:
return "PARENTHESIS_LEFT_PARENTHESES";
case PM_TOKEN_PARENTHESIS_RIGHT:
return "PARENTHESIS_RIGHT";
case PM_TOKEN_PERCENT:
return "PERCENT";
case PM_TOKEN_PERCENT_EQUAL:
return "PERCENT_EQUAL";
case PM_TOKEN_PERCENT_LOWER_I:
return "PERCENT_LOWER_I";
case PM_TOKEN_PERCENT_LOWER_W:
return "PERCENT_LOWER_W";
case PM_TOKEN_PERCENT_LOWER_X:
return "PERCENT_LOWER_X";
case PM_TOKEN_PERCENT_UPPER_I:
return "PERCENT_UPPER_I";
case PM_TOKEN_PERCENT_UPPER_W:
return "PERCENT_UPPER_W";
case PM_TOKEN_PIPE:
return "PIPE";
case PM_TOKEN_PIPE_EQUAL:
return "PIPE_EQUAL";
case PM_TOKEN_PIPE_PIPE:
return "PIPE_PIPE";
case PM_TOKEN_PIPE_PIPE_EQUAL:
return "PIPE_PIPE_EQUAL";
case PM_TOKEN_PLUS:
return "PLUS";
case PM_TOKEN_PLUS_EQUAL:
return "PLUS_EQUAL";
case PM_TOKEN_QUESTION_MARK:
return "QUESTION_MARK";
case PM_TOKEN_REGEXP_BEGIN:
return "REGEXP_BEGIN";
case PM_TOKEN_REGEXP_END:
return "REGEXP_END";
case PM_TOKEN_SEMICOLON:
return "SEMICOLON";
case PM_TOKEN_SLASH:
return "SLASH";
case PM_TOKEN_SLASH_EQUAL:
return "SLASH_EQUAL";
case PM_TOKEN_STAR:
return "STAR";
case PM_TOKEN_STAR_EQUAL:
return "STAR_EQUAL";
case PM_TOKEN_STAR_STAR:
return "STAR_STAR";
case PM_TOKEN_STAR_STAR_EQUAL:
return "STAR_STAR_EQUAL";
case PM_TOKEN_STRING_BEGIN:
return "STRING_BEGIN";
case PM_TOKEN_STRING_CONTENT:
return "STRING_CONTENT";
case PM_TOKEN_STRING_END:
return "STRING_END";
case PM_TOKEN_SYMBOL_BEGIN:
return "SYMBOL_BEGIN";
case PM_TOKEN_TILDE:
return "TILDE";
case PM_TOKEN_UAMPERSAND:
return "UAMPERSAND";
case PM_TOKEN_UCOLON_COLON:
return "UCOLON_COLON";
case PM_TOKEN_UDOT_DOT:
return "UDOT_DOT";
case PM_TOKEN_UDOT_DOT_DOT:
return "UDOT_DOT_DOT";
case PM_TOKEN_UMINUS:
return "UMINUS";
case PM_TOKEN_UMINUS_NUM:
return "UMINUS_NUM";
case PM_TOKEN_UPLUS:
return "UPLUS";
case PM_TOKEN_USTAR:
return "USTAR";
case PM_TOKEN_USTAR_STAR:
return "USTAR_STAR";
case PM_TOKEN_WORDS_SEP:
return "WORDS_SEP";
case PM_TOKEN___END__:
return "__END__";
case PM_TOKEN_MAXIMUM:
assert(false && "unreachable");
return "";
}
// Provide a default, because some compilers can't determine that the above
// switch is exhaustive.
assert(false && "unreachable");
return "";
}
/**
* Returns the human name of the given token type.
*/
const char *
pm_token_type_human(pm_token_type_t token_type) {
switch (token_type) {
case PM_TOKEN_EOF:
return "end-of-input";
case PM_TOKEN_MISSING:
return "missing token";
case PM_TOKEN_NOT_PROVIDED:
return "not provided token";
case PM_TOKEN_AMPERSAND:
return "'&'";
case PM_TOKEN_AMPERSAND_AMPERSAND:
return "'&&'";
case PM_TOKEN_AMPERSAND_AMPERSAND_EQUAL:
return "'&&='";
case PM_TOKEN_AMPERSAND_DOT:
return "'&.'";
case PM_TOKEN_AMPERSAND_EQUAL:
return "'&='";
case PM_TOKEN_BACKTICK:
return "'`'";
case PM_TOKEN_BACK_REFERENCE:
return "back reference";
case PM_TOKEN_BANG:
return "'!'";
case PM_TOKEN_BANG_EQUAL:
return "'!='";
case PM_TOKEN_BANG_TILDE:
return "'!~'";
case PM_TOKEN_BRACE_LEFT:
return "'{'";
case PM_TOKEN_BRACE_RIGHT:
return "'}'";
case PM_TOKEN_BRACKET_LEFT:
return "'['";
case PM_TOKEN_BRACKET_LEFT_ARRAY:
return "'['";
case PM_TOKEN_BRACKET_LEFT_RIGHT:
return "'[]'";
case PM_TOKEN_BRACKET_LEFT_RIGHT_EQUAL:
return "'[]='";
case PM_TOKEN_BRACKET_RIGHT:
return "']'";
case PM_TOKEN_CARET:
return "'^'";
case PM_TOKEN_CARET_EQUAL:
return "'^='";
case PM_TOKEN_CHARACTER_LITERAL:
return "character literal";
case PM_TOKEN_CLASS_VARIABLE:
return "class variable";
case PM_TOKEN_COLON:
return "':'";
case PM_TOKEN_COLON_COLON:
return "'::'";
case PM_TOKEN_COMMA:
return "','";
case PM_TOKEN_COMMENT:
return "comment";
case PM_TOKEN_CONSTANT:
return "constant";
case PM_TOKEN_DOT:
return "'.'";
case PM_TOKEN_DOT_DOT:
return "..";
case PM_TOKEN_DOT_DOT_DOT:
return "...";
case PM_TOKEN_EMBDOC_BEGIN:
return "'=begin'";
case PM_TOKEN_EMBDOC_END:
return "'=end'";
case PM_TOKEN_EMBDOC_LINE:
return "embedded documentation line";
case PM_TOKEN_EMBEXPR_BEGIN:
return "'#{'";
case PM_TOKEN_EMBEXPR_END:
return "'}'";
case PM_TOKEN_EMBVAR:
return "'#'";
case PM_TOKEN_EQUAL:
return "'='";
case PM_TOKEN_EQUAL_EQUAL:
return "'=='";
case PM_TOKEN_EQUAL_EQUAL_EQUAL:
return "'==='";
case PM_TOKEN_EQUAL_GREATER:
return "'=>'";
case PM_TOKEN_EQUAL_TILDE:
return "'=~'";
case PM_TOKEN_FLOAT:
return "float";
case PM_TOKEN_FLOAT_IMAGINARY:
return "imaginary";
case PM_TOKEN_FLOAT_RATIONAL:
return "rational";
case PM_TOKEN_FLOAT_RATIONAL_IMAGINARY:
return "imaginary";
case PM_TOKEN_GLOBAL_VARIABLE:
return "global variable";
case PM_TOKEN_GREATER:
return "'>'";
case PM_TOKEN_GREATER_EQUAL:
return "'>='";
case PM_TOKEN_GREATER_GREATER:
return ">>";
case PM_TOKEN_GREATER_GREATER_EQUAL:
return ">>=";
case PM_TOKEN_HEREDOC_END:
return "heredoc ending";
case PM_TOKEN_HEREDOC_START:
return "heredoc beginning";
case PM_TOKEN_IDENTIFIER:
return "local variable or method";
case PM_TOKEN_IGNORED_NEWLINE:
return "ignored newline";
case PM_TOKEN_INSTANCE_VARIABLE:
return "instance variable";
case PM_TOKEN_INTEGER:
return "integer";
case PM_TOKEN_INTEGER_IMAGINARY:
return "imaginary";
case PM_TOKEN_INTEGER_RATIONAL:
return "rational";
case PM_TOKEN_INTEGER_RATIONAL_IMAGINARY:
return "imaginary";
case PM_TOKEN_KEYWORD_ALIAS:
return "'alias'";
case PM_TOKEN_KEYWORD_AND:
return "'and'";
case PM_TOKEN_KEYWORD_BEGIN:
return "'begin'";
case PM_TOKEN_KEYWORD_BEGIN_UPCASE:
return "'BEGIN'";
case PM_TOKEN_KEYWORD_BREAK:
return "'break'";
case PM_TOKEN_KEYWORD_CASE:
return "'case'";
case PM_TOKEN_KEYWORD_CLASS:
return "'class'";
case PM_TOKEN_KEYWORD_DEF:
return "'def'";
case PM_TOKEN_KEYWORD_DEFINED:
return "'defined?'";
case PM_TOKEN_KEYWORD_DO:
return "'do'";
case PM_TOKEN_KEYWORD_DO_LOOP:
return "'do'";
case PM_TOKEN_KEYWORD_ELSE:
return "'else'";
case PM_TOKEN_KEYWORD_ELSIF:
return "'elsif'";
case PM_TOKEN_KEYWORD_END:
return "'end'";
case PM_TOKEN_KEYWORD_END_UPCASE:
return "'END'";
case PM_TOKEN_KEYWORD_ENSURE:
return "'ensure'";
case PM_TOKEN_KEYWORD_FALSE:
return "'false'";
case PM_TOKEN_KEYWORD_FOR:
return "'for'";
case PM_TOKEN_KEYWORD_IF:
return "'if'";
case PM_TOKEN_KEYWORD_IF_MODIFIER:
return "'if'";
case PM_TOKEN_KEYWORD_IN:
return "'in'";
case PM_TOKEN_KEYWORD_MODULE:
return "'module'";
case PM_TOKEN_KEYWORD_NEXT:
return "'next'";
case PM_TOKEN_KEYWORD_NIL:
return "'nil'";
case PM_TOKEN_KEYWORD_NOT:
return "'not'";
case PM_TOKEN_KEYWORD_OR:
return "'or'";
case PM_TOKEN_KEYWORD_REDO:
return "'redo'";
case PM_TOKEN_KEYWORD_RESCUE:
return "'rescue'";
case PM_TOKEN_KEYWORD_RESCUE_MODIFIER:
return "'rescue' modifier";
case PM_TOKEN_KEYWORD_RETRY:
return "'retry'";
case PM_TOKEN_KEYWORD_RETURN:
return "'return'";
case PM_TOKEN_KEYWORD_SELF:
return "'self'";
case PM_TOKEN_KEYWORD_SUPER:
return "'super'";
case PM_TOKEN_KEYWORD_THEN:
return "'then'";
case PM_TOKEN_KEYWORD_TRUE:
return "'true'";
case PM_TOKEN_KEYWORD_UNDEF:
return "'undef'";
case PM_TOKEN_KEYWORD_UNLESS:
return "'unless'";
case PM_TOKEN_KEYWORD_UNLESS_MODIFIER:
return "'unless'";
case PM_TOKEN_KEYWORD_UNTIL:
return "'until'";
case PM_TOKEN_KEYWORD_UNTIL_MODIFIER:
return "'until'";
case PM_TOKEN_KEYWORD_WHEN:
return "'when'";
case PM_TOKEN_KEYWORD_WHILE:
return "'while'";
case PM_TOKEN_KEYWORD_WHILE_MODIFIER:
return "'while'";
case PM_TOKEN_KEYWORD_YIELD:
return "'yield'";
case PM_TOKEN_KEYWORD___ENCODING__:
return "'__ENCODING__'";
case PM_TOKEN_KEYWORD___FILE__:
return "'__FILE__'";
case PM_TOKEN_KEYWORD___LINE__:
return "'__LINE__'";
case PM_TOKEN_LABEL:
return "label";
case PM_TOKEN_LABEL_END:
return "label terminator";
case PM_TOKEN_LAMBDA_BEGIN:
return "'{'";
case PM_TOKEN_LESS:
return "'<'";
case PM_TOKEN_LESS_EQUAL:
return "'<='";
case PM_TOKEN_LESS_EQUAL_GREATER:
return "'<=>'";
case PM_TOKEN_LESS_LESS:
return "<<";
case PM_TOKEN_LESS_LESS_EQUAL:
return "<<=";
case PM_TOKEN_METHOD_NAME:
return "method name";
case PM_TOKEN_MINUS:
return "'-'";
case PM_TOKEN_MINUS_EQUAL:
return "'-='";
case PM_TOKEN_MINUS_GREATER:
return "'->'";
case PM_TOKEN_NEWLINE:
return "newline";
case PM_TOKEN_NUMBERED_REFERENCE:
return "numbered reference";
case PM_TOKEN_PARENTHESIS_LEFT:
return "'('";
case PM_TOKEN_PARENTHESIS_LEFT_PARENTHESES:
return "'('";
case PM_TOKEN_PARENTHESIS_RIGHT:
return "')'";
case PM_TOKEN_PERCENT:
return "'%'";
case PM_TOKEN_PERCENT_EQUAL:
return "'%='";
case PM_TOKEN_PERCENT_LOWER_I:
return "'%i'";
case PM_TOKEN_PERCENT_LOWER_W:
return "'%w'";
case PM_TOKEN_PERCENT_LOWER_X:
return "'%x'";
case PM_TOKEN_PERCENT_UPPER_I:
return "'%I'";
case PM_TOKEN_PERCENT_UPPER_W:
return "'%W'";
case PM_TOKEN_PIPE:
return "'|'";
case PM_TOKEN_PIPE_EQUAL:
return "'|='";
case PM_TOKEN_PIPE_PIPE:
return "'||'";
case PM_TOKEN_PIPE_PIPE_EQUAL:
return "'||='";
case PM_TOKEN_PLUS:
return "'+'";
case PM_TOKEN_PLUS_EQUAL:
return "'+='";
case PM_TOKEN_QUESTION_MARK:
return "'?'";
case PM_TOKEN_REGEXP_BEGIN:
return "regular expression beginning";
case PM_TOKEN_REGEXP_END:
return "regular expression ending";
case PM_TOKEN_SEMICOLON:
return "';'";
case PM_TOKEN_SLASH:
return "'/'";
case PM_TOKEN_SLASH_EQUAL:
return "'/='";
case PM_TOKEN_STAR:
return "'*'";
case PM_TOKEN_STAR_EQUAL:
return "'*='";
case PM_TOKEN_STAR_STAR:
return "'**'";
case PM_TOKEN_STAR_STAR_EQUAL:
return "'**='";
case PM_TOKEN_STRING_BEGIN:
return "string literal";
case PM_TOKEN_STRING_CONTENT:
return "string content";
case PM_TOKEN_STRING_END:
return "string ending";
case PM_TOKEN_SYMBOL_BEGIN:
return "symbol literal";
case PM_TOKEN_TILDE:
return "'~'";
case PM_TOKEN_UAMPERSAND:
return "'&'";
case PM_TOKEN_UCOLON_COLON:
return "'::'";
case PM_TOKEN_UDOT_DOT:
return "'..'";
case PM_TOKEN_UDOT_DOT_DOT:
return "'...'";
case PM_TOKEN_UMINUS:
return "'-'";
case PM_TOKEN_UMINUS_NUM:
return "'-'";
case PM_TOKEN_UPLUS:
return "'+'";
case PM_TOKEN_USTAR:
return "*";
case PM_TOKEN_USTAR_STAR:
return "**";
case PM_TOKEN_WORDS_SEP:
return "string separator";
case PM_TOKEN___END__:
return "'__END__'";
case PM_TOKEN_MAXIMUM:
assert(false && "unreachable");
return "";
}
// Provide a default, because some compilers can't determine that the above
// switch is exhaustive.
assert(false && "unreachable");
return "";
}

View File

@@ -0,0 +1,357 @@
#include "prism/util/pm_buffer.h"
/**
* Return the size of the pm_buffer_t struct.
*/
size_t
pm_buffer_sizeof(void) {
return sizeof(pm_buffer_t);
}
/**
* Initialize a pm_buffer_t with the given capacity.
*/
bool
pm_buffer_init_capacity(pm_buffer_t *buffer, size_t capacity) {
buffer->length = 0;
buffer->capacity = capacity;
buffer->value = (char *) xmalloc(capacity);
return buffer->value != NULL;
}
/**
* Initialize a pm_buffer_t with its default values.
*/
bool
pm_buffer_init(pm_buffer_t *buffer) {
return pm_buffer_init_capacity(buffer, 1024);
}
/**
* Return the value of the buffer.
*/
char *
pm_buffer_value(const pm_buffer_t *buffer) {
return buffer->value;
}
/**
* Return the length of the buffer.
*/
size_t
pm_buffer_length(const pm_buffer_t *buffer) {
return buffer->length;
}
/**
* Append the given amount of space to the buffer.
*/
static inline bool
pm_buffer_append_length(pm_buffer_t *buffer, size_t length) {
size_t next_length = buffer->length + length;
if (next_length > buffer->capacity) {
if (buffer->capacity == 0) {
buffer->capacity = 1;
}
while (next_length > buffer->capacity) {
buffer->capacity *= 2;
}
buffer->value = xrealloc(buffer->value, buffer->capacity);
if (buffer->value == NULL) return false;
}
buffer->length = next_length;
return true;
}
/**
* Append a generic pointer to memory to the buffer.
*/
static inline void
pm_buffer_append(pm_buffer_t *buffer, const void *source, size_t length) {
size_t cursor = buffer->length;
if (pm_buffer_append_length(buffer, length)) {
memcpy(buffer->value + cursor, source, length);
}
}
/**
* Append the given amount of space as zeroes to the buffer.
*/
void
pm_buffer_append_zeroes(pm_buffer_t *buffer, size_t length) {
size_t cursor = buffer->length;
if (pm_buffer_append_length(buffer, length)) {
memset(buffer->value + cursor, 0, length);
}
}
/**
* Append a formatted string to the buffer.
*/
void
pm_buffer_append_format(pm_buffer_t *buffer, const char *format, ...) {
va_list arguments;
va_start(arguments, format);
int result = vsnprintf(NULL, 0, format, arguments);
va_end(arguments);
if (result < 0) return;
size_t length = (size_t) (result + 1);
size_t cursor = buffer->length;
if (pm_buffer_append_length(buffer, length)) {
va_start(arguments, format);
vsnprintf(buffer->value + cursor, length, format, arguments);
va_end(arguments);
buffer->length--;
}
}
/**
* Append a string to the buffer.
*/
void
pm_buffer_append_string(pm_buffer_t *buffer, const char *value, size_t length) {
pm_buffer_append(buffer, value, length);
}
/**
* Append a list of bytes to the buffer.
*/
void
pm_buffer_append_bytes(pm_buffer_t *buffer, const uint8_t *value, size_t length) {
pm_buffer_append(buffer, (const char *) value, length);
}
/**
* Append a single byte to the buffer.
*/
void
pm_buffer_append_byte(pm_buffer_t *buffer, uint8_t value) {
const void *source = &value;
pm_buffer_append(buffer, source, sizeof(uint8_t));
}
/**
* Append a 32-bit unsigned integer to the buffer as a variable-length integer.
*/
void
pm_buffer_append_varuint(pm_buffer_t *buffer, uint32_t value) {
if (value < 128) {
pm_buffer_append_byte(buffer, (uint8_t) value);
} else {
uint32_t n = value;
while (n >= 128) {
pm_buffer_append_byte(buffer, (uint8_t) (n | 128));
n >>= 7;
}
pm_buffer_append_byte(buffer, (uint8_t) n);
}
}
/**
* Append a 32-bit signed integer to the buffer as a variable-length integer.
*/
void
pm_buffer_append_varsint(pm_buffer_t *buffer, int32_t value) {
uint32_t unsigned_int = ((uint32_t)(value) << 1) ^ ((uint32_t)(value >> 31));
pm_buffer_append_varuint(buffer, unsigned_int);
}
/**
* Append a double to the buffer.
*/
void
pm_buffer_append_double(pm_buffer_t *buffer, double value) {
const void *source = &value;
pm_buffer_append(buffer, source, sizeof(double));
}
/**
* Append a unicode codepoint to the buffer.
*/
bool
pm_buffer_append_unicode_codepoint(pm_buffer_t *buffer, uint32_t value) {
if (value <= 0x7F) {
pm_buffer_append_byte(buffer, (uint8_t) value); // 0xxxxxxx
return true;
} else if (value <= 0x7FF) {
uint8_t bytes[] = {
(uint8_t) (0xC0 | ((value >> 6) & 0x3F)), // 110xxxxx
(uint8_t) (0x80 | (value & 0x3F)) // 10xxxxxx
};
pm_buffer_append_bytes(buffer, bytes, 2);
return true;
} else if (value <= 0xFFFF) {
uint8_t bytes[] = {
(uint8_t) (0xE0 | ((value >> 12) & 0x3F)), // 1110xxxx
(uint8_t) (0x80 | ((value >> 6) & 0x3F)), // 10xxxxxx
(uint8_t) (0x80 | (value & 0x3F)) // 10xxxxxx
};
pm_buffer_append_bytes(buffer, bytes, 3);
return true;
} else if (value <= 0x10FFFF) {
uint8_t bytes[] = {
(uint8_t) (0xF0 | ((value >> 18) & 0x3F)), // 11110xxx
(uint8_t) (0x80 | ((value >> 12) & 0x3F)), // 10xxxxxx
(uint8_t) (0x80 | ((value >> 6) & 0x3F)), // 10xxxxxx
(uint8_t) (0x80 | (value & 0x3F)) // 10xxxxxx
};
pm_buffer_append_bytes(buffer, bytes, 4);
return true;
} else {
return false;
}
}
/**
* Append a slice of source code to the buffer.
*/
void
pm_buffer_append_source(pm_buffer_t *buffer, const uint8_t *source, size_t length, pm_buffer_escaping_t escaping) {
for (size_t index = 0; index < length; index++) {
const uint8_t byte = source[index];
if ((byte <= 0x06) || (byte >= 0x0E && byte <= 0x1F) || (byte >= 0x7F)) {
if (escaping == PM_BUFFER_ESCAPING_RUBY) {
pm_buffer_append_format(buffer, "\\x%02X", byte);
} else {
pm_buffer_append_format(buffer, "\\u%04X", byte);
}
} else {
switch (byte) {
case '\a':
if (escaping == PM_BUFFER_ESCAPING_RUBY) {
pm_buffer_append_string(buffer, "\\a", 2);
} else {
pm_buffer_append_format(buffer, "\\u%04X", byte);
}
break;
case '\b':
pm_buffer_append_string(buffer, "\\b", 2);
break;
case '\t':
pm_buffer_append_string(buffer, "\\t", 2);
break;
case '\n':
pm_buffer_append_string(buffer, "\\n", 2);
break;
case '\v':
if (escaping == PM_BUFFER_ESCAPING_RUBY) {
pm_buffer_append_string(buffer, "\\v", 2);
} else {
pm_buffer_append_format(buffer, "\\u%04X", byte);
}
break;
case '\f':
pm_buffer_append_string(buffer, "\\f", 2);
break;
case '\r':
pm_buffer_append_string(buffer, "\\r", 2);
break;
case '"':
pm_buffer_append_string(buffer, "\\\"", 2);
break;
case '#': {
if (escaping == PM_BUFFER_ESCAPING_RUBY && index + 1 < length) {
const uint8_t next_byte = source[index + 1];
if (next_byte == '{' || next_byte == '@' || next_byte == '$') {
pm_buffer_append_byte(buffer, '\\');
}
}
pm_buffer_append_byte(buffer, '#');
break;
}
case '\\':
pm_buffer_append_string(buffer, "\\\\", 2);
break;
default:
pm_buffer_append_byte(buffer, byte);
break;
}
}
}
}
/**
* Prepend the given string to the buffer.
*/
void
pm_buffer_prepend_string(pm_buffer_t *buffer, const char *value, size_t length) {
size_t cursor = buffer->length;
if (pm_buffer_append_length(buffer, length)) {
memmove(buffer->value + length, buffer->value, cursor);
memcpy(buffer->value, value, length);
}
}
/**
* Concatenate one buffer onto another.
*/
void
pm_buffer_concat(pm_buffer_t *destination, const pm_buffer_t *source) {
if (source->length > 0) {
pm_buffer_append(destination, source->value, source->length);
}
}
/**
* Clear the buffer by reducing its size to 0. This does not free the allocated
* memory, but it does allow the buffer to be reused.
*/
void
pm_buffer_clear(pm_buffer_t *buffer) {
buffer->length = 0;
}
/**
* Strip the whitespace from the end of the buffer.
*/
void
pm_buffer_rstrip(pm_buffer_t *buffer) {
while (buffer->length > 0 && pm_char_is_whitespace((uint8_t) buffer->value[buffer->length - 1])) {
buffer->length--;
}
}
/**
* Checks if the buffer includes the given value.
*/
size_t
pm_buffer_index(const pm_buffer_t *buffer, char value) {
const char *first = memchr(buffer->value, value, buffer->length);
return (first == NULL) ? SIZE_MAX : (size_t) (first - buffer->value);
}
/**
* Insert the given string into the buffer at the given index.
*/
void
pm_buffer_insert(pm_buffer_t *buffer, size_t index, const char *value, size_t length) {
assert(index <= buffer->length);
if (index == buffer->length) {
pm_buffer_append_string(buffer, value, length);
} else {
pm_buffer_append_zeroes(buffer, length);
memmove(buffer->value + index + length, buffer->value + index, buffer->length - length - index);
memcpy(buffer->value + index, value, length);
}
}
/**
* Free the memory associated with the buffer.
*/
void
pm_buffer_free(pm_buffer_t *buffer) {
xfree(buffer->value);
}

View File

@@ -0,0 +1,318 @@
#include "prism/util/pm_char.h"
#define PRISM_CHAR_BIT_WHITESPACE (1 << 0)
#define PRISM_CHAR_BIT_INLINE_WHITESPACE (1 << 1)
#define PRISM_CHAR_BIT_REGEXP_OPTION (1 << 2)
#define PRISM_NUMBER_BIT_BINARY_DIGIT (1 << 0)
#define PRISM_NUMBER_BIT_BINARY_NUMBER (1 << 1)
#define PRISM_NUMBER_BIT_OCTAL_DIGIT (1 << 2)
#define PRISM_NUMBER_BIT_OCTAL_NUMBER (1 << 3)
#define PRISM_NUMBER_BIT_DECIMAL_DIGIT (1 << 4)
#define PRISM_NUMBER_BIT_DECIMAL_NUMBER (1 << 5)
#define PRISM_NUMBER_BIT_HEXADECIMAL_DIGIT (1 << 6)
#define PRISM_NUMBER_BIT_HEXADECIMAL_NUMBER (1 << 7)
static const uint8_t pm_byte_table[256] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 3, 3, 3, 0, 0, // 0x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3x
0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 4x
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, // 5x
0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, // 6x
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, // 7x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ax
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Bx
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Cx
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Dx
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Ex
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Fx
};
static const uint8_t pm_number_table[256] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 1x
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 2x
0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xf0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 3x
0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 4x
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, // 5x
0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 6x
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 7x
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 8x
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 9x
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Ax
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Bx
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Cx
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Dx
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Ex
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Fx
};
/**
* Returns the number of characters at the start of the string that match the
* given kind. Disallows searching past the given maximum number of characters.
*/
static inline size_t
pm_strspn_char_kind(const uint8_t *string, ptrdiff_t length, uint8_t kind) {
if (length <= 0) return 0;
size_t size = 0;
size_t maximum = (size_t) length;
while (size < maximum && (pm_byte_table[string[size]] & kind)) size++;
return size;
}
/**
* Returns the number of characters at the start of the string that are
* whitespace. Disallows searching past the given maximum number of characters.
*/
size_t
pm_strspn_whitespace(const uint8_t *string, ptrdiff_t length) {
return pm_strspn_char_kind(string, length, PRISM_CHAR_BIT_WHITESPACE);
}
/**
* Returns the number of characters at the start of the string that are
* whitespace while also tracking the location of each newline. Disallows
* searching past the given maximum number of characters.
*/
size_t
pm_strspn_whitespace_newlines(const uint8_t *string, ptrdiff_t length, pm_newline_list_t *newline_list) {
if (length <= 0) return 0;
size_t size = 0;
size_t maximum = (size_t) length;
while (size < maximum && (pm_byte_table[string[size]] & PRISM_CHAR_BIT_WHITESPACE)) {
if (string[size] == '\n') {
pm_newline_list_append(newline_list, string + size);
}
size++;
}
return size;
}
/**
* Returns the number of characters at the start of the string that are inline
* whitespace. Disallows searching past the given maximum number of characters.
*/
size_t
pm_strspn_inline_whitespace(const uint8_t *string, ptrdiff_t length) {
return pm_strspn_char_kind(string, length, PRISM_CHAR_BIT_INLINE_WHITESPACE);
}
/**
* Returns the number of characters at the start of the string that are regexp
* options. Disallows searching past the given maximum number of characters.
*/
size_t
pm_strspn_regexp_option(const uint8_t *string, ptrdiff_t length) {
return pm_strspn_char_kind(string, length, PRISM_CHAR_BIT_REGEXP_OPTION);
}
/**
* Returns true if the given character matches the given kind.
*/
static inline bool
pm_char_is_char_kind(const uint8_t b, uint8_t kind) {
return (pm_byte_table[b] & kind) != 0;
}
/**
* Returns true if the given character is a whitespace character.
*/
bool
pm_char_is_whitespace(const uint8_t b) {
return pm_char_is_char_kind(b, PRISM_CHAR_BIT_WHITESPACE);
}
/**
* Returns true if the given character is an inline whitespace character.
*/
bool
pm_char_is_inline_whitespace(const uint8_t b) {
return pm_char_is_char_kind(b, PRISM_CHAR_BIT_INLINE_WHITESPACE);
}
/**
* Scan through the string and return the number of characters at the start of
* the string that match the given kind. Disallows searching past the given
* maximum number of characters.
*/
static inline size_t
pm_strspn_number_kind(const uint8_t *string, ptrdiff_t length, uint8_t kind) {
if (length <= 0) return 0;
size_t size = 0;
size_t maximum = (size_t) length;
while (size < maximum && (pm_number_table[string[size]] & kind)) size++;
return size;
}
/**
* Scan through the string and return the number of characters at the start of
* the string that match the given kind. Disallows searching past the given
* maximum number of characters.
*
* Additionally, report the location of the last invalid underscore character
* found in the string through the out invalid parameter.
*/
static inline size_t
pm_strspn_number_kind_underscores(const uint8_t *string, ptrdiff_t length, const uint8_t **invalid, uint8_t kind) {
if (length <= 0) return 0;
size_t size = 0;
size_t maximum = (size_t) length;
bool underscore = false;
while (size < maximum && (pm_number_table[string[size]] & kind)) {
if (string[size] == '_') {
if (underscore) *invalid = string + size;
underscore = true;
} else {
underscore = false;
}
size++;
}
if (size > 0 && string[size - 1] == '_') *invalid = string + size - 1;
return size;
}
/**
* Returns the number of characters at the start of the string that are binary
* digits or underscores. Disallows searching past the given maximum number of
* characters.
*
* If multiple underscores are found in a row or if an underscore is
* found at the end of the number, then the invalid pointer is set to the index
* of the first invalid underscore.
*/
size_t
pm_strspn_binary_number(const uint8_t *string, ptrdiff_t length, const uint8_t **invalid) {
return pm_strspn_number_kind_underscores(string, length, invalid, PRISM_NUMBER_BIT_BINARY_NUMBER);
}
/**
* Returns the number of characters at the start of the string that are octal
* digits or underscores. Disallows searching past the given maximum number of
* characters.
*
* If multiple underscores are found in a row or if an underscore is
* found at the end of the number, then the invalid pointer is set to the index
* of the first invalid underscore.
*/
size_t
pm_strspn_octal_number(const uint8_t *string, ptrdiff_t length, const uint8_t **invalid) {
return pm_strspn_number_kind_underscores(string, length, invalid, PRISM_NUMBER_BIT_OCTAL_NUMBER);
}
/**
* Returns the number of characters at the start of the string that are decimal
* digits. Disallows searching past the given maximum number of characters.
*/
size_t
pm_strspn_decimal_digit(const uint8_t *string, ptrdiff_t length) {
return pm_strspn_number_kind(string, length, PRISM_NUMBER_BIT_DECIMAL_DIGIT);
}
/**
* Returns the number of characters at the start of the string that are decimal
* digits or underscores. Disallows searching past the given maximum number of
* characters.
*
* If multiple underscores are found in a row or if an underscore is
* found at the end of the number, then the invalid pointer is set to the index
* of the first invalid underscore
*/
size_t
pm_strspn_decimal_number(const uint8_t *string, ptrdiff_t length, const uint8_t **invalid) {
return pm_strspn_number_kind_underscores(string, length, invalid, PRISM_NUMBER_BIT_DECIMAL_NUMBER);
}
/**
* Returns the number of characters at the start of the string that are
* hexadecimal digits. Disallows searching past the given maximum number of
* characters.
*/
size_t
pm_strspn_hexadecimal_digit(const uint8_t *string, ptrdiff_t length) {
return pm_strspn_number_kind(string, length, PRISM_NUMBER_BIT_HEXADECIMAL_DIGIT);
}
/**
* Returns the number of characters at the start of the string that are
* hexadecimal digits or underscores. Disallows searching past the given maximum
* number of characters.
*
* If multiple underscores are found in a row or if an underscore is
* found at the end of the number, then the invalid pointer is set to the index
* of the first invalid underscore.
*/
size_t
pm_strspn_hexadecimal_number(const uint8_t *string, ptrdiff_t length, const uint8_t **invalid) {
return pm_strspn_number_kind_underscores(string, length, invalid, PRISM_NUMBER_BIT_HEXADECIMAL_NUMBER);
}
/**
* Returns true if the given character matches the given kind.
*/
static inline bool
pm_char_is_number_kind(const uint8_t b, uint8_t kind) {
return (pm_number_table[b] & kind) != 0;
}
/**
* Returns true if the given character is a binary digit.
*/
bool
pm_char_is_binary_digit(const uint8_t b) {
return pm_char_is_number_kind(b, PRISM_NUMBER_BIT_BINARY_DIGIT);
}
/**
* Returns true if the given character is an octal digit.
*/
bool
pm_char_is_octal_digit(const uint8_t b) {
return pm_char_is_number_kind(b, PRISM_NUMBER_BIT_OCTAL_DIGIT);
}
/**
* Returns true if the given character is a decimal digit.
*/
bool
pm_char_is_decimal_digit(const uint8_t b) {
return pm_char_is_number_kind(b, PRISM_NUMBER_BIT_DECIMAL_DIGIT);
}
/**
* Returns true if the given character is a hexadecimal digit.
*/
bool
pm_char_is_hexadecimal_digit(const uint8_t b) {
return pm_char_is_number_kind(b, PRISM_NUMBER_BIT_HEXADECIMAL_DIGIT);
}
#undef PRISM_CHAR_BIT_WHITESPACE
#undef PRISM_CHAR_BIT_INLINE_WHITESPACE
#undef PRISM_CHAR_BIT_REGEXP_OPTION
#undef PRISM_NUMBER_BIT_BINARY_DIGIT
#undef PRISM_NUMBER_BIT_BINARY_NUMBER
#undef PRISM_NUMBER_BIT_OCTAL_DIGIT
#undef PRISM_NUMBER_BIT_OCTAL_NUMBER
#undef PRISM_NUMBER_BIT_DECIMAL_DIGIT
#undef PRISM_NUMBER_BIT_DECIMAL_NUMBER
#undef PRISM_NUMBER_BIT_HEXADECIMAL_NUMBER
#undef PRISM_NUMBER_BIT_HEXADECIMAL_DIGIT

View File

@@ -0,0 +1,342 @@
#include "prism/util/pm_constant_pool.h"
/**
* Initialize a list of constant ids.
*/
void
pm_constant_id_list_init(pm_constant_id_list_t *list) {
list->ids = NULL;
list->size = 0;
list->capacity = 0;
}
/**
* Initialize a list of constant ids with a given capacity.
*/
void
pm_constant_id_list_init_capacity(pm_constant_id_list_t *list, size_t capacity) {
if (capacity) {
list->ids = xcalloc(capacity, sizeof(pm_constant_id_t));
if (list->ids == NULL) abort();
} else {
list->ids = NULL;
}
list->size = 0;
list->capacity = capacity;
}
/**
* Append a constant id to a list of constant ids. Returns false if any
* potential reallocations fail.
*/
bool
pm_constant_id_list_append(pm_constant_id_list_t *list, pm_constant_id_t id) {
if (list->size >= list->capacity) {
list->capacity = list->capacity == 0 ? 8 : list->capacity * 2;
list->ids = (pm_constant_id_t *) xrealloc(list->ids, sizeof(pm_constant_id_t) * list->capacity);
if (list->ids == NULL) return false;
}
list->ids[list->size++] = id;
return true;
}
/**
* Insert a constant id into a list of constant ids at the specified index.
*/
void
pm_constant_id_list_insert(pm_constant_id_list_t *list, size_t index, pm_constant_id_t id) {
assert(index < list->capacity);
assert(list->ids[index] == PM_CONSTANT_ID_UNSET);
list->ids[index] = id;
list->size++;
}
/**
* Checks if the current constant id list includes the given constant id.
*/
bool
pm_constant_id_list_includes(pm_constant_id_list_t *list, pm_constant_id_t id) {
for (size_t index = 0; index < list->size; index++) {
if (list->ids[index] == id) return true;
}
return false;
}
/**
* Free the memory associated with a list of constant ids.
*/
void
pm_constant_id_list_free(pm_constant_id_list_t *list) {
if (list->ids != NULL) {
xfree(list->ids);
}
}
/**
* A relatively simple hash function (djb2) that is used to hash strings. We are
* optimizing here for simplicity and speed.
*/
static inline uint32_t
pm_constant_pool_hash(const uint8_t *start, size_t length) {
// This is a prime number used as the initial value for the hash function.
uint32_t value = 5381;
for (size_t index = 0; index < length; index++) {
value = ((value << 5) + value) + start[index];
}
return value;
}
/**
* https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
*/
static uint32_t
next_power_of_two(uint32_t v) {
// Avoid underflow in subtraction on next line.
if (v == 0) {
// 1 is the nearest power of 2 to 0 (2^0)
return 1;
}
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
#ifndef NDEBUG
static bool
is_power_of_two(uint32_t size) {
return (size & (size - 1)) == 0;
}
#endif
/**
* Resize a constant pool to a given capacity.
*/
static inline bool
pm_constant_pool_resize(pm_constant_pool_t *pool) {
assert(is_power_of_two(pool->capacity));
uint32_t next_capacity = pool->capacity * 2;
if (next_capacity < pool->capacity) return false;
const uint32_t mask = next_capacity - 1;
const size_t element_size = sizeof(pm_constant_pool_bucket_t) + sizeof(pm_constant_t);
void *next = xcalloc(next_capacity, element_size);
if (next == NULL) return false;
pm_constant_pool_bucket_t *next_buckets = next;
pm_constant_t *next_constants = (void *)(((char *) next) + next_capacity * sizeof(pm_constant_pool_bucket_t));
// For each bucket in the current constant pool, find the index in the
// next constant pool, and insert it.
for (uint32_t index = 0; index < pool->capacity; index++) {
pm_constant_pool_bucket_t *bucket = &pool->buckets[index];
// If an id is set on this constant, then we know we have content here.
// In this case we need to insert it into the next constant pool.
if (bucket->id != PM_CONSTANT_ID_UNSET) {
uint32_t next_index = bucket->hash & mask;
// This implements linear scanning to find the next available slot
// in case this index is already taken. We don't need to bother
// comparing the values since we know that the hash is unique.
while (next_buckets[next_index].id != PM_CONSTANT_ID_UNSET) {
next_index = (next_index + 1) & mask;
}
// Here we copy over the entire bucket, which includes the id so
// that they are consistent between resizes.
next_buckets[next_index] = *bucket;
}
}
// The constants are stable with respect to hash table resizes.
memcpy(next_constants, pool->constants, pool->size * sizeof(pm_constant_t));
// pool->constants and pool->buckets are allocated out of the same chunk
// of memory, with the buckets coming first.
xfree(pool->buckets);
pool->constants = next_constants;
pool->buckets = next_buckets;
pool->capacity = next_capacity;
return true;
}
/**
* Initialize a new constant pool with a given capacity.
*/
bool
pm_constant_pool_init(pm_constant_pool_t *pool, uint32_t capacity) {
const uint32_t maximum = (~((uint32_t) 0));
if (capacity >= ((maximum / 2) + 1)) return false;
capacity = next_power_of_two(capacity);
const size_t element_size = sizeof(pm_constant_pool_bucket_t) + sizeof(pm_constant_t);
void *memory = xcalloc(capacity, element_size);
if (memory == NULL) return false;
pool->buckets = memory;
pool->constants = (void *)(((char *)memory) + capacity * sizeof(pm_constant_pool_bucket_t));
pool->size = 0;
pool->capacity = capacity;
return true;
}
/**
* Return a pointer to the constant indicated by the given constant id.
*/
pm_constant_t *
pm_constant_pool_id_to_constant(const pm_constant_pool_t *pool, pm_constant_id_t constant_id) {
assert(constant_id != PM_CONSTANT_ID_UNSET && constant_id <= pool->size);
return &pool->constants[constant_id - 1];
}
/**
* Find a constant in a constant pool. Returns the id of the constant, or 0 if
* the constant is not found.
*/
pm_constant_id_t
pm_constant_pool_find(const pm_constant_pool_t *pool, const uint8_t *start, size_t length) {
assert(is_power_of_two(pool->capacity));
const uint32_t mask = pool->capacity - 1;
uint32_t hash = pm_constant_pool_hash(start, length);
uint32_t index = hash & mask;
pm_constant_pool_bucket_t *bucket;
while (bucket = &pool->buckets[index], bucket->id != PM_CONSTANT_ID_UNSET) {
pm_constant_t *constant = &pool->constants[bucket->id - 1];
if ((constant->length == length) && memcmp(constant->start, start, length) == 0) {
return bucket->id;
}
index = (index + 1) & mask;
}
return PM_CONSTANT_ID_UNSET;
}
/**
* Insert a constant into a constant pool and return its index in the pool.
*/
static inline pm_constant_id_t
pm_constant_pool_insert(pm_constant_pool_t *pool, const uint8_t *start, size_t length, pm_constant_pool_bucket_type_t type) {
if (pool->size >= (pool->capacity / 4 * 3)) {
if (!pm_constant_pool_resize(pool)) return PM_CONSTANT_ID_UNSET;
}
assert(is_power_of_two(pool->capacity));
const uint32_t mask = pool->capacity - 1;
uint32_t hash = pm_constant_pool_hash(start, length);
uint32_t index = hash & mask;
pm_constant_pool_bucket_t *bucket;
while (bucket = &pool->buckets[index], bucket->id != PM_CONSTANT_ID_UNSET) {
// If there is a collision, then we need to check if the content is the
// same as the content we are trying to insert. If it is, then we can
// return the id of the existing constant.
pm_constant_t *constant = &pool->constants[bucket->id - 1];
if ((constant->length == length) && memcmp(constant->start, start, length) == 0) {
// Since we have found a match, we need to check if this is
// attempting to insert a shared or an owned constant. We want to
// prefer shared constants since they don't require allocations.
if (type == PM_CONSTANT_POOL_BUCKET_OWNED) {
// If we're attempting to insert an owned constant and we have
// an existing constant, then either way we don't want the given
// memory. Either it's duplicated with the existing constant or
// it's not necessary because we have a shared version.
xfree((void *) start);
} else if (bucket->type == PM_CONSTANT_POOL_BUCKET_OWNED) {
// If we're attempting to insert a shared constant and the
// existing constant is owned, then we can free the owned
// constant and replace it with the shared constant.
xfree((void *) constant->start);
constant->start = start;
bucket->type = (unsigned int) (PM_CONSTANT_POOL_BUCKET_DEFAULT & 0x3);
}
return bucket->id;
}
index = (index + 1) & mask;
}
// IDs are allocated starting at 1, since the value 0 denotes a non-existent
// constant.
uint32_t id = ++pool->size;
assert(pool->size < ((uint32_t) (1 << 30)));
*bucket = (pm_constant_pool_bucket_t) {
.id = (unsigned int) (id & 0x3fffffff),
.type = (unsigned int) (type & 0x3),
.hash = hash
};
pool->constants[id - 1] = (pm_constant_t) {
.start = start,
.length = length,
};
return id;
}
/**
* Insert a constant into a constant pool. Returns the id of the constant, or
* PM_CONSTANT_ID_UNSET if any potential calls to resize fail.
*/
pm_constant_id_t
pm_constant_pool_insert_shared(pm_constant_pool_t *pool, const uint8_t *start, size_t length) {
return pm_constant_pool_insert(pool, start, length, PM_CONSTANT_POOL_BUCKET_DEFAULT);
}
/**
* Insert a constant into a constant pool from memory that is now owned by the
* constant pool. Returns the id of the constant, or PM_CONSTANT_ID_UNSET if any
* potential calls to resize fail.
*/
pm_constant_id_t
pm_constant_pool_insert_owned(pm_constant_pool_t *pool, uint8_t *start, size_t length) {
return pm_constant_pool_insert(pool, start, length, PM_CONSTANT_POOL_BUCKET_OWNED);
}
/**
* Insert a constant into a constant pool from memory that is constant. Returns
* the id of the constant, or PM_CONSTANT_ID_UNSET if any potential calls to
* resize fail.
*/
pm_constant_id_t
pm_constant_pool_insert_constant(pm_constant_pool_t *pool, const uint8_t *start, size_t length) {
return pm_constant_pool_insert(pool, start, length, PM_CONSTANT_POOL_BUCKET_CONSTANT);
}
/**
* Free the memory associated with a constant pool.
*/
void
pm_constant_pool_free(pm_constant_pool_t *pool) {
// For each constant in the current constant pool, free the contents if the
// contents are owned.
for (uint32_t index = 0; index < pool->capacity; index++) {
pm_constant_pool_bucket_t *bucket = &pool->buckets[index];
// If an id is set on this constant, then we know we have content here.
if (bucket->id != PM_CONSTANT_ID_UNSET && bucket->type == PM_CONSTANT_POOL_BUCKET_OWNED) {
pm_constant_t *constant = &pool->constants[bucket->id - 1];
xfree((void *) constant->start);
}
}
xfree(pool->buckets);
}

View File

@@ -0,0 +1,670 @@
#include "prism/util/pm_integer.h"
/**
* Pull out the length and values from the integer, regardless of the form in
* which the length/values are stored.
*/
#define INTEGER_EXTRACT(integer, length_variable, values_variable) \
if ((integer)->values == NULL) { \
length_variable = 1; \
values_variable = &(integer)->value; \
} else { \
length_variable = (integer)->length; \
values_variable = (integer)->values; \
}
/**
* Adds two positive pm_integer_t with the given base.
* Return pm_integer_t with values allocated. Not normalized.
*/
static void
big_add(pm_integer_t *destination, pm_integer_t *left, pm_integer_t *right, uint64_t base) {
size_t left_length;
uint32_t *left_values;
INTEGER_EXTRACT(left, left_length, left_values)
size_t right_length;
uint32_t *right_values;
INTEGER_EXTRACT(right, right_length, right_values)
size_t length = left_length < right_length ? right_length : left_length;
uint32_t *values = (uint32_t *) xmalloc(sizeof(uint32_t) * (length + 1));
if (values == NULL) return;
uint64_t carry = 0;
for (size_t index = 0; index < length; index++) {
uint64_t sum = carry + (index < left_length ? left_values[index] : 0) + (index < right_length ? right_values[index] : 0);
values[index] = (uint32_t) (sum % base);
carry = sum / base;
}
if (carry > 0) {
values[length] = (uint32_t) carry;
length++;
}
*destination = (pm_integer_t) { length, values, 0, false };
}
/**
* Internal use for karatsuba_multiply. Calculates `a - b - c` with the given
* base. Assume a, b, c, a - b - c all to be positive.
* Return pm_integer_t with values allocated. Not normalized.
*/
static void
big_sub2(pm_integer_t *destination, pm_integer_t *a, pm_integer_t *b, pm_integer_t *c, uint64_t base) {
size_t a_length;
uint32_t *a_values;
INTEGER_EXTRACT(a, a_length, a_values)
size_t b_length;
uint32_t *b_values;
INTEGER_EXTRACT(b, b_length, b_values)
size_t c_length;
uint32_t *c_values;
INTEGER_EXTRACT(c, c_length, c_values)
uint32_t *values = (uint32_t*) xmalloc(sizeof(uint32_t) * a_length);
int64_t carry = 0;
for (size_t index = 0; index < a_length; index++) {
int64_t sub = (
carry +
a_values[index] -
(index < b_length ? b_values[index] : 0) -
(index < c_length ? c_values[index] : 0)
);
if (sub >= 0) {
values[index] = (uint32_t) sub;
carry = 0;
} else {
sub += 2 * (int64_t) base;
values[index] = (uint32_t) ((uint64_t) sub % base);
carry = sub / (int64_t) base - 2;
}
}
while (a_length > 1 && values[a_length - 1] == 0) a_length--;
*destination = (pm_integer_t) { a_length, values, 0, false };
}
/**
* Multiply two positive integers with the given base using karatsuba algorithm.
* Return pm_integer_t with values allocated. Not normalized.
*/
static void
karatsuba_multiply(pm_integer_t *destination, pm_integer_t *left, pm_integer_t *right, uint64_t base) {
size_t left_length;
uint32_t *left_values;
INTEGER_EXTRACT(left, left_length, left_values)
size_t right_length;
uint32_t *right_values;
INTEGER_EXTRACT(right, right_length, right_values)
if (left_length > right_length) {
size_t temporary_length = left_length;
left_length = right_length;
right_length = temporary_length;
uint32_t *temporary_values = left_values;
left_values = right_values;
right_values = temporary_values;
}
if (left_length <= 10) {
size_t length = left_length + right_length;
uint32_t *values = (uint32_t *) xcalloc(length, sizeof(uint32_t));
if (values == NULL) return;
for (size_t left_index = 0; left_index < left_length; left_index++) {
uint32_t carry = 0;
for (size_t right_index = 0; right_index < right_length; right_index++) {
uint64_t product = (uint64_t) left_values[left_index] * right_values[right_index] + values[left_index + right_index] + carry;
values[left_index + right_index] = (uint32_t) (product % base);
carry = (uint32_t) (product / base);
}
values[left_index + right_length] = carry;
}
while (length > 1 && values[length - 1] == 0) length--;
*destination = (pm_integer_t) { length, values, 0, false };
return;
}
if (left_length * 2 <= right_length) {
uint32_t *values = (uint32_t *) xcalloc(left_length + right_length, sizeof(uint32_t));
for (size_t start_offset = 0; start_offset < right_length; start_offset += left_length) {
size_t end_offset = start_offset + left_length;
if (end_offset > right_length) end_offset = right_length;
pm_integer_t sliced_left = {
.length = left_length,
.values = left_values,
.value = 0,
.negative = false
};
pm_integer_t sliced_right = {
.length = end_offset - start_offset,
.values = right_values + start_offset,
.value = 0,
.negative = false
};
pm_integer_t product;
karatsuba_multiply(&product, &sliced_left, &sliced_right, base);
uint32_t carry = 0;
for (size_t index = 0; index < product.length; index++) {
uint64_t sum = (uint64_t) values[start_offset + index] + product.values[index] + carry;
values[start_offset + index] = (uint32_t) (sum % base);
carry = (uint32_t) (sum / base);
}
if (carry > 0) values[start_offset + product.length] += carry;
pm_integer_free(&product);
}
*destination = (pm_integer_t) { left_length + right_length, values, 0, false };
return;
}
size_t half = left_length / 2;
pm_integer_t x0 = { half, left_values, 0, false };
pm_integer_t x1 = { left_length - half, left_values + half, 0, false };
pm_integer_t y0 = { half, right_values, 0, false };
pm_integer_t y1 = { right_length - half, right_values + half, 0, false };
pm_integer_t z0 = { 0 };
karatsuba_multiply(&z0, &x0, &y0, base);
pm_integer_t z2 = { 0 };
karatsuba_multiply(&z2, &x1, &y1, base);
// For simplicity to avoid considering negative values,
// use `z1 = (x0 + x1) * (y0 + y1) - z0 - z2` instead of original karatsuba algorithm.
pm_integer_t x01 = { 0 };
big_add(&x01, &x0, &x1, base);
pm_integer_t y01 = { 0 };
big_add(&y01, &y0, &y1, base);
pm_integer_t xy = { 0 };
karatsuba_multiply(&xy, &x01, &y01, base);
pm_integer_t z1;
big_sub2(&z1, &xy, &z0, &z2, base);
size_t length = left_length + right_length;
uint32_t *values = (uint32_t*) xcalloc(length, sizeof(uint32_t));
assert(z0.values != NULL);
memcpy(values, z0.values, sizeof(uint32_t) * z0.length);
assert(z2.values != NULL);
memcpy(values + 2 * half, z2.values, sizeof(uint32_t) * z2.length);
uint32_t carry = 0;
for(size_t index = 0; index < z1.length; index++) {
uint64_t sum = (uint64_t) carry + values[index + half] + z1.values[index];
values[index + half] = (uint32_t) (sum % base);
carry = (uint32_t) (sum / base);
}
for(size_t index = half + z1.length; carry > 0; index++) {
uint64_t sum = (uint64_t) carry + values[index];
values[index] = (uint32_t) (sum % base);
carry = (uint32_t) (sum / base);
}
while (length > 1 && values[length - 1] == 0) length--;
pm_integer_free(&z0);
pm_integer_free(&z1);
pm_integer_free(&z2);
pm_integer_free(&x01);
pm_integer_free(&y01);
pm_integer_free(&xy);
*destination = (pm_integer_t) { length, values, 0, false };
}
/**
* The values of a hexadecimal digit, where the index is the ASCII character.
* Note that there's an odd exception here where _ is mapped to 0. This is
* because it's possible for us to end up trying to parse a number that has
* already had an error attached to it, and we want to provide _something_ to
* the user.
*/
static const int8_t pm_integer_parse_digit_values[256] = {
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 1x
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 2x
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, // 3x
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 4x
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, // 5x
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 6x
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 7x
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 8x
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 9x
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Ax
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Bx
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Cx
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Dx
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Ex
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // Fx
};
/**
* Return the value of a hexadecimal digit in a uint8_t.
*/
static uint8_t
pm_integer_parse_digit(const uint8_t character) {
int8_t value = pm_integer_parse_digit_values[character];
assert(value != -1 && "invalid digit");
return (uint8_t) value;
}
/**
* Create a pm_integer_t from uint64_t with the given base. It is assumed that
* the memory for the pm_integer_t pointer has been zeroed.
*/
static void
pm_integer_from_uint64(pm_integer_t *integer, uint64_t value, uint64_t base) {
if (value < base) {
integer->value = (uint32_t) value;
return;
}
size_t length = 0;
uint64_t length_value = value;
while (length_value > 0) {
length++;
length_value /= base;
}
uint32_t *values = (uint32_t *) xmalloc(sizeof(uint32_t) * length);
if (values == NULL) return;
for (size_t value_index = 0; value_index < length; value_index++) {
values[value_index] = (uint32_t) (value % base);
value /= base;
}
integer->length = length;
integer->values = values;
}
/**
* Normalize pm_integer_t.
* Heading zero values will be removed. If the integer fits into uint32_t,
* values is set to NULL, length is set to 0, and value field will be used.
*/
static void
pm_integer_normalize(pm_integer_t *integer) {
if (integer->values == NULL) {
return;
}
while (integer->length > 1 && integer->values[integer->length - 1] == 0) {
integer->length--;
}
if (integer->length > 1) {
return;
}
uint32_t value = integer->values[0];
bool negative = integer->negative && value != 0;
pm_integer_free(integer);
*integer = (pm_integer_t) { .values = NULL, .value = value, .length = 0, .negative = negative };
}
/**
* Convert base of the integer.
* In practice, it converts 10**9 to 1<<32 or 1<<32 to 10**9.
*/
static void
pm_integer_convert_base(pm_integer_t *destination, const pm_integer_t *source, uint64_t base_from, uint64_t base_to) {
size_t source_length;
const uint32_t *source_values;
INTEGER_EXTRACT(source, source_length, source_values)
size_t bigints_length = (source_length + 1) / 2;
assert(bigints_length > 0);
pm_integer_t *bigints = (pm_integer_t *) xcalloc(bigints_length, sizeof(pm_integer_t));
if (bigints == NULL) return;
for (size_t index = 0; index < source_length; index += 2) {
uint64_t value = source_values[index] + base_from * (index + 1 < source_length ? source_values[index + 1] : 0);
pm_integer_from_uint64(&bigints[index / 2], value, base_to);
}
pm_integer_t base = { 0 };
pm_integer_from_uint64(&base, base_from, base_to);
while (bigints_length > 1) {
pm_integer_t next_base;
karatsuba_multiply(&next_base, &base, &base, base_to);
pm_integer_free(&base);
base = next_base;
size_t next_length = (bigints_length + 1) / 2;
pm_integer_t *next_bigints = (pm_integer_t *) xcalloc(next_length, sizeof(pm_integer_t));
for (size_t bigints_index = 0; bigints_index < bigints_length; bigints_index += 2) {
if (bigints_index + 1 == bigints_length) {
next_bigints[bigints_index / 2] = bigints[bigints_index];
} else {
pm_integer_t multiplied = { 0 };
karatsuba_multiply(&multiplied, &base, &bigints[bigints_index + 1], base_to);
big_add(&next_bigints[bigints_index / 2], &bigints[bigints_index], &multiplied, base_to);
pm_integer_free(&bigints[bigints_index]);
pm_integer_free(&bigints[bigints_index + 1]);
pm_integer_free(&multiplied);
}
}
xfree(bigints);
bigints = next_bigints;
bigints_length = next_length;
}
*destination = bigints[0];
destination->negative = source->negative;
pm_integer_normalize(destination);
xfree(bigints);
pm_integer_free(&base);
}
#undef INTEGER_EXTRACT
/**
* Convert digits to integer with the given power-of-two base.
*/
static void
pm_integer_parse_powof2(pm_integer_t *integer, uint32_t base, const uint8_t *digits, size_t digits_length) {
size_t bit = 1;
while (base > (uint32_t) (1 << bit)) bit++;
size_t length = (digits_length * bit + 31) / 32;
uint32_t *values = (uint32_t *) xcalloc(length, sizeof(uint32_t));
for (size_t digit_index = 0; digit_index < digits_length; digit_index++) {
size_t bit_position = bit * (digits_length - digit_index - 1);
uint32_t value = digits[digit_index];
size_t index = bit_position / 32;
size_t shift = bit_position % 32;
values[index] |= value << shift;
if (32 - shift < bit) values[index + 1] |= value >> (32 - shift);
}
while (length > 1 && values[length - 1] == 0) length--;
*integer = (pm_integer_t) { .length = length, .values = values, .value = 0, .negative = false };
pm_integer_normalize(integer);
}
/**
* Convert decimal digits to pm_integer_t.
*/
static void
pm_integer_parse_decimal(pm_integer_t *integer, const uint8_t *digits, size_t digits_length) {
const size_t batch = 9;
size_t length = (digits_length + batch - 1) / batch;
uint32_t *values = (uint32_t *) xcalloc(length, sizeof(uint32_t));
uint32_t value = 0;
for (size_t digits_index = 0; digits_index < digits_length; digits_index++) {
value = value * 10 + digits[digits_index];
size_t reverse_index = digits_length - digits_index - 1;
if (reverse_index % batch == 0) {
values[reverse_index / batch] = value;
value = 0;
}
}
// Convert base from 10**9 to 1<<32.
pm_integer_convert_base(integer, &((pm_integer_t) { .length = length, .values = values, .value = 0, .negative = false }), 1000000000, ((uint64_t) 1 << 32));
xfree(values);
}
/**
* Parse a large integer from a string that does not fit into uint32_t.
*/
static void
pm_integer_parse_big(pm_integer_t *integer, uint32_t multiplier, const uint8_t *start, const uint8_t *end) {
// Allocate an array to store digits.
uint8_t *digits = xmalloc(sizeof(uint8_t) * (size_t) (end - start));
size_t digits_length = 0;
for (; start < end; start++) {
if (*start == '_') continue;
digits[digits_length++] = pm_integer_parse_digit(*start);
}
// Construct pm_integer_t from the digits.
if (multiplier == 10) {
pm_integer_parse_decimal(integer, digits, digits_length);
} else {
pm_integer_parse_powof2(integer, multiplier, digits, digits_length);
}
xfree(digits);
}
/**
* Parse an integer from a string. This assumes that the format of the integer
* has already been validated, as internal validation checks are not performed
* here.
*/
void
pm_integer_parse(pm_integer_t *integer, pm_integer_base_t base, const uint8_t *start, const uint8_t *end) {
// Ignore unary +. Unary - is parsed differently and will not end up here.
// Instead, it will modify the parsed integer later.
if (*start == '+') start++;
// Determine the multiplier from the base, and skip past any prefixes.
uint32_t multiplier = 10;
switch (base) {
case PM_INTEGER_BASE_DEFAULT:
while (*start == '0') start++; // 01 -> 1
break;
case PM_INTEGER_BASE_BINARY:
start += 2; // 0b
multiplier = 2;
break;
case PM_INTEGER_BASE_OCTAL:
start++; // 0
if (*start == '_' || *start == 'o' || *start == 'O') start++; // o
multiplier = 8;
break;
case PM_INTEGER_BASE_DECIMAL:
if (*start == '0' && (end - start) > 1) start += 2; // 0d
break;
case PM_INTEGER_BASE_HEXADECIMAL:
start += 2; // 0x
multiplier = 16;
break;
case PM_INTEGER_BASE_UNKNOWN:
if (*start == '0' && (end - start) > 1) {
switch (start[1]) {
case '_': start += 2; multiplier = 8; break;
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': start++; multiplier = 8; break;
case 'b': case 'B': start += 2; multiplier = 2; break;
case 'o': case 'O': start += 2; multiplier = 8; break;
case 'd': case 'D': start += 2; break;
case 'x': case 'X': start += 2; multiplier = 16; break;
default: assert(false && "unreachable"); break;
}
}
break;
}
// It's possible that we've consumed everything at this point if there is an
// invalid integer. If this is the case, we'll just return 0.
if (start >= end) return;
const uint8_t *cursor = start;
uint64_t value = (uint64_t) pm_integer_parse_digit(*cursor++);
for (; cursor < end; cursor++) {
if (*cursor == '_') continue;
value = value * multiplier + (uint64_t) pm_integer_parse_digit(*cursor);
if (value > UINT32_MAX) {
// If the integer is too large to fit into a single uint32_t, then
// we'll parse it as a big integer.
pm_integer_parse_big(integer, multiplier, start, end);
return;
}
}
integer->value = (uint32_t) value;
}
/**
* Compare two integers. This function returns -1 if the left integer is less
* than the right integer, 0 if they are equal, and 1 if the left integer is
* greater than the right integer.
*/
int
pm_integer_compare(const pm_integer_t *left, const pm_integer_t *right) {
if (left->negative != right->negative) return left->negative ? -1 : 1;
int negative = left->negative ? -1 : 1;
if (left->values == NULL && right->values == NULL) {
if (left->value < right->value) return -1 * negative;
if (left->value > right->value) return 1 * negative;
return 0;
}
if (left->values == NULL || left->length < right->length) return -1 * negative;
if (right->values == NULL || left->length > right->length) return 1 * negative;
for (size_t index = 0; index < left->length; index++) {
size_t value_index = left->length - index - 1;
uint32_t left_value = left->values[value_index];
uint32_t right_value = right->values[value_index];
if (left_value < right_value) return -1 * negative;
if (left_value > right_value) return 1 * negative;
}
return 0;
}
/**
* Reduce a ratio of integers to its simplest form.
*/
void pm_integers_reduce(pm_integer_t *numerator, pm_integer_t *denominator) {
// If either the numerator or denominator do not fit into a 32-bit integer,
// then this function is a no-op. In the future, we may consider reducing
// even the larger numbers, but for now we're going to keep it simple.
if (
// If the numerator doesn't fit into a 32-bit integer, return early.
numerator->length != 0 ||
// If the denominator doesn't fit into a 32-bit integer, return early.
denominator->length != 0 ||
// If the numerator is 0, then return early.
numerator->value == 0 ||
// If the denominator is 1, then return early.
denominator->value == 1
) return;
// Find the greatest common divisor of the numerator and denominator.
uint32_t divisor = numerator->value;
uint32_t remainder = denominator->value;
while (remainder != 0) {
uint32_t temporary = remainder;
remainder = divisor % remainder;
divisor = temporary;
}
// Divide the numerator and denominator by the greatest common divisor.
numerator->value /= divisor;
denominator->value /= divisor;
}
/**
* Convert an integer to a decimal string.
*/
PRISM_EXPORTED_FUNCTION void
pm_integer_string(pm_buffer_t *buffer, const pm_integer_t *integer) {
if (integer->negative) {
pm_buffer_append_byte(buffer, '-');
}
// If the integer fits into a single uint32_t, then we can just append the
// value directly to the buffer.
if (integer->values == NULL) {
pm_buffer_append_format(buffer, "%" PRIu32, integer->value);
return;
}
// If the integer is two uint32_t values, then we can | them together and
// append the result to the buffer.
if (integer->length == 2) {
const uint64_t value = ((uint64_t) integer->values[0]) | ((uint64_t) integer->values[1] << 32);
pm_buffer_append_format(buffer, "%" PRIu64, value);
return;
}
// Otherwise, first we'll convert the base from 1<<32 to 10**9.
pm_integer_t converted = { 0 };
pm_integer_convert_base(&converted, integer, (uint64_t) 1 << 32, 1000000000);
if (converted.values == NULL) {
pm_buffer_append_format(buffer, "%" PRIu32, converted.value);
pm_integer_free(&converted);
return;
}
// Allocate a buffer that we'll copy the decimal digits into.
size_t digits_length = converted.length * 9;
char *digits = xcalloc(digits_length, sizeof(char));
if (digits == NULL) return;
// Pack bigdecimal to digits.
for (size_t value_index = 0; value_index < converted.length; value_index++) {
uint32_t value = converted.values[value_index];
for (size_t digit_index = 0; digit_index < 9; digit_index++) {
digits[digits_length - 9 * value_index - digit_index - 1] = (char) ('0' + value % 10);
value /= 10;
}
}
size_t start_offset = 0;
while (start_offset < digits_length - 1 && digits[start_offset] == '0') start_offset++;
// Finally, append the string to the buffer and free the digits.
pm_buffer_append_string(buffer, digits + start_offset, digits_length - start_offset);
xfree(digits);
pm_integer_free(&converted);
}
/**
* Free the internal memory of an integer. This memory will only be allocated if
* the integer exceeds the size of a single uint32_t.
*/
PRISM_EXPORTED_FUNCTION void
pm_integer_free(pm_integer_t *integer) {
if (integer->values) {
xfree(integer->values);
}
}

View File

@@ -0,0 +1,49 @@
#include "prism/util/pm_list.h"
/**
* Returns true if the given list is empty.
*/
PRISM_EXPORTED_FUNCTION bool
pm_list_empty_p(pm_list_t *list) {
return list->head == NULL;
}
/**
* Returns the size of the list.
*/
PRISM_EXPORTED_FUNCTION size_t
pm_list_size(pm_list_t *list) {
return list->size;
}
/**
* Append a node to the given list.
*/
void
pm_list_append(pm_list_t *list, pm_list_node_t *node) {
if (list->head == NULL) {
list->head = node;
} else {
list->tail->next = node;
}
list->tail = node;
list->size++;
}
/**
* Deallocate the internal state of the given list.
*/
PRISM_EXPORTED_FUNCTION void
pm_list_free(pm_list_t *list) {
pm_list_node_t *node = list->head;
pm_list_node_t *next;
while (node != NULL) {
next = node->next;
xfree(node);
node = next;
}
list->size = 0;
}

View File

@@ -0,0 +1,35 @@
#include "prism/util/pm_memchr.h"
#define PRISM_MEMCHR_TRAILING_BYTE_MINIMUM 0x40
/**
* We need to roll our own memchr to handle cases where the encoding changes and
* we need to search for a character in a buffer that could be the trailing byte
* of a multibyte character.
*/
void *
pm_memchr(const void *memory, int character, size_t number, bool encoding_changed, const pm_encoding_t *encoding) {
if (encoding_changed && encoding->multibyte && character >= PRISM_MEMCHR_TRAILING_BYTE_MINIMUM) {
const uint8_t *source = (const uint8_t *) memory;
size_t index = 0;
while (index < number) {
if (source[index] == character) {
return (void *) (source + index);
}
size_t width = encoding->char_width(source + index, (ptrdiff_t) (number - index));
if (width == 0) {
return NULL;
}
index += width;
}
return NULL;
} else {
return memchr(memory, character, number);
}
}
#undef PRISM_MEMCHR_TRAILING_BYTE_MINIMUM

View File

@@ -0,0 +1,125 @@
#include "prism/util/pm_newline_list.h"
/**
* Initialize a new newline list with the given capacity. Returns true if the
* allocation of the offsets succeeds, otherwise returns false.
*/
bool
pm_newline_list_init(pm_newline_list_t *list, const uint8_t *start, size_t capacity) {
list->offsets = (size_t *) xcalloc(capacity, sizeof(size_t));
if (list->offsets == NULL) return false;
list->start = start;
// This is 1 instead of 0 because we want to include the first line of the
// file as having offset 0, which is set because of calloc.
list->size = 1;
list->capacity = capacity;
return true;
}
/**
* Clear out the newlines that have been appended to the list.
*/
void
pm_newline_list_clear(pm_newline_list_t *list) {
list->size = 1;
}
/**
* Append a new offset to the newline list. Returns true if the reallocation of
* the offsets succeeds (if one was necessary), otherwise returns false.
*/
bool
pm_newline_list_append(pm_newline_list_t *list, const uint8_t *cursor) {
if (list->size == list->capacity) {
size_t *original_offsets = list->offsets;
list->capacity = (list->capacity * 3) / 2;
list->offsets = (size_t *) xcalloc(list->capacity, sizeof(size_t));
if (list->offsets == NULL) return false;
memcpy(list->offsets, original_offsets, list->size * sizeof(size_t));
xfree(original_offsets);
}
assert(*cursor == '\n');
assert(cursor >= list->start);
size_t newline_offset = (size_t) (cursor - list->start + 1);
assert(list->size == 0 || newline_offset > list->offsets[list->size - 1]);
list->offsets[list->size++] = newline_offset;
return true;
}
/**
* Returns the line of the given offset. If the offset is not in the list, the
* line of the closest offset less than the given offset is returned.
*/
int32_t
pm_newline_list_line(const pm_newline_list_t *list, const uint8_t *cursor, int32_t start_line) {
assert(cursor >= list->start);
size_t offset = (size_t) (cursor - list->start);
size_t left = 0;
size_t right = list->size - 1;
while (left <= right) {
size_t mid = left + (right - left) / 2;
if (list->offsets[mid] == offset) {
return ((int32_t) mid) + start_line;
}
if (list->offsets[mid] < offset) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return ((int32_t) left) + start_line - 1;
}
/**
* Returns the line and column of the given offset. If the offset is not in the
* list, the line and column of the closest offset less than the given offset
* are returned.
*/
pm_line_column_t
pm_newline_list_line_column(const pm_newline_list_t *list, const uint8_t *cursor, int32_t start_line) {
assert(cursor >= list->start);
size_t offset = (size_t) (cursor - list->start);
size_t left = 0;
size_t right = list->size - 1;
while (left <= right) {
size_t mid = left + (right - left) / 2;
if (list->offsets[mid] == offset) {
return ((pm_line_column_t) { ((int32_t) mid) + start_line, 0 });
}
if (list->offsets[mid] < offset) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return ((pm_line_column_t) {
.line = ((int32_t) left) + start_line - 1,
.column = (uint32_t) (offset - list->offsets[left - 1])
});
}
/**
* Free the internal memory allocated for the newline list.
*/
void
pm_newline_list_free(pm_newline_list_t *list) {
xfree(list->offsets);
}

View File

@@ -0,0 +1,383 @@
#include "prism/util/pm_string.h"
/**
* Returns the size of the pm_string_t struct. This is necessary to allocate the
* correct amount of memory in the FFI backend.
*/
PRISM_EXPORTED_FUNCTION size_t
pm_string_sizeof(void) {
return sizeof(pm_string_t);
}
/**
* Initialize a shared string that is based on initial input.
*/
void
pm_string_shared_init(pm_string_t *string, const uint8_t *start, const uint8_t *end) {
assert(start <= end);
*string = (pm_string_t) {
.type = PM_STRING_SHARED,
.source = start,
.length = (size_t) (end - start)
};
}
/**
* Initialize an owned string that is responsible for freeing allocated memory.
*/
void
pm_string_owned_init(pm_string_t *string, uint8_t *source, size_t length) {
*string = (pm_string_t) {
.type = PM_STRING_OWNED,
.source = source,
.length = length
};
}
/**
* Initialize a constant string that doesn't own its memory source.
*/
void
pm_string_constant_init(pm_string_t *string, const char *source, size_t length) {
*string = (pm_string_t) {
.type = PM_STRING_CONSTANT,
.source = (const uint8_t *) source,
.length = length
};
}
#ifdef _WIN32
/**
* Represents a file handle on Windows, where the path will need to be freed
* when the file is closed.
*/
typedef struct {
/** The path to the file, which will become allocated memory. */
WCHAR *path;
/** The handle to the file, which will start as uninitialized memory. */
HANDLE file;
} pm_string_file_handle_t;
/**
* Open the file indicated by the filepath parameter for reading on Windows.
* Perform any kind of normalization that needs to happen on the filepath.
*/
static pm_string_init_result_t
pm_string_file_handle_open(pm_string_file_handle_t *handle, const char *filepath) {
int length = MultiByteToWideChar(CP_UTF8, 0, filepath, -1, NULL, 0);
if (length == 0) return PM_STRING_INIT_ERROR_GENERIC;
handle->path = xmalloc(sizeof(WCHAR) * ((size_t) length));
if ((handle->path == NULL) || (MultiByteToWideChar(CP_UTF8, 0, filepath, -1, handle->path, length) == 0)) {
xfree(handle->path);
return PM_STRING_INIT_ERROR_GENERIC;
}
handle->file = CreateFileW(handle->path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (handle->file == INVALID_HANDLE_VALUE) {
pm_string_init_result_t result = PM_STRING_INIT_ERROR_GENERIC;
if (GetLastError() == ERROR_ACCESS_DENIED) {
DWORD attributes = GetFileAttributesW(handle->path);
if ((attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
result = PM_STRING_INIT_ERROR_DIRECTORY;
}
}
xfree(handle->path);
return result;
}
return PM_STRING_INIT_SUCCESS;
}
/**
* Close the file handle and free the path.
*/
static void
pm_string_file_handle_close(pm_string_file_handle_t *handle) {
xfree(handle->path);
CloseHandle(handle->file);
}
#endif
/**
* Read the file indicated by the filepath parameter into source and load its
* contents and size into the given `pm_string_t`. The given `pm_string_t`
* should be freed using `pm_string_free` when it is no longer used.
*
* We want to use demand paging as much as possible in order to avoid having to
* read the entire file into memory (which could be detrimental to performance
* for large files). This means that if we're on windows we'll use
* `MapViewOfFile`, on POSIX systems that have access to `mmap` we'll use
* `mmap`, and on other POSIX systems we'll use `read`.
*/
PRISM_EXPORTED_FUNCTION pm_string_init_result_t
pm_string_mapped_init(pm_string_t *string, const char *filepath) {
#ifdef _WIN32
// Open the file for reading.
pm_string_file_handle_t handle;
pm_string_init_result_t result = pm_string_file_handle_open(&handle, filepath);
if (result != PM_STRING_INIT_SUCCESS) return result;
// Get the file size.
DWORD file_size = GetFileSize(handle.file, NULL);
if (file_size == INVALID_FILE_SIZE) {
pm_string_file_handle_close(&handle);
return PM_STRING_INIT_ERROR_GENERIC;
}
// If the file is empty, then we don't need to do anything else, we'll set
// the source to a constant empty string and return.
if (file_size == 0) {
pm_string_file_handle_close(&handle);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
return PM_STRING_INIT_SUCCESS;
}
// Create a mapping of the file.
HANDLE mapping = CreateFileMapping(handle.file, NULL, PAGE_READONLY, 0, 0, NULL);
if (mapping == NULL) {
pm_string_file_handle_close(&handle);
return PM_STRING_INIT_ERROR_GENERIC;
}
// Map the file into memory.
uint8_t *source = (uint8_t *) MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
CloseHandle(mapping);
pm_string_file_handle_close(&handle);
if (source == NULL) {
return PM_STRING_INIT_ERROR_GENERIC;
}
*string = (pm_string_t) { .type = PM_STRING_MAPPED, .source = source, .length = (size_t) file_size };
return PM_STRING_INIT_SUCCESS;
#elif defined(_POSIX_MAPPED_FILES)
// Open the file for reading
int fd = open(filepath, O_RDONLY);
if (fd == -1) {
return PM_STRING_INIT_ERROR_GENERIC;
}
// Stat the file to get the file size
struct stat sb;
if (fstat(fd, &sb) == -1) {
close(fd);
return PM_STRING_INIT_ERROR_GENERIC;
}
// Ensure it is a file and not a directory
if (S_ISDIR(sb.st_mode)) {
close(fd);
return PM_STRING_INIT_ERROR_DIRECTORY;
}
// mmap the file descriptor to virtually get the contents
size_t size = (size_t) sb.st_size;
uint8_t *source = NULL;
if (size == 0) {
close(fd);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
return PM_STRING_INIT_SUCCESS;
}
source = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (source == MAP_FAILED) {
close(fd);
return PM_STRING_INIT_ERROR_GENERIC;
}
close(fd);
*string = (pm_string_t) { .type = PM_STRING_MAPPED, .source = source, .length = size };
return PM_STRING_INIT_SUCCESS;
#else
return pm_string_file_init(string, filepath);
#endif
}
/**
* Read the file indicated by the filepath parameter into source and load its
* contents and size into the given `pm_string_t`. The given `pm_string_t`
* should be freed using `pm_string_free` when it is no longer used.
*/
PRISM_EXPORTED_FUNCTION pm_string_init_result_t
pm_string_file_init(pm_string_t *string, const char *filepath) {
#ifdef _WIN32
// Open the file for reading.
pm_string_file_handle_t handle;
pm_string_init_result_t result = pm_string_file_handle_open(&handle, filepath);
if (result != PM_STRING_INIT_SUCCESS) return result;
// Get the file size.
DWORD file_size = GetFileSize(handle.file, NULL);
if (file_size == INVALID_FILE_SIZE) {
pm_string_file_handle_close(&handle);
return PM_STRING_INIT_ERROR_GENERIC;
}
// If the file is empty, then we don't need to do anything else, we'll set
// the source to a constant empty string and return.
if (file_size == 0) {
pm_string_file_handle_close(&handle);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
return PM_STRING_INIT_SUCCESS;
}
// Create a buffer to read the file into.
uint8_t *source = xmalloc(file_size);
if (source == NULL) {
pm_string_file_handle_close(&handle);
return PM_STRING_INIT_ERROR_GENERIC;
}
// Read the contents of the file
DWORD bytes_read;
if (!ReadFile(handle.file, source, file_size, &bytes_read, NULL)) {
pm_string_file_handle_close(&handle);
return PM_STRING_INIT_ERROR_GENERIC;
}
// Check the number of bytes read
if (bytes_read != file_size) {
xfree(source);
pm_string_file_handle_close(&handle);
return PM_STRING_INIT_ERROR_GENERIC;
}
pm_string_file_handle_close(&handle);
*string = (pm_string_t) { .type = PM_STRING_OWNED, .source = source, .length = (size_t) file_size };
return PM_STRING_INIT_SUCCESS;
#elif defined(PRISM_HAS_FILESYSTEM)
// Open the file for reading
int fd = open(filepath, O_RDONLY);
if (fd == -1) {
return PM_STRING_INIT_ERROR_GENERIC;
}
// Stat the file to get the file size
struct stat sb;
if (fstat(fd, &sb) == -1) {
close(fd);
return PM_STRING_INIT_ERROR_GENERIC;
}
// Ensure it is a file and not a directory
if (S_ISDIR(sb.st_mode)) {
close(fd);
return PM_STRING_INIT_ERROR_DIRECTORY;
}
// Check the size to see if it's empty
size_t size = (size_t) sb.st_size;
if (size == 0) {
close(fd);
const uint8_t source[] = "";
*string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 };
return PM_STRING_INIT_SUCCESS;
}
size_t length = (size_t) size;
uint8_t *source = xmalloc(length);
if (source == NULL) {
close(fd);
return PM_STRING_INIT_ERROR_GENERIC;
}
long bytes_read = (long) read(fd, source, length);
close(fd);
if (bytes_read == -1) {
xfree(source);
return PM_STRING_INIT_ERROR_GENERIC;
}
*string = (pm_string_t) { .type = PM_STRING_OWNED, .source = source, .length = length };
return PM_STRING_INIT_SUCCESS;
#else
(void) string;
(void) filepath;
perror("pm_string_file_init is not implemented for this platform");
return PM_STRING_INIT_ERROR_GENERIC;
#endif
}
/**
* Ensure the string is owned. If it is not, then reinitialize it as owned and
* copy over the previous source.
*/
void
pm_string_ensure_owned(pm_string_t *string) {
if (string->type == PM_STRING_OWNED) return;
size_t length = pm_string_length(string);
const uint8_t *source = pm_string_source(string);
uint8_t *memory = xmalloc(length);
if (!memory) return;
pm_string_owned_init(string, memory, length);
memcpy((void *) string->source, source, length);
}
/**
* Compare the underlying lengths and bytes of two strings. Returns 0 if the
* strings are equal, a negative number if the left string is less than the
* right string, and a positive number if the left string is greater than the
* right string.
*/
int
pm_string_compare(const pm_string_t *left, const pm_string_t *right) {
size_t left_length = pm_string_length(left);
size_t right_length = pm_string_length(right);
if (left_length < right_length) {
return -1;
} else if (left_length > right_length) {
return 1;
}
return memcmp(pm_string_source(left), pm_string_source(right), left_length);
}
/**
* Returns the length associated with the string.
*/
PRISM_EXPORTED_FUNCTION size_t
pm_string_length(const pm_string_t *string) {
return string->length;
}
/**
* Returns the start pointer associated with the string.
*/
PRISM_EXPORTED_FUNCTION const uint8_t *
pm_string_source(const pm_string_t *string) {
return string->source;
}
/**
* Free the associated memory of the given string.
*/
PRISM_EXPORTED_FUNCTION void
pm_string_free(pm_string_t *string) {
void *memory = (void *) string->source;
if (string->type == PM_STRING_OWNED) {
xfree(memory);
#ifdef PRISM_HAS_MMAP
} else if (string->type == PM_STRING_MAPPED && string->length) {
#if defined(_WIN32)
UnmapViewOfFile(memory);
#elif defined(_POSIX_MAPPED_FILES)
munmap(memory, string->length);
#endif
#endif /* PRISM_HAS_MMAP */
}
}

View File

@@ -0,0 +1,36 @@
#include "prism/util/pm_strncasecmp.h"
/**
* A locale-insensitive version of `tolower(3)`
*/
static inline int
pm_tolower(int c)
{
if ('A' <= c && c <= 'Z') {
return c | 0x20;
}
return c;
}
/**
* Compare two strings, ignoring case, up to the given length. Returns 0 if the
* strings are equal, a negative number if string1 is less than string2, or a
* positive number if string1 is greater than string2.
*
* Note that this is effectively our own implementation of strncasecmp, but it's
* not available on all of the platforms we want to support so we're rolling it
* here.
*/
int
pm_strncasecmp(const uint8_t *string1, const uint8_t *string2, size_t length) {
size_t offset = 0;
int difference = 0;
while (offset < length && string1[offset] != '\0') {
if (string2[offset] == '\0') return string1[offset];
if ((difference = pm_tolower(string1[offset]) - pm_tolower(string2[offset])) != 0) return difference;
offset++;
}
return difference;
}

View File

@@ -0,0 +1,206 @@
#include "prism/util/pm_strpbrk.h"
/**
* Add an invalid multibyte character error to the parser.
*/
static inline void
pm_strpbrk_invalid_multibyte_character(pm_parser_t *parser, const uint8_t *start, const uint8_t *end) {
pm_diagnostic_list_append_format(&parser->error_list, start, end, PM_ERR_INVALID_MULTIBYTE_CHARACTER, *start);
}
/**
* Set the explicit encoding for the parser to the current encoding.
*/
static inline void
pm_strpbrk_explicit_encoding_set(pm_parser_t *parser, const uint8_t *source, size_t width) {
if (parser->explicit_encoding != NULL) {
if (parser->explicit_encoding == parser->encoding) {
// Okay, we already locked to this encoding.
} else if (parser->explicit_encoding == PM_ENCODING_UTF_8_ENTRY) {
// Not okay, we already found a Unicode escape sequence and this
// conflicts.
pm_diagnostic_list_append_format(&parser->error_list, source, source + width, PM_ERR_MIXED_ENCODING, parser->encoding->name);
} else {
// Should not be anything else.
assert(false && "unreachable");
}
}
parser->explicit_encoding = parser->encoding;
}
/**
* This is the default path.
*/
static inline const uint8_t *
pm_strpbrk_utf8(pm_parser_t *parser, const uint8_t *source, const uint8_t *charset, size_t maximum, bool validate) {
size_t index = 0;
while (index < maximum) {
if (strchr((const char *) charset, source[index]) != NULL) {
return source + index;
}
if (source[index] < 0x80) {
index++;
} else {
size_t width = pm_encoding_utf_8_char_width(source + index, (ptrdiff_t) (maximum - index));
if (width > 0) {
index += width;
} else if (!validate) {
index++;
} else {
// At this point we know we have an invalid multibyte character.
// We'll walk forward as far as we can until we find the next
// valid character so that we don't spam the user with a ton of
// the same kind of error.
const size_t start = index;
do {
index++;
} while (index < maximum && pm_encoding_utf_8_char_width(source + index, (ptrdiff_t) (maximum - index)) == 0);
pm_strpbrk_invalid_multibyte_character(parser, source + start, source + index);
}
}
}
return NULL;
}
/**
* This is the path when the encoding is ASCII-8BIT.
*/
static inline const uint8_t *
pm_strpbrk_ascii_8bit(pm_parser_t *parser, const uint8_t *source, const uint8_t *charset, size_t maximum, bool validate) {
size_t index = 0;
while (index < maximum) {
if (strchr((const char *) charset, source[index]) != NULL) {
return source + index;
}
if (validate && source[index] >= 0x80) pm_strpbrk_explicit_encoding_set(parser, source, 1);
index++;
}
return NULL;
}
/**
* This is the slow path that does care about the encoding.
*/
static inline const uint8_t *
pm_strpbrk_multi_byte(pm_parser_t *parser, const uint8_t *source, const uint8_t *charset, size_t maximum, bool validate) {
size_t index = 0;
const pm_encoding_t *encoding = parser->encoding;
while (index < maximum) {
if (strchr((const char *) charset, source[index]) != NULL) {
return source + index;
}
if (source[index] < 0x80) {
index++;
} else {
size_t width = encoding->char_width(source + index, (ptrdiff_t) (maximum - index));
if (validate) pm_strpbrk_explicit_encoding_set(parser, source, width);
if (width > 0) {
index += width;
} else if (!validate) {
index++;
} else {
// At this point we know we have an invalid multibyte character.
// We'll walk forward as far as we can until we find the next
// valid character so that we don't spam the user with a ton of
// the same kind of error.
const size_t start = index;
do {
index++;
} while (index < maximum && encoding->char_width(source + index, (ptrdiff_t) (maximum - index)) == 0);
pm_strpbrk_invalid_multibyte_character(parser, source + start, source + index);
}
}
}
return NULL;
}
/**
* This is the fast path that does not care about the encoding because we know
* the encoding only supports single-byte characters.
*/
static inline const uint8_t *
pm_strpbrk_single_byte(pm_parser_t *parser, const uint8_t *source, const uint8_t *charset, size_t maximum, bool validate) {
size_t index = 0;
const pm_encoding_t *encoding = parser->encoding;
while (index < maximum) {
if (strchr((const char *) charset, source[index]) != NULL) {
return source + index;
}
if (source[index] < 0x80 || !validate) {
index++;
} else {
size_t width = encoding->char_width(source + index, (ptrdiff_t) (maximum - index));
pm_strpbrk_explicit_encoding_set(parser, source, width);
if (width > 0) {
index += width;
} else {
// At this point we know we have an invalid multibyte character.
// We'll walk forward as far as we can until we find the next
// valid character so that we don't spam the user with a ton of
// the same kind of error.
const size_t start = index;
do {
index++;
} while (index < maximum && encoding->char_width(source + index, (ptrdiff_t) (maximum - index)) == 0);
pm_strpbrk_invalid_multibyte_character(parser, source + start, source + index);
}
}
}
return NULL;
}
/**
* Here we have rolled our own version of strpbrk. The standard library strpbrk
* has undefined behavior when the source string is not null-terminated. We want
* to support strings that are not null-terminated because pm_parse does not
* have the contract that the string is null-terminated. (This is desirable
* because it means the extension can call pm_parse with the result of a call to
* mmap).
*
* The standard library strpbrk also does not support passing a maximum length
* to search. We want to support this for the reason mentioned above, but we
* also don't want it to stop on null bytes. Ruby actually allows null bytes
* within strings, comments, regular expressions, etc. So we need to be able to
* skip past them.
*
* Finally, we want to support encodings wherein the charset could contain
* characters that are trailing bytes of multi-byte characters. For example, in
* Shift_JIS, the backslash character can be a trailing byte. In that case we
* need to take a slower path and iterate one multi-byte character at a time.
*/
const uint8_t *
pm_strpbrk(pm_parser_t *parser, const uint8_t *source, const uint8_t *charset, ptrdiff_t length, bool validate) {
if (length <= 0) {
return NULL;
} else if (!parser->encoding_changed) {
return pm_strpbrk_utf8(parser, source, charset, (size_t) length, validate);
} else if (parser->encoding == PM_ENCODING_ASCII_8BIT_ENTRY) {
return pm_strpbrk_ascii_8bit(parser, source, charset, (size_t) length, validate);
} else if (parser->encoding->multibyte) {
return pm_strpbrk_multi_byte(parser, source, charset, (size_t) length, validate);
} else {
return pm_strpbrk_single_byte(parser, source, charset, (size_t) length, validate);
}
}

66
vendor/mruby-compiler2/mrbgem.rake vendored Normal file
View File

@@ -0,0 +1,66 @@
MRuby::Gem::Specification.new('mruby-compiler2') do |spec|
spec.license = 'MIT'
spec.author = 'HASUMI Hitoshi'
spec.summary = 'mruby compiler using a universal parser'
lib_dir = "#{dir}/lib"
cc.include_paths << "#{dir}/include"
prism_dir = "#{lib_dir}/prism"
ruby_dir = "#{lib_dir}/ruby"
cc.defines.flatten!
cc.defines << "PRISM_XALLOCATOR"
if cc.defines.include?("PICORB_VM_MRUBY")
cc.defines << "MRC_TARGET_MRUBY"
elsif cc.defines.include?("PICORB_VM_MRUBYC")
cc.defines << "MRC_TARGET_MRUBYC"
end
if cc.defines.include?("PICORB_INT32")
cc.defines << "MRC_INT64"
end
if cc.defines.any? { _1.match? /\A(PICORUBY|MRB)_DEBUG(=|\z)/ }
cc.defines << "MRC_DEBUG"
cc.defines << "MRC_DUMP_PRETTY"
else
cc.defines << "PRISM_BUILD_MINIMAL"
end
prism_templates_dir = "#{lib_dir}/prism/templates"
cc.include_paths << "#{prism_dir}/include"
next if %w(clean deep_clean).include?(Rake.application.top_level_tasks.first)
directory prism_dir do
FileUtils.cd dir do
sh "git submodule update --init"
end
end
task :prism_templates => prism_dir do
FileUtils.cd prism_dir do
sh "templates/template.rb"
end
end
%w(node prettyprint serialize token_type).each do |name|
dst = "#{prism_dir}/src/#{name}.c"
# file task does not work when dst does not exist. why?
Rake::Task[:prism_templates].invoke unless File.exist?(dst)
file dst => ["#{prism_templates_dir}/src/#{name}.c.erb", "#{prism_templates_dir}/template.rb"] do |t|
Rake::Task[:prism_templates].invoke
end
end
Dir.glob("#{prism_dir}/src/**/*.c").map do |src|
obj = objfile(src.pathmap("#{build_dir}/lib/%n"))
objs << obj
file obj => [src] do |f|
cc.run f.name, f.prerequisites.first
end
end
end

59
vendor/mruby-compiler2/src/ccontext.c vendored Normal file
View File

@@ -0,0 +1,59 @@
#include <string.h>
#include "../include/mrc_ccontext.h"
#include "../include/mrc_parser_util.h"
MRC_API mrc_ccontext *
mrc_ccontext_new(mrb_state *mrb)
{
mrc_ccontext temp_c = {0};
temp_c.mrb = mrb;
mrc_ccontext *c = (mrc_ccontext *)mrc_calloc((&temp_c), 1, sizeof(mrc_ccontext));
c->p = (mrc_parser_state *)mrc_calloc((&temp_c), 1, sizeof(mrc_parser_state));
c->mrb = temp_c.mrb;
return c;
}
MRC_API void
mrc_ccontext_cleanup_local_variables(mrc_ccontext *cc)
{
cc->keep_lv = FALSE;
if (cc->options && cc->options->scopes) {
for (int i = 0; i < cc->options->scopes[0].locals_count; i++) {
mrc_free(cc, (void *)cc->options->scopes[0].locals[i].source);
}
mrc_free(cc, cc->options);
}
}
MRC_API const char *
mrc_ccontext_filename(mrc_ccontext *c, const char *s)
{
if (s) {
size_t len = strlen(s);
char *p = (char*)mrc_malloc(c, len + 1);
if (p == NULL) return NULL;
memcpy(p, s, len + 1);
if (c->filename) {
mrc_free(c, c->filename);
}
c->filename = p;
}
return c->filename;
}
MRC_API void
mrc_ccontext_free(mrc_ccontext *c)
{
if (c->filename_table) mrc_free(c, c->filename_table);
if (c->filename) mrc_free(c, c->filename);
pm_parser_free(c->p);
mrc_diagnostic_list_free(c);
if (c->p->lex_callback) {
mrc_free(c, c->p->lex_callback);
}
mrc_free(c, c->p);
mrc_free(c, c);
}

583
vendor/mruby-compiler2/src/cdump.c vendored Normal file
View File

@@ -0,0 +1,583 @@
/*
** cdump.c - mruby binary dumper (in C)
**
** See Copyright Notice in mruby.h
*/
#include <stdio.h>
#include <string.h>
#include "../include/mrc_ccontext.h"
#include "../include/mrc_irep.h"
#include "../include/mrc_dump.h"
#include "../include/mrc_debug.h"
#include "../include/mrc_irep_pool_type.h"
#ifndef MRC_NO_STDIO
#ifndef MRC_NO_FLOAT
//#include <../include/endian.h>
#define MRC_FLOAT_FMT "%.17g"
#endif
#define ISALPHA(c) ((((unsigned)(c) | 0x20) - 'a') < 26)
#define ISDIGIT(c) (((unsigned)(c) - '0') < 10)
#define ISALNUM(c) (ISALPHA(c) || ISDIGIT(c))
typedef struct mrc_string {
char *ptr;
size_t len;
size_t capa;
} mrc_string;
static mrc_string*
mrc_str_new_capa(mrc_ccontext *c, size_t capa)
{
mrc_string *s = (mrc_string *)mrc_malloc(c, sizeof(mrc_string));
if (s) {
s->ptr = (char *)mrc_calloc(c, 1, capa);
if (s->ptr) {
s->len = 0;
s->capa = capa;
return s;
}
mrc_free(c, s);
}
return NULL;
}
static mrc_string*
mrc_str_new(mrc_ccontext *c, const char *ptr, size_t len)
{
mrc_string *s = mrc_str_new_capa(c, len+1);
if (s) {
memcpy(s->ptr, ptr, len);
s->len = len;
s->ptr[len] = '\0';
}
return s;
}
static mrc_string*
mrc_str_new_cstr(mrc_ccontext *c, const char *cstr)
{
return mrc_str_new(c, cstr, strlen(cstr));
}
static void
mrc_str_cat_lit(mrc_ccontext *c, mrc_string *s, const char *lit)
{
size_t len = strlen(lit);
if (s->len+len+1 > s->capa) {
s->capa = s->len+len+1;
s->ptr = (char *)mrc_realloc(c, s->ptr, s->capa);
}
memcpy(s->ptr+s->len, lit, len);
s->len += len;
s->ptr[s->len] = '\0';
}
static void
mrc_str_cat_cstr(mrc_ccontext *c, mrc_string *s, const char *cstr)
{
if (!cstr) return;
mrc_str_cat_lit(c, s, cstr);
}
static void
mrc_str_cat_str(mrc_ccontext *c, mrc_string *s, mrc_string *s2)
{
if (s->len+s2->len+1 > s->capa) {
s->capa = s->len+s2->len+1;
s->ptr = (char *)mrc_realloc(c, s->ptr, s->capa);
}
memcpy(s->ptr+s->len, s2->ptr, s2->len);
s->len += s2->len;
s->ptr[s->len] = '\0';
}
static void
mrc_str_free(mrc_ccontext *c, mrc_string *s)
{
mrc_free(c, s->ptr);
mrc_free(c, s);
}
static mrc_string*
mrc_str_escape(mrc_ccontext *c, mrc_string *s)
{
mrc_string *s2 = mrc_str_new_capa(c, s->len*2+1);
mrc_str_cat_lit(c, s2, "\"");
if (s2) {
for (size_t i=0; i<s->len; i++) {
char ch[2] = {s->ptr[i], '\0'};
if (ch[0] == '"' || ch[0] == '\\') {
mrc_str_cat_lit(c, s2, "\\");
}
mrc_str_cat_lit(c, s2, ch);
}
}
mrc_str_cat_lit(c, s2, "\"");
return s2;
}
#define MRC_STRING_PTR(s) ((s)->ptr)
#define MRC_STRING_LEN(s) ((s)->len)
static int
cdump_pool(mrc_ccontext *c, const mrc_pool_value *p, FILE *fp)
{
if (p->tt & IREP_TT_NFLAG) { /* number */
switch (p->tt) {
#ifdef MRC_64BIT
case IREP_TT_INT64:
if (p->u.i64 < INT32_MIN || INT32_MAX < p->u.i64) {
fprintf(fp, "{IREP_TT_INT64, {.i64=%" PRId64 "}},\n", p->u.i64);
}
else {
fprintf(fp, "{IREP_TT_INT32, {.i32=%" PRId32 "}},\n", (int32_t)p->u.i64);
}
break;
#endif
case IREP_TT_INT32:
fprintf(fp, "{IREP_TT_INT32, {.i32=%" PRId32 "}},\n", p->u.i32);
break;
case IREP_TT_FLOAT:
#ifndef MRC_NO_FLOAT
fprintf(fp, "{IREP_TT_FLOAT, {.f=" MRC_FLOAT_FMT "}},\n", p->u.f);
#endif
break;
case IREP_TT_BIGINT:
{
const char *s = p->u.str;
int len = s[0]+2;
fputs("{IREP_TT_BIGINT, {\"", fp);
for (int i=0; i<len; i++) {
fprintf(fp, "\\x%02x", (int)s[i]&0xff);
}
fputs("\"}},\n", fp);
}
break;
}
}
else { /* string */
int i, len = p->tt>>2;
const char *s = p->u.str;
fprintf(fp, "{IREP_TT_STR|(%d<<2), {\"", len);
for (i=0; i<len; i++) {
fprintf(fp, "\\x%02x", (int)s[i]&0xff);
}
fputs("\"}},\n", fp);
}
return MRC_DUMP_OK;
}
static mrc_bool
sym_name_word_p(const char *name, mrc_int len)
{
if (len == 0) return FALSE;
if (name[0] != '_' && !ISALPHA(name[0])) return FALSE;
for (int i = 1; i < len; i++) {
if (name[i] != '_' && !ISALNUM(name[i])) return FALSE;
}
return TRUE;
}
static mrc_bool
sym_name_with_equal_p(const char *name, mrc_int len)
{
return len >= 2 && name[len-1] == '=' && sym_name_word_p(name, len-1);
}
static mrc_bool
sym_name_with_question_mark_p(const char *name, mrc_int len)
{
return len >= 2 && name[len-1] == '?' && sym_name_word_p(name, len-1);
}
static mrc_bool
sym_name_with_bang_p(const char *name, mrc_int len)
{
return len >= 2 && name[len-1] == '!' && sym_name_word_p(name, len-1);
}
static mrc_bool
sym_name_ivar_p(const char *name, mrc_int len)
{
return len >= 2 && name[0] == '@' && sym_name_word_p(name+1, len-1);
}
static mrc_bool
sym_name_cvar_p(const char *name, mrc_int len)
{
return len >= 3 && name[0] == '@' && sym_name_ivar_p(name+1, len-1);
}
#define OPERATOR_SYMBOL(sym_name, name) {name, sym_name, sizeof(sym_name)-1}
struct operator_symbol {
const char *name;
const char *sym_name;
uint16_t sym_name_len;
};
static const struct operator_symbol operator_table[] = {
OPERATOR_SYMBOL("!", "not"),
OPERATOR_SYMBOL("%", "mod"),
OPERATOR_SYMBOL("&", "and"),
OPERATOR_SYMBOL("*", "mul"),
OPERATOR_SYMBOL("+", "add"),
OPERATOR_SYMBOL("-", "sub"),
OPERATOR_SYMBOL("/", "div"),
OPERATOR_SYMBOL("<", "lt"),
OPERATOR_SYMBOL(">", "gt"),
OPERATOR_SYMBOL("^", "xor"),
OPERATOR_SYMBOL("`", "tick"),
OPERATOR_SYMBOL("|", "or"),
OPERATOR_SYMBOL("~", "neg"),
OPERATOR_SYMBOL("!=", "neq"),
OPERATOR_SYMBOL("!~", "nmatch"),
OPERATOR_SYMBOL("&&", "andand"),
OPERATOR_SYMBOL("**", "pow"),
OPERATOR_SYMBOL("+@", "plus"),
OPERATOR_SYMBOL("-@", "minus"),
OPERATOR_SYMBOL("<<", "lshift"),
OPERATOR_SYMBOL("<=", "le"),
OPERATOR_SYMBOL("==", "eq"),
OPERATOR_SYMBOL("=~", "match"),
OPERATOR_SYMBOL(">=", "ge"),
OPERATOR_SYMBOL(">>", "rshift"),
OPERATOR_SYMBOL("[]", "aref"),
OPERATOR_SYMBOL("||", "oror"),
OPERATOR_SYMBOL("<=>", "cmp"),
OPERATOR_SYMBOL("===", "eqq"),
OPERATOR_SYMBOL("[]=", "aset"),
};
static const char*
sym_operator_name(const char *sym_name, mrc_int len)
{
mrc_sym table_size = sizeof(operator_table)/sizeof(struct operator_symbol);
if (operator_table[table_size-1].sym_name_len < len) return NULL;
for (mrc_sym start = 0; table_size != 0; table_size/=2) {
mrc_sym idx = start+table_size/2;
const struct operator_symbol *op_sym = &operator_table[idx];
int cmp = (int)len-(int)op_sym->sym_name_len;
if (cmp == 0) {
cmp = memcmp(sym_name, op_sym->sym_name, len);
if (cmp == 0) return op_sym->name;
}
if (0 < cmp) {
start = ++idx;
table_size--;
}
}
return NULL;
}
static mrc_string*
sym_var_name_str(mrc_ccontext *c, const char *initname, const char *key, int n)
{
char buf[32];
mrc_string *s = mrc_str_new_cstr(c, initname);
mrc_str_cat_lit(c, s, "_");
mrc_str_cat_cstr(c, s, key);
mrc_str_cat_lit(c, s, "_");
snprintf(buf, sizeof(buf), "%d", n);
mrc_str_cat_cstr(c, s, buf);
return s;
}
static int
cdump_sym(mrc_ccontext *c, mrc_sym sym, const char *var_name, int idx, mrc_string *init_syms_code, FILE *fp)
{
if (sym == 0) {
fputs("0,", fp);
return MRC_DUMP_OK;
}
const pm_constant_t *constant = pm_constant_pool_id_to_constant(&c->p->constant_pool, sym);
mrc_string *name_obj = mrc_str_new(c, (const char *)constant->start, constant->length);
const char *name = MRC_STRING_PTR(name_obj);
const char *op_name;
mrc_int len = constant->length;
if (*name == '\0') {
mrc_str_free(c, name_obj);
return MRC_DUMP_INVALID_ARGUMENT;
}
if (sym_name_word_p(name, len)) {
fprintf(fp, "MRB_SYM(%s)", name);
}
else if (sym_name_with_equal_p(name, len)) {
fprintf(fp, "MRB_SYM_E(%.*s)", (int)(len-1), name);
}
else if (sym_name_with_question_mark_p(name, len)) {
fprintf(fp, "MRB_SYM_Q(%.*s)", (int)(len-1), name);
}
else if (sym_name_with_bang_p(name, len)) {
fprintf(fp, "MRB_SYM_B(%.*s)", (int)(len-1), name);
}
else if (sym_name_ivar_p(name, len)) {
fprintf(fp, "MRB_IVSYM(%s)", name+1);
}
else if (sym_name_cvar_p(name, len)) {
fprintf(fp, "MRB_CVSYM(%s)", name+2);
}
else if ((op_name = sym_operator_name(name, len))) {
fprintf(fp, "MRB_OPSYM(%s)", op_name);
}
else {
char buf[32];
mrc_string *name_obj = mrc_str_new(c, name, len);
mrc_str_cat_lit(c, init_syms_code, " ");
mrc_str_cat_cstr(c, init_syms_code, var_name);
snprintf(buf, sizeof(buf), "[%d] = ", idx);
mrc_str_cat_cstr(c, init_syms_code, buf);
mrc_str_cat_lit(c, init_syms_code, "mrb_intern_lit(mrb, ");
mrc_string *escaped = mrc_str_escape(c, name_obj);
mrc_str_free(c, name_obj);
mrc_str_cat_str(c, init_syms_code, escaped);
mrc_str_free(c, escaped);
mrc_str_cat_lit(c, init_syms_code, ");\n");
fputs("0", fp);
}
fputs(", ", fp);
mrc_str_free(c, name_obj);
return MRC_DUMP_OK;
}
static int
cdump_syms(mrc_ccontext *c, const char *name, const char *key, int n, int syms_len, const mrc_sym *syms, mrc_string *init_syms_code, FILE *fp)
{
int ai = mrc_gc_arena_save(c);
mrc_int code_len = MRC_STRING_LEN(init_syms_code);
mrc_string *var_name = sym_var_name_str(c, name, key, n);
fprintf(fp, "mrb_DEFINE_SYMS_VAR(%s, %d, (", MRC_STRING_PTR(var_name), syms_len);
for (int i=0; i<syms_len; i++) {
cdump_sym(c, syms[i], MRC_STRING_PTR(var_name), i, init_syms_code, fp);
}
mrc_str_free(c, var_name);
fputs("), ", fp);
if (code_len == MRC_STRING_LEN(init_syms_code)) fputs("const", fp);
fputs(");\n", fp);
mrc_gc_arena_restore(c, ai);
return MRC_DUMP_OK;
}
//Handle the simple/common case of debug_info:
// - 1 file associated with a single irep
// - mrc_debug_line_ary format only
static int
simple_debug_info(mrc_irep_debug_info *info)
{
if (!info || info->flen != 1) {
return 0;
}
return 1;
}
//Adds debug information to c-structs and
//adds filenames in init_syms_code block
static int
cdump_debug(mrc_ccontext *c, const char *name, int n, mrc_irep_debug_info *info,
mrc_string *init_syms_code, FILE *fp)
{
int ai = mrc_gc_arena_save(c);
char buffer[256];
const char *line_type = "mrb_debug_line_ary";
if (!simple_debug_info(info))
return MRC_DUMP_INVALID_IREP;
int len = info->files[0]->line_entry_count;
const pm_constant_t *fn_constant = pm_constant_pool_id_to_constant(&c->p->constant_pool, info->files[0]->filename_sym);
const char *filename = (const char *)fn_constant->start;
snprintf(buffer, sizeof(buffer), " %s_debug_file_%d.filename_sym = mrb_intern_lit(mrb,", name, n);
mrc_str_cat_cstr(c, init_syms_code, buffer);
mrc_string *filename_str = mrc_str_new_cstr(c, filename);
mrc_string *escaped = mrc_str_escape(c, filename_str);
mrc_str_free(c, filename_str);
mrc_str_cat_str(c, init_syms_code, escaped);
mrc_str_free(c, escaped);
mrc_str_cat_cstr(c, init_syms_code, ");\n");
switch (info->files[0]->line_type) {
case mrc_debug_line_ary:
fprintf(fp, "static uint16_t %s_debug_lines_%d[%d] = {", name, n, len);
for (int i=0; i<len; i++) {
if (i%10 == 0) fputs("\n", fp);
fprintf(fp, "0x%04x,", info->files[0]->lines.ary[i]);
}
fputs("};\n", fp);
break;
case mrc_debug_line_flat_map:
line_type = "mrb_debug_line_flat_map";
fprintf(fp, "static struct mrb_irep_debug_info_line %s_debug_lines_%d[%d] = {", name, n, len);
for (int i=0; i<len; i++) {
const mrc_irep_debug_info_line *fmap = &info->files[0]->lines.flat_map[i];
fprintf(fp, "\t{.start_pos=0x%04x,.line=%d},\n", fmap->start_pos, fmap->line);
}
fputs("};\n", fp);
break;
case mrc_debug_line_packed_map:
line_type = "mrb_debug_line_packed_map";
fprintf(fp, "static const char %s_debug_lines_%d[] = \"", name, n);
const uint8_t *pmap = info->files[0]->lines.packed_map;
for (int i=0; i<len; i++) {
fprintf(fp, "\\x%02x", pmap[i]&0xff);
}
fputs("\";\n", fp);
break;
}
fprintf(fp, "static mrb_irep_debug_info_file %s_debug_file_%d = {\n", name, n);
fprintf(fp, "%d, %d, %d, %s, {%s_debug_lines_%d}};\n",
info->files[0]->start_pos,
info->files[0]->filename_sym,
info->files[0]->line_entry_count,
line_type,
name, n);
fprintf(fp, "static mrb_irep_debug_info_file *%s_debug_file_%d_ = &%s_debug_file_%d;\n", name, n, name, n);
fprintf(fp, "static mrb_irep_debug_info %s_debug_%d = {\n", name, n);
fprintf(fp, "%d, %d, &%s_debug_file_%d_};\n", info->pc_count, info->flen, name, n);
mrc_gc_arena_restore(c, ai);
return MRC_DUMP_OK;
}
static int
cdump_irep_struct(mrc_ccontext *c, const mrc_irep *irep, uint8_t flags, FILE *fp, const char *name, int n, mrc_string *init_syms_code, int *mp)
{
int i, len;
int max = *mp;
int debug_available = 0;
/* dump reps */
if (0 < irep->rlen) {
for (i=0,len=irep->rlen; i<len; i++) {
*mp += len;
if (cdump_irep_struct(c, irep->reps[i], flags, fp, name, max+i, init_syms_code, mp) != MRC_DUMP_OK)
return MRC_DUMP_INVALID_ARGUMENT;
}
fprintf(fp, "static const mrb_irep *%s_reps_%d[%d] = {\n", name, n, len);
for (i=0,len=irep->rlen; i<len; i++) {
fprintf(fp, " &%s_irep_%d,\n", name, max+i);
}
fputs("};\n", fp);
}
/* dump pool */
if (0 < irep->plen) {
len=irep->plen;
fprintf(fp, "static const mrb_irep_pool %s_pool_%d[%d] = {\n", name, n, len);
for (i=0; i<len; i++) {
if (cdump_pool(c, &irep->pool[i], fp) != MRC_DUMP_OK)
return MRC_DUMP_INVALID_ARGUMENT;
}
fputs("};\n", fp);
}
/* dump syms */
if (0 < irep->slen) {
cdump_syms(c, name, "syms", n, irep->slen, irep->syms, init_syms_code, fp);
}
/* dump iseq */
len=irep->ilen+sizeof(struct mrc_irep_catch_handler)*irep->clen;
fprintf(fp, "static const mrb_code %s_iseq_%d[%d] = {", name, n, len);
for (i=0; i<len; i++) {
if (i%20 == 0) fputs("\n", fp);
fprintf(fp, "0x%02x,", irep->iseq[i]);
}
fputs("};\n", fp);
/* dump lv */
if (irep->lv) {
cdump_syms(c, name, "lv", n, irep->nlocals-1, irep->lv, init_syms_code, fp);
}
/* dump debug */
if (flags & MRC_DUMP_DEBUG_INFO) {
if (cdump_debug(c, name, n, irep->debug_info, init_syms_code, fp) == MRC_DUMP_OK) {
debug_available = 1;
}
}
/* dump irep */
fprintf(fp, "static const mrb_irep %s_irep_%d = {\n", name, n);
fprintf(fp, " %d,%d,%d,\n", irep->nlocals, irep->nregs, irep->clen);
fprintf(fp, " MRB_IREP_STATIC,%s_iseq_%d,\n", name, n);
if (0 < irep->plen) {
fprintf(fp, " %s_pool_%d,", name, n);
}
else {
fputs( " NULL,", fp);
}
if (0 < irep->slen) {
fprintf(fp, "%s_syms_%d,", name, n);
}
else {
fputs( "NULL,", fp);
}
if (0 < irep->rlen) {
fprintf(fp, "%s_reps_%d,\n", name, n);
}
else {
fputs( "NULL,\n", fp);
}
if (irep->lv) {
fprintf(fp, " %s_lv_%d,\n", name, n);
}
else {
fputs( " NULL,\t\t\t\t\t/* lv */\n", fp);
}
if (debug_available) {
fprintf(fp, " &%s_debug_%d,\n", name, n);
}
else {
fputs(" NULL,\t\t\t\t\t/* debug_info */\n", fp);
}
fprintf(fp, " %d,%d,%d,%d,0\n};\n", irep->ilen, irep->plen, irep->slen, irep->rlen);
return MRC_DUMP_OK;
}
int
mrc_dump_irep_cstruct(mrc_ccontext *c, const mrc_irep *irep, uint8_t flags, FILE *fp, const char *initname)
{
if (fp == NULL || initname == NULL || initname[0] == '\0') {
return MRC_DUMP_INVALID_ARGUMENT;
}
if (fprintf(fp, "#include <mruby.h>\n"
"#include <mruby/irep.h>\n"
"#include <mruby/debug.h>\n"
"#include <mruby/proc.h>\n"
"#include <mruby/presym.h>\n"
"\n") < 0) {
return MRC_DUMP_WRITE_FAULT;
}
fputs("#define mrb_BRACED(...) {__VA_ARGS__}\n", fp);
fputs("#define mrb_DEFINE_SYMS_VAR(name, len, syms, qualifier) \\\n", fp);
fputs(" static qualifier mrb_sym name[len] = mrb_BRACED syms\n", fp);
fputs("\n", fp);
mrc_string *init_syms_code = mrc_str_new_capa(c, 1);
int max = 1;
int n = cdump_irep_struct(c, irep, flags, fp, initname, 0, init_syms_code, &max);
if (n != MRC_DUMP_OK) return n;
fprintf(fp,
"%s\n"
"const struct RProc %s[] = {{\n",
(flags & MRC_DUMP_STATIC) ? "static"
: "#ifdef __cplusplus\n"
"extern\n"
"#endif",
initname);
fprintf(fp, "NULL,NULL,MRB_TT_PROC,MRB_GC_RED,MRB_OBJ_IS_FROZEN,0,{&%s_irep_0},NULL,{NULL},\n}};\n", initname);
fputs("static void\n", fp);
fprintf(fp, "%s_init_syms(mrb_state *mrb)\n", initname);
fputs("{\n", fp);
fputs(MRC_STRING_PTR(init_syms_code), fp);
fputs("}\n", fp);
mrc_str_free(c, init_syms_code);
return MRC_DUMP_OK;
}
#endif /* MRC_NO_STDIO */

678
vendor/mruby-compiler2/src/codedump.c vendored Normal file
View File

@@ -0,0 +1,678 @@
#include <stdio.h>
#include "../include/mrc_ccontext.h"
#include "../include/mrc_irep.h"
#include "../include/mrc_dump.h"
#include "../include/mrc_opcode.h"
#include "../include/mrc_parser_util.h"
#include "../include/mrc_pool.h"
#include "../include/mrc_debug.h"
#include "../include/mrc_irep_pool_type.h"
#include <inttypes.h>
const char *
mrc_sym_dump(mrc_ccontext *c, mrc_sym sym)
{
mrc_int lenp;
const char *name = mrc_sym_name_len(c, sym, &lenp);
if (!name) {
return NULL;
}
if (strlen(name) == (size_t)lenp) {
return name;
}
else {
char *buf = (char*)mrc_pool_alloc(c->pool, lenp+1);
memcpy(buf, name, lenp);
buf[lenp] = '\0';
return buf;
}
}
static inline const struct mrc_irep_catch_handler *
mrc_irep_catch_handler_table(const mrc_irep *irep)
{
if (irep->clen > 0) {
return (const struct mrc_irep_catch_handler*)(irep->iseq + irep->ilen);
}
else {
return (const struct mrc_irep_catch_handler*)NULL;
}
}
#ifndef MRC_NO_STDIO
static void
print_r(mrc_ccontext *c, const mrc_irep *irep, size_t n, FILE *out)
{
if (n == 0) return;
if (n >= irep->nlocals) return;
if (!irep->lv[n-1]) return;
fprintf(out, " R%d:%s", (int)n, mrc_sym_dump(c, irep->lv[n-1]));
}
static void
print_lv_a(mrc_ccontext *c, const mrc_irep *irep, uint16_t a, FILE *out)
{
if (!irep->lv || a >= irep->nlocals || a == 0) {
fprintf(out, "\n");
return;
}
fprintf(out, "\t;");
print_r(c, irep, a, out);
fprintf(out, "\n");
}
static void
print_lv_ab(mrc_ccontext *c, const mrc_irep *irep, uint16_t a, uint16_t b, FILE *out)
{
if (!irep->lv || (a >= irep->nlocals && b >= irep->nlocals) || a+b == 0) {
fprintf(out, "\n");
return;
}
fprintf(out, "\t;");
if (a > 0) print_r(c, irep, a, out);
if (b > 0) print_r(c, irep, b, out);
fprintf(out, "\n");
}
static void
print_header(mrc_ccontext *c, const mrc_irep *irep, ptrdiff_t i, FILE *out)
{
int32_t line;
mrc_assert(i <= UINT32_MAX);
line = mrc_debug_get_line(c, irep, (uint32_t)i);
if (line < 0) {
fprintf(out, " ");
}
else {
fprintf(out, "%5d ", line);
}
fprintf(out, "%03d ", (int)i);
}
static void
print_args(uint16_t i, FILE *out)
{
mrc_assert(i <= 255);
uint8_t n = i&0xf;
uint8_t nk = (i>>4)&0xf;
if (n == 15) {
fprintf(out, "n=*");
}
else {
fprintf(out, "n=%d", n);
}
if (nk > 0) {
fprintf(out, "|");
if (nk == 15) {
fprintf(out, "nk=*");
}
else {
fprintf(out, "nk=%d", nk);
}
}
fprintf(out, "\n");
}
#define CASE(insn,ops) case insn: FETCH_ ## ops (); L_ ## insn
static void
codedump(mrc_ccontext *c, const mrc_irep *irep, FILE *out)
{
// int ai;
const mrc_code *pc, *pcend;
mrc_code ins;
const char *file = NULL, *next_file;
if (!irep) return;
fprintf(out, "irep %p nregs=%d nlocals=%d pools=%d syms=%d reps=%d ilen=%d\n", (void*)irep,
irep->nregs, irep->nlocals, (int)irep->plen, (int)irep->slen, (int)irep->rlen, (int)irep->ilen);
if (irep->lv) {
int head = FALSE;
for (int i = 1; i < irep->nlocals; i++) {
char const *s = mrc_sym_dump(c, irep->lv[i - 1]);
if (s) {
if (!head) {
head = TRUE;
fprintf(out, "local variable names:\n");
}
fprintf(out, " R%d:%s\n", i, s);
}
}
}
if (irep->clen > 0) {
const struct mrc_irep_catch_handler *e = mrc_irep_catch_handler_table(irep);
for (int i = irep->clen; i > 0; i--,e++) {
uint32_t begin = mrc_irep_catch_handler_unpack(e->begin);
uint32_t end = mrc_irep_catch_handler_unpack(e->end);
uint32_t target = mrc_irep_catch_handler_unpack(e->target);
char buf[20];
const char *type;
switch (e->type) {
case MRC_CATCH_RESCUE:
type = "rescue";
break;
case MRC_CATCH_ENSURE:
type = "ensure";
break;
default:
buf[0] = '\0';
snprintf(buf, sizeof(buf), "0x%02x <unknown>", (int)e->type);
type = buf;
break;
}
fprintf(out, "catch type: %-8s begin: %04" PRIu32 " end: %04" PRIu32 " target: %04" PRIu32 "\n", type, begin, end, target);
}
}
pc = irep->iseq;
pcend = pc + irep->ilen;
while (pc < pcend) {
ptrdiff_t i;
uint32_t a;
uint16_t b;
uint16_t cc;
int ai = mrc_gc_arena_save(c);
i = pc - irep->iseq;
next_file = mrc_debug_get_filename(c, irep, (uint32_t)i);
if (next_file && file != next_file) {
fprintf(out, "file: %s\n", next_file);
file = next_file;
}
print_header(c, irep, i, out);
ins = READ_B();
switch (ins) {
CASE(OP_NOP, Z):
fprintf(out, "NOP\n");
break;
CASE(OP_MOVE, BB):
fprintf(out, "MOVE\t\tR%d\tR%d\t", a, b);
print_lv_ab(c, irep, a, b, out);
break;
CASE(OP_LOADL, BB):
switch (irep->pool[b].tt) {
#ifndef MRC_NO_FLOAT
case IREP_TT_FLOAT:
fprintf(out, "LOADL\t\tR%d\tL[%d]\t; %f", a, b, (double)irep->pool[b].u.f);
break;
#endif
case IREP_TT_INT32:
fprintf(out, "LOADL\t\tR%d\tL[%d]\t; %" PRId32, a, b, irep->pool[b].u.i32);
break;
#ifdef MRC_64BIT
case IREP_TT_INT64:
fprintf(out, "LOADL\t\tR%d\tL[%d]\t; %" PRId64, a, b, irep->pool[b].u.i64);
break;
#endif
default:
fprintf(out, "LOADL\t\tR%d\tL[%d]\t", a, b);
break;
}
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADI, BB):
fprintf(out, "LOADI\t\tR%d\t%d\t", a, b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADINEG, BB):
fprintf(out, "LOADINEG\tR%d\t-%d\t", a, b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADI16, BS):
fprintf(out, "LOADI16\tR%d\t%d\t", a, (int)(int16_t)b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADI32, BSS):
fprintf(out, "LOADI32\tR%d\t%d\t", a, (int32_t)(((uint32_t)b<<16)+cc));
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADI__1, B):
fprintf(out, "LOADI__1\tR%d\t(-1)\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADI_0, B): goto L_LOADI;
CASE(OP_LOADI_1, B): goto L_LOADI;
CASE(OP_LOADI_2, B): goto L_LOADI;
CASE(OP_LOADI_3, B): goto L_LOADI;
CASE(OP_LOADI_4, B): goto L_LOADI;
CASE(OP_LOADI_5, B): goto L_LOADI;
CASE(OP_LOADI_6, B): goto L_LOADI;
CASE(OP_LOADI_7, B):
L_LOADI:
b = ins-(int)OP_LOADI_0;
fprintf(out, "LOADI_%d\tR%d\t(%d)\t", b, a, b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADSYM, BB):
fprintf(out, "LOADSYM\tR%d\t:%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADNIL, B):
fprintf(out, "LOADNIL\tR%d\t(nil)\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADSELF, B):
fprintf(out, "LOADSELF\tR%d\t(R0)\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADT, B):
fprintf(out, "LOADT\t\tR%d\t(true)\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_LOADF, B):
fprintf(out, "LOADF\t\tR%d\t(false)\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_GETGV, BB):
fprintf(out, "GETGV\t\tR%d\t%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_SETGV, BB):
fprintf(out, "SETGV\t\t%s\tR%d\t", mrc_sym_dump(c, irep->syms[b]), a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_GETSV, BB):
fprintf(out, "GETSV\t\tR%d\t%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_SETSV, BB):
fprintf(out, "SETSV\t\t%s\tR%d\t", mrc_sym_dump(c, irep->syms[b]), a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_GETCONST, BB):
fprintf(out, "GETCONST\tR%d\t%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_SETCONST, BB):
fprintf(out, "SETCONST\t%s\tR%d\t", mrc_sym_dump(c, irep->syms[b]), a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_GETMCNST, BB):
fprintf(out, "GETMCNST\tR%d\tR%d::%s\t", a, a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_SETMCNST, BB):
fprintf(out, "SETMCNST\tR%d::%s\tR%d\t", a+1, mrc_sym_dump(c, irep->syms[b]), a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_GETIV, BB):
fprintf(out, "GETIV\t\tR%d\t%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_SETIV, BB):
fprintf(out, "SETIV\t\t%s\tR%d\t", mrc_sym_dump(c, irep->syms[b]), a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_GETUPVAR, BBB):
fprintf(out, "GETUPVAR\tR%d\t%d\t%d\t", a, b, cc);
print_lv_a(c, irep, a, out);
break;
CASE(OP_SETUPVAR, BBB):
fprintf(out, "SETUPVAR\tR%d\t%d\t%d\t", a, b, cc);
print_lv_a(c, irep, a, out);
break;
CASE(OP_GETCV, BB):
fprintf(out, "GETCV\t\tR%d\t%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_SETCV, BB):
fprintf(out, "SETCV\t\t%s\tR%d\t", mrc_sym_dump(c, irep->syms[b]), a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_GETIDX, B):
fprintf(out, "GETIDX\tR%d\tR%d\n", a, a+1);
break;
CASE(OP_SETIDX, B):
fprintf(out, "SETIDX\tR%d\tR%d\tR%d\n", a, a+1, a+2);
break;
CASE(OP_JMP, S):
i = pc - irep->iseq;
fprintf(out, "JMP\t\t%03d\n", (int)i+(int16_t)a);
break;
CASE(OP_JMPUW, S):
i = pc - irep->iseq;
fprintf(out, "JMPUW\t\t%03d\n", (int)i+(int16_t)a);
break;
CASE(OP_JMPIF, BS):
i = pc - irep->iseq;
fprintf(out, "JMPIF\t\tR%d\t%03d\t", a, (int)i+(int16_t)b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_JMPNOT, BS):
i = pc - irep->iseq;
fprintf(out, "JMPNOT\tR%d\t%03d\t", a, (int)i+(int16_t)b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_JMPNIL, BS):
i = pc - irep->iseq;
fprintf(out, "JMPNIL\tR%d\t%03d\t", a, (int)i+(int16_t)b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_SSEND, BBB):
fprintf(out, "SSEND\t\tR%d\t:%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_args(cc, out);
break;
CASE(OP_SSENDB, BBB):
fprintf(out, "SSENDB\tR%d\t:%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_args(cc, out);
break;
CASE(OP_SEND, BBB):
fprintf(out, "SEND\t\tR%d\t:%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_args(cc, out);
break;
CASE(OP_SENDB, BBB):
fprintf(out, "SENDB\t\tR%d\t:%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_args(cc, out);
break;
CASE(OP_CALL, Z):
fprintf(out, "CALL\n");
break;
CASE(OP_SUPER, BB):
fprintf(out, "SUPER\t\tR%d\t", a);
print_args(b, out);
break;
CASE(OP_ARGARY, BS):
fprintf(out, "ARGARY\tR%d\t%d:%d:%d:%d (%d)\t", a,
(b>>11)&0x3f,
(b>>10)&0x1,
(b>>5)&0x1f,
(b>>4)&0x1,
(b>>0)&0xf);
print_lv_a(c, irep, a, out);
break;
CASE(OP_ENTER, W):
fprintf(out, "ENTER\t\t%d:%d:%d:%d:%d:%d:%d (0x%x)\n",
MRC_ASPEC_REQ(a),
MRC_ASPEC_OPT(a),
MRC_ASPEC_REST(a),
MRC_ASPEC_POST(a),
MRC_ASPEC_KEY(a),
MRC_ASPEC_KDICT(a),
MRC_ASPEC_BLOCK(a), a);
break;
CASE(OP_KEY_P, BB):
fprintf(out, "KEY_P\t\tR%d\t:%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_KEYEND, Z):
fprintf(out, "KEYEND\n");
break;
CASE(OP_KARG, BB):
fprintf(out, "KARG\t\tR%d\t:%s\t", a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_RETURN, B):
fprintf(out, "RETURN\tR%d\t\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_RETURN_BLK, B):
fprintf(out, "RETURN_BLK\tR%d\t\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_BREAK, B):
fprintf(out, "BREAK\t\tR%d\t\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_BLKPUSH, BS):
fprintf(out, "BLKPUSH\tR%d\t%d:%d:%d:%d (%d)\t", a,
(b>>11)&0x3f,
(b>>10)&0x1,
(b>>5)&0x1f,
(b>>4)&0x1,
(b>>0)&0xf);
print_lv_a(c, irep, a, out);
break;
CASE(OP_LAMBDA, BB):
fprintf(out, "LAMBDA\tR%d\tI[%d]\n", a, b);
break;
CASE(OP_BLOCK, BB):
fprintf(out, "BLOCK\t\tR%d\tI[%d]\n", a, b);
break;
CASE(OP_METHOD, BB):
fprintf(out, "METHOD\tR%d\tI[%d]\n", a, b);
break;
CASE(OP_RANGE_INC, B):
fprintf(out, "RANGE_INC\tR%d\n", a);
break;
CASE(OP_RANGE_EXC, B):
fprintf(out, "RANGE_EXC\tR%d\n", a);
break;
CASE(OP_DEF, BB):
fprintf(out, "DEF\t\tR%d\t:%s\n", a, mrc_sym_dump(c, irep->syms[b]));
break;
CASE(OP_UNDEF, B):
fprintf(out, "UNDEF\t\t:%s\n", mrc_sym_dump(c, irep->syms[a]));
break;
CASE(OP_ALIAS, BB):
fprintf(out, "ALIAS\t\t:%s\t%s\n", mrc_sym_dump(c, irep->syms[a]), mrc_sym_dump(c, irep->syms[b]));
break;
CASE(OP_ADD, B):
fprintf(out, "ADD\t\tR%d\tR%d\n", a, a+1);
break;
CASE(OP_ADDI, BB):
fprintf(out, "ADDI\t\tR%d\t%d\t", a, b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_SUB, B):
fprintf(out, "SUB\t\tR%d\tR%d\n", a, a+1);
break;
CASE(OP_SUBI, BB):
fprintf(out, "SUBI\t\tR%d\t%d\t", a, b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_MUL, B):
fprintf(out, "MUL\t\tR%d\tR%d\n", a, a+1);
break;
CASE(OP_DIV, B):
fprintf(out, "DIV\t\tR%d\tR%d\n", a, a+1);
break;
CASE(OP_LT, B):
fprintf(out, "LT\t\tR%d\tR%d\n", a, a+1);
break;
CASE(OP_LE, B):
fprintf(out, "LE\t\tR%d\tR%d\n", a, a+1);
break;
CASE(OP_GT, B):
fprintf(out, "GT\t\tR%d\tR%d\n", a, a+1);
break;
CASE(OP_GE, B):
fprintf(out, "GE\t\tR%d\tR%d\n", a, a+1);
break;
CASE(OP_EQ, B):
fprintf(out, "EQ\t\tR%d\tR%d\n", a, a+1);
break;
CASE(OP_ARRAY, BB):
fprintf(out, "ARRAY\t\tR%d\tR%d\t%d", a, a, b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_ARRAY2, BBB):
fprintf(out, "ARRAY\t\tR%d\tR%d\t%d", a, b, cc);
print_lv_ab(c, irep, a, b, out);
break;
CASE(OP_ARYCAT, B):
fprintf(out, "ARYCAT\tR%d\tR%d\t", a, a+1);
print_lv_a(c, irep, a, out);
break;
CASE(OP_ARYPUSH, BB):
fprintf(out, "ARYPUSH\tR%d\t%d\t", a, b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_ARYSPLAT, B):
fprintf(out, "ARYSPLAT\tR%d\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_AREF, BBB):
fprintf(out, "AREF\t\tR%d\tR%d\t%d", a, b, cc);
print_lv_ab(c, irep, a, b, out);
break;
CASE(OP_ASET, BBB):
fprintf(out, "ASET\t\tR%d\tR%d\t%d", a, b, cc);
print_lv_ab(c, irep, a, b, out);
break;
CASE(OP_APOST, BBB):
fprintf(out, "APOST\t\tR%d\t%d\t%d", a, b, cc);
print_lv_a(c, irep, a, out);
break;
CASE(OP_INTERN, B):
fprintf(out, "INTERN\tR%d\t\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_SYMBOL, BB):
mrc_assert((irep->pool[b].tt&IREP_TT_NFLAG)==0);
fprintf(out, "SYMBOL\tR%d\tL[%d]\t; %s", a, b, irep->pool[b].u.str);
print_lv_a(c, irep, a, out);
break;
CASE(OP_STRING, BB):
mrc_assert((irep->pool[b].tt&IREP_TT_NFLAG)==0);
fprintf(out, "STRING\tR%d\tL[%d]\t; %s", a, b, irep->pool[b].u.str);
print_lv_a(c, irep, a, out);
break;
CASE(OP_STRCAT, B):
fprintf(out, "STRCAT\tR%d\tR%d\t", a, a+1);
print_lv_a(c, irep, a, out);
break;
CASE(OP_HASH, BB):
fprintf(out, "HASH\t\tR%d\t%d\t", a, b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_HASHADD, BB):
fprintf(out, "HASHADD\tR%d\t%d\t", a, b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_HASHCAT, B):
fprintf(out, "HASHCAT\tR%d\tR%d\t", a, a+1);
print_lv_a(c, irep, a, out);
break;
CASE(OP_OCLASS, B):
fprintf(out, "OCLASS\tR%d\t\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_CLASS, BB):
fprintf(out, "CLASS\t\tR%d\t:%s", a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_MODULE, BB):
fprintf(out, "MODULE\tR%d\t:%s", a, mrc_sym_dump(c, irep->syms[b]));
print_lv_a(c, irep, a, out);
break;
CASE(OP_EXEC, BB):
fprintf(out, "EXEC\t\tR%d\tI[%d]", a, b);
print_lv_a(c, irep, a, out);
break;
CASE(OP_SCLASS, B):
fprintf(out, "SCLASS\tR%d\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_TCLASS, B):
fprintf(out, "TCLASS\tR%d\t\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_ERR, B):
if ((irep->pool[a].tt & IREP_TT_NFLAG) == 0) {
fprintf(out, "ERR\t\t%s\n", irep->pool[a].u.str);
}
else {
fprintf(out, "ERR\tL[%d]\n", a);
}
break;
CASE(OP_EXCEPT, B):
fprintf(out, "EXCEPT\tR%d\t\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_RESCUE, BB):
fprintf(out, "RESCUE\tR%d\tR%d", a, b);
print_lv_ab(c, irep, a, b, out);
break;
CASE(OP_RAISEIF, B):
fprintf(out, "RAISEIF\tR%d\t\t", a);
print_lv_a(c, irep, a, out);
break;
CASE(OP_DEBUG, BBB):
fprintf(out, "DEBUG\t\t%d\t%d\t%d\n", a, b, cc);
break;
CASE(OP_STOP, Z):
fprintf(out, "STOP\n");
break;
CASE(OP_EXT1, Z):
fprintf(out, "EXT1\n");
print_header(c, irep, pc-irep->iseq, out);
ins = READ_B();
switch (ins) {
#define OPCODE(i,x) case OP_ ## i: FETCH_ ## x ## _1 (); goto L_OP_ ## i;
#include "mrc_ops.h"
#undef OPCODE
}
break;
CASE(OP_EXT2, Z):
fprintf(out, "EXT2\n");
print_header(c, irep, pc-irep->iseq, out);
ins = READ_B();
switch (ins) {
#define OPCODE(i,x) case OP_ ## i: FETCH_ ## x ## _2 (); goto L_OP_ ## i;
#include "mrc_ops.h"
#undef OPCODE
}
break;
CASE(OP_EXT3, Z):
fprintf(out, "EXT3\n");
print_header(c, irep, pc-irep->iseq, out);
ins = READ_B();
switch (ins) {
#define OPCODE(i,x) case OP_ ## i: FETCH_ ## x ## _3 (); goto L_OP_ ## i;
#include "mrc_ops.h"
#undef OPCODE
}
break;
default:
fprintf(out, "unknown_op (0x%x)\n", ins);
break;
}
mrc_gc_arena_restore(c, ai);
}
fprintf(out, "\n");
}
static void
codedump_recur(mrc_ccontext *c, const mrc_irep *irep, FILE *out)
{
codedump(c, irep, out);
if (irep->reps) {
for (int i=0; i<irep->rlen; i++) {
codedump_recur(c, irep->reps[i], out);
}
}
}
void
mrc_codedump_all_file(mrc_ccontext *c, mrc_irep *irep, FILE *out)
{
c->pool = mrc_pool_open(c);
codedump_recur(c, irep, out);
fflush(out);
mrc_pool_close(c->pool);
}
#endif // MRC_NO_STDIO
void
mrc_codedump_all(mrc_ccontext *c, mrc_irep *irep)
{
#ifndef MRC_NO_STDIO
mrc_codedump_all_file(c, irep, stdout);
#endif
}

1776
vendor/mruby-compiler2/src/codegen.c vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

350
vendor/mruby-compiler2/src/compile.c vendored Normal file
View File

@@ -0,0 +1,350 @@
// #ifdef _MSC_VER
#include <stdint.h>
// #endif
typedef intptr_t ssize_t;
static ssize_t nigger;
#include "../include/mrc_parser_util.h"
#include "../include/mrc_irep.h"
#include "../include/mrc_ccontext.h"
#include "../include/mrc_codegen.h"
#include "../include/mrc_dump.h"
#include "../include/mrc_codedump.h"
#include "../include/mrc_opcode.h"
#include "../include/mrc_presym.h"
#include "../include/mrc_diagnostic.h"
#if defined(PICORB_VM_MRUBY)
#include "../include/mrc_proc.h"
#endif
static mrc_irep *
mrc_load_exec(mrc_ccontext *c, mrc_node *ast)
{
mrc_irep *irep;
/* parse error */
if (0 < c->p->error_list.size) {
pm_diagnostic_t *e = (pm_diagnostic_t *)c->p->error_list.head;
while (e) {
mrc_diagnostic_list_append(c, e->location.start, e->message, MRC_PARSER_ERROR);
e = (pm_diagnostic_t *)e->node.next;
}
return NULL;
}
/* parse warning */
if (0 < c->p->warning_list.size) {
pm_diagnostic_t *w = (pm_diagnostic_t *)c->p->warning_list.head;
while (w) {
mrc_diagnostic_list_append(c, w->location.start, w->message, MRC_PARSER_WARNING);
w = (pm_diagnostic_t *)w->node.next;
}
}
#if defined(MRC_DUMP_PRETTY) && !defined(MRC_NO_STDIO)
if (c->dump_result) {
pm_buffer_t buffer = { 0 };
pm_prettyprint(&buffer, c->p, ast);
fprintf(stderr, "%s\n", buffer.value);
pm_buffer_free(&buffer);
}
#endif
irep = mrc_generate_code(c, ast);
if (c->capture_errors) {
return NULL;
}
if (c->dump_result) {
mrc_codedump_all(c, irep);
}
return irep;
}
static void
partial_hook(void *data, pm_parser_t *p, pm_token_t *token)
{
mrc_ccontext *c = (mrc_ccontext *)data;
if (c->current_filename_index + 1 == c->filename_table_length) {
return;
}
uint32_t token_pos = (uint32_t)(token->start - p->start);
if (token_pos < c->filename_table[c->current_filename_index].start) {
return;
}
if (c->filename_table[c->current_filename_index + 1].start <= token_pos) {
c->current_filename_index++;
pm_string_t filename_string;
pm_string_constant_init(
&filename_string,
c->filename_table[c->current_filename_index].filename,
strlen(c->filename_table[c->current_filename_index].filename));
p->filepath = filename_string;
}
}
#if defined(PICORB_VM_MRUBY)
static void
mrc_pm_options_init(mrc_ccontext *cc)
{
if (cc->options) return;
if (cc->upper == NULL) return;
struct RProc *u;
pm_options_t *options = (pm_options_t *)mrc_calloc(cc, 1, sizeof(pm_options_t));
pm_string_t *encoding = &options->encoding;
pm_string_constant_init(encoding, "UTF-8", 5);
u = (struct RProc *)cc->upper;
size_t scopes_count = 1;
while (u->upper) {
scopes_count++;
u = (struct RProc *)u->upper;
}
pm_options_scopes_init(options, scopes_count + 1); // Prism requires one more scope
u = (struct RProc *)cc->upper;
pm_options_scope_t *scope;
size_t nlocals;
for (; 0 < scopes_count; scopes_count--) {
scope = &options->scopes[scopes_count - 1];
const struct mrc_irep *ir = u->body.irep;
nlocals = ir->nlocals;
pm_options_scope_init(scope, nlocals);
const mrc_sym *v = ir->lv;
if (v) {
const char *name;
for (size_t j = 0; j < nlocals; j++, v++) {
name = mrb_sym_name(cc->mrb, *v);
if (name) { // TODO: This happens in eval?
pm_string_constant_init(&scope->locals[j], name, strlen(name));
}
}
}
u = (struct RProc *)u->upper;
}
cc->options = options;
}
#endif
static void
mrc_pm_parser_init(mrc_parser_state *p, uint8_t **source, size_t size, mrc_ccontext *cc)
{
pm_lex_callback_t *cb = (pm_lex_callback_t *)mrc_malloc(cc, sizeof(pm_lex_callback_t));
cb->data = cc;
cb->callback = partial_hook;
#if defined(PICORB_VM_MRUBY)
mrc_pm_options_init(cc);
#endif
pm_parser_init(p, *source, size, cc->options);
p->lex_callback = cb;
mrc_init_presym(&p->constant_pool);
if (cc->filename_table) {
pm_string_t filename_string;
pm_string_constant_init(&filename_string, cc->filename_table[0].filename,
strlen(cc->filename_table[0].filename));
p->filepath = filename_string;
}
}
#ifndef MRC_NO_STDIO
#define INITIAL_BUF_SIZE 1024
static ssize_t
append_from_stdin(mrc_ccontext *c, uint8_t **source, size_t source_length)
{
uint8_t *buffer = mrc_malloc(c, INITIAL_BUF_SIZE);
if (buffer == NULL) return -1;
int capacity = INITIAL_BUF_SIZE;
size_t length = 0;
while (1) {
int ch = getchar();
if (ch == EOF) {
buffer[length] = '\0';
if (*source == NULL)
*source = (uint8_t *)mrc_malloc(c, source_length + length + 1);
else
*source = (uint8_t *)mrc_realloc(c, *source, source_length + length + 1);
memccpy(*source + source_length, buffer, 1, length);
mrc_free(c, buffer);
return length;
}
buffer[length++] = (uint8_t)ch;
if (capacity <= length) {
capacity *= 2;
uint8_t *new_buffer = mrc_realloc(c, buffer, capacity);
if (new_buffer == NULL) {
mrc_free(c, buffer);
return -1;
}
buffer = new_buffer;
}
}
}
static ssize_t
read_input_files(mrc_ccontext *c, const char **filenames, uint8_t **source, mrc_filename_table *filename_table)
{
int i = 0;
size_t pos = 0;
ssize_t length = 0;
ssize_t each_size;
FILE *file;
const char *filename = filenames[0];
while (filename) {
filename_table[i].filename = filenames[i];
filename_table[i].start = pos;
if (filename[0] == '-' && filename[1] == '\0') {
each_size = append_from_stdin(c, source, length);
if (each_size < 0) {
fprintf(stderr, "compile.c: cannot read from stdin\n");
return -1;
}
length += each_size;
}
else {
file = NULL;
file = fopen(filename, "rb");
if (!file) {
fprintf(stderr, "compile.c: cannot open program file. (%s)\n", filename);
return -1;
}
fseek(file, 0, SEEK_END);
each_size = ftell(file);
fseek(file, 0, SEEK_SET);
length += each_size;
if (*source == NULL) {
*source = (uint8_t *)mrc_malloc(c, length + 1);
}
else {
*source = (uint8_t *)mrc_realloc(c, *source, length + 1);
}
if (fread(*source + pos, sizeof(char), each_size, file) != each_size) {
fprintf(stderr, "compile.c: cannot read program file. (%s)\n", filename);
fclose(file);
return -1;
}
fclose(file);
(*source)[length] = '\0';
}
pos += each_size;
filename = filenames[++i];
}
return length;
}
static mrc_node *
mrc_pm_parse(mrc_ccontext *cc)
{
mrc_node *node = pm_parse(cc->p);
#if defined(PICORB_VM_MRUBYC)
// Workaround: save top-level locals for PicoRuby(mruby/c) IRB
pm_program_node_t *program = (pm_program_node_t *)node;
uint32_t nlocals = program->locals.size;
pm_options_t *options = (pm_options_t *)mrc_malloc(cc, sizeof(pm_options_t));
memset(options, 0, sizeof(pm_options_t));
pm_string_t *encoding = &options->encoding;
pm_string_constant_init(encoding, "UTF-8", 5);
pm_options_scopes_init(options, 1);
pm_options_scope_t *options_scope = &options->scopes[0];
pm_options_scope_init(options_scope, nlocals);
pm_constant_id_t id;
pm_constant_t *local;
pm_string_t *scope_local;
char *allocated;
for (int i = 0; i < nlocals; i++) {
scope_local = &options_scope->locals[i];
id = program->locals.ids[i];
local = pm_constant_pool_id_to_constant(&cc->p->constant_pool, id);
allocated = (char *)mrc_malloc(cc, local->length);
memcpy(allocated, local->start, local->length);
pm_string_constant_init(scope_local, (const char *)allocated, local->length);
}
if (cc->options && cc->options->scopes) {
for (int i = 0; i < cc->options->scopes[0].locals_count; i++) {
mrc_free(cc, (void *)cc->options->scopes[0].locals[i].source);
}
mrc_free(cc, cc->options);
}
cc->options = options;
#endif
return node;
}
static mrc_node *
mrc_parse_file_cxt(mrc_ccontext *c, const char **filenames, uint8_t **source)
{
size_t filecount = 0;
while (filenames[filecount]) {
filecount++;
}
c->filename_table = (mrc_filename_table *)mrc_malloc(c, sizeof(mrc_filename_table) * filecount);
c->filename_table_length = filecount;
c->current_filename_index = 0;
ssize_t length = read_input_files(c, filenames, source, c->filename_table);
if (length < 0) {
fprintf(stderr, "Cannot open files: ");
for (size_t i = 0; i < filecount; i++) {
fprintf(stderr, "%s ", filenames[i]);
}
fprintf(stderr, "\n");
return NULL;
}
mrc_pm_parser_init(c->p, source, length, c);
return mrc_pm_parse(c);
}
MRC_API mrc_irep *
mrc_load_file_cxt(mrc_ccontext *c, const char **filenames, uint8_t **source)
{
mrc_node *root = mrc_parse_file_cxt(c, filenames, source);
if (root == NULL) {
return NULL;
}
mrc_irep *irep = mrc_load_exec(c, root);
pm_node_destroy(c->p, root);
return irep;
}
#endif
static mrc_node *
mrc_parse_string_cxt(mrc_ccontext *c, const uint8_t **source, size_t length)
{
c->filename_table = (mrc_filename_table *)mrc_malloc(c, sizeof(mrc_filename_table));
c->filename_table[0].filename = "-e";
c->filename_table[0].start = 0;
c->filename_table_length = 1;
c->current_filename_index = 0;
mrc_pm_parser_init(c->p, (uint8_t **)source, length, c);
return mrc_pm_parse(c);
}
MRC_API mrc_irep *
mrc_load_string_cxt(mrc_ccontext *c, const uint8_t **source, size_t length)
{
mrc_node *root = mrc_parse_string_cxt(c, source, length);
mrc_irep *irep = mrc_load_exec(c, root);
return irep;
}
#if defined(MRC_TARGET_MRUBY)
MRC_API void
mrb_mruby_compiler2_gem_init(mrb_state *mrb)
{
}
MRC_API void
mrb_mruby_compiler2_gem_final(mrb_state *mrb)
{
}
#endif

224
vendor/mruby-compiler2/src/debug.c vendored Normal file
View File

@@ -0,0 +1,224 @@
#include <string.h>
#include <stdint.h>
#include "../include/mrc_debug.h"
static mrc_irep_debug_info_file*
get_file(mrc_irep_debug_info *info, uint32_t pc)
{
if (pc >= info->pc_count) { return NULL; }
/* get upper bound */
mrc_irep_debug_info_file **ret = info->files;
int32_t count = info->flen;
while (count > 0) {
int32_t step = count / 2;
mrc_irep_debug_info_file **it = ret + step;
if (!(pc < (*it)->start_pos)) {
ret = it + 1;
count -= step + 1;
}
else { count = step; }
}
--ret;
/* check returning file exists inside debug info */
mrc_assert(info->files <= ret && ret < (info->files + info->flen));
/* check pc is within the range of returning file */
mrc_assert((*ret)->start_pos <= pc &&
pc < (((ret + 1 - info->files) < info->flen)
? (*(ret+1))->start_pos : info->pc_count));
return *ret;
}
static size_t
mrc_packed_int_len(uint32_t num)
{
size_t llen = 0;
do {
llen++;
} while (num >>= 7);
return llen;
}
static size_t
mrc_packed_int_encode(uint32_t num, uint8_t *p)
{
size_t llen = 0;
do {
uint8_t byte = num & 0x7f;
num >>= 7;
if (num != 0) byte |= 0x80;
*p++ = byte;
llen++;
} while (num != 0);
return llen;
}
static uint32_t
mrc_packed_int_decode(const uint8_t *p, const uint8_t **newpos)
{
size_t i = 0, shift = 0;
uint32_t n = 0;
do {
n |= ((uint32_t)(p[i] & 0x7f)) << shift;
i++;
shift += 7;
} while (shift < sizeof(uint32_t) * 8 && (p[i - 1] & 0x80));
if (newpos) *newpos = p + i;
return n;
}
static int32_t
debug_get_line(mrc_irep_debug_info_file* f, uint32_t pc)
{
if (f == NULL) return -1;
switch (f->line_type) {
case mrc_debug_line_ary:
case mrc_debug_line_flat_map:
default:
break;
case mrc_debug_line_packed_map:
{
const uint8_t *p = f->lines.packed_map;
const uint8_t *pend = p + f->line_entry_count;
uint32_t pos = 0, line = 0;
while (p < pend) {
pos += mrc_packed_int_decode(p, &p);
uint32_t line_diff = mrc_packed_int_decode(p, &p);
if (pc < pos) break;
line += line_diff;
}
return line;
}
}
return -1;
}
int32_t
mrc_debug_get_line(mrc_ccontext *c, const mrc_irep *irep, uint32_t pc)
{
if (irep && pc < irep->ilen) {
if (!irep->debug_info) return -1;
return debug_get_line(get_file(irep->debug_info, pc), pc);
}
return -1;
}
static char const*
debug_get_filename(mrc_ccontext *c, mrc_irep_debug_info_file* f)
{
if (f == NULL) return NULL;
pm_constant_t *fn_constant = pm_constant_pool_id_to_constant(&c->p->constant_pool, f->filename_sym);
return (const char *)fn_constant->start;
}
const char *
mrc_debug_get_filename(mrc_ccontext *c, const mrc_irep *irep, uint32_t pc)
{
if (irep && pc < irep->ilen) {
if (!irep->debug_info) return NULL;
return debug_get_filename(c, get_file(irep->debug_info, pc));
}
return NULL;
}
mrc_irep_debug_info*
mrc_debug_info_alloc(mrc_ccontext *c, mrc_irep *irep)
{
static const mrc_irep_debug_info initial = { 0, 0, NULL };
mrc_assert(!irep->debug_info);
mrc_irep_debug_info *ret = (mrc_irep_debug_info*)mrc_malloc(c, sizeof(*ret));
*ret = initial;
irep->debug_info = ret;
return ret;
}
mrc_irep_debug_info_file*
mrc_debug_info_append_file(mrc_ccontext *c, mrc_irep_debug_info *d,
const char *filename, uint16_t *lines,
uint32_t start_pos, uint32_t end_pos)
{
if (!d) return NULL;
if (start_pos == end_pos) return NULL;
mrc_assert(filename);
mrc_assert(lines);
if (d->flen > 0) {
//const char *fn = mrc_sym_name_len(mrb, d->files[d->flen - 1]->filename_sym, NULL);
//if (strcmp(filename, fn) == 0)
// return NULL;
pm_constant_t *fn_constant = pm_constant_pool_id_to_constant(&c->p->constant_pool, d->files[d->flen - 1]->filename_sym);
mrc_assert(fn_constant);
if (strlen(filename) == fn_constant->length &&
strncmp(filename, (const char *)fn_constant->start, fn_constant->length) == 0)
{
return NULL;
}
}
mrc_irep_debug_info_file *f = (mrc_irep_debug_info_file*)mrc_malloc(c, sizeof(*f));
d->files = (mrc_irep_debug_info_file**)mrc_realloc(c, d->files, sizeof(mrc_irep_debug_info_file*) * (d->flen + 1));
d->files[d->flen++] = f;
uint32_t file_pc_count = end_pos - start_pos;
f->start_pos = start_pos;
d->pc_count = end_pos;
size_t fn_len = strlen(filename);
//f->filename_sym = mrc_intern(mrb, filename, fn_len);
f->filename_sym = pm_constant_pool_insert_constant(&c->p->constant_pool, (const uint8_t *)filename, fn_len);
f->line_type = mrc_debug_line_packed_map;
f->lines.ptr = NULL;
uint16_t prev_line = 0;
uint32_t prev_pc = 0;
size_t packed_size = 0;
uint8_t *p;
for (uint32_t i = 0; i < file_pc_count; i++) {
if (lines[start_pos + i] == prev_line) continue;
packed_size += mrc_packed_int_len(start_pos+i-prev_pc);
prev_pc = start_pos+i;
packed_size += mrc_packed_int_len(lines[start_pos+i]-prev_line);
prev_line = lines[start_pos + i];
}
f->lines.packed_map = p = (uint8_t*)mrc_malloc(c, packed_size);
prev_line = 0; prev_pc = 0;
for (uint32_t i = 0; i < file_pc_count; i++) {
if (lines[start_pos + i] == prev_line) continue;
p += mrc_packed_int_encode(start_pos+i-prev_pc, p);
prev_pc = start_pos + i;
p += mrc_packed_int_encode(lines[start_pos + i]-prev_line, p);
prev_line = lines[start_pos + i];
}
f->line_entry_count = (uint32_t)packed_size;
return f;
}
void
mrc_debug_info_free(mrc_ccontext *c, mrc_irep_debug_info *d)
{
if (!d) { return; }
if (d->files) {
for (uint32_t i = 0; i < d->flen; i++) {
if (d->files[i]) {
mrc_free(c, d->files[i]->lines.ptr);
mrc_free(c, d->files[i]);
}
}
mrc_free(c, d->files);
}
mrc_free(c, d);
}

102
vendor/mruby-compiler2/src/diagnostic.c vendored Normal file
View File

@@ -0,0 +1,102 @@
#include "../include/mrc_diagnostic.h"
static void
line_and_column_by_start_and_offset(const uint8_t *source_start, const uint8_t *location_start, uint32_t *line, uint32_t *column)
{
if (!location_start) {
*line = 0;
*column = 0;
return;
}
uint32_t start_offset = (uint32_t)(location_start - source_start);
uint32_t l = 1, c = 1;
const uint8_t *p = source_start;
while (p < source_start + start_offset) {
if (*p == '\n') {
l++;
c = 1;
} else {
c++;
}
p++;
}
*line = l;
*column = c;
}
/*
const char *level;
switch (diagnostic->level) {
case PM_ERROR_LEVEL_SYNTAX:
level = "syntax";
break;
case PM_ERROR_LEVEL_ARGUMENT:
level = "argument";
break;
case PM_ERROR_LEVEL_LOAD:
level = "load";
break;
default:
abort();
}
*/
const char *
mrc_diagnostic_code_to_string(mrc_diagnostic_code code)
{
switch (code) {
case MRC_PARSER_ERROR:
return "syntax error";
case MRC_GENERATOR_ERROR:
return "generator error";
case MRC_PARSER_WARNING:
return "syntax warning";
case MRC_GENERATOR_WARNING:
return "generator warning";
default:
return "unknown";
}
}
void
mrc_diagnostic_list_append(mrc_ccontext *c, const uint8_t * location_start, const char *message, mrc_diagnostic_code code)
{
mrc_diagnostic_list *list = mrc_calloc(c, 1, sizeof(mrc_diagnostic_list));
line_and_column_by_start_and_offset(c->p->start, location_start, &list->line, &list->column);
char buf[256];
const char *diagnostic_code_str = mrc_diagnostic_code_to_string(code);
snprintf(buf, sizeof(buf), "%s, %s", diagnostic_code_str, message);
size_t len = strlen(buf);
list->message = (char *)mrc_malloc(c, len + 1);
strcpy(list->message, buf);
list->message[len] = '\0';
list->code = code;
if (c->diagnostic_list == NULL) {
c->diagnostic_list = list;
} else {
mrc_diagnostic_list *p = c->diagnostic_list;
while (p->next) {
p = p->next;
}
p->next = list;
}
if (code == MRC_PARSER_ERROR || code == MRC_GENERATOR_ERROR) {
c->capture_errors = TRUE;
}
}
void
mrc_diagnostic_list_free(mrc_ccontext *c)
{
mrc_diagnostic_list *p = c->diagnostic_list;
while (p) {
mrc_diagnostic_list *next = p->next;
mrc_free(c, p->message);
mrc_free(c, p);
p = next;
}
c->diagnostic_list = NULL;
}

929
vendor/mruby-compiler2/src/dump.c vendored Normal file
View File

@@ -0,0 +1,929 @@
#include <stdio.h>
#include "../include/mrc_ccontext.h"
#include "../include/mrc_irep.h"
#include "../include/mrc_dump.h"
#include "../include/mrc_parser_util.h"
#include "../include/mrc_debug.h"
#include "../include/mrc_irep_pool_type.h"
#if !defined(BYTE_ORDER) && defined(__BYTE_ORDER__)
# define BYTE_ORDER __BYTE_ORDER__
#endif
#if !defined(BIG_ENDIAN) && defined(__ORDER_BIG_ENDIAN__)
# define BIG_ENDIAN __ORDER_BIG_ENDIAN__
#endif
#if !defined(LITTLE_ENDIAN) && defined(__ORDER_LITTLE_ENDIAN__)
# define LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__
#endif
#ifdef BYTE_ORDER
# if BYTE_ORDER == BIG_ENDIAN
# define littleendian 0
# elif BYTE_ORDER == LITTLE_ENDIAN
# define littleendian 1
# endif
#endif
#ifndef littleendian
/* can't distinguish endian in compile time */
static inline int
check_little_endian(void)
{
unsigned int n = 1;
return (*(unsigned char*)&n == 1);
}
# define littleendian check_little_endian()
#endif
static size_t get_irep_record_size_1(mrc_ccontext *c, const mrc_irep *irep);
#if UINT32_MAX > SIZE_MAX
# error This code cannot be built on your environment.
#endif
static size_t
get_irep_header_size(mrc_ccontext *c)
{
size_t size = 0;
size += sizeof(uint32_t) * 1;
size += sizeof(uint16_t) * 3;
return size;
}
static ptrdiff_t
write_irep_header(mrc_ccontext *c, const mrc_irep *irep, uint8_t *buf)
{
uint8_t *cur = buf;
cur += mrc_uint32_to_bin((uint32_t)get_irep_record_size_1(c, irep), cur); /* record size */
cur += mrc_uint16_to_bin((uint16_t)irep->nlocals, cur); /* number of local variable */
cur += mrc_uint16_to_bin((uint16_t)irep->nregs, cur); /* number of register variable */
cur += mrc_uint16_to_bin((uint16_t)irep->rlen, cur); /* number of child irep */
return cur - buf;
}
static size_t
get_iseq_block_size(mrc_ccontext *c, const mrc_irep *irep)
{
size_t size = 0;
size += sizeof(uint16_t); /* clen */
size += sizeof(uint32_t); /* ilen */
size += irep->ilen * sizeof(mrc_code); /* iseq(n) */
size += irep->clen * sizeof(struct mrc_irep_catch_handler);
return size;
}
static ptrdiff_t
write_iseq_block(mrc_ccontext *c, const mrc_irep *irep, uint8_t *buf, uint8_t flags)
{
uint8_t *cur = buf;
size_t seqlen = irep->ilen * sizeof(mrc_code) +
irep->clen * sizeof(struct mrc_irep_catch_handler);
cur += mrc_uint16_to_bin(irep->clen, cur); /* number of catch handlers */
cur += mrc_uint32_to_bin(irep->ilen, cur); /* number of opcode */
memcpy(cur, irep->iseq, seqlen);
cur += seqlen;
return cur - buf;
}
#ifndef MRC_NO_FLOAT
static void
dump_float(mrc_ccontext *c, uint8_t *buf, mrc_float f)
{
/* dump IEEE754 binary in little endian */
union {
double f;
char s[sizeof(double)];
} u = {.f = (double)f};
if (littleendian) {
memcpy(buf, u.s, sizeof(double));
}
else {
for (size_t i=0; i<sizeof(double); i++) {
buf[i] = u.s[sizeof(double)-i-1];
}
}
}
#endif
static size_t
get_pool_block_size(mrc_ccontext *c, const mrc_irep *irep)
{
int pool_no;
size_t size = 0;
size += sizeof(uint16_t); /* plen */
size += irep->plen * sizeof(uint8_t); /* len(n) */
for (pool_no = 0; pool_no < irep->plen; pool_no++) {
int ai = mrc_gc_arena_save(c);
switch (irep->pool[pool_no].tt) {
case IREP_TT_INT64:
#if defined(MRC_64BIT) || defined(MRC_INT64)
{
int64_t i = irep->pool[pool_no].u.i64;
if (i < INT32_MIN || INT32_MAX < i)
size += 8;
else
size += 4;
}
break;
#else
/* fall through */
#endif
case IREP_TT_INT32:
size += 4; /* 32bits = 4bytes */
break;
case IREP_TT_BIGINT:
{
mrc_int len = irep->pool[pool_no].u.str[0];
mrc_assert_int_fit(mrc_int, len, size_t, SIZE_MAX);
size += (size_t)len+2;
}
break;
case IREP_TT_FLOAT:
#ifndef MRC_NO_FLOAT
{
size += sizeof(double);
}
#endif
break;
default: /* packed IREP_TT_STRING */
{
mrc_int len = irep->pool[pool_no].tt >> 2; /* unpack length */
mrc_assert_int_fit(mrc_int, len, size_t, SIZE_MAX);
size += sizeof(uint16_t);
size += (size_t)len+1;
}
break;
}
mrc_gc_arena_restore(c, ai);
}
return size;
}
static ptrdiff_t
write_pool_block(mrc_ccontext *c, const mrc_irep *irep, uint8_t *buf)
{
int pool_no;
uint8_t *cur = buf;
mrc_int len;
const char *ptr;
cur += mrc_uint16_to_bin(irep->plen, cur); /* number of pool */
for (pool_no = 0; pool_no < irep->plen; pool_no++) {
int ai = mrc_gc_arena_save(c);
switch (irep->pool[pool_no].tt) {
case IREP_TT_INT64:
#if defined(MRC_64BIT) || defined(MRC_INT64)
{
int64_t i = irep->pool[pool_no].u.i64;
if (i < INT32_MIN || INT32_MAX < i) {
cur += mrc_uint8_to_bin(IREP_TT_INT64, cur); /* data type */
cur += mrc_uint32_to_bin((uint32_t)((i>>32) & 0xffffffff), cur); /* i64 hi */
cur += mrc_uint32_to_bin((uint32_t)((i ) & 0xffffffff), cur); /* i64 lo */
}
else {
cur += mrc_uint8_to_bin(IREP_TT_INT32, cur); /* data type */
cur += mrc_uint32_to_bin(irep->pool[pool_no].u.i32, cur); /* i32 */
}
}
break;
#endif
case IREP_TT_INT32:
cur += mrc_uint8_to_bin(IREP_TT_INT32, cur); /* data type */
cur += mrc_uint32_to_bin(irep->pool[pool_no].u.i32, cur); /* i32 */
break;
case IREP_TT_BIGINT:
cur += mrc_uint8_to_bin(IREP_TT_BIGINT, cur); /* data type */
len = irep->pool[pool_no].u.str[0];
memcpy(cur, irep->pool[pool_no].u.str, (size_t)len+2);
cur += len+2;
break;
case IREP_TT_FLOAT:
cur += mrc_uint8_to_bin(IREP_TT_FLOAT, cur); /* data type */
#ifndef MRC_NO_FLOAT
{
dump_float(c, cur,irep->pool[pool_no].u.f);
cur += sizeof(double);
}
#else
cur += mrc_uint16_to_bin(0, cur); /* zero length */
#endif
break;
default: /* string */
cur += mrc_uint8_to_bin(IREP_TT_STR, cur); /* data type */
ptr = irep->pool[pool_no].u.str;
len = irep->pool[pool_no].tt>>2;
mrc_assert_int_fit(mrc_int, len, uint16_t, UINT16_MAX);
cur += mrc_uint16_to_bin((uint16_t)len, cur); /* data length */
memcpy(cur, ptr, (size_t)len);
cur += len;
*cur++ = '\0';
break;
}
mrc_gc_arena_restore(c, ai);
}
return cur - buf;
}
static size_t
get_syms_block_size(mrc_ccontext *c, const mrc_irep *irep)
{
size_t size = 0;
int sym_no;
mrc_int len;
size += sizeof(uint16_t); /* slen */
for (sym_no = 0; sym_no < irep->slen; sym_no++) {
size += sizeof(uint16_t); /* snl(n) */
if (irep->syms[sym_no] != 0) {
mrc_sym_name_len(c, irep->syms[sym_no], &len);
size += len + 1; /* sn(n) + null char */
}
}
return size;
}
static ptrdiff_t
write_syms_block(mrc_ccontext *c, const mrc_irep *irep, uint8_t *buf)
{
int sym_no;
uint8_t *cur = buf;
const char *name;
cur += mrc_uint16_to_bin(irep->slen, cur); /* number of symbol */
for (sym_no = 0; sym_no < irep->slen; sym_no++) {
if (irep->syms[sym_no] != 0) {
mrc_int len;
name = mrc_sym_name_len(c, irep->syms[sym_no], &len);
mrc_assert_int_fit(mrc_int, len, uint16_t, UINT16_MAX);
cur += mrc_uint16_to_bin((uint16_t)len, cur); /* length of symbol name */
memcpy(cur, name, len); /* symbol name */
cur += (uint16_t)len;
*cur++ = '\0';
}
else {
cur += mrc_uint16_to_bin(MRC_DUMP_NULL_SYM_LEN, cur); /* length of symbol name */
}
}
return cur - buf;
}
static size_t
get_irep_record_size_1(mrc_ccontext *c, const mrc_irep *irep)
{
size_t size = 0;
size += get_irep_header_size(c);
size += get_iseq_block_size(c, irep);
size += get_pool_block_size(c, irep);
size += get_syms_block_size(c, irep);
return size;
}
static size_t
get_irep_record_size(mrc_ccontext *c, const mrc_irep *irep)
{
size_t size = 0;
size = get_irep_record_size_1(c, irep);
for (int irep_no = 0; irep_no < irep->rlen; irep_no++) {
size += get_irep_record_size(c, irep->reps[irep_no]);
}
return size;
}
static int
write_irep_record(mrc_ccontext *c, const mrc_irep *irep, uint8_t *bin, size_t *irep_record_size, uint8_t flags)
{
uint8_t *src = bin;
if (irep == NULL) {
return MRC_DUMP_INVALID_IREP;
}
bin += write_irep_header(c, irep, bin);
bin += write_iseq_block(c, irep, bin, flags);
bin += write_pool_block(c, irep, bin);
bin += write_syms_block(c, irep, bin);
for (int i = 0; i < irep->rlen; i++) {
int result;
size_t rsize;
result = write_irep_record(c, irep->reps[i], bin, &rsize, flags);
if (result != MRC_DUMP_OK) {
return result;
}
bin += rsize;
}
*irep_record_size = bin - src;
return MRC_DUMP_OK;
}
static uint32_t
write_footer(mrc_ccontext *c, uint8_t *bin)
{
struct rite_binary_footer footer;
memcpy(footer.section_ident, RITE_BINARY_EOF, sizeof(footer.section_ident));
mrc_uint32_to_bin(sizeof(struct rite_binary_footer), footer.section_size);
memcpy(bin, &footer, sizeof(struct rite_binary_footer));
return sizeof(struct rite_binary_footer);
}
static int
write_section_irep_header(mrc_ccontext *c, size_t section_size, uint8_t *bin)
{
struct rite_section_irep_header *header = (struct rite_section_irep_header*)bin;
memcpy(header->section_ident, RITE_SECTION_IREP_IDENT, sizeof(header->section_ident));
mrc_assert_int_fit(size_t, section_size, uint32_t, UINT32_MAX);
mrc_uint32_to_bin((uint32_t)section_size, header->section_size);
memcpy(header->rite_version, RITE_VM_VER, sizeof(header->rite_version));
return MRC_DUMP_OK;
}
static int
write_section_irep(mrc_ccontext *c, const mrc_irep *irep, uint8_t *bin, size_t *len_p, uint8_t flags)
{
int result;
size_t rsize = 0;
uint8_t *cur = bin;
if (c == NULL || bin == NULL) {
return MRC_DUMP_INVALID_ARGUMENT;
}
cur += sizeof(struct rite_section_irep_header);
result = write_irep_record(c, irep, cur, &rsize, flags);
if (result != MRC_DUMP_OK) {
return result;
}
mrc_assert(rsize == get_irep_record_size(c, irep));
*len_p = cur - bin + rsize;
write_section_irep_header(c, *len_p, bin);
return MRC_DUMP_OK;
}
static size_t
get_debug_record_size(mrc_ccontext *c, const mrc_irep *irep)
{
size_t ret = 0;
uint16_t f_idx;
ret += sizeof(uint32_t); /* record size */
ret += sizeof(uint16_t); /* file count */
for (f_idx = 0; f_idx < irep->debug_info->flen; ++f_idx) {
mrc_irep_debug_info_file const* file = irep->debug_info->files[f_idx];
ret += sizeof(uint32_t); /* position */
ret += sizeof(uint16_t); /* filename index */
/* lines */
ret += sizeof(uint32_t); /* entry count */
ret += sizeof(uint8_t); /* line type */
switch (file->line_type) {
case mrc_debug_line_ary:
ret += sizeof(uint16_t) * (size_t)(file->line_entry_count);
break;
case mrc_debug_line_flat_map:
ret += (sizeof(uint32_t) + sizeof(uint16_t)) * (size_t)(file->line_entry_count);
break;
case mrc_debug_line_packed_map:
ret += (size_t)(file->line_entry_count);
break;
default: mrc_assert(0); break;
}
}
for (int i=0; i<irep->rlen; i++) {
ret += get_debug_record_size(c, irep->reps[i]);
}
return ret;
}
static int
find_filename_index(const mrc_sym *ary, int ary_len, mrc_sym s)
{
for (int i = 0; i < ary_len; i++) {
if (ary[i] == s) { return i; }
}
return -1;
}
static size_t
get_filename_table_size(mrc_ccontext *c, const mrc_irep *irep, mrc_sym **fp, uint16_t *lp)
{
mrc_sym *filenames = *fp;
size_t size = 0;
const mrc_irep_debug_info *di = irep->debug_info;
mrc_assert(lp);
for (int i = 0; i < di->flen; i++) {
mrc_irep_debug_info_file *file;
mrc_int filename_len;
file = di->files[i];
if (find_filename_index(filenames, *lp, file->filename_sym) == -1) {
/* register filename */
*lp += 1;
*fp = filenames = (mrc_sym*)mrc_realloc(c, filenames, sizeof(mrc_sym) * (*lp));
filenames[*lp - 1] = file->filename_sym;
/* filename */
mrc_sym_name_len(c, file->filename_sym, &filename_len);
size += sizeof(uint16_t) + (size_t)filename_len;
}
}
for (int i=0; i<irep->rlen; i++) {
size += get_filename_table_size(c, irep->reps[i], fp, lp);
}
return size;
}
static size_t
write_debug_record_1(mrc_ccontext *c, const mrc_irep *irep, uint8_t *bin, mrc_sym const* filenames, uint16_t filenames_len)
{
uint8_t *cur;
ptrdiff_t ret;
cur = bin + sizeof(uint32_t); /* skip record size */
cur += mrc_uint16_to_bin(irep->debug_info->flen, cur); /* file count */
for (int f_idx = 0; f_idx < irep->debug_info->flen; ++f_idx) {
int filename_idx;
const mrc_irep_debug_info_file *file = irep->debug_info->files[f_idx];
/* position */
cur += mrc_uint32_to_bin(file->start_pos, cur);
/* filename index */
filename_idx = find_filename_index(filenames, filenames_len,
file->filename_sym);
mrc_assert_int_fit(int, filename_idx, uint16_t, UINT16_MAX);
cur += mrc_uint16_to_bin((uint16_t)filename_idx, cur);
/* lines */
cur += mrc_uint32_to_bin(file->line_entry_count, cur);
cur += mrc_uint8_to_bin(file->line_type, cur);
switch (file->line_type) {
case mrc_debug_line_ary: {
uint32_t l;
for (l = 0; l < file->line_entry_count; ++l) {
cur += mrc_uint16_to_bin(file->lines.ary[l], cur);
}
} break;
case mrc_debug_line_flat_map: {
uint32_t line;
for (line = 0; line < file->line_entry_count; ++line) {
cur += mrc_uint32_to_bin(file->lines.flat_map[line].start_pos, cur);
cur += mrc_uint16_to_bin(file->lines.flat_map[line].line, cur);
}
} break;
case mrc_debug_line_packed_map: {
memcpy(cur, file->lines.packed_map, file->line_entry_count);
cur += file->line_entry_count;
} break;
default: mrc_assert(0); break;
}
}
ret = cur - bin;
mrc_assert_int_fit(ptrdiff_t, ret, uint32_t, UINT32_MAX);
mrc_uint32_to_bin((uint32_t)ret, bin);
mrc_assert_int_fit(ptrdiff_t, ret, size_t, SIZE_MAX);
return (size_t)ret;
}
static size_t
write_debug_record(mrc_ccontext *c, const mrc_irep *irep, uint8_t *bin, mrc_sym const* filenames, uint16_t filenames_len)
{
size_t size = write_debug_record_1(c, irep, bin, filenames, filenames_len);
bin += size;
for (int irep_no = 0; irep_no < irep->rlen; irep_no++) {
size_t len = write_debug_record(c, irep->reps[irep_no], bin, filenames, filenames_len);
bin += len;
size += len;
}
mrc_assert(size == get_debug_record_size(c, irep));
return size;
}
static int
write_section_debug(mrc_ccontext *c, const mrc_irep *irep, uint8_t *cur, mrc_sym const *filenames, uint16_t filenames_len)
{
size_t section_size = 0;
const uint8_t *bin = cur;
struct rite_section_debug_header *header;
size_t dlen;
if (c == NULL || cur == NULL) {
return MRC_DUMP_INVALID_ARGUMENT;
}
header = (struct rite_section_debug_header*)bin;
cur += sizeof(struct rite_section_debug_header);
section_size += sizeof(struct rite_section_debug_header);
/* filename table */
cur += mrc_uint16_to_bin(filenames_len, cur);
section_size += sizeof(uint16_t);
for (int i = 0; i < filenames_len; i++) {
char const *sym;
mrc_int sym_len;
sym = mrc_sym_name_len(c, filenames[i], &sym_len);
mrc_assert(sym);
cur += mrc_uint16_to_bin((uint16_t)sym_len, cur);
memcpy(cur, sym, sym_len);
cur += sym_len;
section_size += sizeof(uint16_t) + sym_len;
}
/* debug records */
dlen = write_debug_record(c, irep, cur, filenames, filenames_len);
section_size += dlen;
memcpy(header->section_ident, RITE_SECTION_DEBUG_IDENT, sizeof(header->section_ident));
mrc_assert(section_size <= INT32_MAX);
mrc_uint32_to_bin((uint32_t)section_size, header->section_size);
return MRC_DUMP_OK;
}
static void
create_lv_sym_table(mrc_ccontext *c, const mrc_irep *irep, mrc_sym **syms, uint32_t *syms_len)
{
pm_constant_id_t null_mark = pm_constant_pool_find(&c->p->constant_pool, NULL, 0);
if (*syms == NULL) {
*syms = (mrc_sym*)mrc_malloc(c, sizeof(mrc_sym) * 1);
}
for (int i = 0; i + 1 < irep->nlocals; i++) {
mrc_sym const name = irep->lv[i];
if (name == null_mark) continue;
if (find_filename_index(*syms, *syms_len, name) != -1) continue;
++(*syms_len);
*syms = (mrc_sym*)mrc_realloc(c, *syms, sizeof(mrc_sym) * (*syms_len));
(*syms)[*syms_len - 1] = name;
}
for (int i = 0; i < irep->rlen; i++) {
create_lv_sym_table(c, irep->reps[i], syms, syms_len);
}
}
static int
write_lv_sym_table(mrc_ccontext *c, uint8_t **start, mrc_sym const *syms, uint32_t syms_len)
{
uint8_t *cur = *start;
const char *str;
mrc_int str_len;
cur += mrc_uint32_to_bin(syms_len, cur);
for (uint32_t i = 0; i < syms_len; i++) {
str = mrc_sym_name_len(c, syms[i], &str_len);
cur += mrc_uint16_to_bin((uint16_t)str_len, cur);
memcpy(cur, str, str_len);
cur += str_len;
}
*start = cur;
return MRC_DUMP_OK;
}
static int
write_lv_record(mrc_ccontext *c, const mrc_irep *irep, uint8_t **start, mrc_sym const *syms, uint32_t syms_len)
{
uint8_t *cur = *start;
pm_constant_id_t null_mark = pm_constant_pool_find(&c->p->constant_pool, NULL, 0);
for (int i = 0; i + 1 < irep->nlocals; i++) {
if (irep->lv[i] == null_mark) {
cur += mrc_uint16_to_bin(RITE_LV_NULL_MARK, cur);
}
else {
int const sym_idx = find_filename_index(syms, syms_len, irep->lv[i]);
mrc_assert(sym_idx != -1); /* local variable name must be in syms */
cur += mrc_uint16_to_bin(sym_idx, cur);
}
}
for (int i = 0; i < irep->rlen; i++) {
write_lv_record(c, irep->reps[i], &cur, syms, syms_len);
}
*start = cur;
return MRC_DUMP_OK;
}
static size_t
get_lv_record_size(mrc_ccontext *c, const mrc_irep *irep)
{
size_t ret = sizeof(uint16_t) * (irep->nlocals - 1);
for (int i = 0; i < irep->rlen; i++) {
ret += get_lv_record_size(c, irep->reps[i]);
}
return ret;
}
static size_t
get_lv_section_size(mrc_ccontext *c, const mrc_irep *irep, mrc_sym const *syms, uint32_t syms_len)
{
size_t ret = sizeof(uint32_t); /* syms_len */
ret += sizeof(uint16_t) * syms_len; /* symbol name lengths */
for (uint32_t i = 0; i < syms_len; i++) {
mrc_int str_len;
mrc_sym_name_len(c, syms[i], &str_len);
ret += str_len;
}
ret += get_lv_record_size(c, irep);
return ret;
}
static int
write_section_lv(mrc_ccontext *c, const mrc_irep *irep, uint8_t *start, mrc_sym const *syms, uint32_t const syms_len)
{
uint8_t *cur = start;
struct rite_section_lv_header *header;
ptrdiff_t diff;
int result = MRC_DUMP_OK;
if (c == NULL || cur == NULL) {
return MRC_DUMP_INVALID_ARGUMENT;
}
header = (struct rite_section_lv_header*)cur;
cur += sizeof(struct rite_section_lv_header);
result = write_lv_sym_table(c, &cur, syms, syms_len);
if (result != MRC_DUMP_OK) {
goto lv_section_exit;
}
result = write_lv_record(c, irep, &cur, syms, syms_len);
if (result != MRC_DUMP_OK) {
goto lv_section_exit;
}
memcpy(header->section_ident, RITE_SECTION_LV_IDENT, sizeof(header->section_ident));
diff = cur - start;
mrc_assert_int_fit(ptrdiff_t, diff, size_t, SIZE_MAX);
mrc_uint32_to_bin((uint32_t)diff, header->section_size);
lv_section_exit:
return result;
}
static int
write_rite_binary_header(mrc_ccontext *c, size_t binary_size, uint8_t *bin, uint8_t flags)
{
struct rite_binary_header *header = (struct rite_binary_header*)bin;
memcpy(header->binary_ident, RITE_BINARY_IDENT, sizeof(header->binary_ident));
memcpy(header->major_version, RITE_BINARY_MAJOR_VER, sizeof(header->major_version));
memcpy(header->minor_version, RITE_BINARY_MINOR_VER, sizeof(header->minor_version));
memcpy(header->compiler_name, RITE_COMPILER_NAME, sizeof(header->compiler_name));
memcpy(header->compiler_version, RITE_COMPILER_VERSION, sizeof(header->compiler_version));
mrc_assert(binary_size <= UINT32_MAX);
mrc_uint32_to_bin((uint32_t)binary_size, header->binary_size);
return MRC_DUMP_OK;
}
static mrc_bool
debug_info_defined_p(const mrc_irep *irep)
{
if (!irep->debug_info) return FALSE;
for (int i=0; i<irep->rlen; i++) {
if (!debug_info_defined_p(irep->reps[i])) return FALSE;
}
return TRUE;
}
static mrc_bool
lv_defined_p(const mrc_irep *irep)
{
if (irep->lv && 0 < ((pm_constant_id_list_t *)irep->lv)->size) { return TRUE; }
for (int i = 0; i < irep->rlen; i++) {
if (lv_defined_p(irep->reps[i])) { return TRUE; }
}
return FALSE;
}
int
mrc_dump_irep(mrc_ccontext *c, const mrc_irep *irep, uint8_t flags, uint8_t **bin, size_t *bin_size)
{
int result = MRC_DUMP_GENERAL_FAILURE;
size_t malloc_size;
size_t section_irep_size;
size_t section_lineno_size = 0, section_lv_size = 0;
uint8_t *cur = NULL;
mrc_bool const debug_info_defined = debug_info_defined_p(irep), lv_defined = lv_defined_p(irep);
mrc_sym *lv_syms = NULL; uint32_t lv_syms_len = 0;
mrc_sym *filenames = NULL; uint16_t filenames_len = 0;
if (c == NULL) {
*bin = NULL;
return MRC_DUMP_GENERAL_FAILURE;
}
section_irep_size = sizeof(struct rite_section_irep_header);
section_irep_size += get_irep_record_size(c, irep);
/* DEBUG section size */
if (flags & MRC_DUMP_DEBUG_INFO) {
if (debug_info_defined) {
section_lineno_size += sizeof(struct rite_section_debug_header);
/* filename table */
filenames = (mrc_sym*)mrc_malloc(c, sizeof(mrc_sym) + 1);
/* filename table size */
section_lineno_size += sizeof(uint16_t);
section_lineno_size += get_filename_table_size(c, irep, &filenames, &filenames_len);
section_lineno_size += get_debug_record_size(c, irep);
}
}
if (lv_defined) {
section_lv_size += sizeof(struct rite_section_lv_header);
create_lv_sym_table(c, irep, &lv_syms, &lv_syms_len);
section_lv_size += get_lv_section_size(c, irep, lv_syms, lv_syms_len);
}
malloc_size = sizeof(struct rite_binary_header) +
section_irep_size + section_lineno_size + section_lv_size +
sizeof(struct rite_binary_footer);
cur = *bin = (uint8_t*)mrc_malloc(c, malloc_size);
cur += sizeof(struct rite_binary_header);
result = write_section_irep(c, irep, cur, &section_irep_size, flags);
if (result != MRC_DUMP_OK) {
goto error_exit;
}
cur += section_irep_size;
*bin_size = sizeof(struct rite_binary_header) +
section_irep_size + section_lineno_size + section_lv_size +
sizeof(struct rite_binary_footer);
/* write DEBUG section */
if (flags & MRC_DUMP_DEBUG_INFO) {
if (debug_info_defined) {
result = write_section_debug(c, irep, cur, filenames, filenames_len);
if (result != MRC_DUMP_OK) {
goto error_exit;
}
}
cur += section_lineno_size;
}
if (lv_defined) {
result = write_section_lv(c, irep, cur, lv_syms, lv_syms_len);
if (result != MRC_DUMP_OK) {
goto error_exit;
}
cur += section_lv_size;
}
write_footer(c, cur);
write_rite_binary_header(c, *bin_size, *bin, flags);
error_exit:
if (result != MRC_DUMP_OK) {
mrc_free(c, *bin);
*bin = NULL;
}
mrc_free(c, lv_syms);
mrc_free(c, filenames);
return result;
}
#ifndef MRC_NO_STDIO
int
mrc_dump_irep_binary(mrc_ccontext *c, const mrc_irep *irep, uint8_t flags, FILE* fp)
{
uint8_t *bin = NULL;
size_t bin_size = 0;
int result;
if (fp == NULL) {
return MRC_DUMP_INVALID_ARGUMENT;
}
result = mrc_dump_irep(c, irep, flags, &bin, &bin_size);
if (result == MRC_DUMP_OK) {
if (fwrite(bin, sizeof(bin[0]), bin_size, fp) != bin_size) {
result = MRC_DUMP_WRITE_FAULT;
}
}
mrc_free(c, bin);
return result;
}
int
mrc_dump_irep_cfunc(mrc_ccontext *c, const mrc_irep *irep, uint8_t flags, FILE *fp, const char *initname)
{
uint8_t *bin = NULL;
if (fp == NULL || initname == NULL || initname[0] == '\0') {
return MRC_DUMP_INVALID_ARGUMENT;
}
size_t bin_size, bin_idx = 0;
int result = mrc_dump_irep(c, irep, flags, &bin, &bin_size);
if (result == MRC_DUMP_OK) {
if (fprintf(fp, "#include <stdint.h>\n") < 0) { /* for uint8_t under at least Darwin */
mrc_free(c, bin);
return MRC_DUMP_WRITE_FAULT;
}
if (fprintf(fp,
"%s\n"
"const uint8_t %s[] = {",
(flags & MRC_DUMP_STATIC) ? "static"
: "#ifdef __cplusplus\n"
"extern\n"
"#endif",
initname) < 0) {
mrc_free(c, bin);
return MRC_DUMP_WRITE_FAULT;
}
while (bin_idx < bin_size) {
if (bin_idx % 16 == 0) {
if (fputs("\n", fp) == EOF) {
mrc_free(c, bin);
return MRC_DUMP_WRITE_FAULT;
}
}
if (fprintf(fp, "0x%02x,", bin[bin_idx++]) < 0) {
mrc_free(c, bin);
return MRC_DUMP_WRITE_FAULT;
}
}
if (fputs("\n};\n", fp) == EOF) {
mrc_free(c, bin);
return MRC_DUMP_WRITE_FAULT;
}
}
mrc_free(c, bin);
return result;
}
#endif // MRC_NO_STDIO

53
vendor/mruby-compiler2/src/irep.c vendored Normal file
View File

@@ -0,0 +1,53 @@
#include "../include/mrc_irep.h"
#include "../include/mrc_debug.h"
#include "../include/mrc_irep_pool_type.h"
void
mrc_irep_remove_lv(mrc_ccontext *c, mrc_irep *irep)
{
int i;
if (irep->flags & MRC_IREP_NO_FREE) return;
if (irep->lv) {
mrc_free(c, (void*)irep->lv);
irep->lv = NULL;
}
if (!irep->reps) return;
for (i = 0; i < irep->rlen; i++) {
mrc_irep_remove_lv(c, (mrc_irep*)irep->reps[i]);
}
}
void
mrc_irep_free(mrc_ccontext *c, mrc_irep *irep)
{
int i;
if (irep->flags & MRC_IREP_NO_FREE) return;
if (!(irep->flags & MRC_ISEQ_NO_FREE))
mrc_free(c, (void*)irep->iseq);
if (irep->pool) {
for (i=0; i<irep->plen; i++) {
if ((irep->pool[i].tt & 3) == IREP_TT_STR ||
irep->pool[i].tt == IREP_TT_BIGINT) {
mrc_free(c, (void*)irep->pool[i].u.str);
}
}
mrc_free(c, (void*)irep->pool);
}
mrc_free(c, (void*)irep->syms);
if (irep->reps) {
for (i=0; i<irep->rlen; i++) {
// if (irep->reps[i])
// mrb_irep_decref((mrb_irep*)irep->reps[i]);
mrc_irep_free(c, (mrc_irep*)irep->reps[i]);
}
mrc_free(c, (void*)irep->reps);
}
mrc_free(c, (void*)irep->lv);
mrc_debug_info_free(c, irep->debug_info);
#ifdef MRC_DEBUG
memset(irep, -1, sizeof(*irep));
#endif
mrc_free(c, irep);
}

40
vendor/mruby-compiler2/src/mrc_presym.c vendored Normal file
View File

@@ -0,0 +1,40 @@
#include <string.h>
#include "mrc_ccontext.h"
typedef struct {
int index;
const char *lit;
} mrc_sym_entry;
static mrc_sym_entry symTable[] = {
#define MRC_OPSYM_2(name, lit, num) {num, #lit},
#define MRC_SYM_1(lit, num) {num, #lit},
#define MRC_SYM_2(name, lit, num) {num, #lit},
#include "mrc_presym.inc"
#undef MRC_OPSYM_2
#undef MRC_SYM_1
#undef MRC_SYM_2
{0, NULL} // sentinel
};
static uint32_t offset = 0;
mrc_sym mrc_sym_offset(mrc_sym sym)
{
return sym + offset;
}
void
mrc_init_presym(pm_constant_pool_t *pool)
{
offset = pool->size;
for (int i = 0; ; i++) {
if (symTable[i].lit == NULL) { break; }
#ifdef MRC_DEBUG
pm_constant_id_t id = pm_constant_pool_insert_constant(pool, (const uint8_t *)symTable[i].lit, strlen(symTable[i].lit));
mrc_assert(id == symTable[i].index + offset);
#else
pm_constant_pool_insert_constant(pool, (const uint8_t *)symTable[i].lit, strlen(symTable[i].lit));
#endif
}
}

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