Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 126 additions & 3 deletions crates/sploosh-ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,26 @@ pub enum ItemKind {
#[derive(Debug, Clone, PartialEq)]
pub struct Attribute {
pub name: Ident,
pub args: Vec<AttrArg>,
/// Covers `@` through the closing `)` (or the name, when there are no args).
pub span: Span,
}

/// `attr_arg = IDENT [ ":" expr | "=" expr | "(" expr ")" ] | expr` (§16).
/// The `IDENT`-headed alternatives overlap with `expr`; the parser stores the
/// most specific form that matches.
#[derive(Debug, Clone, PartialEq)]
pub enum AttrArg {
/// Bare `IDENT` — `@derive(Debug)`, `@overflow(wrapping)`.
Ident(Ident),
/// `IDENT ":" expr` — `@mailbox(capacity: 2048)`.
Named { name: Ident, value: Expr },
/// `IDENT "=" expr`.
Assigned { name: Ident, value: Expr },
/// `IDENT "(" expr ")"`.
Call { name: Ident, arg: Expr },
/// Any other bare expression.
Expr(Expr),
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -136,7 +156,16 @@ pub enum VariantKind {
pub struct Actor {
pub name: Ident,
pub fields: Vec<Field>,
pub handlers: Vec<Function>,
pub handlers: Vec<Handler>,
}

/// A handler is a `fn_def` inside an `actor` body (§16), so it carries its
/// own attributes (`@mailbox(capacity: N)`, ...). Item-position `fn` attrs
/// stay hoisted on `Item.attrs` during bootstrap.
#[derive(Debug, Clone, PartialEq)]
pub struct Handler {
pub attrs: Vec<Attribute>,
pub function: Function,
}

#[derive(Debug, Clone, PartialEq)]
Expand Down Expand Up @@ -239,6 +268,100 @@ pub enum Stmt {
Continue,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UnaryOp {
/// `!`
Not,
/// `-`
Neg,
/// `*`
Deref,
/// `&`
Ref,
}

impl UnaryOp {
pub fn as_str(self) -> &'static str {
match self {
Self::Not => "!",
Self::Neg => "-",
Self::Deref => "*",
Self::Ref => "&",
}
}
}

impl std::fmt::Display for UnaryOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinaryOp {
/// `|>`
Pipe,
/// `+`
Add,
/// `-`
Sub,
/// `*`
Mul,
/// `/`
Div,
/// `%`
Rem,
/// `==`
Eq,
/// `!=`
Ne,
/// `<`
Lt,
/// `>`
Gt,
/// `<=`
Le,
/// `>=`
Ge,
/// `&&`
And,
/// `||`
Or,
/// `..`
Range,
/// `..=`
RangeInclusive,
}

impl BinaryOp {
pub fn as_str(self) -> &'static str {
match self {
Self::Pipe => "|>",
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
Self::Rem => "%",
Self::Eq => "==",
Self::Ne => "!=",
Self::Lt => "<",
Self::Gt => ">",
Self::Le => "<=",
Self::Ge => ">=",
Self::And => "&&",
Self::Or => "||",
Self::Range => "..",
Self::RangeInclusive => "..=",
}
}
}

impl std::fmt::Display for BinaryOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}

#[derive(Debug, Clone, PartialEq)]
pub struct Expr {
pub kind: ExprKind,
Expand All @@ -263,11 +386,11 @@ pub enum ExprKind {
index: Box<Expr>,
},
Unary {
op: String,
op: UnaryOp,
expr: Box<Expr>,
},
Binary {
op: String,
op: BinaryOp,
left: Box<Expr>,
right: Box<Expr>,
},
Expand Down
54 changes: 38 additions & 16 deletions crates/sploosh-lexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,21 @@

use sploosh_ast::Span;

#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Token {
pub kind: TokenKind,
pub lexeme: String,
pub span: Span,
}

#[derive(Debug, Clone, PartialEq, Eq)]
impl Token {
/// The token's source text, sliced from the file it was lexed from.
/// Tokens carry only spans; slicing on demand keeps lexing allocation-free.
pub fn text<'src>(&self, source: &'src str) -> &'src str {
&source[self.span.start..self.span.end]
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TokenKind {
Ident,
Keyword(Keyword),
Expand Down Expand Up @@ -148,6 +155,13 @@ pub fn is_contextual_keyword(text: &str) -> bool {
)
}

/// §16.1 numeric suffixes, longest-first so prefix scanning never matches a
/// shorter suffix inside a longer one. Shared by suffix scanning and
/// separator validation.
const NUMERIC_SUFFIXES: [&str; 13] = [
"i128", "u128", "u256", "i64", "u64", "f64", "i32", "u32", "f32", "i16", "u16", "i8", "u8",
];

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LexError {
pub message: String,
Expand Down Expand Up @@ -339,28 +353,20 @@ impl Lexer<'_> {
}
}

fn numeric_suffix(&mut self) -> Option<String> {
fn numeric_suffix(&mut self) -> Option<&'static str> {
let rest = &self.source[self.pos..];
let suffixes = [
"i128", "u128", "u256", "i64", "u64", "f64", "i32", "u32", "f32", "i16", "u16", "i8",
"u8",
];
for suffix in suffixes {
for suffix in NUMERIC_SUFFIXES {
if rest.starts_with(suffix) {
self.pos += suffix.len();
return Some(suffix.to_string());
return Some(suffix);
}
}
None
}

fn validate_numeric_body(&mut self, start: usize, base: u8) {
let text = &self.source[start..self.pos];
let suffixes = [
"i128", "u128", "u256", "i64", "u64", "f64", "i32", "u32", "f32", "i16", "u16", "i8",
"u8",
];
let body = suffixes
let body = NUMERIC_SUFFIXES
.iter()
.find_map(|suffix| text.strip_suffix(suffix))
.unwrap_or(text);
Expand Down Expand Up @@ -560,7 +566,6 @@ impl Lexer<'_> {
fn push(&mut self, kind: TokenKind, start: usize, end: usize) {
self.tokens.push(Token {
kind,
lexeme: self.source[start..end].to_string(),
span: Span::new(start, end),
});
}
Expand Down Expand Up @@ -628,6 +633,23 @@ mod tests {
assert_eq!(tokens[4].kind, TokenKind::IntLit);
}

#[test]
fn every_numeric_suffix_lexes() {
for suffix in NUMERIC_SUFFIXES {
let source = format!("1{suffix}");
let tokens = lex(&source).unwrap();
assert_eq!(tokens.len(), 1, "{source}");
let expected = if suffix.starts_with('f') {
TokenKind::FloatLit
} else {
TokenKind::IntLit
};
assert_eq!(tokens[0].kind, expected, "{source}");
assert_eq!(tokens[0].text(&source), source);
assert_eq!(tokens[0].span, Span::new(0, source.len()));
}
}

#[test]
fn rejects_bad_numeric_separators() {
let err = lex("1__2").unwrap_err();
Expand Down
Loading