Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for L?[x] list access syntax #262

Merged
merged 2 commits into from Apr 22, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/dreamchecker/lib.rs
Expand Up @@ -1892,10 +1892,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 @@ -1914,7 +1914,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
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
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
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
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
Expand Up @@ -2105,23 +2105,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 @@ -2167,10 +2180,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
Expand Up @@ -561,7 +561,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