use alloc::string::String;
use alloc::vec::Vec;
use bytecode_lang::Hook;
use hir_lang::Span;
use crate::intrinsics::Intrinsics;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum ArraySemantics {
#[default]
Php,
Reference,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum KeyCoercion {
#[default]
Php,
None,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum BodyMode {
#[default]
Start,
Entry,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Options {
arrays: ArraySemantics,
keys: KeyCoercion,
body: BodyMode,
file: String,
host_module: String,
hooks: Vec<(Hook, String, String)>,
intrinsics: Intrinsics,
max_instructions: u64,
ignore_extra: bool,
string_offsets: Option<String>,
constructor: Option<String>,
destructor: Option<String>,
clone_method: Option<String>,
reference_counting: bool,
}
impl Default for Options {
fn default() -> Self {
Self::new()
}
}
impl Options {
pub const DEFAULT_MAX_INSTRUCTIONS: u64 = 64 << 20;
#[must_use]
pub fn new() -> Self {
Self {
arrays: ArraySemantics::Php,
keys: KeyCoercion::Php,
body: BodyMode::Start,
file: String::from("<hir>"),
host_module: String::from("host"),
hooks: Vec::new(),
intrinsics: Intrinsics::core(),
max_instructions: Self::DEFAULT_MAX_INSTRUCTIONS,
ignore_extra: false,
string_offsets: None,
constructor: Some(String::from("__construct")),
destructor: Some(String::from("__destruct")),
clone_method: Some(String::from("__clone")),
reference_counting: false,
}
}
#[must_use]
pub fn with_arrays(mut self, arrays: ArraySemantics) -> Self {
self.arrays = arrays;
self
}
#[must_use]
pub fn with_keys(mut self, keys: KeyCoercion) -> Self {
self.keys = keys;
self
}
#[must_use]
pub fn with_body(mut self, body: BodyMode) -> Self {
self.body = body;
self
}
#[must_use]
pub fn with_file(mut self, file: &str) -> Self {
self.file = String::from(file);
self
}
#[must_use]
pub fn with_host_module(mut self, module: &str) -> Self {
self.host_module = String::from(module);
self
}
#[must_use]
pub fn with_hook(mut self, hook: Hook, module: &str, name: &str) -> Self {
self.hooks.retain(|(h, _, _)| *h != hook);
self.hooks
.push((hook, String::from(module), String::from(name)));
self
}
#[must_use]
pub fn with_intrinsics(mut self, intrinsics: Intrinsics) -> Self {
self.intrinsics = intrinsics;
self
}
#[must_use]
pub fn with_max_instructions(mut self, limit: u64) -> Self {
self.max_instructions = limit;
self
}
#[must_use]
pub fn with_ignore_extra(mut self, ignore: bool) -> Self {
self.ignore_extra = ignore;
self
}
#[must_use]
pub fn with_string_offsets(mut self, name: &str) -> Self {
self.string_offsets = Some(String::from(name));
self
}
#[must_use]
pub fn with_constructor(mut self, name: Option<&str>) -> Self {
self.constructor = name.map(String::from);
self
}
#[must_use]
pub fn with_destructor(mut self, name: Option<&str>) -> Self {
self.destructor = name.map(String::from);
self
}
#[must_use]
pub fn with_reference_counting(mut self, rc: bool) -> Self {
self.reference_counting = rc;
self
}
#[must_use]
pub fn with_clone_method(mut self, name: Option<&str>) -> Self {
self.clone_method = name.map(String::from);
self
}
#[must_use]
pub fn arrays(&self) -> ArraySemantics {
self.arrays
}
#[must_use]
pub fn keys(&self) -> KeyCoercion {
self.keys
}
#[must_use]
pub fn body(&self) -> BodyMode {
self.body
}
#[must_use]
pub fn file(&self) -> &str {
&self.file
}
#[must_use]
pub fn host_module(&self) -> &str {
&self.host_module
}
#[must_use]
pub fn hooks(&self) -> &[(Hook, String, String)] {
&self.hooks
}
#[must_use]
pub fn intrinsics(&self) -> &Intrinsics {
&self.intrinsics
}
#[must_use]
pub fn max_instructions(&self) -> u64 {
self.max_instructions
}
#[must_use]
pub fn ignore_extra(&self) -> bool {
self.ignore_extra
}
#[must_use]
pub fn string_offsets(&self) -> Option<&str> {
self.string_offsets.as_deref()
}
#[must_use]
pub fn constructor(&self) -> Option<&str> {
self.constructor.as_deref()
}
#[must_use]
pub fn destructor(&self) -> Option<&str> {
self.destructor.as_deref()
}
#[must_use]
pub fn reference_counting(&self) -> bool {
self.reference_counting
}
#[must_use]
pub fn clone_method(&self) -> Option<&str> {
self.clone_method.as_deref()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Location<'a> {
pub file: &'a str,
pub line: u32,
pub column: u32,
}
pub trait Locate {
fn locate(&self, span: Span) -> Location<'_>;
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LineIndex {
file: String,
base: u32,
starts: Vec<u32>,
}
impl LineIndex {
#[must_use]
pub fn new(file: &str, base: u32, text: &str) -> Self {
let mut starts = Vec::with_capacity(text.len() / 32 + 1);
starts.push(0);
for (i, b) in text.bytes().enumerate() {
if b == b'\n' {
starts.push(u32::try_from(i + 1).unwrap_or(u32::MAX));
}
}
Self {
file: String::from(file),
base,
starts,
}
}
#[must_use]
pub fn lines(&self) -> usize {
self.starts.len()
}
}
impl Locate for LineIndex {
fn locate(&self, span: Span) -> Location<'_> {
let offset = span.start().to_u32().saturating_sub(self.base);
let line = self.starts.partition_point(|&s| s <= offset).max(1);
let start = self.starts.get(line - 1).copied().unwrap_or(0);
Location {
file: &self.file,
line: u32::try_from(line).unwrap_or(u32::MAX),
column: offset - start + 1,
}
}
}
#[cfg(test)]
mod tests {
use super::{LineIndex, Locate, Options};
use hir_lang::Span;
#[test]
fn line_index_counts_from_the_base() {
let l = LineIndex::new("f", 10, "ab\ncd\n\nx");
let at = |o: u32| {
let p = l.locate(Span::new(o, o));
(p.line, p.column)
};
assert_eq!(at(10), (1, 1));
assert_eq!(at(12), (1, 3));
assert_eq!(at(13), (2, 1));
assert_eq!(at(16), (3, 1));
assert_eq!(at(17), (4, 1));
assert_eq!(at(0), (1, 1));
assert_eq!(at(1_000), (4, 984));
}
#[test]
fn a_later_hook_binding_replaces_an_earlier_one() {
let o = Options::new()
.with_hook(bytecode_lang::Hook::Eq, "a", "b")
.with_hook(bytecode_lang::Hook::Eq, "c", "d");
assert_eq!(o.hooks().len(), 1);
assert_eq!(o.hooks()[0].1, "c");
}
}