Add Path parsing, AST node, sketching semantic analysis logic. WIP.
This commit is contained in:
parent
2dfd8e8a29
commit
347a9ec9c9
@ -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(),
|
||||
|
||||
@ -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
43
dmc-lib/src/ast/path.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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) =
|
||||
|
||||
@ -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<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)]
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user