Work on basic name analysis.

This commit is contained in:
Jesse Brault 2025-05-15 21:09:55 -05:00
parent 9805a3aad5
commit ce20cece21
11 changed files with 610 additions and 25 deletions

View File

@ -0,0 +1,10 @@
ns greeter
fn main() {
let x = 'Hello';
let y = 'World';
{
let test = 'Test';
};
x = y;
}

View File

@ -14,13 +14,11 @@ pub fn build_ast(compilation_unit_pair: Pair<Rule>) -> CompilationUnit {
} }
fn build_identifier(identifier_pair: Pair<Rule>) -> Identifier { fn build_identifier(identifier_pair: Pair<Rule>) -> Identifier {
Identifier { Identifier::new(identifier_pair.as_span().as_str())
name: identifier_pair.as_str().to_string(),
}
} }
fn build_fqn(fqn_pair: Pair<Rule>) -> FullyQualifiedName { fn build_fqn(fqn_pair: Pair<Rule>) -> FullyQualifiedName {
FullyQualifiedName( FullyQualifiedName::new(
fqn_pair fqn_pair
.into_inner() .into_inner()
.map(|identifier_pair| { .map(|identifier_pair| {
@ -745,14 +743,10 @@ fn build_call_statement(call_statement_pair: Pair<Rule>) -> CallStatement {
while let Some(inner_pair) = inner.next() { while let Some(inner_pair) = inner.next() {
match inner_pair.as_rule() { match inner_pair.as_rule() {
Rule::ObjectAccess => { Rule::ObjectAccess => {
result = Expression::ObjectAccess(build_object_access( result = Expression::ObjectAccess(build_object_access(result, inner_pair));
result, inner_pair,
));
} }
Rule::ParenthesesCall => { Rule::ParenthesesCall => {
result = Expression::Call(build_call_expression( result = Expression::Call(build_call_expression(result, inner_pair));
result, inner_pair,
));
} }
Rule::PlusPlus => { Rule::PlusPlus => {
result = Expression::UnarySuffix(SuffixExpression { result = Expression::UnarySuffix(SuffixExpression {
@ -971,16 +965,10 @@ fn build_suffix_expression(suffix_pair: Pair<Rule>) -> Expression {
while let Some(suffix_pair) = inner.next() { while let Some(suffix_pair) = inner.next() {
match suffix_pair.as_rule() { match suffix_pair.as_rule() {
Rule::ObjectAccess => { Rule::ObjectAccess => {
result = Expression::ObjectAccess(build_object_access( result = Expression::ObjectAccess(build_object_access(result, suffix_pair))
result,
suffix_pair,
))
} }
Rule::ParenthesesCall => { Rule::ParenthesesCall => {
result = Expression::Call(build_call_expression( result = Expression::Call(build_call_expression(result, suffix_pair))
result,
suffix_pair,
))
} }
Rule::PlusPlus => { Rule::PlusPlus => {
result = Expression::UnarySuffix(SuffixExpression { result = Expression::UnarySuffix(SuffixExpression {

View File

@ -1,6 +1,8 @@
use crate::compile::name_analysis::Symbol;
use pest::Parser; use pest::Parser;
pub mod build; pub mod build;
pub mod named;
pub mod pretty_print; pub mod pretty_print;
pub mod unparse; pub mod unparse;
// Operators // Operators
@ -52,10 +54,68 @@ pub enum SuffixUnaryOperator {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Identifier { pub struct Identifier {
pub name: String, pub name: String,
scope_id: Option<usize>,
symbol: Option<Symbol>,
}
impl Identifier {
pub fn new(name: &str) -> Self {
Identifier {
name: name.to_string(),
scope_id: None,
symbol: None,
}
}
pub fn set_scope_id(&mut self, id: usize) {
self.scope_id = Some(id);
}
pub fn scope_id(&self) -> Option<usize> {
self.scope_id
}
pub fn set_symbol(&mut self, symbol: Symbol) {
self.symbol = Some(symbol);
}
pub fn symbol(&self) -> &Option<Symbol> {
&self.symbol
}
} }
#[derive(Debug)] #[derive(Debug)]
pub struct FullyQualifiedName(pub Vec<Identifier>); pub struct FullyQualifiedName {
pub identifiers: Vec<Identifier>,
scope_id: Option<usize>,
symbol: Option<Symbol>,
}
impl FullyQualifiedName {
pub fn new(identifiers: Vec<Identifier>) -> Self {
FullyQualifiedName {
identifiers,
scope_id: None,
symbol: None,
}
}
pub fn set_scope_id(&mut self, scope_id: usize) {
self.scope_id = Some(scope_id);
}
pub fn scope_id(&self) -> Option<usize> {
self.scope_id
}
pub fn set_symbol(&mut self, symbol: Symbol) {
self.symbol = Some(symbol);
}
pub fn symbol(&self) -> &Option<Symbol> {
&self.symbol
}
}
/* Type Use */ /* Type Use */
@ -400,10 +460,10 @@ pub enum Statement {
#[derive(Debug)] #[derive(Debug)]
pub struct VariableDeclarationStatement { pub struct VariableDeclarationStatement {
is_mutable: bool, pub is_mutable: bool,
identifier: Identifier, pub identifier: Identifier,
declared_type: Option<TypeUse>, pub declared_type: Option<TypeUse>,
initializer: Option<Expression>, pub initializer: Option<Expression>,
} }
#[derive(Debug)] #[derive(Debug)]

29
src/ast/named.rs Normal file
View File

@ -0,0 +1,29 @@
use crate::ast::{FullyQualifiedName, Identifier};
use std::borrow::Cow;
pub trait Named {
fn name(&self) -> Cow<'_, str>;
}
impl Named for Identifier {
fn name(&self) -> Cow<'_, str> {
Cow::Borrowed(&self.name)
}
}
impl Named for FullyQualifiedName {
fn name(&self) -> Cow<'_, str> {
if self.identifiers.len() == 1 {
self.identifiers[0].name()
} else {
let mut acc = String::new();
for (i, identifier) in self.identifiers.iter().enumerate() {
acc += &identifier.name();
if i < self.identifiers.len() - 1 {
acc += "::";
}
}
Cow::Owned(acc)
}
}
}

View File

@ -74,7 +74,7 @@ impl PrettyPrint for FullyQualifiedName {
fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> {
writer.writeln_indented("FullyQualifiedName")?; writer.writeln_indented("FullyQualifiedName")?;
writer.increase_indent(); writer.increase_indent();
for identifier in &self.0 { for identifier in &self.identifiers {
identifier.pretty_print(writer)?; identifier.pretty_print(writer)?;
} }
writer.decrease_indent(); writer.decrease_indent();

View File

@ -147,7 +147,7 @@ impl ListUnparse for FullyQualifiedName {
} }
fn inner(&self) -> Vec<&dyn Unparse> { fn inner(&self) -> Vec<&dyn Unparse> {
to_unparse_vec!(self.0) to_unparse_vec!(self.identifiers)
} }
} }

View File

@ -1,8 +1,10 @@
mod name_analysis;
mod p3; mod p3;
mod unparse; mod unparse;
use std::path::PathBuf; use std::path::PathBuf;
use crate::name_analysis::name_analysis;
use crate::p3::pretty_print_parse; use crate::p3::pretty_print_parse;
use crate::unparse::unparse; use crate::unparse::unparse;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
@ -24,6 +26,9 @@ enum Commands {
P3 { P3 {
paths: Vec<PathBuf>, paths: Vec<PathBuf>,
}, },
NameAnalysis {
paths: Vec<PathBuf>,
},
} }
fn main() { fn main() {
@ -39,5 +44,10 @@ fn main() {
pretty_print_parse(&path) pretty_print_parse(&path)
} }
} }
Commands::NameAnalysis { paths } => {
for path in paths {
name_analysis(&path)
}
}
} }
} }

View File

@ -0,0 +1,36 @@
use deimos::ast::build::build_ast;
use deimos::compile::name_analysis::{analyze_names, SymbolTable};
use deimos::parser::{DeimosParser, Rule};
use pest::Parser;
use std::path::Path;
pub fn name_analysis(path: &Path) {
let src = std::fs::read_to_string(path).unwrap();
let parse_result = DeimosParser::parse(
Rule::CompilationUnit,
&src
);
match parse_result {
Ok(mut pairs) => {
let compilation_unit_pair = pairs.next().unwrap();
let mut compilation_unit = build_ast(compilation_unit_pair);
let mut symbol_table = SymbolTable::new();
let name_analysis_result = analyze_names(
&mut compilation_unit,
&mut symbol_table,
);
match name_analysis_result {
Err(e) => {
eprintln!("{}", e);
}
Ok(_) => {
println!("name_analysis complete");
println!("{}", symbol_table);
}
}
}
Err(e) => {
eprintln!("{}", e);
}
}
}

1
src/compile/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod name_analysis;

View File

@ -0,0 +1,450 @@
use crate::ast::named::Named;
use crate::ast::*;
use std::collections::HashMap;
use std::fmt::Display;
#[derive(Debug, Clone)]
pub enum Symbol {
Function(FunctionSymbol),
Variable(VariableSymbol),
}
impl Display for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
use Symbol::*;
match self {
Function(function_symbol) => write!(f, "{}", function_symbol),
Variable(variable_symbol) => write!(f, "{}", variable_symbol),
}
}
}
#[derive(Debug, Clone)]
pub struct FunctionSymbol {
pub fqn: String,
pub declared_name: String,
pub is_public: bool,
}
impl Display for FunctionSymbol {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"FunctionSymbol(fqn = {}, declared_name = {}, is_public = {})",
self.fqn, self.declared_name, self.is_public
)
}
}
#[derive(Debug, Clone)]
pub struct VariableSymbol {
pub name: String,
pub is_mutable: bool,
}
impl Display for VariableSymbol {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"VariableSymbol(name = {}, is_mutable = {})",
self.name, self.is_mutable
)
}
}
#[derive(Default)]
pub struct Scope {
parent: Option<usize>,
symbols: HashMap<String, Symbol>,
debug_name: String,
}
#[derive(Default)]
pub struct SymbolTable {
scopes: Vec<Scope>,
current_scope_id: usize,
}
/// Contains a vec of scopes, like a flattened tree
impl SymbolTable {
pub fn new() -> Self {
let mut t = SymbolTable::default();
t.scopes.push(Scope::default());
t.current_scope_id = 0;
t
}
pub fn current_scope_id(&self) -> usize {
self.current_scope_id
}
pub fn push_scope(&mut self, debug_name: &str) {
let id = self.scopes.len();
self.scopes.push(Scope {
symbols: HashMap::new(),
parent: Some(self.current_scope_id),
debug_name: debug_name.to_string(),
});
self.current_scope_id = id;
}
pub fn pop_scope(&mut self) {
if let Some(parent_id) = self.scopes[self.current_scope_id].parent {
self.current_scope_id = parent_id;
}
}
pub fn insert(&mut self, name: String, symbol: Symbol) -> Result<(), String> {
if let Some(current_symbol) = self.scopes[self.current_scope_id].symbols.get(&name) {
Err(format!("Symbol '{}' already defined", current_symbol))
} else {
self.scopes[self.current_scope_id]
.symbols
.insert(name, symbol);
Ok(())
}
}
pub fn lookup(&self, name: &str, scope_id: usize) -> Result<&Symbol, String> {
let mut scope_opt = Some(&self.scopes[scope_id]);
while let Some(scope) = scope_opt {
if let Some(symbol) = scope.symbols.get(name) {
return Ok(symbol);
}
scope_opt = if let Some(parent_id) = scope.parent {
Some(&self.scopes[parent_id])
} else {
None
};
}
Err(format!("Symbol '{}' not found", name))
}
}
impl Display for SymbolTable {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
for (i, scope) in self.scopes.iter().enumerate() {
writeln!(f, "Scope {} {}", i, scope.debug_name)?;
for (name, symbol) in &scope.symbols {
writeln!(f, " {}({})", name, symbol)?;
}
}
Ok(())
}
}
struct FqnContext {
stack: Vec<String>,
}
impl FqnContext {
fn new() -> Self {
FqnContext { stack: Vec::new() }
}
fn push(&mut self, name: String) {
self.stack.push(name);
}
fn pop(&mut self) {
self.stack.pop();
}
fn current(&self) -> String {
let mut acc = String::new();
for (i, name) in self.stack.iter().enumerate() {
acc.push_str(name);
if i != self.stack.len() - 1 {
acc.push_str("::")
}
}
acc
}
fn resolve(&self, name: &str) -> String {
let mut acc = String::new();
if !self.stack.is_empty() {
acc.push_str(&self.current());
acc.push_str("::");
}
acc.push_str(name);
acc
}
}
pub fn analyze_names(
compilation_unit: &mut CompilationUnit,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
let mut fqn_context = FqnContext::new();
if let Some(namespace) = &compilation_unit.namespace {
fqn_context.push(namespace.name().to_string());
}
for declaration in &mut compilation_unit.declarations {
gather_module_level_declaration(declaration, symbol_table, &mut fqn_context)?;
}
assert_eq!(symbol_table.current_scope_id, 0);
for declaration in &mut compilation_unit.declarations {
resolve_module_level_declaration(declaration, symbol_table)?;
}
Ok(())
}
fn gather_module_level_declaration(
declaration: &mut ModuleLevelDeclaration,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
) -> Result<(), String> {
use ModuleLevelDeclaration::*;
match declaration {
Function(function_definition) => {
gather_function_definition(function_definition, symbol_table, fqn_context)
}
_ => todo!(),
}
}
fn gather_function_definition(
function: &mut FunctionDefinition,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
) -> Result<(), String> {
let declared_name = function.identifier.name().to_string();
let resolved_name = fqn_context.resolve(&declared_name);
symbol_table.insert(
declared_name.clone(),
Symbol::Function(FunctionSymbol {
fqn: resolved_name.clone(),
declared_name,
is_public: function.is_public,
}),
)?;
function
.identifier
.set_scope_id(symbol_table.current_scope_id());
symbol_table.push_scope(&format!("FunctionScope({})", resolved_name));
// TODO: params
gather_function_body(&mut function.body, symbol_table, fqn_context)?;
symbol_table.pop_scope();
Ok(())
}
fn gather_function_body(
function_body: &mut FunctionBody,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
) -> Result<(), String> {
use FunctionBody::*;
match function_body {
Block(block) => gather_block_statement(block, symbol_table, fqn_context),
_ => todo!(),
}
}
fn gather_block_statement(
block: &mut BlockStatement,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
) -> Result<(), String> {
symbol_table.push_scope("BlockStatementScope");
for statement in &mut block.statements {
gather_statement(statement, symbol_table, fqn_context)?;
}
symbol_table.pop_scope();
Ok(())
}
fn gather_statement(
statement: &mut Statement,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
) -> Result<(), String> {
use Statement::*;
match statement {
BlockStatement(block) => gather_block_statement(block, symbol_table, fqn_context),
VariableDeclarationStatement(variable_declaration) => {
gather_variable_declaration(variable_declaration, symbol_table, fqn_context)
}
AssignStatement(assign_statement) => {
gather_assign_statement(assign_statement, symbol_table, fqn_context)
}
_ => todo!(),
}
}
fn gather_variable_declaration(
variable_declaration: &mut VariableDeclarationStatement,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
) -> Result<(), String> {
let variable_name = variable_declaration.identifier.name().to_string();
symbol_table.insert(
variable_name.clone(),
Symbol::Variable(VariableSymbol {
name: variable_name,
is_mutable: variable_declaration.is_mutable,
}),
)?;
variable_declaration
.identifier
.set_scope_id(symbol_table.current_scope_id());
Ok(())
}
fn gather_assign_statement(
assign_statement: &mut AssignStatement,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
) -> Result<(), String> {
gather_expression(&mut assign_statement.lhs, symbol_table, fqn_context)?;
gather_expression(&mut assign_statement.rhs, symbol_table, fqn_context)?;
Ok(())
}
fn gather_expression(
expression: &mut Expression,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
) -> Result<(), String> {
use Expression::*;
match expression {
FullyQualifiedName(fully_qualified_name) => {
gather_fully_qualified_name(fully_qualified_name, symbol_table, fqn_context)?;
}
_ => {}
}
Ok(())
}
fn gather_fully_qualified_name(
fully_qualified_name: &mut FullyQualifiedName,
symbol_table: &mut SymbolTable,
fqn_context: &mut FqnContext,
) -> Result<(), String> {
fully_qualified_name.set_scope_id(symbol_table.current_scope_id());
Ok(())
}
/* Resolve */
fn resolve_module_level_declaration(
declaration: &mut ModuleLevelDeclaration,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
use ModuleLevelDeclaration::*;
match declaration {
Function(function_definition) => {
resolve_function_definition(function_definition, symbol_table)
}
_ => todo!(),
}
}
fn resolve_function_definition(
function_definition: &mut FunctionDefinition,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
resolve_function_body(&mut function_definition.body, symbol_table)
}
fn resolve_function_body(
function_body: &mut FunctionBody,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
use FunctionBody::*;
match function_body {
Block(block) => resolve_block(block, symbol_table),
_ => todo!(),
}
}
fn resolve_block(
block_statement: &mut BlockStatement,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
for statement in &mut block_statement.statements {
resolve_statement(statement, symbol_table)?;
}
Ok(())
}
fn resolve_statement(
statement: &mut Statement,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
use Statement::*;
match statement {
BlockStatement(block) => resolve_block(block, symbol_table),
VariableDeclarationStatement(variable_declaration) => {
resolve_variable_declaration(variable_declaration, symbol_table)
}
AssignStatement(assign_statement) => {
resolve_assign_statement(assign_statement, symbol_table)
}
CallStatement(call_statement) => resolve_call_statement(call_statement, symbol_table),
_ => todo!(),
}
}
fn resolve_variable_declaration(
variable_declaration: &mut VariableDeclarationStatement,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
if let Some(initializer) = &mut variable_declaration.initializer {
resolve_expression(initializer, symbol_table)
} else {
Ok(())
}
}
fn resolve_assign_statement(
assign_statement: &mut AssignStatement,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
resolve_expression(&mut assign_statement.lhs, symbol_table)?;
resolve_expression(&mut assign_statement.rhs, symbol_table)?;
Ok(())
}
fn resolve_call_statement(
call_statement: &mut CallStatement,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
resolve_expression(&mut call_statement.0, symbol_table)
}
fn resolve_expression(
expression: &mut Expression,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
use Expression::*;
match expression {
FullyQualifiedName(fqn) => resolve_fully_qualified_name(fqn, symbol_table),
Literal(_) => Ok(()),
_ => todo!(),
}
}
fn resolve_fully_qualified_name(
fully_qualified_name: &mut FullyQualifiedName,
symbol_table: &mut SymbolTable,
) -> Result<(), String> {
let lookup_result = symbol_table.lookup(
fully_qualified_name.name().as_ref(),
fully_qualified_name.scope_id().expect(&format!(
"FullyQualifiedName has no scope_id set: {:?}",
fully_qualified_name
)),
);
match lookup_result {
Ok(symbol) => {
fully_qualified_name.set_symbol(symbol.clone());
Ok(())
}
Err(e) => Err(e),
}
}

View File

@ -1,5 +1,6 @@
#![allow(warnings)] #![allow(warnings)]
pub mod ast; pub mod ast;
pub mod compile;
pub mod module; pub mod module;
pub mod object_file; pub mod object_file;
pub mod parser; pub mod parser;