WIP never ending name analysis.

This commit is contained in:
Jesse Brault 2025-10-24 14:33:55 -05:00
parent 664aebfd61
commit 93c6a71185
13 changed files with 286 additions and 263 deletions

View File

@ -116,7 +116,7 @@ fn generate_node_file(build_specs: &[BuildSpec]) -> AstGeneratedFile {
use std::range::Range;
use std::rc::Rc;
use std::cell::RefCell;
use crate::name_analysis::symbol::use_symbol::UseSymbol;
use crate::name_analysis::symbol::use_symbol::*;
#(#types)*
};

View File

@ -3,12 +3,12 @@ use codespan_reporting::term;
use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
use deimos::ast::build::build_ast;
use deimos::name_analysis::analyze_names;
use deimos::name_analysis::symbol_table::symbol_tree::SymbolTree;
use deimos::name_analysis::symbol_table::SymbolTable;
use deimos::parser::{DeimosParser, Rule};
use deimos::std_core::add_std_core_symbols;
use pest::Parser;
use std::path::PathBuf;
use deimos::name_analysis::symbol_tree::SymbolTree;
pub fn name_analysis(paths: &Vec<PathBuf>) -> Result<(), Box<dyn std::error::Error>> {
let mut compilation_units = vec![];
@ -30,10 +30,13 @@ pub fn name_analysis(paths: &Vec<PathBuf>) -> Result<(), Box<dyn std::error::Err
}
let mut symbol_table = SymbolTable::new();
let mut symbol_tree = SymbolTree::new();
add_std_core_symbols(&mut symbol_table).expect("Failed to add std::core symbols.");
let diagnostics = analyze_names(&mut compilation_units, &files, &mut symbol_table, &mut symbol_tree);
let diagnostics = analyze_names(
&mut compilation_units,
&files,
&mut symbol_table,
);
if diagnostics.is_empty() {
println!("Name analysis complete.");
println!("{}", symbol_table);

View File

@ -1,136 +1,119 @@
use crate::ast::node::{CompilationUnit, Identifier, UseStatement, UseStatementSuffix};
use crate::ast::node::{
CompilationUnit, ConcreteUseStatement, ConcreteUseStatementSuffix, Identifier, UseStatement,
UseStatementIdentifier, UseStatementPrefix,
};
use crate::diagnostic::DmDiagnostic;
use crate::name_analysis::fqn_context::FqnContext;
use crate::name_analysis::symbol::source_definition::SourceDefinition;
use crate::name_analysis::symbol::use_symbol::{ConcreteUseSymbol, StarUseSymbol, UseSymbol};
use crate::name_analysis::symbol::use_symbol::ConcreteUseSymbol;
use crate::name_analysis::symbol_table::SymbolTable;
use crate::name_analysis::symbol_tree::SymbolTree;
use crate::name_analysis::util;
use crate::name_analysis::util::{handle_insert_error, use_statement_base_fqn};
use std::cell::RefCell;
use std::rc::Rc;
use crate::name_analysis::util::handle_insert_error;
macro_rules! insert_symbol {
($insert_method:ident, $symbol:expr, $symbol_table:ident, $name:expr, $node:ident, $symbol_kinds:literal, $diagnostics:ident) => {
if let Err(insert_error) = $symbol_table.$insert_method($symbol) {
util::handle_insert_error(
insert_error,
$name,
$node.file_id(),
$node.range(),
$symbol_kinds,
$diagnostics,
)
}
};
}
pub fn nap1_compilation_unit(
pub fn na_p1_compilation_unit(
file_name: &str,
compilation_unit: &mut CompilationUnit,
symbol_table: &mut SymbolTable,
symbol_tree: &mut SymbolTree,
diagnostics: &mut Vec<DmDiagnostic>,
) {
let mut fqn_context = FqnContext::new();
symbol_table.push_scope(&format!("FileScope {}", file_name));
if let Some(namespace) = compilation_unit.namespace() {
for identifier in namespace.fqn().identifiers() {
fqn_context.push(identifier.name());
}
symbol_table.set_current_fqn(
&namespace
.fqn()
.identifiers()
.map(Identifier::name)
.collect::<Vec<_>>(),
);
}
symbol_table.push_scope(&format!("FileScope {}", file_name));
for use_statement in compilation_unit.use_statements_mut() {
nap1_use_statement(use_statement, symbol_table, diagnostics);
na_p1_use_statement(use_statement, symbol_table, diagnostics);
}
symbol_table.pop_scope();
}
fn maybe_insert_concrete_use_symbol(
use_statement: &UseStatement,
identifier: &Identifier,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) -> Option<Rc<RefCell<ConcreteUseSymbol>>> {
let base_fqn = use_statement_base_fqn(use_statement);
let to_insert = ConcreteUseSymbol::new(
&base_fqn,
identifier.name(),
Some(SourceDefinition::from_identifier(identifier)),
);
match symbol_table.insert_concrete_use_symbol(to_insert) {
Ok(inserted) => Some(inserted),
Err(insert_error) => {
handle_insert_error(
insert_error,
identifier.name(),
identifier.file_id(),
identifier.range(),
"Use Symbol",
diagnostics,
);
None
}
}
}
fn nap1_use_statement(
fn na_p1_use_statement(
use_statement: &mut UseStatement,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
let base_fqn = use_statement_base_fqn(use_statement);
match use_statement {
UseStatement::ConcreteUseStatement(concrete_use_statement) => {
na_p1_concrete_use_statement(concrete_use_statement, symbol_table, diagnostics);
}
UseStatement::StarUseStatement(star_use_statement) => {
todo!()
}
}
}
match use_statement.suffix() {
UseStatementSuffix::Identifier(identifier) => {
let maybe_symbol = maybe_insert_concrete_use_symbol(
use_statement,
&identifier,
fn na_p1_concrete_use_statement(
concrete_use_statement: &mut ConcreteUseStatement,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
let prefixes: Vec<String> = concrete_use_statement
.prefixes()
.map(UseStatementPrefix::identifier)
.map(Identifier::name)
.map(ToString::to_string)
.collect::<Vec<_>>();
match concrete_use_statement.suffix_mut() {
ConcreteUseStatementSuffix::UseStatementIdentifier(use_statement_identifier) => {
handle_concrete_use_statement_identifier(
&prefixes,
use_statement_identifier,
symbol_table,
diagnostics,
);
if let Some(concrete_use_symbol) = maybe_symbol {
use_statement.use_symbols_mut().push(UseSymbol::Concrete(concrete_use_symbol));
}
}
UseStatementSuffix::Star => {
let maybe_symbol = match symbol_table.insert_star_use_symbol(
StarUseSymbol::new(&base_fqn, Some(SourceDefinition::from_use_statement(use_statement)))
) {
Ok(inserted) => {
Some(todo!())
},
Err(insert_error) => {
handle_insert_error(
insert_error,
&base_fqn,
use_statement.file_id(),
use_statement.range(),
"Star Use Symbol",
diagnostics,
);
None
}
};
}
UseStatementSuffix::UseList(use_list) => {
let maybe_symbols = use_list.identifiers()
.map(|identifier| {
maybe_insert_concrete_use_symbol(
use_statement,
identifier,
symbol_table,
diagnostics,
)
})
.collect::<Vec<_>>();
for maybe_symbol in maybe_symbols {
if let Some(symbol) = maybe_symbol {
use_statement.use_symbols_mut().push(UseSymbol::Concrete(symbol));
}
ConcreteUseStatementSuffix::UseList(use_list) => {
for use_statement_identifier in use_list.identifiers_mut() {
handle_concrete_use_statement_identifier(
&prefixes,
use_statement_identifier,
symbol_table,
diagnostics,
);
}
}
}
}
fn handle_concrete_use_statement_identifier(
prefixes: &Vec<String>,
use_statement_identifier: &mut UseStatementIdentifier,
symbol_table: &mut SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
let mut fqn_parts: Vec<&str> = vec![];
for prefix in prefixes {
fqn_parts.push(prefix);
}
fqn_parts.push(use_statement_identifier.identifier().name());
let to_insert = ConcreteUseSymbol::new(
&fqn_parts,
Some(SourceDefinition::from_identifier(
use_statement_identifier.identifier(),
)),
);
match symbol_table.insert_concrete_use_symbol(to_insert) {
Ok(inserted) => {
use_statement_identifier.set_symbol(inserted);
}
Err(insert_error) => {
handle_insert_error(
insert_error,
use_statement_identifier.identifier().name(),
use_statement_identifier.identifier().file_id(),
use_statement_identifier.identifier().range(),
"Use Symbol",
diagnostics,
);
}
}
}

View File

@ -23,40 +23,36 @@ The resolve phase has one main responsibility: resolve all references based on t
use crate::ast::ast_node::AstNode;
use crate::ast::node::CompilationUnit;
use crate::diagnostic::DmDiagnostic;
use crate::name_analysis::first_pass::nap1_compilation_unit;
use crate::name_analysis::second_pass::nap2_compilation_unit;
use crate::name_analysis::first_pass::na_p1_compilation_unit;
use crate::name_analysis::second_pass::na_p2_compilation_unit;
use crate::name_analysis::symbol_table::SymbolTable;
use codespan_reporting::files::Files;
use std::hash::Hash;
use crate::name_analysis::symbol_tree::SymbolTree;
pub(self) mod fqn_context;
// mod resolve;
mod first_pass;
mod scope_table;
mod second_pass;
pub mod symbol;
pub mod symbol_table;
pub mod symbol_tree;
mod util;
pub fn analyze_names<'a, F: Files<'a, FileId = usize, Name = String>>(
compilation_units: &mut Vec<Box<CompilationUnit>>,
files: &'a F,
symbol_table: &mut SymbolTable,
symbol_tree: &mut SymbolTree,
) -> Vec<DmDiagnostic> {
let mut diagnostics = vec![];
// gather symbols
for compilation_unit in compilation_units.iter_mut() {
let file_name = files.name(compilation_unit.file_id()).unwrap();
nap1_compilation_unit(&file_name, compilation_unit, symbol_table, symbol_tree, &mut diagnostics);
na_p1_compilation_unit(&file_name, compilation_unit, symbol_table, &mut diagnostics);
}
// resolve symbols
for compilation_unit in compilation_units {
nap2_compilation_unit(compilation_unit, symbol_table, symbol_tree, &mut diagnostics);
na_p2_compilation_unit(compilation_unit, symbol_table, &mut diagnostics);
}
diagnostics.into()

View File

@ -1,125 +1,103 @@
use crate::ast::node::{CompilationUnit, Identifier, UseStatement, UseStatementSuffix};
use crate::ast::node::{
CompilationUnit, ConcreteUseStatement, ConcreteUseStatementSuffix, Identifier, UseStatement,
UseStatementIdentifier, UseStatementPrefix,
};
use crate::diagnostic::DmDiagnostic;
use crate::name_analysis::symbol::usable_symbol::UsableSymbol;
use crate::name_analysis::symbol::use_symbol::{ConcreteUseSymbol, UseSymbol};
use crate::name_analysis::symbol_table::{SymbolLookupError, SymbolTable};
use crate::name_analysis::symbol_tree::SymbolTree;
use crate::name_analysis::util::{handle_lookup_error, use_statement_base_fqn};
use std::cell::RefCell;
use std::rc::Rc;
use crate::name_analysis::util::handle_lookup_error;
pub fn nap2_compilation_unit(
pub fn na_p2_compilation_unit(
compilation_unit: &mut CompilationUnit,
symbol_table: &SymbolTable,
symbol_tree: &SymbolTree,
diagnostics: &mut Vec<DmDiagnostic>,
) {
// TODO: check namespace for proper file name
for use_statement in compilation_unit.use_statements_mut() {
nap2_use_statement(use_statement, symbol_tree, diagnostics);
na_p2_use_statement(use_statement, symbol_table, diagnostics);
}
// TODO: declarations
}
fn find_usable_symbol(
use_statement: &UseStatement,
identifier: &Identifier,
symbol_tree: &SymbolTree,
) -> Option<UsableSymbol> {
let fqn_parts: Vec<&str> = {
let mut base: Vec<&str> = use_statement
.prefixes()
.map(|prefix| prefix.identifier().name())
.collect();
base.push(identifier.name());
base
};
symbol_tree
.find_interface(&fqn_parts)
.map(|interface_symbol| UsableSymbol::Interface(interface_symbol))
.or_else(|| {
symbol_tree
.find_class(&fqn_parts)
.map(|class_symbol| UsableSymbol::Class(class_symbol))
})
.or_else(|| {
symbol_tree
.find_function(&fqn_parts)
.map(|function_symbol| UsableSymbol::Function(function_symbol))
})
}
fn find_matching_concrete_use_symbol(
use_statement: &UseStatement,
identifier: &Identifier,
) -> Option<Rc<RefCell<ConcreteUseSymbol>>> {
let base_fqn = use_statement_base_fqn(use_statement);
use_statement
.use_symbols()
.iter()
.find(|use_symbol| match use_symbol {
UseSymbol::Concrete(concrete_use_symbol) => {
let borrowed = concrete_use_symbol.borrow();
borrowed.base_fqn() == base_fqn && borrowed.declared_name() == identifier.name()
}
_ => false,
})
.map(|use_symbol| match use_symbol {
UseSymbol::Concrete(concrete) => concrete.clone(),
_ => panic!(),
})
}
fn handle_concrete_use(
use_statement: &UseStatement,
identifier: &Identifier,
symbol_tree: &SymbolTree,
fn na_p2_use_statement(
use_statement: &mut UseStatement,
symbol_table: &SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
let maybe_usable_symbol = find_usable_symbol(use_statement, identifier, symbol_tree);
if let Some(usable_symbol) = maybe_usable_symbol {
let maybe_concrete_use_symbol =
find_matching_concrete_use_symbol(use_statement, identifier);
if let Some(concrete_use_symbol) = maybe_concrete_use_symbol {
concrete_use_symbol
.borrow_mut()
.set_resolved_symbol(usable_symbol);
} else {
panic!("Can't find matching ConcreteUseSymbol");
match use_statement {
UseStatement::ConcreteUseStatement(concrete_use_statement) => {
na_p2_concrete_use_statement(concrete_use_statement, symbol_table, diagnostics);
}
UseStatement::StarUseStatement(star_use_statement) => {
todo!()
}
}
}
fn na_p2_concrete_use_statement(
concrete_use_statement: &mut ConcreteUseStatement,
symbol_table: &SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
let base_fqn_parts: Vec<String> = concrete_use_statement
.prefixes()
.map(UseStatementPrefix::identifier)
.map(Identifier::name)
.map(ToString::to_string)
.collect();
match concrete_use_statement.suffix_mut() {
ConcreteUseStatementSuffix::UseStatementIdentifier(use_statement_identifier) => {
handle_concrete_use_statement_identifier(
&base_fqn_parts,
use_statement_identifier,
symbol_table,
diagnostics,
);
}
ConcreteUseStatementSuffix::UseList(use_list) => {
for use_statement_identifier in use_list.identifiers_mut() {
handle_concrete_use_statement_identifier(
&base_fqn_parts,
use_statement_identifier,
symbol_table,
diagnostics,
);
}
}
}
}
fn handle_concrete_use_statement_identifier(
base_fqn_parts: &[String],
use_statement_identifier: &mut UseStatementIdentifier,
symbol_table: &SymbolTable,
diagnostics: &mut Vec<DmDiagnostic>,
) {
let fqn_parts = {
let mut all_parts: Vec<&str> = vec![];
for part in base_fqn_parts {
all_parts.push(part);
}
all_parts.push(use_statement_identifier.identifier().name());
all_parts
};
let maybe_usable_symbol = symbol_table.find_usable_symbol(&fqn_parts);
if let Some(usable_symbol) = maybe_usable_symbol {
use_statement_identifier
.symbol_mut()
.expect("Should have set the symbol on the use statement already.")
.borrow_mut()
.set_resolved_symbol(usable_symbol);
} else {
handle_lookup_error(
SymbolLookupError::NoDefinition,
&format!(
"{}::{}",
use_statement_base_fqn(use_statement),
identifier.name()
),
use_statement.file_id(),
use_statement.range(),
&fqn_parts.join("::"),
use_statement_identifier.identifier().file_id(),
use_statement_identifier.identifier().range(),
"Usable Symbol",
diagnostics,
);
}
}
fn nap2_use_statement(
use_statement: &mut UseStatement,
symbol_tree: &SymbolTree,
diagnostics: &mut Vec<DmDiagnostic>,
) {
match use_statement.suffix() {
UseStatementSuffix::Identifier(identifier) => {
handle_concrete_use(use_statement, identifier, symbol_tree, diagnostics);
}
UseStatementSuffix::Star => {
todo!("Resolve star symbols")
}
UseStatementSuffix::UseList(use_list) => {
for identifier in use_list.identifiers() {
handle_concrete_use(use_statement, identifier, symbol_tree, diagnostics);
}
}
}
}

View File

@ -1,7 +1,5 @@
use std::cell::RefCell;
use crate::ast::node::{Identifier, Operator};
use std::range::Range;
use std::rc::Rc;
use crate::ast::node::{Identifier, Operator, UseStatement};
#[derive(Clone, Debug)]
pub struct SourceDefinition {
@ -17,13 +15,6 @@ impl SourceDefinition {
}
}
pub fn from_use_statement(use_statement: &UseStatement) -> Self {
Self {
file_id: use_statement.file_id(),
range: use_statement.range(),
}
}
pub fn from_operator(operator: &Operator) -> Self {
Self {
file_id: operator.file_id(),

View File

@ -21,32 +21,29 @@ impl UseSymbol {
#[derive(Clone)]
pub struct ConcreteUseSymbol {
base_fqn: String,
declared_name: String,
fqn_parts: Vec<String>,
source_definition: Option<SourceDefinition>,
resolved_symbol: Option<UsableSymbol>,
}
impl ConcreteUseSymbol {
pub fn new(
base_fqn: &str,
declared_name: &str,
fqn_parts: &[&str],
source_definition: Option<SourceDefinition>,
) -> Self {
Self {
base_fqn: base_fqn.to_string(),
declared_name: declared_name.to_string(),
fqn_parts: fqn_parts.iter().map(ToString::to_string).collect(),
source_definition,
resolved_symbol: None,
}
}
pub fn base_fqn(&self) -> &str {
&self.base_fqn
pub fn fqn_parts(&self) -> &[String] {
self.fqn_parts.as_slice()
}
pub fn declared_name(&self) -> &str {
&self.declared_name
self.fqn_parts.last().unwrap()
}
pub fn source_definition(&self) -> Option<&SourceDefinition> {
@ -65,8 +62,7 @@ impl ConcreteUseSymbol {
impl Debug for ConcreteUseSymbol {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ConcreteUseStatementSymbol")
.field("base_fqn", &self.base_fqn)
.field("declared_name", &self.declared_name)
.field("fqn_parts", &self.fqn_parts)
.field("source_definition", &self.source_definition)
.finish()
}

View File

@ -1,3 +1,4 @@
#[derive(Debug)]
pub struct FqnContext {
stack: Vec<String>,
}

View File

@ -5,6 +5,8 @@ use crate::name_analysis::symbol::parameter_symbol::ParameterSymbol;
use crate::name_analysis::symbol::use_symbol::{ConcreteUseSymbol, StarUseSymbol, UseSymbol};
use crate::name_analysis::symbol::variable_symbol::VariableSymbol;
use crate::name_analysis::symbol::*;
use crate::name_analysis::symbol_table::fqn_context::FqnContext;
use crate::name_analysis::symbol_table::symbol_tree::SymbolTree;
use crate::name_analysis::symbol_table::SymbolInsertError::SymbolAlreadyDefined;
use crate::name_analysis::symbol_table::SymbolLookupError::NoDefinition;
use scope::Scope;
@ -12,8 +14,11 @@ use std::cell::RefCell;
use std::fmt::Display;
use std::ops::Deref;
use std::rc::Rc;
use crate::name_analysis::symbol::usable_symbol::UsableSymbol;
pub(self) mod fqn_context;
mod scope;
pub mod symbol_tree;
#[derive(Debug)]
pub enum SymbolInsertError {
@ -29,6 +34,8 @@ pub enum SymbolLookupError {
pub struct SymbolTable {
scopes: Vec<Scope>,
current_scope_id: usize,
symbol_tree: Box<SymbolTree>,
fqn_context: Box<FqnContext>,
}
/// Contains a vec of scopes, like a flattened tree
@ -37,6 +44,8 @@ impl SymbolTable {
Self {
scopes: vec![Scope::new(None, 0, String::from("GlobalScope"))],
current_scope_id: 0,
symbol_tree: Box::new(SymbolTree::new()),
fqn_context: Box::new(FqnContext::new()),
}
}
@ -60,6 +69,41 @@ impl SymbolTable {
self.current_scope_id = parent_id;
}
}
pub fn set_current_fqn(&mut self, names: &[&str]) {
self.fqn_context = Box::new(FqnContext::new());
for name in names {
self.fqn_context.push(*name);
}
}
pub fn push_fqn_part(&mut self, part: &str) {
self.fqn_context.push(part);
}
pub fn pop_fqn_part(&mut self) {
self.fqn_context.pop();
}
pub fn symbol_tree(&self) -> &SymbolTree {
&self.symbol_tree
}
pub fn find_usable_symbol(&self, fqn_parts: &[&str]) -> Option<UsableSymbol> {
self.symbol_tree
.find_interface(fqn_parts)
.map(|interface_symbol| UsableSymbol::Interface(interface_symbol))
.or_else(|| {
self.symbol_tree
.find_class(fqn_parts)
.map(|class_symbol| UsableSymbol::Class(class_symbol))
})
.or_else(|| {
self.symbol_tree
.find_function(fqn_parts)
.map(|function_symbol| UsableSymbol::Function(function_symbol))
})
}
fn current_scope(&self) -> &Scope {
&self.scopes[self.current_scope_id]

View File

@ -5,6 +5,7 @@ use crate::name_analysis::symbol::interface_symbol::InterfaceSymbol;
use std::collections::HashMap;
use std::rc::Rc;
#[derive(Debug)]
pub struct SymbolTree {
children: Box<HashMap<String, SymbolTree>>,
classes: Box<HashMap<String, Rc<RefCell<ClassSymbol>>>>,

View File

@ -1,17 +1,8 @@
use crate::ast::node::UseStatement;
use crate::diagnostic::DmDiagnostic;
use crate::name_analysis::symbol_table::{SymbolInsertError, SymbolLookupError};
use codespan_reporting::diagnostic::{Diagnostic, Label};
use std::range::Range;
pub fn use_statement_base_fqn(use_statement: &UseStatement) -> String {
use_statement
.prefixes()
.map(|prefix| prefix.identifier().name())
.collect::<Vec<_>>()
.join("::")
}
pub fn handle_insert_error(
err: SymbolInsertError,
error_symbol_name: &str,

View File

@ -231,6 +231,11 @@ Namespace:
member:
rule: FullyQualifiedName
UseStatement:
tree_enum:
rules:
- ConcreteUseStatement
- StarUseStatement
ConcreteUseStatement:
struct:
children:
- use_kw:
@ -241,34 +246,54 @@ UseStatement:
rule: UseStatementPrefix
- suffix:
member:
rule: UseStatementSuffix
rule: ConcreteUseStatementSuffix
- file_id:
special:
kind: file_id
- range:
special:
kind: range
StarUseStatement:
struct:
children:
- use_kw:
skip:
rule: Use
- prefixes:
vec:
rule: UseStatementPrefix
- star_sym:
skip:
rule: Star
- file_id:
special:
kind: file_id
- range:
special:
kind: range
fields:
- use_symbols:
kind: UseSymbol
vec: true
UseStatementPrefix:
struct:
children:
- identifier
UseStatementSuffix:
ConcreteUseStatementSuffix:
tree_enum:
rules:
- Identifier
- Star:
child: false
- UseStatementIdentifier
- UseList
UseStatementIdentifier:
struct:
children:
- identifier
fields:
- symbol:
kind: ConcreteUseSymbol
wrap: rc_ref_cell
UseList:
struct:
children:
- identifiers:
vec:
rule: Identifier
rule: UseStatementIdentifier
# Level declarations
ModuleLevelDeclaration:

View File

@ -322,9 +322,20 @@ Namespace = {
}
UseStatement = {
ConcreteUseStatement
| StarUseStatement
}
ConcreteUseStatement = {
Use
~ UseStatementPrefix*
~ UseStatementSuffix
~ ConcreteUseStatementSuffix
}
StarUseStatement = {
Use
~ UseStatementPrefix*
~ Star
}
UseStatementPrefix = {
@ -332,16 +343,19 @@ UseStatementPrefix = {
~ "::"
}
UseStatementSuffix = {
Identifier
| Star
ConcreteUseStatementSuffix = {
UseStatementIdentifier
| UseList
}
UseStatementIdentifier = {
Identifier
}
UseList = {
"{"
~ Identifier
~ ( "," ~ Identifier )*
~ UseStatementIdentifier
~ ( "," ~ UseStatementIdentifier )*
~ "}"
}