Skip to content

Commit

Permalink
Add support for L?[x] list access syntax (#262)
Browse files Browse the repository at this point in the history
I renamed the current `IndexKind` enum to `PropertyAccessKind` purely 
based off of the fact that somewhere in the byond ref both procs and 
vars are referred to as a datum's "properties" and I had no better name 
ideas.

The checker treats conditional list accesses exactly the same as normal 
ones. I was going to add some linting stuff for them but their expected 
behaviour is completely up in the air at the moment.
  • Loading branch information
willox authored Apr 22, 2021
1 parent ba09a43 commit 9c75634
Show file tree
Hide file tree
Showing 7 changed files with 65 additions and 38 deletions.
10 changes: 5 additions & 5 deletions src/dreamchecker/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1898,10 +1898,10 @@ impl<'o, 's> AnalyzeProc<'o, 's> {

fn visit_follow(&mut self, location: Location, lhs: Analysis<'o>, rhs: &'o Follow, local_vars: &mut HashMap<String, LocalVar<'o>>) -> Analysis<'o> {
match rhs {
Follow::Field(IndexKind::Colon, _) => Analysis::empty(),
Follow::Field(IndexKind::SafeColon, _) => Analysis::empty(),
Follow::Call(IndexKind::Colon, _, args) |
Follow::Call(IndexKind::SafeColon, _, args) => {
Follow::Field(PropertyAccessKind::Colon, _) => Analysis::empty(),
Follow::Field(PropertyAccessKind::SafeColon, _) => Analysis::empty(),
Follow::Call(PropertyAccessKind::Colon, _, args) |
Follow::Call(PropertyAccessKind::SafeColon, _, args) => {
// No analysis yet, but be sure to visit the arguments
for arg in args {
let mut argument_value = arg;
Expand All @@ -1920,7 +1920,7 @@ impl<'o, 's> AnalyzeProc<'o, 's> {
Analysis::empty()
},

Follow::Index(expr) => {
Follow::Index(_, expr) => {
self.visit_expression(location, expr, None, local_vars);
// TODO: differentiate between L[1] and L[non_numeric_key]
match lhs.static_ty {
Expand Down
4 changes: 4 additions & 0 deletions src/dreamchecker/tests/static_type_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use dc::test_helpers::*;

pub const FIELD_ACCESS_ERRORS: &[(u32, u16, &str)] = &[
(3, 9, "field access requires static type: \"name\""),
(4, 10, "field access requires static type: \"name\""),
];

#[test]
Expand All @@ -13,12 +14,14 @@ fn field_access() {
/proc/test()
var/list/L = list()
L[1].name
L?[1].name
"##.trim();
check_errors_match(code, FIELD_ACCESS_ERRORS);
}

pub const PROC_CALL_ERRORS: &[(u32, u16, &str)] = &[
(3, 9, "proc call requires static type: \"foo\""),
(4, 10, "proc call requires static type: \"foo\""),
];

#[test]
Expand All @@ -27,6 +30,7 @@ fn proc_call() {
/proc/test()
var/list/L = list()
L[1].foo()
L?[1].foo()
/mob/proc/foo()
"##.trim();
check_errors_match(code, PROC_CALL_ERRORS);
Expand Down
2 changes: 1 addition & 1 deletion src/dreamchecker/type_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ impl<'o> TypeExprCompiler<'o> {
) -> Result<TypeExpr<'o>, DMError> {
match rhs {
// X[_] => static type of argument X with one /list stripped
Follow::Index(expr) => match expr.as_term() {
Follow::Index(_, expr) => match expr.as_term() {
Some(Term::Ident(name)) if name == "_" => match lhs {
TypeExpr::ParamTypepath {
name,
Expand Down
31 changes: 20 additions & 11 deletions src/dreammaker/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,9 +639,18 @@ impl From<Expression> for Term {
}
}

/// The possible kinds of access operators for lists
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ListAccessKind {
/// `[]`
Normal,
/// `?[]`
Safe,
}

/// The possible kinds of index operators, for both fields and methods.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum IndexKind {
pub enum PropertyAccessKind {
/// `a.b`
Dot,
/// `a:b`
Expand All @@ -652,18 +661,18 @@ pub enum IndexKind {
SafeColon,
}

impl IndexKind {
impl PropertyAccessKind {
pub fn name(self) -> &'static str {
match self {
IndexKind::Dot => ".",
IndexKind::Colon => ":",
IndexKind::SafeDot => "?.",
IndexKind::SafeColon => "?:",
PropertyAccessKind::Dot => ".",
PropertyAccessKind::Colon => ":",
PropertyAccessKind::SafeDot => "?.",
PropertyAccessKind::SafeColon => "?:",
}
}
}

impl fmt::Display for IndexKind {
impl fmt::Display for PropertyAccessKind {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(self.name())
}
Expand All @@ -673,17 +682,17 @@ impl fmt::Display for IndexKind {
#[derive(Debug, Clone, PartialEq)]
pub enum Follow {
/// Index the value by an expression.
Index(Box<Expression>),
Index(ListAccessKind, Box<Expression>),
/// Access a field of the value.
Field(IndexKind, Ident),
Field(PropertyAccessKind, Ident),
/// Call a method of the value.
Call(IndexKind, Ident, Vec<Expression>),
Call(PropertyAccessKind, Ident, Vec<Expression>),
}

/// Like a `Follow` but only supports field accesses.
#[derive(Debug, Clone, PartialEq)]
pub struct Field {
pub kind: IndexKind,
pub kind: PropertyAccessKind,
pub ident: Ident,
}

Expand Down
7 changes: 4 additions & 3 deletions src/dreammaker/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ table! {
"?", QuestionMark;
"?.", SafeDot;
"?:", SafeColon;
"?[", SafeLBracket;
"[", LBracket;
"]", RBracket;
"^", BitXor;
Expand Down Expand Up @@ -151,15 +152,15 @@ static SPEEDY_TABLE: [(usize, usize); 127] = [
(2, 3), (3, 5), (5, 6), (6, 8), (0, 0), (8, 10), (10, 14), (14, 15),
(15, 16), (16, 17), (17, 20), (20, 23), (23, 24), (24, 27), (27, 30), (30, 34),
(0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),
(0, 0), (0, 0), (34, 36), (36, 37), (37, 42), (42, 44), (44, 48), (48, 51),
(0, 0), (0, 0), (34, 36), (36, 37), (37, 42), (42, 44), (44, 48), (48, 52),
(0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),
(0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),
(0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),
(0, 0), (0, 0), (0, 0), (51, 52), (0, 0), (52, 53), (53, 55), (0, 0),
(0, 0), (0, 0), (0, 0), (52, 53), (0, 0), (53, 54), (54, 56), (0, 0),
(0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),
(0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),
(0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0),
(0, 0), (0, 0), (0, 0), (55, 57), (57, 61), (61, 62), (62, 65)];
(0, 0), (0, 0), (0, 0), (56, 58), (58, 62), (62, 63), (63, 66)];

#[test]
fn make_speedy_table() {
Expand Down
47 changes: 30 additions & 17 deletions src/dreammaker/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2110,23 +2110,36 @@ impl<'ctx, 'an, 'inp> Parser<'ctx, 'an, 'inp> {
success(Spanned::new(start, term))
}

fn follow(&mut self, belongs_to: &mut Vec<Ident>, in_ternary: bool) -> Status<Spanned<Follow>> {
fn list_access(&mut self, belongs_to: &mut Vec<Ident>) -> Status<Spanned<Follow>> {
let first_location = self.updated_location();

// follow :: ('[' | '?[') expression ']'
let kind = match self.next("field access")? {
// follow :: '[' expression ']'
Token::Punct(Punctuation::LBracket) => {
belongs_to.clear();
let expr = require!(self.expression());
require!(self.exact(Token::Punct(Punctuation::RBracket)));
return success(Spanned::new(first_location, Follow::Index(Box::new(expr))))
}
Token::Punct(Punctuation::LBracket) => ListAccessKind::Normal,
Token::Punct(Punctuation::SafeLBracket) => ListAccessKind::Safe,
other => return self.try_another(other),
};

belongs_to.clear();
let expr = require!(self.expression());
require!(self.exact(Token::Punct(Punctuation::RBracket)));
success(Spanned::new(first_location, Follow::Index(kind, Box::new(expr))))
}

// follow :: '.' ident arglist?
fn follow(&mut self, belongs_to: &mut Vec<Ident>, in_ternary: bool) -> Status<Spanned<Follow>> {
let first_location = self.updated_location();

if let Some(follow) = self.list_access(belongs_to)? {
return success(follow);
}

// follow :: '.' ident arglist?
let kind = match self.next("field access")? {
// TODO: only apply these rules if there is no whitespace around the punctuation
Token::Punct(Punctuation::Dot) => IndexKind::Dot,
Token::Punct(Punctuation::CloseColon) if !belongs_to.is_empty() || !in_ternary => IndexKind::Colon,
Token::Punct(Punctuation::SafeDot) => IndexKind::SafeDot,
Token::Punct(Punctuation::SafeColon) => IndexKind::SafeColon,
Token::Punct(Punctuation::Dot) => PropertyAccessKind::Dot,
Token::Punct(Punctuation::CloseColon) if !belongs_to.is_empty() || !in_ternary => PropertyAccessKind::Colon,
Token::Punct(Punctuation::SafeDot) => PropertyAccessKind::SafeDot,
Token::Punct(Punctuation::SafeColon) => PropertyAccessKind::SafeColon,

other => return self.try_another(other),
};
Expand Down Expand Up @@ -2172,10 +2185,10 @@ impl<'ctx, 'an, 'inp> Parser<'ctx, 'an, 'inp> {
let kind = match self.next("field access")? {
// follow :: '.' ident
// TODO: only apply these rules if there is no whitespace around the punctuation
Token::Punct(Punctuation::Dot) => IndexKind::Dot,
Token::Punct(Punctuation::CloseColon) if !belongs_to.is_empty() || !in_ternary => IndexKind::Colon,
Token::Punct(Punctuation::SafeDot) => IndexKind::SafeDot,
Token::Punct(Punctuation::SafeColon) => IndexKind::SafeColon,
Token::Punct(Punctuation::Dot) => PropertyAccessKind::Dot,
Token::Punct(Punctuation::CloseColon) if !belongs_to.is_empty() || !in_ternary => PropertyAccessKind::Colon,
Token::Punct(Punctuation::SafeDot) => PropertyAccessKind::SafeDot,
Token::Punct(Punctuation::SafeColon) => PropertyAccessKind::SafeColon,

other => return self.try_another(other),
};
Expand Down
2 changes: 1 addition & 1 deletion src/langserver/find_references.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ impl<'o> WalkProc<'o> {

fn visit_follow(&mut self, location: Location, lhs: StaticType<'o>, rhs: &'o Follow) -> StaticType<'o> {
match rhs {
Follow::Index(expr) => {
Follow::Index(_, expr) => {
self.visit_expression(location, expr, None);
// TODO: call operator[] or operator[]=
// TODO: differentiate between L[1] and L[non_numeric_key]
Expand Down

0 comments on commit 9c75634

Please sign in to comment.