Add Path parsing, AST node, sketching semantic analysis logic. WIP.

This commit is contained in:
Jesse Brault 2026-08-22 17:41:25 -05:00
parent 2dfd8e8a29
commit 347a9ec9c9
8 changed files with 153 additions and 17 deletions

View File

@ -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(),

View File

@ -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;

43
dmc-lib/src/ast/path.rs Normal file
View File

@ -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<Expression>,
identifier: Box<Identifier>,
}
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
}
}

View File

@ -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) =

View File

@ -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(&current.kind()) {
break;
}
}
}
}
}
fn advance(&mut self) {
fn fetch(lexer: &mut Lexer, diagnostics: &mut Diagnostics) -> Option<Token> {
let mut maybe_token: Option<Token> = 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<Identifier> {
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<Expression> {
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<T> 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)]

View File

@ -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());
}

View File

@ -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,

View File

@ -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);