diff --git a/dmc-lib/src/ast/expression.rs b/dmc-lib/src/ast/expression.rs index c2dbda2..7271402 100644 --- a/dmc-lib/src/ast/expression.rs +++ b/dmc-lib/src/ast/expression.rs @@ -5,6 +5,7 @@ use crate::ast::double_literal::DoubleLiteral; use crate::ast::identifier::Identifier; use crate::ast::integer_literal::IntegerLiteral; use crate::ast::negative_expression::NegativeExpression; +use crate::ast::path::Path; use crate::ast::string_literal::StringLiteral; use crate::source_range::SourceRange; @@ -12,6 +13,7 @@ pub enum Expression { Binary(BinaryExpression), Negative(NegativeExpression), Call(Call), + Path(Path), Identifier(Identifier), Integer(IntegerLiteral), Double(DoubleLiteral), @@ -24,6 +26,7 @@ impl Expression { Expression::Binary(binary_expression) => binary_expression.node_id(), Expression::Negative(negative_expression) => negative_expression.node_id(), Expression::Call(call) => call.node_id(), + Expression::Path(path) => path.node_id(), Expression::Identifier(identifier) => identifier.node_id(), Expression::Integer(integer_literal) => integer_literal.node_id(), Expression::Double(double_literal) => double_literal.node_id(), @@ -36,6 +39,7 @@ impl Expression { Expression::Binary(binary_expression) => binary_expression.source_range(), Expression::Negative(negative_expression) => negative_expression.source_range(), Expression::Call(call) => call.source_range(), + Expression::Path(path) => path.source_range(), Expression::Identifier(identifier) => identifier.source_range(), Expression::Integer(integer_literal) => integer_literal.source_range(), Expression::Double(double_literal) => double_literal.source_range(), diff --git a/dmc-lib/src/ast/mod.rs b/dmc-lib/src/ast/mod.rs index a9968e4..0df3de2 100644 --- a/dmc-lib/src/ast/mod.rs +++ b/dmc-lib/src/ast/mod.rs @@ -16,6 +16,7 @@ pub mod integer_literal; pub mod let_statement; pub mod negative_expression; pub mod parameter; +pub mod path; pub mod statement; pub mod string_literal; pub mod type_use; diff --git a/dmc-lib/src/ast/path.rs b/dmc-lib/src/ast/path.rs new file mode 100644 index 0000000..9c6d059 --- /dev/null +++ b/dmc-lib/src/ast/path.rs @@ -0,0 +1,43 @@ +use crate::ast::NodeId; +use crate::ast::expression::Expression; +use crate::ast::identifier::Identifier; +use crate::source_range::SourceRange; + +pub struct Path { + node_id: NodeId, + source_range: SourceRange, + base: Box, + identifier: Box, +} + +impl Path { + pub fn new( + node_id: NodeId, + source_range: SourceRange, + base: Expression, + identifier: Identifier, + ) -> Self { + Self { + node_id, + source_range, + base: base.into(), + identifier: identifier.into(), + } + } + + pub fn node_id(&self) -> NodeId { + self.node_id + } + + pub fn source_range(&self) -> &SourceRange { + &self.source_range + } + + pub fn base(&self) -> &Expression { + &self.base + } + + pub fn identifier(&self) -> &Identifier { + &self.identifier + } +} diff --git a/dmc-lib/src/lowering/mod.rs b/dmc-lib/src/lowering/mod.rs index 19dc2cf..624409d 100644 --- a/dmc-lib/src/lowering/mod.rs +++ b/dmc-lib/src/lowering/mod.rs @@ -439,6 +439,9 @@ fn lower_expression_to_ir_operation( )) } Expression::Call(call) => IrOperation::Call(lower_to_ir_call(call, ctx, fn_ctx)), + Expression::Path(_path) => { + todo!() + } Expression::Identifier(identifier) => { let identifier_symbol_id = ctx.nodes_to_symbols()[&identifier.node_id()]; let identifier_ir_variable = @@ -533,6 +536,9 @@ fn lower_expression_to_ir_expression( // return an expression referencing the temp var IrExpression::Variable(t_var_ir_variable) } + Expression::Path(_path) => { + todo!() + } Expression::Identifier(identifier) => { let rhs_symbol_id = ctx.nodes_to_symbols()[&identifier.node_id()]; if let Some(rhs_ir_variable) = diff --git a/dmc-lib/src/parser.rs b/dmc-lib/src/parser.rs index 3640fd5..e078166 100644 --- a/dmc-lib/src/parser.rs +++ b/dmc-lib/src/parser.rs @@ -18,6 +18,7 @@ use crate::ast::integer_literal::IntegerLiteral; use crate::ast::let_statement::LetStatement; use crate::ast::negative_expression::NegativeExpression; use crate::ast::parameter::Parameter; +use crate::ast::path::Path; use crate::ast::statement::Statement; use crate::ast::string_literal::StringLiteral; use crate::ast::type_use::TypeUse; @@ -166,21 +167,6 @@ impl<'a> Parser<'a> { } } - fn advance_until(&mut self, token_kinds: &[TokenKind]) { - while self.current.is_some() { - match &self.current { - None => { - // reached eoi - } - Some(current) => { - if token_kinds.contains(¤t.kind()) { - break; - } - } - } - } - } - fn advance(&mut self) { fn fetch(lexer: &mut Lexer, diagnostics: &mut Diagnostics) -> Option { let mut maybe_token: Option = None; @@ -1038,6 +1024,21 @@ impl<'a> Parser<'a> { TokenKind::LeftParentheses => { expression = Expression::Call(self.call(expression)); } + TokenKind::Dot => { + self.advance(); // . + if let Some(identifier) = self.identifier() { + let source_range = SourceRange::new( + expression.source_range().start(), + identifier.source_range().end(), + ); + expression = Expression::Path(Path::new( + self.next_node_id(), + source_range, + expression, + identifier, + )); + } + } _ => break, } } @@ -1047,6 +1048,18 @@ impl<'a> Parser<'a> { } } + fn identifier(&mut self) -> Option { + if let Some(identifier_token) = self.expect_advance(TokenKind::Identifier) { + Some(Identifier::new( + self.next_node_id(), + self.token_text(&identifier_token), + SourceRange::new(identifier_token.start(), identifier_token.end()), + )) + } else { + None + } + } + fn expression_base(&mut self) -> Option { let current = match self.current.as_ref() { Some(current) => current, @@ -1350,6 +1363,21 @@ mod smoke_tests { fn class_with_generic_param() { smoke_test("class Foo end"); } + + #[test] + fn single_member_access() { + smoke_test("fn main(foo: Foo) foo.bar end") + } + + #[test] + fn double_member_access() { + smoke_test("fn main(foo: Foo) foo.bar.baz end") + } + + #[test] + fn call_member() { + smoke_test("fn main(s: String) s.len() end") + } } #[cfg(test)] diff --git a/dmc-lib/src/semantic_analysis/collect_scopes.rs b/dmc-lib/src/semantic_analysis/collect_scopes.rs index 1b1c253..f5e4fcf 100644 --- a/dmc-lib/src/semantic_analysis/collect_scopes.rs +++ b/dmc-lib/src/semantic_analysis/collect_scopes.rs @@ -9,11 +9,10 @@ use crate::ast::function::Function; use crate::ast::identifier::Identifier; use crate::ast::let_statement::LetStatement; use crate::ast::negative_expression::NegativeExpression; +use crate::ast::path::Path; use crate::ast::statement::Statement; use crate::semantic_analysis::analysis_context::AnalysisContext; -/// N.b.: the parent_scope_id refers to the **parent** of the compilation unit scope, and must -/// be a valid (non-panicking) index of `scopes`. pub fn collect_scopes_compilation_unit( compilation_unit: &CompilationUnit, ctx: &mut AnalysisContext, @@ -112,6 +111,9 @@ fn collect_scopes_expression(expression: &Expression, ctx: &mut AnalysisContext) Expression::Call(call) => { collect_scopes_call(call, ctx); } + Expression::Path(path) => { + collect_scopes_path(path, ctx); + } Expression::Identifier(identifier) => { collect_scopes_identifier(identifier, ctx); } @@ -143,6 +145,11 @@ fn collect_scopes_call(call: &Call, ctx: &mut AnalysisContext) { collect_scopes_expression(call.callee(), ctx); } +fn collect_scopes_path(path: &Path, ctx: &mut AnalysisContext) { + collect_scopes_expression(path.base(), ctx); + // do not need to collect a scope for the identifier, since that is looked up via the base +} + fn collect_scopes_identifier(identifier: &Identifier, ctx: &mut AnalysisContext) { ctx.associate_node_to_current_scope(identifier.node_id()); } diff --git a/dmc-lib/src/semantic_analysis/resolve_names.rs b/dmc-lib/src/semantic_analysis/resolve_names.rs index 10f9d5c..ffa6f13 100644 --- a/dmc-lib/src/semantic_analysis/resolve_names.rs +++ b/dmc-lib/src/semantic_analysis/resolve_names.rs @@ -10,6 +10,7 @@ use crate::ast::identifier::Identifier; use crate::ast::let_statement::LetStatement; use crate::ast::negative_expression::NegativeExpression; use crate::ast::parameter::Parameter; +use crate::ast::path::Path; use crate::ast::statement::Statement; use crate::diagnostic::Diagnostics; use crate::semantic_analysis::analysis_context::AnalysisContext; @@ -195,6 +196,9 @@ fn resolve_names_expression( Expression::Call(call) => { resolve_names_call(call, ctx, diagnostics, phase); } + Expression::Path(path) => { + resolve_names_path(path, ctx, diagnostics, phase); + } Expression::Identifier(identifier) => { resolve_names_identifier(identifier, ctx, diagnostics, phase); } @@ -235,6 +239,18 @@ fn resolve_names_call( resolve_names_expression(call.callee(), ctx, diagnostics, phase); } +fn resolve_names_path( + path: &Path, + ctx: &mut AnalysisContext, + diagnostics: &mut Diagnostics, + phase: ExpressionResolutionPhase, +) { + // resolve base (left part) + resolve_names_expression(path.base(), ctx, diagnostics, phase); + // We cannot yet determine if the path's identifier (right part) is a valid memeber of the left, + // since we don't have type information yet. Therefore, it is deferred until type-checking. +} + fn resolve_names_identifier( identifier: &Identifier, ctx: &mut AnalysisContext, diff --git a/dmc-lib/src/semantic_analysis/resolve_types/mod.rs b/dmc-lib/src/semantic_analysis/resolve_types/mod.rs index 57831ee..c8d3342 100644 --- a/dmc-lib/src/semantic_analysis/resolve_types/mod.rs +++ b/dmc-lib/src/semantic_analysis/resolve_types/mod.rs @@ -10,6 +10,7 @@ use crate::ast::identifier::Identifier; use crate::ast::let_statement::LetStatement; use crate::ast::negative_expression::NegativeExpression; use crate::ast::parameter::Parameter; +use crate::ast::path::Path; use crate::ast::statement::Statement; use crate::diagnostic::{Diagnostic, Diagnostics}; use crate::error_codes::BINARY_INCOMPATIBLE_TYPES; @@ -138,6 +139,9 @@ fn resolve_types_expression( Expression::Call(call) => { resolve_types_call(call, ctx, diagnostics); } + Expression::Path(path) => { + resolve_types_path(path, ctx, diagnostics); + } Expression::Identifier(identifier) => { resolve_types_identifier(identifier, ctx); } @@ -300,6 +304,33 @@ fn resolve_types_call(call: &Call, ctx: &mut AnalysisContext, diagnostics: &mut } } +fn resolve_types_path(path: &Path, ctx: &mut AnalysisContext, diagnostics: &mut Diagnostics) { + resolve_types_expression(path.base(), ctx, diagnostics); + let base_type_info = ctx.get_type_info_for_node(path.base().node_id()); + match base_type_info { + // TypeInfo::ClassInstance => {} todo + TypeInfo::__Error => { + // bubble it up + ctx.insert_type_info_for_node(TypeInfo::__Error, path.node_id()); + } + _ => { + let message = format!( + "Incompatible type: cannot access a field on {}", + base_type_info + ); + let diagnostic = Diagnostic::new( + &message, + path.source_range().start(), + path.source_range().end(), + ); + diagnostics.push(diagnostic); + + // bubble it up + ctx.insert_type_info_for_node(TypeInfo::__Error, path.node_id()); + } + } +} + fn resolve_types_identifier(identifier: &Identifier, ctx: &mut AnalysisContext) { let type_info_id = ctx.lookup_type_for_node_via_symbol(identifier.node_id()); ctx.associate_node_to_type(identifier.node_id(), type_info_id);