Skip to content

Commit 42a9641

Browse files
authored
feat: implement type resolution from external string representations (#28)
1 parent 9f4fd91 commit 42a9641

3 files changed

Lines changed: 45 additions & 72 deletions

File tree

src/arena.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,19 @@ impl StringArena {
3939
self.cache.get(&self.hasher.hash_one(value)).copied()
4040
}
4141

42+
/// Like ['str_ref'] but case-insensitive
43+
pub fn str_ref_no_case(&self, value: &str) -> Option<StrRef> {
44+
self.cache
45+
.get(&self.hasher.hash_one(Ascii::new(value)))
46+
.copied()
47+
}
48+
4249
/// Interns a string using case-insensitive hashing.
4350
///
4451
/// Two strings that differ only in ASCII case will resolve to the same [`StrRef`].
4552
/// The original casing of the first insertion is preserved.
4653
pub fn alloc_no_case(&mut self, value: &str) -> StrRef {
47-
let hash = Ascii::new(value);
48-
match self.cache.entry(self.hasher.hash_one(hash)) {
54+
match self.cache.entry(self.hasher.hash_one(Ascii::new(value))) {
4955
Entry::Occupied(entry) => *entry.get(),
5056
Entry::Vacant(entry) => {
5157
let key = StrRef(self.slots.len());

src/lib.rs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@ mod typing;
1616
use crate::arena::Arena;
1717
use crate::lexer::tokenize;
1818
use crate::prelude::{
19-
Analysis, AnalysisOptions, FunArgs, Scope, Typed, display_type, name_to_type, parse,
19+
Analysis, AnalysisOptions, FunArgs, Scope, Typed, display_type, parse, resolve_type_from_str,
2020
};
2121
use crate::token::Token;
2222
pub use ast::*;
2323
use rustc_hash::FxHashMap;
2424
pub use typing::Type;
25-
use unicase::Ascii;
2625

2726
/// Convenience module that re-exports all public types and functions.
2827
///
@@ -151,6 +150,7 @@ pub type Result<A> = std::result::Result<A, error::Error>;
151150
/// It allows for the configuration of analysis options, such as declaring
152151
/// functions (both regular and aggregate), event types, and custom types,
153152
/// before building an `EventQL` parsing session.
153+
#[derive(Default)]
154154
pub struct SessionBuilder {
155155
arena: Arena,
156156
options: AnalysisOptions,
@@ -316,9 +316,8 @@ impl SessionBuilder {
316316
/// * `name` - The name of the custom type.
317317
pub fn declare_custom_type_when(mut self, test: bool, name: &str) -> Self {
318318
if test {
319-
self.options
320-
.custom_types
321-
.insert(Ascii::new(name.to_owned()));
319+
let name = self.arena.strings.alloc_no_case(name);
320+
self.options.custom_types.insert(name);
322321
}
323322

324323
self
@@ -332,11 +331,8 @@ impl SessionBuilder {
332331
/// # Arguments
333332
///
334333
/// * `name` - The name of the custom type.
335-
pub fn declare_custom_type(mut self, name: &str) -> Self {
336-
self.options
337-
.custom_types
338-
.insert(Ascii::new(name.to_owned()));
339-
self
334+
pub fn declare_custom_type(self, name: &str) -> Self {
335+
self.declare_custom_type_when(true, name)
340336
}
341337

342338
/// Includes the standard library of functions and event types in the session.
@@ -437,15 +433,6 @@ impl SessionBuilder {
437433
}
438434
}
439435

440-
impl Default for SessionBuilder {
441-
fn default() -> Self {
442-
Self {
443-
arena: Default::default(),
444-
options: AnalysisOptions::empty(),
445-
}
446-
}
447-
}
448-
449436
/// `Session` is the main entry point for parsing and analyzing EventQL queries.
450437
///
451438
/// It holds the necessary context, such as the expression arena and analysis options,
@@ -550,9 +537,10 @@ impl Session {
550537
/// - `"date"` → [`Type::Date`]
551538
/// - `"time"` → [`Type::Time`]
552539
/// - `"datetime"` → [`Type::DateTime`]
553-
pub fn get_type_from_name(&self, name: &str) -> Option<Type> {
554-
let str_ref = self.arena.strings.str_ref(name)?;
555-
name_to_type(&self.arena, &self.options, str_ref)
540+
///
541+
/// note: Registered custom types are also recognized (case-insensitive).
542+
pub fn resolve_type(&self, name: &str) -> Option<Type> {
543+
resolve_type_from_str(&self.arena, &self.options, name)
556544
}
557545

558546
/// Provides human-readable string formatting for types.

src/typing/analysis.rs

Lines changed: 27 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use rustc_hash::FxHashMap;
22
use serde::Serialize;
3-
use std::{borrow::Cow, collections::HashSet, mem};
4-
use unicase::Ascii;
3+
use std::{collections::HashSet, mem};
54

65
use crate::arena::Arena;
76
use crate::typing::{Record, Type};
@@ -49,6 +48,7 @@ pub type AnalysisResult<A> = Result<A, AnalysisError>;
4948
/// This structure contains the type information needed to perform static analysis
5049
/// on EventQL queries, including the default scope with built-in functions and
5150
/// the type information for event records.
51+
#[derive(Default)]
5252
pub struct AnalysisOptions {
5353
/// The default scope containing built-in functions and their type signatures.
5454
pub default_scope: Scope,
@@ -59,40 +59,7 @@ pub struct AnalysisOptions {
5959
/// This set allows users to register custom type names that can be used
6060
/// in type conversion expressions (e.g., `field AS CustomType`). Custom
6161
/// type names are case-insensitive.
62-
pub custom_types: HashSet<Ascii<String>>,
63-
}
64-
65-
impl AnalysisOptions {
66-
/// Adds a custom type name to the analysis options.
67-
///
68-
/// Custom types allow you to use type conversion syntax with types that are
69-
/// not part of the standard EventQL type system. The type name is stored
70-
/// case-insensitively.
71-
///
72-
/// # Arguments
73-
///
74-
/// * `value` - The custom type name to register
75-
///
76-
/// # Returns
77-
///
78-
/// Returns `self` to allow for method chaining.
79-
pub fn add_custom_type<'a>(mut self, value: impl Into<Cow<'a, str>>) -> Self {
80-
match value.into() {
81-
Cow::Borrowed(t) => self.custom_types.insert(Ascii::new(t.to_owned())),
82-
Cow::Owned(t) => self.custom_types.insert(Ascii::new(t)),
83-
};
84-
85-
self
86-
}
87-
88-
/// Creates empty analysis options with no functions, no event type, and no custom types.
89-
pub fn empty() -> Self {
90-
Self {
91-
default_scope: Scope::default(),
92-
event_type_info: Type::default(),
93-
custom_types: HashSet::default(),
94-
}
95-
}
62+
pub custom_types: HashSet<StrRef>,
9663
}
9764

9865
/// Represents a variable scope during static analysis.
@@ -1125,7 +1092,7 @@ impl<'a> Analysis<'a> {
11251092
Operator::As => {
11261093
let rhs = self.arena.exprs.get(binary.rhs);
11271094
if let Value::Id(name) = rhs.value {
1128-
return if let Some(tpe) = name_to_type(self.arena, self.options, name) {
1095+
return if let Some(tpe) = resolve_type(self.arena, self.options, name) {
11291096
// NOTE - we could check if it's safe to convert the left branch to that type
11301097
Ok(tpe)
11311098
} else {
@@ -1466,7 +1433,7 @@ impl<'a> Analysis<'a> {
14661433
Operator::Add | Operator::Sub | Operator::Mul | Operator::Div => Type::Number,
14671434
Operator::As => {
14681435
if let Value::Id(n) = self.arena.exprs.get(binary.rhs).value
1469-
&& let Some(tpe) = name_to_type(self.arena, self.options, n)
1436+
&& let Some(tpe) = resolve_type(self.arena, self.options, n)
14701437
{
14711438
tpe
14721439
} else {
@@ -1644,34 +1611,46 @@ impl Arena {
16441611
/// - `"date"` → [`Type::Date`]
16451612
/// - `"time"` → [`Type::Time`]
16461613
/// - `"datetime"` → [`Type::DateTime`]
1647-
pub(crate) fn name_to_type(
1614+
///
1615+
/// note: Registered custom types are also recognized (case-insensitive).
1616+
pub(crate) fn resolve_type_from_str(
16481617
arena: &Arena,
16491618
opts: &AnalysisOptions,
1650-
name_ref: StrRef,
1619+
name: &str,
16511620
) -> Option<Type> {
1652-
let name = arena.strings.get(name_ref);
1653-
16541621
if name.eq_ignore_ascii_case("string") {
16551622
Some(Type::String)
1656-
} else if name.eq_ignore_ascii_case("int") || name.eq_ignore_ascii_case("float64") {
1623+
} else if name.eq_ignore_ascii_case("int")
1624+
|| name.eq_ignore_ascii_case("float64")
1625+
|| name.eq_ignore_ascii_case("number")
1626+
{
16571627
Some(Type::Number)
1658-
} else if name.eq_ignore_ascii_case("boolean") {
1628+
} else if name.eq_ignore_ascii_case("boolean") || name.eq_ignore_ascii_case("bool") {
16591629
Some(Type::Bool)
16601630
} else if name.eq_ignore_ascii_case("date") {
16611631
Some(Type::Date)
16621632
} else if name.eq_ignore_ascii_case("time") {
16631633
Some(Type::Time)
16641634
} else if name.eq_ignore_ascii_case("datetime") {
16651635
Some(Type::DateTime)
1666-
} else if opts.custom_types.contains(&Ascii::new(name.to_owned())) {
1667-
// ^ Sad we have to allocate here for no reason
1668-
1669-
Some(Type::Custom(name_ref))
1636+
} else if let Some(str_ref) = arena.strings.str_ref_no_case(name)
1637+
&& opts.custom_types.contains(&str_ref)
1638+
{
1639+
Some(Type::Custom(str_ref))
16701640
} else {
16711641
None
16721642
}
16731643
}
16741644

1645+
pub(crate) fn resolve_type(
1646+
arena: &Arena,
1647+
opts: &AnalysisOptions,
1648+
name_ref: StrRef,
1649+
) -> Option<Type> {
1650+
let name = arena.strings.get(name_ref);
1651+
resolve_type_from_str(arena, opts, name)
1652+
}
1653+
16751654
pub(crate) fn display_type(arena: &Arena, tpe: Type) -> String {
16761655
fn go(buffer: &mut String, arena: &Arena, tpe: Type) {
16771656
match tpe {

0 commit comments

Comments
 (0)