Compare commits

...

2 Commits

Author SHA1 Message Date
Jesse Brault
6bcef184eb Successfully fixed implicit global println test. 2025-11-01 21:38:52 -05:00
Jesse Brault
0550df534e Move parameter gathering to second pass in order to match analysis of all locals there. 2025-11-01 12:23:41 -05:00
19 changed files with 836 additions and 384 deletions

View File

@ -11,6 +11,10 @@ use convert_case::{Case, Casing};
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
fn make_result() -> TokenStream {
quote! { std::fmt::Result }
}
fn make_polymorphic_enum_loop_p2_impl(spec: &PolymorphicEnumLoopBuildSpec) -> TokenStream { fn make_polymorphic_enum_loop_p2_impl(spec: &PolymorphicEnumLoopBuildSpec) -> TokenStream {
let type_ident = format_ident!("{}", spec.name()); let type_ident = format_ident!("{}", spec.name());
let type_string = spec.name(); let type_string = spec.name();
@ -44,9 +48,11 @@ fn make_polymorphic_enum_loop_p2_impl(spec: &PolymorphicEnumLoopBuildSpec) -> To
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let result = make_result();
quote! { quote! {
impl PrettyPrint for #type_ident { impl PrettyPrint for #type_ident {
fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { fn pretty_print(&self, writer: &mut IndentWriter) -> #result {
writer.writeln_indented(#type_string)?; writer.writeln_indented(#type_string)?;
writer.increase_indent(); writer.increase_indent();
#(#child_print_statements)* #(#child_print_statements)*
@ -70,9 +76,11 @@ fn make_polymorphic_type_p2_impl(spec: &PolymorphicTypeBuildSpec) -> TokenStream
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let result = make_result();
quote! { quote! {
impl PrettyPrint for #type_ident { impl PrettyPrint for #type_ident {
fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { fn pretty_print(&self, writer: &mut IndentWriter) -> #result {
match self { match self {
#(#child_matchers,)* #(#child_matchers,)*
} }
@ -94,9 +102,11 @@ fn make_leaf_enum_p2_impl(spec: &LeafEnumBuildSpec) -> TokenStream {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let result = make_result();
quote! { quote! {
impl PrettyPrint for #type_ident { impl PrettyPrint for #type_ident {
fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { fn pretty_print(&self, writer: &mut IndentWriter) -> #result {
match self { match self {
#(#child_matchers,)* #(#child_matchers,)*
} }
@ -137,9 +147,11 @@ fn make_tree_enum_p2_impl(spec: &TreeEnumBuildSpec) -> TokenStream {
.map(Option::unwrap) .map(Option::unwrap)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let result = make_result();
quote! { quote! {
impl PrettyPrint for #type_ident { impl PrettyPrint for #type_ident {
fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { fn pretty_print(&self, writer: &mut IndentWriter) -> #result {
writer.writeln_indented(#type_str)?; writer.writeln_indented(#type_str)?;
writer.increase_indent(); writer.increase_indent();
match self { match self {
@ -184,9 +196,11 @@ fn make_leaf_struct_p2_impl(leaf_struct_build_spec: &LeafStructBuildSpec) -> Tok
.map(Option::unwrap) .map(Option::unwrap)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let result = make_result();
quote! { quote! {
impl PrettyPrint for #type_ident { impl PrettyPrint for #type_ident {
fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { fn pretty_print(&self, writer: &mut IndentWriter) -> #result {
writer.writeln_indented(&format!(#format_string, #(#members),*)) writer.writeln_indented(&format!(#format_string, #(#members),*))
} }
} }
@ -232,7 +246,7 @@ fn make_struct_p2_impl(struct_build_spec: &StructSpec) -> TokenStream {
}) })
} }
}, },
StructChild::Special(_) => None StructChild::Special(_) => None,
}) })
.filter(Option::is_some) .filter(Option::is_some)
.map(Option::unwrap) .map(Option::unwrap)
@ -241,9 +255,11 @@ fn make_struct_p2_impl(struct_build_spec: &StructSpec) -> TokenStream {
let type_ident = format_ident!("{}", struct_build_spec.build()); let type_ident = format_ident!("{}", struct_build_spec.build());
let type_string = struct_build_spec.build(); let type_string = struct_build_spec.build();
let result = make_result();
quote! { quote! {
impl PrettyPrint for #type_ident { impl PrettyPrint for #type_ident {
fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { fn pretty_print(&self, writer: &mut IndentWriter) -> #result {
writer.writeln_indented(#type_string)?; writer.writeln_indented(#type_string)?;
writer.increase_indent(); writer.increase_indent();
#(#child_print_statements)* #(#child_print_statements)*

View File

@ -166,7 +166,7 @@ pub mod pretty_print {
use crate::util::indent_writer::IndentWriter; use crate::util::indent_writer::IndentWriter;
pub trait PrettyPrint { pub trait PrettyPrint {
fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()>; fn pretty_print(&self, writer: &mut IndentWriter) -> std::fmt::Result;
} }
include!(concat!(env!("OUT_DIR"), "/src/ast/pretty_print.rs")); include!(concat!(env!("OUT_DIR"), "/src/ast/pretty_print.rs"));

View File

@ -59,7 +59,9 @@ pub fn name_analysis(paths: &[PathBuf]) -> Result<(), Box<dyn std::error::Error>
} }
if !parse_errors.is_empty() { if !parse_errors.is_empty() {
return Err(Box::new(ParseErrors { errors: parse_errors })); return Err(Box::new(ParseErrors {
errors: parse_errors,
}));
} }
let mut symbol_table = SymbolTable::new(); let mut symbol_table = SymbolTable::new();

View File

@ -11,10 +11,12 @@ pub fn pretty_print_parse(path: &PathBuf) {
match parse_result { match parse_result {
Ok(mut pairs) => { Ok(mut pairs) => {
let compilation_unit = build_ast(0, &mut pairs); let compilation_unit = build_ast(0, &mut pairs);
let mut indent_writer = IndentWriter::new(0, " ", Box::new(std::io::stdout())); let mut acc = String::new();
let mut indent_writer = IndentWriter::new(0, " ", &mut acc);
compilation_unit compilation_unit
.pretty_print(&mut indent_writer) .pretty_print(&mut indent_writer)
.expect("Unable to pretty-print."); .expect("Unable to pretty-print.");
println!("{}", acc);
} }
Err(e) => { Err(e) => {
eprintln!("{}", e); eprintln!("{}", e);

View File

@ -1,25 +1,20 @@
use crate::ast::node::{ use crate::ast::node::{
CompilationUnit, ConcreteUseStatement, ConcreteUseStatementSuffix, Function, FunctionBody, CompilationUnit, ConcreteUseStatement, ConcreteUseStatementSuffix, Function, FunctionBody
GenericParameters, Identifier, IdentifierOrFqn, Module, ModuleLevelDeclaration, Parameter, , Identifier, IdentifierOrFqn, Module, ModuleLevelDeclaration
Parameters, PrimitiveType, ReturnType, StarUseStatement, TypeUse, TypedArray, UseStatement, , StarUseStatement, UseStatement,
UseStatementIdentifier, UseStatementPrefix, UseStatementIdentifier, UseStatementPrefix,
}; };
use crate::diagnostic::DmDiagnostic; use crate::diagnostic::DmDiagnostic;
use crate::name_analysis::symbol::function_symbol::FunctionSymbol; use crate::name_analysis::symbol::function_symbol::FunctionSymbol;
use crate::name_analysis::symbol::generic_type_symbol::GenericTypeSymbol;
use crate::name_analysis::symbol::module_level_symbol::ModuleLevelSymbol; use crate::name_analysis::symbol::module_level_symbol::ModuleLevelSymbol;
use crate::name_analysis::symbol::module_symbol::ModuleSymbol; use crate::name_analysis::symbol::module_symbol::ModuleSymbol;
use crate::name_analysis::symbol::parameter_symbol::ParameterSymbol;
use crate::name_analysis::symbol::primitive_type_symbol::PrimitiveTypeSymbol;
use crate::name_analysis::symbol::source_definition::SourceDefinition; use crate::name_analysis::symbol::source_definition::SourceDefinition;
use crate::name_analysis::symbol::type_symbol::TypeSymbol;
use crate::name_analysis::symbol::use_symbol::{ConcreteUseSymbol, StarUseSymbol}; use crate::name_analysis::symbol::use_symbol::{ConcreteUseSymbol, StarUseSymbol};
use crate::name_analysis::symbol_table::SymbolTable; use crate::name_analysis::symbol_table::SymbolTable;
use crate::name_analysis::util::{ use crate::name_analysis::util::{
format_fqn, handle_insert_error, handle_lookup_error, join_fqn_parts, handle_insert_error, join_fqn_parts,
}; };
use std::cell::RefCell; use std::cell::RefCell;
use std::fmt::format;
use std::rc::Rc; use std::rc::Rc;
pub fn na_p1_compilation_unit( pub fn na_p1_compilation_unit(
@ -38,6 +33,8 @@ pub fn na_p1_compilation_unit(
.set_current_fqn(&fqn.identifiers().map(Identifier::name).collect::<Vec<_>>()); .set_current_fqn(&fqn.identifiers().map(Identifier::name).collect::<Vec<_>>());
} }
} }
} else {
symbol_table.set_current_fqn(&[]);
} }
symbol_table.push_scope(&format!("FileScope {}", file_name)); symbol_table.push_scope(&format!("FileScope {}", file_name));
@ -250,7 +247,7 @@ fn na_p1_function(
false, false,
Some(SourceDefinition::from_identifier(function.identifier())), Some(SourceDefinition::from_identifier(function.identifier())),
); );
let function_symbol = match symbol_table.insert_function_symbol(to_insert) { let maybe_function_symbol = match symbol_table.insert_function_symbol(to_insert) {
Ok(function_symbol) => Some(function_symbol), Ok(function_symbol) => Some(function_symbol),
Err(symbol_insert_error) => { Err(symbol_insert_error) => {
handle_insert_error( handle_insert_error(
@ -264,232 +261,14 @@ fn na_p1_function(
} }
}; };
if function_symbol.is_some() { if maybe_function_symbol.is_some() {
let mut as_ref_mut = function_symbol.as_ref().unwrap().borrow_mut(); function.set_function_symbol(maybe_function_symbol.as_ref().unwrap().clone());
}
// push a scope for this function // create a scope for this function
symbol_table.push_scope(&format!("FunctionScope {}", function.identifier().name())); symbol_table.push_scope(&format!("FunctionScope {}", function.identifier().name()));
function.set_scope_id(symbol_table.current_scope_id());
// generics
na_p1_generic_parameters(function.generics_mut(), symbol_table, diagnostics);
// parameters
as_ref_mut.set_parameter_symbols(na_p1_parameters(
function.parameters_mut(),
symbol_table,
diagnostics,
));
// return type
let return_type = na_p1_return_type(function.return_type_mut(), symbol_table, diagnostics);
if let Some(type_symbol) = return_type {
as_ref_mut.set_return_type(type_symbol);
}
symbol_table.push_scope(&format!(
"FunctionBodyScope {}",
function.identifier().name()
));
na_p1_function_body(
function.function_body_mut(),
symbol_table,
diagnostics
);
symbol_table.pop_scope(); symbol_table.pop_scope();
symbol_table.pop_scope();
}
function_symbol maybe_function_symbol
}
fn na_p1_parameters(
parameters: &mut Parameters,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> Vec<Rc<RefCell<ParameterSymbol>>> {
parameters
.parameters_mut()
.map(|parameter| na_p1_parameter(parameter, symbol_table, diagnostics))
.filter(Option::is_some)
.map(Option::unwrap)
.collect()
}
fn na_p1_parameter(
parameter: &mut Parameter,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> Option<Rc<RefCell<ParameterSymbol>>> {
let parameter_type_symbol = na_p1_type_use(parameter.type_use_mut(), symbol_table, diagnostics);
let to_insert = ParameterSymbol::new(
parameter.identifier().name(),
Some(SourceDefinition::from_identifier(parameter.identifier())),
parameter_type_symbol,
);
match symbol_table.insert_parameter_symbol(to_insert) {
Ok(parameter_symbol) => Some(parameter_symbol),
Err(symbol_insert_error) => {
handle_insert_error(
symbol_insert_error,
parameter.identifier().name(),
parameter.identifier().file_id(),
parameter.identifier().range(),
diagnostics,
);
None
}
}
}
fn na_p1_return_type(
return_type: &mut ReturnType,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> Option<TypeSymbol> {
na_p1_type_use(return_type.type_use_mut(), symbol_table, diagnostics)
}
fn na_p1_type_use(
type_use: &mut TypeUse,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> Option<TypeSymbol> {
match type_use {
TypeUse::PrimitiveType(primitive_type) => {
Some(TypeSymbol::Primitive(match primitive_type {
PrimitiveType::Byte => PrimitiveTypeSymbol::Byte,
PrimitiveType::Short => PrimitiveTypeSymbol::Short,
PrimitiveType::Char => PrimitiveTypeSymbol::Char,
PrimitiveType::Int => PrimitiveTypeSymbol::Int,
PrimitiveType::Long => PrimitiveTypeSymbol::Long,
PrimitiveType::Double => PrimitiveTypeSymbol::Double,
PrimitiveType::Bool => PrimitiveTypeSymbol::Boolean,
PrimitiveType::String => PrimitiveTypeSymbol::String,
PrimitiveType::TypedArray(typed_array) => {
na_p1_typed_array(typed_array, symbol_table, diagnostics)
}
PrimitiveType::Any => PrimitiveTypeSymbol::Any,
PrimitiveType::Void => PrimitiveTypeSymbol::Void,
}))
}
TypeUse::InterfaceOrClassTypeUse(interface_or_class_type) => {
match interface_or_class_type.identifier_or_fqn() {
IdentifierOrFqn::Identifier(identifier) => {
match symbol_table.lookup_type(identifier.name()) {
Ok(type_symbol) => {
interface_or_class_type.set_type_symbol(type_symbol.clone());
Some(type_symbol)
}
Err(symbol_lookup_error) => {
handle_lookup_error(
symbol_lookup_error,
identifier.name(),
identifier.file_id(),
identifier.range(),
diagnostics,
);
None
}
}
}
IdentifierOrFqn::FullyQualifiedName(fqn) => {
let fqn_parts = fqn
.identifiers()
.map(Identifier::name)
.collect::<Vec<&str>>();
match symbol_table.lookup_type_by_fqn(&fqn_parts) {
Ok(type_symbol) => {
interface_or_class_type.set_type_symbol(type_symbol.clone());
Some(type_symbol)
}
Err(symbol_lookup_error) => {
handle_lookup_error(
symbol_lookup_error,
&format_fqn(&fqn_parts),
fqn.file_id(),
fqn.range(),
diagnostics,
);
None
}
}
}
}
}
TypeUse::TupleTypeUse(tuple_type) => {
todo!()
}
TypeUse::FunctionTypeUse(function_type) => {
todo!()
}
}
}
fn na_p1_typed_array(
typed_array: &mut TypedArray,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> PrimitiveTypeSymbol {
let inner_type_use = typed_array
.generic_arguments_mut()
.type_use_list_mut()
.type_uses_mut()
.next()
.unwrap();
let inner_type_symbol = na_p1_type_use(inner_type_use, symbol_table, diagnostics);
PrimitiveTypeSymbol::TypedArray {
inner_type: inner_type_symbol.map(Box::from),
}
}
fn na_p1_generic_parameters(
generic_parameters: &mut GenericParameters,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
for identifier in generic_parameters.identifier_list().identifiers() {
let generic_type_symbol = GenericTypeSymbol::new(
identifier.name(),
Some(SourceDefinition::from_identifier(identifier)),
);
match symbol_table.insert_generic_type_symbol(generic_type_symbol) {
Ok(_) => {}
Err(symbol_insert_error) => {
handle_insert_error(
symbol_insert_error,
identifier.name(),
identifier.file_id(),
identifier.range(),
diagnostics,
);
}
}
}
}
fn na_p1_function_body(
function_body: &mut FunctionBody,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
match function_body {
FunctionBody::FunctionAliasBody(alias_body) => {
// set scope id for pass 2; see below
alias_body.set_scope_id(symbol_table.current_scope_id());
}
FunctionBody::FunctionEqualsBody(equals_body) => {
// see below
equals_body.set_scope_id(symbol_table.current_scope_id());
}
FunctionBody::FunctionBlockBody(block_body) => {
// we need to do all insertion/resolution in pass 2, because we
// might call functions/use classes/etc from the same compilation
// unit which haven't been defined yet. So the strategy is to set
// the scope id for the body and then in pass 2, set the symbol
// table's current scope to that id.
block_body.set_scope_id(symbol_table.current_scope_id());
}
}
} }

View File

@ -40,7 +40,7 @@ pub fn analyze_names<
'a, 'a,
F: Files<'a, FileId = usize, Name = &'a str, Source = &'a str> + ?Sized, F: Files<'a, FileId = usize, Name = &'a str, Source = &'a str> + ?Sized,
>( >(
compilation_units: &mut Vec<CompilationUnit>, compilation_units: &mut [CompilationUnit],
files: &'a F, files: &'a F,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
) -> Vec<DmDiagnostic> { ) -> Vec<DmDiagnostic> {
@ -57,7 +57,7 @@ pub fn analyze_names<
} }
// resolve symbols // resolve symbols
for compilation_unit in compilation_units { for compilation_unit in compilation_units.iter_mut() {
na_p2_compilation_unit(compilation_unit, symbol_table, &mut diagnostics); na_p2_compilation_unit(compilation_unit, symbol_table, &mut diagnostics);
} }
@ -67,22 +67,28 @@ pub fn analyze_names<
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::ast::ast_node::AstNodeRef;
use crate::ast::build::build_ast; use crate::ast::build::build_ast;
use crate::ast::pretty_print::PrettyPrint;
use crate::ast::walk::walk_depth_first;
use crate::name_analysis::symbol::use_symbol::StarUseSymbol;
use crate::name_analysis::symbol::{ExpressibleSymbol, PrimitiveTypeSymbol, TypeSymbol};
use crate::name_analysis::util::str_slice_to_rcs;
use crate::parser::{DeimosParser, Rule}; use crate::parser::{DeimosParser, Rule};
use crate::std_core::add_std_core_symbols; use crate::std_core::add_std_core_symbols;
use crate::util::indent_writer::IndentWriter;
use codespan_reporting::files::SimpleFiles; use codespan_reporting::files::SimpleFiles;
use codespan_reporting::term; use codespan_reporting::term;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use pest::Parser; use pest::Parser;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc; use std::rc::Rc;
use crate::name_analysis::symbol::use_symbol::StarUseSymbol;
fn parse_compilation_units<'a>( fn parse_compilation_units<'a>(
files: &mut SimpleFiles<&'a str, &'a str>, files: &mut SimpleFiles<&'a str, &'a str>,
sources: HashMap<&'a str, &'a str>, sources: HashMap<&'a str, &'a str>,
) -> Vec<CompilationUnit> { ) -> Vec<CompilationUnit> {
let mut compilation_units: Vec<CompilationUnit> = vec![]; let mut compilation_units = vec![];
for (file_name, source) in sources { for (file_name, source) in sources {
let parse_result = DeimosParser::parse(Rule::CompilationUnit, source); let parse_result = DeimosParser::parse(Rule::CompilationUnit, source);
@ -104,7 +110,7 @@ mod tests {
sources: HashMap<&'a str, &'a str>, sources: HashMap<&'a str, &'a str>,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
number_of_diagnostics: usize, number_of_diagnostics: usize,
) -> Vec<CompilationUnit> { ) -> (Vec<CompilationUnit>, SimpleFiles<&'a str, &'a str>) {
let mut files = SimpleFiles::<&'a str, &'a str>::new(); let mut files = SimpleFiles::<&'a str, &'a str>::new();
let mut compilation_units = parse_compilation_units(&mut files, sources); let mut compilation_units = parse_compilation_units(&mut files, sources);
@ -119,20 +125,44 @@ mod tests {
} }
eprintln!("{}", symbol_table); eprintln!("{}", symbol_table);
for compilation_unit in &compilation_units {
let mut acc = String::new();
let mut indent_writer = IndentWriter::new(0, " ", &mut acc);
compilation_unit
.pretty_print(&mut indent_writer)
.expect("Pretty print failed");
eprintln!("{}", acc);
}
} }
assert_eq!(number_of_diagnostics, diagnostics.len()); assert_eq!(number_of_diagnostics, diagnostics.len());
compilation_units (compilation_units, files)
} }
fn assert_no_diagnostics( fn assert_no_diagnostics<'a>(
sources: HashMap<&str, &str>, sources: HashMap<&'a str, &'a str>,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
) -> Vec<CompilationUnit> { ) -> (Vec<CompilationUnit>, SimpleFiles<&'a str, &'a str>) {
assert_number_of_diagnostics(sources, symbol_table, 0) assert_number_of_diagnostics(sources, symbol_table, 0)
} }
fn make_names_to_cus_map<'a>(
compilation_units: &'a [CompilationUnit],
files: &SimpleFiles<&'a str, &str>,
) -> HashMap<&'a str, &'a CompilationUnit> {
let mut map = HashMap::new();
for compilation_unit in compilation_units {
let file_id = compilation_unit.file_id();
let name = files.name(file_id).unwrap();
map.insert(name, compilation_unit);
}
map
}
#[test] #[test]
fn params_seen() { fn params_seen() {
let sources = HashMap::from([( let sources = HashMap::from([(
@ -144,7 +174,80 @@ mod tests {
", ",
)]); )]);
assert_no_diagnostics(sources, &mut SymbolTable::new()); let mut symbol_table = SymbolTable::new();
let (cus, files) = assert_no_diagnostics(sources, &mut symbol_table);
let results_map = make_names_to_cus_map(&cus, &files);
let main_cu = *results_map.get("main.dm").unwrap();
let mut found_main = false;
let mut found_x = false;
let mut found_args_ref = false;
walk_depth_first(main_cu, &mut |node| {
use AstNodeRef::*;
match node {
Function(function) => {
if function.identifier().name() == "main" {
found_main = true;
let function_symbol_ref = function.function_symbol().unwrap().borrow();
assert_eq!("main", function_symbol_ref.declared_name());
let parameter_symbols = function_symbol_ref.parameter_symbols();
assert_eq!(parameter_symbols.len(), 1);
let args_ref = parameter_symbols[0].borrow();
assert_eq!(args_ref.declared_name(), "args");
let args_type_symbol = args_ref.type_symbol().unwrap();
match args_type_symbol {
TypeSymbol::Primitive(primitive_type_symbol) => {
match primitive_type_symbol {
PrimitiveTypeSymbol::TypedArray { inner_type } => {
match *inner_type.clone().unwrap() {
TypeSymbol::Primitive(primitive_type_symbol) => {
match primitive_type_symbol {
PrimitiveTypeSymbol::String => {}
_ => panic!("Expected String for args' inner type.")
}
}
_ => panic!("Expected a primitive type symbol for args' inner type.")
}
}
_ => panic!("Expected a TypeArray for args."),
}
}
_ => panic!("Expected a primitive type symbol for args.")
}
}
}
VariableDeclaration(variable_declaration) => {
if variable_declaration.identifier().name() == "x" {
found_x = true;
assert_eq!(
variable_declaration
.variable_symbol()
.unwrap()
.borrow()
.declared_name(),
"x"
);
}
}
IdentifierExpression(identifier_expression) => {
if identifier_expression.identifier().name() == "args" {
found_args_ref = true;
match identifier_expression.expressible_symbol().unwrap() {
ExpressibleSymbol::Parameter(parameter_symbol) => {
assert_eq!(parameter_symbol.borrow().declared_name(), "args")
}
_ => panic!("Expected args variable use to be a parameter symbol."),
}
}
}
_ => {}
}
});
assert!(found_main);
assert!(found_x);
assert!(found_args_ref);
} }
#[test] #[test]
@ -204,17 +307,24 @@ mod tests {
fn main(args: Array<String>) fn main(args: Array<String>)
println args println args
end end
" ",
)]); )]);
let mut symbol_table = SymbolTable::new(); let mut symbol_table = SymbolTable::new();
let global_std_core_use = StarUseSymbol::new( let global_std_core_star_use_to_insert =
&[Rc::from("std"), Rc::from("core")], StarUseSymbol::new(&[Rc::from("std"), Rc::from("core")], None);
None let global_std_core_star_use = symbol_table
); .insert_star_use_symbol(global_std_core_star_use_to_insert)
symbol_table.insert_star_use_symbol(global_std_core_use)
.expect("Failed to insert star use symbol."); .expect("Failed to insert star use symbol.");
add_std_core_symbols(&mut symbol_table).expect("Failed to add std/core symbols."); add_std_core_symbols(&mut symbol_table).expect("Failed to add std/core symbols.");
let resolved_std_core_symbols = symbol_table
.find_usable_symbols_by_base_fqn(&str_slice_to_rcs(&["std", "core"]))
.unwrap();
global_std_core_star_use
.borrow_mut()
.set_resolved_symbols(resolved_std_core_symbols);
assert_no_diagnostics(sources, &mut symbol_table); assert_no_diagnostics(sources, &mut symbol_table);
} }
} }

View File

@ -1,9 +1,23 @@
use crate::ast::node::{AssignmentStatement, CompilationUnit, ConcreteUseStatement, ConcreteUseStatementSuffix, Expression, ExpressionStatement, Function, FunctionAliasBody, FunctionBlockBody, FunctionBody, Identifier, LValue, ModuleLevelDeclaration, StarUseStatement, Statement, TypeUse, UseStatement, UseStatementIdentifier, UseStatementPrefix, VariableDeclaration, VariableUse}; use crate::ast::node::{
AssignmentStatement, Call, CompilationUnit, ConcreteUseStatement, ConcreteUseStatementSuffix,
Expression, ExpressionList, ExpressionStatement, Function, FunctionAliasBody,
FunctionBlockBody, FunctionBody, FunctionEqualsBody, GenericParameters, Identifier,
IdentifierExpression, IdentifierOrFqn, LValue, LValueSuffix, ModuleLevelDeclaration,
ObjectIndex, Parameter, Parameters, PrimitiveType, ReturnType, StarUseStatement, Statement,
SuffixExpression, SuffixOperator, TypeUse, TypedArray, UseStatement, UseStatementIdentifier,
UseStatementPrefix, VariableDeclaration,
};
use crate::diagnostic::DmDiagnostic; use crate::diagnostic::DmDiagnostic;
use crate::name_analysis::symbol::source_definition::SourceDefinition; use crate::name_analysis::symbol::source_definition::SourceDefinition;
use crate::name_analysis::symbol::variable_symbol::VariableSymbol; use crate::name_analysis::symbol::variable_symbol::VariableSymbol;
use crate::name_analysis::symbol::{
GenericTypeSymbol, ParameterSymbol, PrimitiveTypeSymbol, TypeSymbol,
};
use crate::name_analysis::symbol_table::{SymbolLookupError, SymbolTable}; use crate::name_analysis::symbol_table::{SymbolLookupError, SymbolTable};
use crate::name_analysis::util::{handle_insert_error, handle_lookup_error, join_fqn_parts}; use crate::name_analysis::util::{
format_fqn, handle_insert_error, handle_lookup_error, join_fqn_parts,
};
use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
pub fn na_p2_compilation_unit( pub fn na_p2_compilation_unit(
@ -149,9 +163,206 @@ fn na_p2_function(
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>, diagnostics: &mut Vec<DmDiagnostic>,
) { ) {
na_p2_function_body(function.function_body_mut(), symbol_table, diagnostics); // change to this function's scope
symbol_table.set_current_scope(*function.scope_id().unwrap());
// generics
na_p2_generic_parameters(function.generics_mut(), symbol_table, diagnostics);
// parameters
let parameter_symbols = na_p2_parameters(function.parameters_mut(), symbol_table, diagnostics);
function
.function_symbol()
.unwrap()
.borrow_mut()
.set_parameter_symbols(parameter_symbols);
// return type
let maybe_return_type_symbol =
na_p2_return_type(function.return_type_mut(), symbol_table, diagnostics);
if let Some(return_type_symbol) = maybe_return_type_symbol {
function
.function_symbol()
.unwrap()
.borrow_mut()
.set_return_type(return_type_symbol);
} }
// push a scope for the body and check the body
symbol_table.push_scope(&format!(
"FunctionBodyScope {}",
function.identifier().name()
));
na_p2_function_body(function.function_body_mut(), symbol_table, diagnostics);
symbol_table.pop_scope();
}
fn na_p2_generic_parameters(
generic_parameters: &mut GenericParameters,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
for identifier in generic_parameters.identifier_list().identifiers() {
let generic_type_symbol = GenericTypeSymbol::new(
identifier.name(),
Some(SourceDefinition::from_identifier(identifier)),
);
match symbol_table.insert_generic_type_symbol(generic_type_symbol) {
Ok(_) => {}
Err(symbol_insert_error) => {
handle_insert_error(
symbol_insert_error,
identifier.name(),
identifier.file_id(),
identifier.range(),
diagnostics,
);
}
}
}
}
fn na_p2_parameters(
parameters: &mut Parameters,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> Vec<Rc<RefCell<ParameterSymbol>>> {
parameters
.parameters_mut()
.map(|parameter| na_p2_parameter(parameter, symbol_table, diagnostics))
.filter(Option::is_some)
.map(Option::unwrap)
.collect()
}
fn na_p2_parameter(
parameter: &mut Parameter,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> Option<Rc<RefCell<ParameterSymbol>>> {
let parameter_type_symbol = na_p2_type_use(parameter.type_use_mut(), symbol_table, diagnostics);
let to_insert = ParameterSymbol::new(
parameter.identifier().name(),
Some(SourceDefinition::from_identifier(parameter.identifier())),
parameter_type_symbol,
);
match symbol_table.insert_parameter_symbol(to_insert) {
Ok(parameter_symbol) => Some(parameter_symbol),
Err(symbol_insert_error) => {
handle_insert_error(
symbol_insert_error,
parameter.identifier().name(),
parameter.identifier().file_id(),
parameter.identifier().range(),
diagnostics,
);
None
}
}
}
fn na_p2_return_type(
return_type: &mut ReturnType,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> Option<TypeSymbol> {
na_p2_type_use(return_type.type_use_mut(), symbol_table, diagnostics)
}
fn na_p2_type_use(
type_use: &mut TypeUse,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> Option<TypeSymbol> {
match type_use {
TypeUse::PrimitiveType(primitive_type) => {
Some(TypeSymbol::Primitive(match primitive_type {
PrimitiveType::Byte => PrimitiveTypeSymbol::Byte,
PrimitiveType::Short => PrimitiveTypeSymbol::Short,
PrimitiveType::Char => PrimitiveTypeSymbol::Char,
PrimitiveType::Int => PrimitiveTypeSymbol::Int,
PrimitiveType::Long => PrimitiveTypeSymbol::Long,
PrimitiveType::Double => PrimitiveTypeSymbol::Double,
PrimitiveType::Bool => PrimitiveTypeSymbol::Boolean,
PrimitiveType::String => PrimitiveTypeSymbol::String,
PrimitiveType::TypedArray(typed_array) => {
na_p2_typed_array(typed_array, symbol_table, diagnostics)
}
PrimitiveType::Any => PrimitiveTypeSymbol::Any,
PrimitiveType::Void => PrimitiveTypeSymbol::Void,
}))
}
TypeUse::InterfaceOrClassTypeUse(interface_or_class_type) => {
match interface_or_class_type.identifier_or_fqn() {
IdentifierOrFqn::Identifier(identifier) => {
match symbol_table.lookup_type(identifier.name()) {
Ok(type_symbol) => {
interface_or_class_type.set_type_symbol(type_symbol.clone());
Some(type_symbol)
}
Err(symbol_lookup_error) => {
handle_lookup_error(
symbol_lookup_error,
identifier.name(),
identifier.file_id(),
identifier.range(),
diagnostics,
);
None
}
}
}
IdentifierOrFqn::FullyQualifiedName(fqn) => {
let fqn_parts = fqn
.identifiers()
.map(Identifier::name)
.collect::<Vec<&str>>();
match symbol_table.lookup_type_by_fqn(&fqn_parts) {
Ok(type_symbol) => {
interface_or_class_type.set_type_symbol(type_symbol.clone());
Some(type_symbol)
}
Err(symbol_lookup_error) => {
handle_lookup_error(
symbol_lookup_error,
&format_fqn(&fqn_parts),
fqn.file_id(),
fqn.range(),
diagnostics,
);
None
}
}
}
}
}
TypeUse::TupleTypeUse(tuple_type) => {
todo!()
}
TypeUse::FunctionTypeUse(function_type) => {
todo!()
}
}
}
fn na_p2_typed_array(
typed_array: &mut TypedArray,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> PrimitiveTypeSymbol {
let inner_type_use = typed_array
.generic_arguments_mut()
.type_use_list_mut()
.type_uses_mut()
.next()
.unwrap();
let inner_type_symbol = na_p2_type_use(inner_type_use, symbol_table, diagnostics);
PrimitiveTypeSymbol::TypedArray {
inner_type: inner_type_symbol.map(Box::from),
}
}
/// **Note:** caller needs to push a scope before calling.
fn na_p2_function_body( fn na_p2_function_body(
function_body: &mut FunctionBody, function_body: &mut FunctionBody,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
@ -161,7 +372,9 @@ fn na_p2_function_body(
FunctionBody::FunctionAliasBody(alias_body) => { FunctionBody::FunctionAliasBody(alias_body) => {
na_p2_function_alias_body(alias_body, symbol_table, diagnostics); na_p2_function_alias_body(alias_body, symbol_table, diagnostics);
} }
FunctionBody::FunctionEqualsBody(equals_body) => {} FunctionBody::FunctionEqualsBody(equals_body) => {
na_p2_function_equals_body(equals_body, symbol_table, diagnostics);
}
FunctionBody::FunctionBlockBody(block_body) => { FunctionBody::FunctionBlockBody(block_body) => {
na_p2_function_block_body(block_body, symbol_table, diagnostics); na_p2_function_block_body(block_body, symbol_table, diagnostics);
} }
@ -173,10 +386,8 @@ fn na_p2_function_alias_body(
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>, diagnostics: &mut Vec<DmDiagnostic>,
) { ) {
let maybe_function_symbol = symbol_table.lookup_function_symbol( let maybe_function_symbol =
function_alias_body.identifier().name(), symbol_table.lookup_function_symbol(function_alias_body.identifier().name());
*function_alias_body.scope_id().unwrap(),
);
match maybe_function_symbol { match maybe_function_symbol {
Ok(function_symbol) => { Ok(function_symbol) => {
function_alias_body.set_resolved_function_symbol(function_symbol); function_alias_body.set_resolved_function_symbol(function_symbol);
@ -193,12 +404,23 @@ fn na_p2_function_alias_body(
} }
} }
fn na_p2_function_equals_body(
function_equals_body: &mut FunctionEqualsBody,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
na_p2_expression(
function_equals_body.expression_mut(),
symbol_table,
diagnostics,
);
}
fn na_p2_function_block_body( fn na_p2_function_block_body(
function_block_body: &mut FunctionBlockBody, function_block_body: &mut FunctionBlockBody,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>, diagnostics: &mut Vec<DmDiagnostic>,
) { ) {
symbol_table.set_current_scope(*function_block_body.scope_id().unwrap());
for statement in function_block_body.statements_mut() { for statement in function_block_body.statements_mut() {
na_p2_statement(statement, symbol_table, diagnostics); na_p2_statement(statement, symbol_table, diagnostics);
} }
@ -278,8 +500,16 @@ fn na_p2_assignment_statement(
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>, diagnostics: &mut Vec<DmDiagnostic>,
) { ) {
na_p2_l_value(assignment_statement.l_value_mut(), symbol_table, diagnostics); na_p2_l_value(
na_p2_expression(assignment_statement.expression_mut(), symbol_table, diagnostics); assignment_statement.l_value_mut(),
symbol_table,
diagnostics,
);
na_p2_expression(
assignment_statement.expression_mut(),
symbol_table,
diagnostics,
);
} }
fn na_p2_l_value( fn na_p2_l_value(
@ -287,8 +517,43 @@ fn na_p2_l_value(
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>, diagnostics: &mut Vec<DmDiagnostic>,
) { ) {
na_p2_variable_use(l_value.variable_use_mut(), symbol_table, diagnostics); match symbol_table.lookup_lv_symbol(l_value.identifier().name()) {
// TODO: suffixes Ok(lv_symbol) => {
l_value.set_lv_symbol(lv_symbol);
}
Err(symbol_lookup_error) => {
handle_lookup_error(
symbol_lookup_error,
l_value.identifier().name(),
l_value.identifier().file_id(),
l_value.identifier().range(),
diagnostics,
);
}
}
// "Name analysis" of suffixes is done during type-checking. We don't want to deal with getting
// out the different potential return types of things until that point, especially if we end up
// adding dynamic objects to the language. However, suffixes may contain things (like arguments)
// which we do need to recurse into.
for l_value_suffix in l_value.suffixes_mut() {
na_p2_l_value_suffix(l_value_suffix, symbol_table, diagnostics);
}
}
fn na_p2_l_value_suffix(
l_value_suffix: &mut LValueSuffix,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
match l_value_suffix {
LValueSuffix::ObjectProperty(_) => {
// no-op; properties are checked during type-checking for soundness
}
LValueSuffix::ObjectIndex(object_index) => {
// check inner expression
na_p2_expression(object_index.expression_mut(), symbol_table, diagnostics);
}
}
} }
fn na_p2_expression_statement( fn na_p2_expression_statement(
@ -296,29 +561,12 @@ fn na_p2_expression_statement(
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>, diagnostics: &mut Vec<DmDiagnostic>,
) { ) {
na_p2_expression(expression_statement.expression_mut(), symbol_table, diagnostics); na_p2_expression(
} expression_statement.expression_mut(),
symbol_table,
fn na_p2_variable_use(
variable_use: &mut VariableUse,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
match symbol_table.lookup_lv_symbol(variable_use.identifier().name()) {
Ok(lv_symbol) => {
variable_use.set_lv_symbol(lv_symbol);
}
Err(symbol_lookup_error) => {
handle_lookup_error(
symbol_lookup_error,
variable_use.identifier().name(),
variable_use.identifier().file_id(),
variable_use.identifier().range(),
diagnostics, diagnostics,
); );
} }
}
}
fn na_p2_expression( fn na_p2_expression(
expression: &mut Expression, expression: &mut Expression,
@ -351,13 +599,13 @@ fn na_p2_expression(
todo!() todo!()
} }
Expression::Suffix(suffix) => { Expression::Suffix(suffix) => {
todo!() na_p2_suffix_expression(suffix, symbol_table, diagnostics);
} }
Expression::Literal(literal) => { Expression::Literal(literal) => {
todo!() todo!()
} }
Expression::VariableUse(variable_use) => { Expression::Identifier(identifier_expression) => {
na_p2_variable_use(variable_use, symbol_table, diagnostics); na_p2_identifier_expression(identifier_expression, symbol_table, diagnostics);
} }
Expression::Fqn(fqn) => { Expression::Fqn(fqn) => {
todo!() todo!()
@ -371,24 +619,110 @@ fn na_p2_expression(
} }
} }
fn na_p2_type_use( fn na_p2_suffix_expression(
type_use: &mut TypeUse, suffix_expression: &mut SuffixExpression,
symbol_table: &mut SymbolTable, symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>, diagnostics: &mut Vec<DmDiagnostic>,
) { ) {
match type_use { na_p2_expression(
TypeUse::PrimitiveType(primitive_type_use) => { suffix_expression.expression_mut(),
todo!() symbol_table,
diagnostics,
);
na_p2_suffix_operator(suffix_expression.operator_mut(), symbol_table, diagnostics);
} }
TypeUse::InterfaceOrClassTypeUse(interface_or_class_type_use) => {
todo!() fn na_p2_suffix_operator(
suffix_operator: &mut SuffixOperator,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
match suffix_operator {
SuffixOperator::PlusPlus => {
// no-op
} }
TypeUse::TupleTypeUse(tuple_type_use) => { SuffixOperator::MinusMinus => {
todo!() // no-op
} }
TypeUse::FunctionTypeUse(function_type_use) => { SuffixOperator::ObjectProperty(_) => {
todo!() // no-op; props checked during type checking
}
SuffixOperator::ObjectIndex(object_index) => {
na_p2_object_index(object_index, symbol_table, diagnostics);
}
SuffixOperator::Call(call) => {
na_p2_call(call, symbol_table, diagnostics);
} }
} }
} }
fn na_p2_object_index(
object_index: &mut ObjectIndex,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
na_p2_expression(object_index.expression_mut(), symbol_table, diagnostics);
}
fn na_p2_call(
call: &mut Call,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
// TODO: modify the ast-gen so that we don't have to match on these. They should be the same.
match call {
Call::ParenthesesCall(parentheses_call) => {
if let Some(turbo_fish) = parentheses_call.turbo_fish_mut() {
todo!()
}
if let Some(expression_list) = parentheses_call.expression_list_mut() {
na_p2_expression_list(expression_list, symbol_table, diagnostics);
}
if let Some(closure) = parentheses_call.closure_mut() {
todo!()
}
}
Call::NonParenthesesCall(non_parentheses_call) => {
if let Some(turbo_fish) = non_parentheses_call.turbo_fish_mut() {
todo!()
}
if let Some(expression_list) = non_parentheses_call.expression_list_mut() {
na_p2_expression_list(expression_list, symbol_table, diagnostics);
}
if let Some(closure) = non_parentheses_call.closure_mut() {
todo!()
}
}
}
}
fn na_p2_identifier_expression(
identifier_expression: &mut IdentifierExpression,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
match symbol_table.lookup_expressible_symbol(identifier_expression.identifier().name()) {
Ok(expressible_symbol) => {
identifier_expression.set_expressible_symbol(expressible_symbol);
}
Err(symbol_lookup_error) => {
handle_lookup_error(
symbol_lookup_error,
identifier_expression.identifier().name(),
identifier_expression.identifier().file_id(),
identifier_expression.identifier().range(),
diagnostics,
);
}
}
}
fn na_p2_expression_list(
expression_list: &mut ExpressionList,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
for expression in expression_list.expressions_mut() {
na_p2_expression(expression, symbol_table, diagnostics);
}
}

View File

@ -0,0 +1,33 @@
use crate::name_analysis::symbol::{ClassMemberSymbol, ClassSymbol, FunctionSymbol, ParameterSymbol, Symbol, VariableSymbol};
use std::cell::RefCell;
use std::rc::Rc;
pub enum ExpressibleSymbol {
Class(Rc<RefCell<ClassSymbol>>),
Function(Rc<RefCell<FunctionSymbol>>),
ClassMember(Rc<RefCell<ClassMemberSymbol>>),
Parameter(Rc<RefCell<ParameterSymbol>>),
Variable(Rc<RefCell<VariableSymbol>>),
}
impl ExpressibleSymbol {
pub fn to_symbol(self) -> Rc<RefCell<dyn Symbol>> {
match self {
ExpressibleSymbol::Class(class_symbol) => {
class_symbol as Rc<RefCell<dyn Symbol>>
}
ExpressibleSymbol::Function(function_symbol) => {
function_symbol as Rc<RefCell<dyn Symbol>>
}
ExpressibleSymbol::ClassMember(class_member_symbol) => {
class_member_symbol as Rc<RefCell<dyn Symbol>>
}
ExpressibleSymbol::Parameter(parameter_symbol) => {
parameter_symbol as Rc<RefCell<dyn Symbol>>
}
ExpressibleSymbol::Variable(variable_symbol) => {
variable_symbol as Rc<RefCell<dyn Symbol>>
}
}
}
}

View File

@ -1,5 +1,6 @@
pub mod class_member_symbol; pub mod class_member_symbol;
pub mod class_symbol; pub mod class_symbol;
pub mod expressible_symbol;
pub mod function_symbol; pub mod function_symbol;
pub mod generic_type_symbol; pub mod generic_type_symbol;
pub mod interface_symbol; pub mod interface_symbol;
@ -18,10 +19,10 @@ use crate::name_analysis::symbol::source_definition::SourceDefinition;
use std::fmt::Debug; use std::fmt::Debug;
pub use self::{ pub use self::{
class_member_symbol::*, class_symbol::*, function_symbol::*, function_symbol::*, class_member_symbol::*, class_symbol::*, expressible_symbol::*, function_symbol::*,
generic_type_symbol::*, interface_symbol::*, lv_symbol::*, module_level_symbol::*, function_symbol::*, generic_type_symbol::*, interface_symbol::*, lv_symbol::*,
module_symbol::*, parameter_symbol::*, primitive_type_symbol::*, type_symbol::*, module_level_symbol::*, module_symbol::*, parameter_symbol::*, primitive_type_symbol::*,
usable_symbol::*, use_symbol::*, variable_symbol::*, type_symbol::*, usable_symbol::*, use_symbol::*, variable_symbol::*,
}; };
pub trait Symbol: Debug { pub trait Symbol: Debug {

View File

@ -30,6 +30,14 @@ impl ParameterSymbol {
pub fn declared_name_owned(&self) -> Rc<str> { pub fn declared_name_owned(&self) -> Rc<str> {
self.declared_name.clone() self.declared_name.clone()
} }
pub fn type_symbol(&self) -> Option<&TypeSymbol> {
self.type_symbol.as_ref()
}
pub fn type_symbol_mut(&mut self) -> Option<&mut TypeSymbol> {
self.type_symbol.as_mut()
}
} }
impl Symbol for ParameterSymbol { impl Symbol for ParameterSymbol {
@ -43,6 +51,7 @@ impl Debug for ParameterSymbol {
f.debug_struct("ParameterSymbol") f.debug_struct("ParameterSymbol")
.field("declared_name", &self.declared_name) .field("declared_name", &self.declared_name)
.field("source_definition", &self.source_definition) .field("source_definition", &self.source_definition)
.field("type_symbol", &self.type_symbol)
.finish() .finish()
} }
} }

View File

@ -109,9 +109,10 @@ impl Symbol for StarUseSymbol {
impl Debug for StarUseSymbol { impl Debug for StarUseSymbol {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StarUseStatementSymbol") f.debug_struct("StarUseSymbol")
.field("fqn", &join_fqn_parts(&self.fqn_parts)) .field("fqn", &join_fqn_parts(&self.fqn_parts))
.field("source_definition", &self.source_definition) .field("source_definition", &self.source_definition)
.field("resolved_symbols", &self.resolved_symbols)
.finish() .finish()
} }
} }

View File

@ -5,7 +5,8 @@ use crate::name_analysis::symbol::parameter_symbol::ParameterSymbol;
use crate::name_analysis::symbol::type_symbol::TypeSymbol; use crate::name_analysis::symbol::type_symbol::TypeSymbol;
use crate::name_analysis::symbol::usable_symbol::UsableSymbol; use crate::name_analysis::symbol::usable_symbol::UsableSymbol;
use crate::name_analysis::symbol::use_symbol::{ConcreteUseSymbol, StarUseSymbol}; use crate::name_analysis::symbol::use_symbol::{ConcreteUseSymbol, StarUseSymbol};
use crate::name_analysis::symbol::{LVSymbol, Symbol}; use crate::name_analysis::symbol::variable_symbol::VariableSymbol;
use crate::name_analysis::symbol::{ExpressibleSymbol, LVSymbol, Symbol};
use crate::name_analysis::symbol_table::fqn_context::FqnContext; use crate::name_analysis::symbol_table::fqn_context::FqnContext;
use crate::name_analysis::symbol_table::symbol_tree::SymbolTree; use crate::name_analysis::symbol_table::symbol_tree::SymbolTree;
use crate::name_analysis::symbol_table::SymbolInsertError::SymbolAlreadyDefined; use crate::name_analysis::symbol_table::SymbolInsertError::SymbolAlreadyDefined;
@ -14,7 +15,6 @@ use std::cell::RefCell;
use std::fmt::Display; use std::fmt::Display;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use crate::name_analysis::symbol::variable_symbol::VariableSymbol;
pub(self) mod fqn_context; pub(self) mod fqn_context;
mod scope; mod scope;
@ -25,6 +25,7 @@ pub enum SymbolInsertError {
SymbolAlreadyDefined(Rc<RefCell<dyn Symbol>>), SymbolAlreadyDefined(Rc<RefCell<dyn Symbol>>),
} }
#[derive(Debug)]
pub enum SymbolLookupError { pub enum SymbolLookupError {
NoDefinition, NoDefinition,
NoSuchNamespace, NoSuchNamespace,
@ -44,7 +45,7 @@ impl SymbolTable {
Self { Self {
scopes: vec![Scope::new(None, 0, String::from("GlobalScope"))], scopes: vec![Scope::new(None, 0, String::from("GlobalScope"))],
current_scope_id: 0, current_scope_id: 0,
symbol_tree: Box::new(SymbolTree::new()), symbol_tree: Box::new(SymbolTree::new("<DEFAULT>")),
fqn_context: Box::new(FqnContext::new()), fqn_context: Box::new(FqnContext::new()),
} }
} }
@ -122,6 +123,15 @@ impl SymbolTable {
self.symbol_tree.register_module_by_fqn_parts(fqn_parts); self.symbol_tree.register_module_by_fqn_parts(fqn_parts);
} }
pub fn register_function_symbol(
&mut self,
function_symbol: FunctionSymbol,
) -> Rc<RefCell<FunctionSymbol>> {
let as_rc = Rc::new(RefCell::new(function_symbol));
self.symbol_tree.register_function(as_rc.clone());
as_rc
}
pub fn find_usable_symbols_by_base_fqn( pub fn find_usable_symbols_by_base_fqn(
&self, &self,
fqn_parts: &[Rc<str>], fqn_parts: &[Rc<str>],
@ -267,8 +277,11 @@ impl SymbolTable {
todo!() todo!()
} }
pub fn lookup_function_symbol(&self, declared_name: &str, scope_id: usize) -> Result<Rc<RefCell<FunctionSymbol>>, SymbolLookupError> { pub fn lookup_function_symbol(
let mut current_scope: Option<&Scope> = self.scopes.get(scope_id); &self,
declared_name: &str,
) -> Result<Rc<RefCell<FunctionSymbol>>, SymbolLookupError> {
let mut current_scope: Option<&Scope> = Some(self.current_scope());
while let Some(scope) = current_scope.take() { while let Some(scope) = current_scope.take() {
if let Some(function_symbol) = scope.find_function_symbol(declared_name) { if let Some(function_symbol) = scope.find_function_symbol(declared_name) {
return Ok(function_symbol.clone()); return Ok(function_symbol.clone());
@ -285,7 +298,24 @@ impl SymbolTable {
let mut current_scope: Option<&Scope> = Some(self.current_scope()); let mut current_scope: Option<&Scope> = Some(self.current_scope());
while let Some(scope) = current_scope.take() { while let Some(scope) = current_scope.take() {
if let Some(lv_symbol) = scope.find_lv_symbol(declared_name) { if let Some(lv_symbol) = scope.find_lv_symbol(declared_name) {
return Ok(lv_symbol) return Ok(lv_symbol);
} else {
current_scope = scope
.parent()
.and_then(|parent_id| self.scopes.get(parent_id));
}
}
Err(SymbolLookupError::NoDefinition)
}
pub fn lookup_expressible_symbol(
&self,
declared_name: &str,
) -> Result<ExpressibleSymbol, SymbolLookupError> {
let mut current_scope: Option<&Scope> = Some(self.current_scope());
while let Some(scope) = current_scope.take() {
if let Some(expressible_symbol) = scope.find_expressible_symbol(declared_name) {
return Ok(expressible_symbol);
} else { } else {
current_scope = scope current_scope = scope
.parent() .parent()
@ -302,6 +332,8 @@ impl Display for SymbolTable {
for scope in &self.scopes { for scope in &self.scopes {
writeln!(f, "{}", scope)?; writeln!(f, "{}", scope)?;
} }
writeln!(f, "---SymbolTable: SymbolTree---")?;
writeln!(f, "{}", self.symbol_tree)?;
Ok(()) Ok(())
} }
} }

View File

@ -10,6 +10,7 @@ use crate::name_analysis::symbol::parameter_symbol::ParameterSymbol;
use crate::name_analysis::symbol::type_symbol::TypeSymbol; use crate::name_analysis::symbol::type_symbol::TypeSymbol;
use crate::name_analysis::symbol::use_symbol::{ConcreteUseSymbol, StarUseSymbol}; use crate::name_analysis::symbol::use_symbol::{ConcreteUseSymbol, StarUseSymbol};
use crate::name_analysis::symbol::variable_symbol::VariableSymbol; use crate::name_analysis::symbol::variable_symbol::VariableSymbol;
use crate::name_analysis::symbol::{ExpressibleSymbol, UsableSymbol};
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
@ -204,10 +205,83 @@ impl Scope {
}) })
} }
pub fn find_function_symbol(&self, declared_name: &str) -> Option<&Rc<RefCell<FunctionSymbol>>> { pub fn find_function_symbol(
&self,
declared_name: &str,
) -> Option<&Rc<RefCell<FunctionSymbol>>> {
self.function_symbols.get(declared_name) self.function_symbols.get(declared_name)
} }
pub fn find_expressible_symbol(&self, declared_name: &str) -> Option<ExpressibleSymbol> {
// First, try all the usual suspects: classes, functions, members, parameters, and variables
// which are in scope. If we don't find any of those, try looking through imports; start
// with concrete imports by declared name; if none of those match, check all resolved
// symbols of the star imports until one is found or none.
// !! Eventually consider registering star-imported symbols in a scope so that we can just
// check them via the hash maps instead of looping through the star symbols.
self.class_symbols
.get(declared_name)
.map(|class_symbol| ExpressibleSymbol::Class(class_symbol.clone()))
.or_else(|| {
self.function_symbols
.get(declared_name)
.map(|function_symbol| ExpressibleSymbol::Function(function_symbol.clone()))
})
.or_else(|| {
self.class_member_symbols
.get(declared_name)
.map(|class_member_symbol| {
ExpressibleSymbol::ClassMember(class_member_symbol.clone())
})
})
.or_else(|| {
self.parameter_symbols
.get(declared_name)
.map(|parameter_symbol| ExpressibleSymbol::Parameter(parameter_symbol.clone()))
})
.or_else(|| {
self.variable_symbols
.get(declared_name)
.map(|variable_symbol| ExpressibleSymbol::Variable(variable_symbol.clone()))
})
.or_else(|| {
if let Some(concrete_use_symbol) = self.concrete_use_symbols.get(declared_name) {
return match concrete_use_symbol.borrow().resolved_symbol().unwrap() {
UsableSymbol::Interface(_) => {
None
}
UsableSymbol::Class(class_symbol) => {
Some(ExpressibleSymbol::Class(class_symbol.clone()))
}
UsableSymbol::Function(function_symbol) => {
Some(ExpressibleSymbol::Function(function_symbol.clone()))
}
}
}
None
})
.or_else(|| {
for star_use_symbol in self.star_use_symbols.values() {
for usable_symbol in star_use_symbol.borrow().resolved_symbols() {
match usable_symbol {
UsableSymbol::Class(class_symbol) => {
if class_symbol.borrow().declared_name() == declared_name {
return Some(ExpressibleSymbol::Class(class_symbol.clone()));
}
}
UsableSymbol::Function(function_symbol) => {
if function_symbol.borrow().declared_name() == declared_name {
return Some(ExpressibleSymbol::Function(function_symbol.clone()));
}
}
_ => continue,
}
}
}
None
})
}
pub fn debug_name(&self) -> &str { pub fn debug_name(&self) -> &str {
&self.debug_name &self.debug_name
} }

View File

@ -4,12 +4,15 @@ use crate::name_analysis::symbol::interface_symbol::InterfaceSymbol;
use crate::name_analysis::symbol::module_symbol::ModuleSymbol; use crate::name_analysis::symbol::module_symbol::ModuleSymbol;
use crate::name_analysis::symbol::usable_symbol::UsableSymbol; use crate::name_analysis::symbol::usable_symbol::UsableSymbol;
use crate::name_analysis::symbol_table::SymbolLookupError; use crate::name_analysis::symbol_table::SymbolLookupError;
use crate::util::indent_writer::IndentWriter;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::rc::Rc; use std::rc::Rc;
#[derive(Debug)] #[derive(Debug)]
pub struct SymbolTree { pub struct SymbolTree {
debug_name: String,
children: Box<HashMap<Rc<str>, SymbolTree>>, children: Box<HashMap<Rc<str>, SymbolTree>>,
classes: Box<HashMap<Rc<str>, Rc<RefCell<ClassSymbol>>>>, classes: Box<HashMap<Rc<str>, Rc<RefCell<ClassSymbol>>>>,
interfaces: Box<HashMap<Rc<str>, Rc<RefCell<InterfaceSymbol>>>>, interfaces: Box<HashMap<Rc<str>, Rc<RefCell<InterfaceSymbol>>>>,
@ -17,8 +20,9 @@ pub struct SymbolTree {
} }
impl SymbolTree { impl SymbolTree {
pub fn new() -> Self { pub fn new(debug_name: &str) -> Self {
Self { Self {
debug_name: debug_name.to_string(),
children: Box::new(HashMap::new()), children: Box::new(HashMap::new()),
classes: Box::new(HashMap::new()), classes: Box::new(HashMap::new()),
interfaces: Box::new(HashMap::new()), interfaces: Box::new(HashMap::new()),
@ -78,13 +82,13 @@ impl SymbolTree {
} }
if fqn_parts.len() == 1 { if fqn_parts.len() == 1 {
self.children self.children
.insert(fqn_parts[0].clone(), SymbolTree::new()); .insert(fqn_parts[0].clone(), SymbolTree::new(fqn_parts[0].as_ref()));
} else { } else {
if self.children.contains_key(fqn_parts[0].as_ref()) { if self.children.contains_key(fqn_parts[0].as_ref()) {
let child = self.children.get_mut(fqn_parts[0].as_ref()).unwrap(); let child = self.children.get_mut(fqn_parts[0].as_ref()).unwrap();
child.recurse_register_module(&fqn_parts[1..]); child.recurse_register_module(&fqn_parts[1..]);
} else { } else {
let mut child = SymbolTree::new(); let mut child = SymbolTree::new(fqn_parts[0].as_ref());
child.recurse_register_module(&fqn_parts[1..]); child.recurse_register_module(&fqn_parts[1..]);
self.children.insert(fqn_parts[0].clone(), child); self.children.insert(fqn_parts[0].clone(), child);
} }
@ -105,6 +109,12 @@ impl SymbolTree {
panic!("Unable to register function fqn with no parts."); panic!("Unable to register function fqn with no parts.");
} }
if fqn_parts.len() == 1 { if fqn_parts.len() == 1 {
if self.functions.contains_key(fqn_parts[0].as_ref()) {
panic!(
"There is already a function registered in this symbol tree with the name {}",
fqn_parts[0]
);
}
self.functions.insert(fqn_parts[0].clone(), function_symbol); self.functions.insert(fqn_parts[0].clone(), function_symbol);
} else { } else {
if self.children.contains_key(fqn_parts[0].as_ref()) { if self.children.contains_key(fqn_parts[0].as_ref()) {
@ -137,7 +147,7 @@ impl SymbolTree {
_ => { _ => {
if self.children.contains_key(fqn_parts[0].as_ref()) { if self.children.contains_key(fqn_parts[0].as_ref()) {
let child = self.children.get(fqn_parts[0].as_ref()).unwrap(); let child = self.children.get(fqn_parts[0].as_ref()).unwrap();
child.find_all_by_base_fqn(fqn_parts) child.find_all_by_base_fqn(&fqn_parts[1..])
} else { } else {
Err(SymbolLookupError::NoSuchNamespace) Err(SymbolLookupError::NoSuchNamespace)
} }
@ -145,3 +155,55 @@ impl SymbolTree {
} }
} }
} }
enum FormatAction<'a> {
PrintSymbolTree(&'a SymbolTree),
IncreaseIndent,
DecreaseIndent
}
impl Display for SymbolTree {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut acc = String::new();
let mut indent_writer = IndentWriter::new(0, " ", &mut acc);
let mut stack: Vec<FormatAction> = vec![];
stack.push(FormatAction::PrintSymbolTree(self));
while let Some(format_action) = stack.pop() {
match format_action {
FormatAction::PrintSymbolTree(symbol_tree) => {
// reverse order: start with decrease
stack.push(FormatAction::DecreaseIndent);
// add children
for child in symbol_tree.children.values() {
stack.push(FormatAction::PrintSymbolTree(child));
}
// increase for the first child
stack.push(FormatAction::IncreaseIndent);
indent_writer.writeln_indented(&format!("SymbolTree(debug_name = {})", symbol_tree.debug_name))?;
indent_writer.increase_indent();
for interface in symbol_tree.interfaces.values() {
indent_writer.writeln_indented(&format!("Interface({:?}", interface))?;
}
for class in symbol_tree.classes.values() {
indent_writer.writeln_indented(&format!("Class({:?})", class))?;
}
for function in symbol_tree.functions.values() {
indent_writer.writeln_indented(&format!("Function({:?})", function))?;
}
indent_writer.decrease_indent();
}
FormatAction::IncreaseIndent => {
indent_writer.increase_indent();
}
FormatAction::DecreaseIndent => {
indent_writer.decrease_indent();
}
}
}
writeln!(f, "{}", acc)
}
}

View File

@ -50,12 +50,12 @@ pub fn handle_lookup_error(
Label::primary(error_file_id, error_range).with_message("Symbol used here."), Label::primary(error_file_id, error_range).with_message("Symbol used here."),
); );
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
}, }
SymbolLookupError::NoSuchNamespace => { SymbolLookupError::NoSuchNamespace => {
let diagnostic = Diagnostic::error() let diagnostic = Diagnostic::error()
.with_message(format!("No such namespace '{}' found", error_symbol_name)) .with_message(format!("No such namespace '{}' found", error_symbol_name))
.with_label( .with_label(
Label::primary(error_file_id, error_range).with_message("Namespace used here.") Label::primary(error_file_id, error_range).with_message("Namespace used here."),
); );
diagnostics.push(diagnostic); diagnostics.push(diagnostic);
} }
@ -69,3 +69,7 @@ pub fn format_fqn(parts: &[&str]) -> String {
pub fn join_fqn_parts(parts: &[Rc<str>]) -> String { pub fn join_fqn_parts(parts: &[Rc<str>]) -> String {
format_fqn(&parts.iter().map(|part| part.as_ref()).collect::<Vec<_>>()) format_fqn(&parts.iter().map(|part| part.as_ref()).collect::<Vec<_>>())
} }
pub fn str_slice_to_rcs(str_slice: &[&str]) -> Vec<Rc<str>> {
str_slice.iter().map(|s| Rc::from(*s)).collect::<Vec<_>>()
}

View File

@ -447,6 +447,12 @@ Function:
node: node:
or_else: void or_else: void
- function_body - function_body
fields:
- function_symbol:
kind: FunctionSymbol
wrap: rc_ref_cell
- scope_id:
kind: usize
OperatorFunction: OperatorFunction:
struct: struct:
children: children:
@ -609,9 +615,6 @@ FunctionEqualsBody:
struct: struct:
children: children:
- expression - expression
fields:
- scope_id:
kind: usize
FunctionAliasBody: FunctionAliasBody:
struct: struct:
children: children:
@ -620,8 +623,6 @@ FunctionAliasBody:
rule: Alias rule: Alias
- identifier - identifier
fields: fields:
- scope_id:
kind: usize
- resolved_function_symbol: - resolved_function_symbol:
kind: FunctionSymbol kind: FunctionSymbol
wrap: rc_ref_cell wrap: rc_ref_cell
@ -634,9 +635,6 @@ FunctionBlockBody:
- end_kw: - end_kw:
skip: skip:
rule: End rule: End
fields:
- scope_id:
kind: usize
# Class constructs # Class constructs
ClassConstructor: ClassConstructor:
@ -787,25 +785,19 @@ ForStatement:
LValue: LValue:
struct: struct:
children: children:
- variable_use - identifier
- suffixes: - suffixes:
vec: vec:
rule: LValueSuffix rule: LValueSuffix
fields:
- lv_symbol:
kind: LVSymbol
LValueSuffix: LValueSuffix:
tree_enum: tree_enum:
rules: rules:
- ObjectProperty - ObjectProperty
- ObjectIndex - ObjectIndex
# VariableUse
VariableUse:
struct:
children:
- identifier
fields:
- lv_symbol:
kind: LVSymbol
# Expressions # Expressions
Expression: Expression:
polymorphic_type: polymorphic_type:
@ -840,9 +832,9 @@ Expression:
- Literal: - Literal:
inner: inner:
kind: Literal kind: Literal
- VariableUse: - Identifier:
inner: inner:
kind: VariableUse kind: IdentifierExpression
- Fqn: - Fqn:
inner: inner:
kind: FullyQualifiedName kind: FullyQualifiedName
@ -1185,9 +1177,9 @@ PrimaryExpression:
- Literal: - Literal:
inner: inner:
kind: Literal kind: Literal
- VariableUse: - Identifier:
inner: inner:
kind: VariableUse kind: IdentifierExpression
- Fqn: - Fqn:
inner: inner:
kind: FullyQualifiedName kind: FullyQualifiedName
@ -1200,6 +1192,13 @@ PrimaryExpression:
- ParenthesizedExpression: - ParenthesizedExpression:
pass_through: pass_through:
kind: Expression kind: Expression
IdentifierExpression:
struct:
children:
- identifier
fields:
- expressible_symbol:
kind: ExpressibleSymbol
ListExpression: ListExpression:
struct: struct:
children: children:

View File

@ -615,7 +615,7 @@ ForStatement = {
// LValue // LValue
LValue = { LValue = {
VariableUse Identifier
~ LValueSuffix* ~ LValueSuffix*
} }
@ -624,12 +624,6 @@ LValueSuffix = {
| ObjectIndex | ObjectIndex
} }
// Variable Use
VariableUse = {
Identifier
}
// Expressions // Expressions
Expression = { Expression = {
@ -778,13 +772,17 @@ ObjectIndex = {
PrimaryExpression = { PrimaryExpression = {
Literal Literal
| VariableUse | IdentifierExpression
| FullyQualifiedName | FullyQualifiedName
| Closure | Closure
| ListExpression | ListExpression
| ParenthesizedExpression | ParenthesizedExpression
} }
IdentifierExpression = {
Identifier
}
ListExpression = { ListExpression = {
"[" "["
~ ExpressionList? ~ ExpressionList?

View File

@ -26,7 +26,7 @@ pub fn add_std_core_symbols(symbol_table: &mut SymbolTable) -> Result<(), Symbol
&vec![Rc::new(RefCell::new(println_msg_symbol))], &vec![Rc::new(RefCell::new(println_msg_symbol))],
None, None,
); );
symbol_table.insert_function_symbol(println_symbol)?; symbol_table.register_function_symbol(println_symbol);
Ok(()) Ok(())
} }

View File

@ -1,15 +1,11 @@
pub struct IndentWriter { pub struct IndentWriter<'a> {
indent_level: usize, indent_level: usize,
indent_string: String, indent_string: String,
out: Box<dyn std::io::Write>, out: &'a mut dyn std::fmt::Write,
} }
impl IndentWriter { impl<'a> IndentWriter<'a> {
pub fn new( pub fn new(start_level: usize, indent_string: &str, out: &'a mut dyn std::fmt::Write) -> Self {
start_level: usize,
indent_string: &str,
out: Box<dyn std::io::Write>,
) -> IndentWriter {
IndentWriter { IndentWriter {
indent_level: start_level, indent_level: start_level,
indent_string: indent_string.to_string(), indent_string: indent_string.to_string(),
@ -25,22 +21,22 @@ impl IndentWriter {
self.indent_level -= 1; self.indent_level -= 1;
} }
pub fn write(&mut self, s: &str) -> std::io::Result<()> { pub fn write(&mut self, s: &str) -> std::fmt::Result {
write!(self.out, "{}", s) write!(self.out, "{}", s)
} }
pub fn writeln(&mut self, s: &str) -> std::io::Result<()> { pub fn writeln(&mut self, s: &str) -> std::fmt::Result {
self.write(&format!("{}\n", s)) self.write(&format!("{}\n", s))
} }
pub fn write_indented(&mut self, s: &str) -> std::io::Result<()> { pub fn write_indented(&mut self, s: &str) -> std::fmt::Result {
for _ in 0..self.indent_level { for _ in 0..self.indent_level {
write!(self.out, "{}", self.indent_string)?; write!(self.out, "{}", self.indent_string)?;
} }
write!(self.out, "{}", s) write!(self.out, "{}", s)
} }
pub fn writeln_indented(&mut self, s: &str) -> std::io::Result<()> { pub fn writeln_indented(&mut self, s: &str) -> std::fmt::Result {
self.write_indented(&format!("{}\n", s)) self.write_indented(&format!("{}\n", s))
} }
} }