[fine] Fixup let environment

Also handle circular references in types and environments without
exploding, and tweak test output a little bit.
This commit is contained in:
John Doty 2024-01-07 08:05:55 -08:00
parent ebad7fe295
commit 308114f8cf
4 changed files with 119 additions and 45 deletions

View file

@ -66,6 +66,10 @@ impl<'a> SyntaxTree<'a> {
self[t].end_pos
}
pub fn len(&self) -> usize {
self.trees.len()
}
pub fn trees(&self) -> impl Iterator<Item = TreeRef> {
(0..self.trees.len()).map(|i| TreeRef::from_index(i))
}
@ -164,6 +168,23 @@ impl<'a> Tree<'a> {
})
.flatten()
}
pub fn dump(&self, tree: &SyntaxTree<'a>, with_positions: bool, output: &mut String) {
let _ = write!(output, "{:?}", self.kind);
if with_positions {
let _ = write!(output, " [{}, {})", self.start_pos, self.end_pos);
}
let _ = write!(output, "\n");
for child in self.children.iter() {
child.dump_rec(2, tree, with_positions, output);
}
}
}
impl<'a> std::fmt::Debug for Tree<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?} [{}-{})", self.kind, self.start_pos, self.end_pos)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
@ -181,19 +202,6 @@ impl TreeRef {
}
}
impl<'a> Tree<'a> {
pub fn dump(&self, tree: &SyntaxTree<'a>, with_positions: bool, output: &mut String) {
let _ = write!(output, "{:?}", self.kind);
if with_positions {
let _ = write!(output, " [{}, {})", self.start_pos, self.end_pos);
}
let _ = write!(output, "\n");
for child in self.children.iter() {
child.dump_rec(2, tree, with_positions, output);
}
}
}
pub enum Child<'a> {
Token(Token<'a>),
Tree(TreeRef),

View file

@ -172,22 +172,16 @@ impl std::ops::Deref for EnvironmentRef {
}
}
fn set_logical_parents(parents: &mut Vec<Option<TreeRef>>, syntax_tree: &SyntaxTree, t: TreeRef) {
fn set_logical_parents(
parents: &mut Vec<Option<TreeRef>>,
syntax_tree: &SyntaxTree,
t: TreeRef,
parent: Option<TreeRef>,
) {
parents[t.index()] = parent.clone();
let tree = &syntax_tree[t];
// The default logical parent is the physical parent.
if parents.len() <= t.index() {
parents.resize(t.index() + 1, None);
}
parents[t.index()] = tree.parent.clone();
for child in &tree.children {
match child {
Child::Token(_) => (),
Child::Tree(ct) => set_logical_parents(parents, syntax_tree, *ct),
}
}
// eprintln!("SET PARENT {parent:?} => CHILD {tree:?} ({t:?})");
match tree.kind {
TreeKind::Block | TreeKind::File => {
// In a block (or at the top level), each child actually points
@ -199,16 +193,40 @@ fn set_logical_parents(parents: &mut Vec<Option<TreeRef>>, syntax_tree: &SyntaxT
match child {
Child::Token(_) => (),
Child::Tree(ct) => {
parents[ct.index()] = parent;
set_logical_parents(parents, syntax_tree, *ct, parent);
parent = Some(*ct);
}
}
}
}
_ => {}
TreeKind::LetStatement => {
// In a let statement, the logical parent of the children is
// actually the logical parent of the let statement, so that the
// variable doesn't have itself in scope. :P
for child in &tree.children {
match child {
Child::Token(_) => (),
Child::Tree(ct) => set_logical_parents(parents, syntax_tree, *ct, parent),
}
}
}
_ => {
// By default, the parent for each child is current tree.
for child in &tree.children {
match child {
Child::Token(_) => (),
Child::Tree(ct) => set_logical_parents(parents, syntax_tree, *ct, Some(t)),
}
}
}
}
}
enum Incremental<T> {
InProgress,
Complete(T),
}
pub struct Semantics<'a> {
// TODO: Do I really want my own copy here? Should we standardize on Arc
// or Rc or some other nice sharing mechanism?
@ -221,16 +239,17 @@ pub struct Semantics<'a> {
// TODO: State should be externalized instead of this refcell nonsense.
errors: RefCell<Vec<Error>>,
types: RefCell<HashMap<TreeRef, Type>>,
environments: RefCell<HashMap<TreeRef, EnvironmentRef>>,
types: RefCell<HashMap<TreeRef, Incremental<Type>>>,
environments: RefCell<HashMap<TreeRef, Incremental<EnvironmentRef>>>,
empty_environment: EnvironmentRef,
}
impl<'a> Semantics<'a> {
pub fn new(tree: &'a SyntaxTree<'a>, lines: &'a Lines) -> Self {
let mut logical_parents = Vec::new();
let mut logical_parents = Vec::with_capacity(tree.len());
logical_parents.resize(tree.len(), None);
if let Some(root) = tree.root() {
set_logical_parents(&mut logical_parents, tree, root);
set_logical_parents(&mut logical_parents, tree, root, None);
}
let mut semantics = Semantics {
@ -323,11 +342,26 @@ impl<'a> Semantics<'a> {
}
pub fn environment_of(&self, t: TreeRef) -> EnvironmentRef {
if let Some(existing) = self.environments.borrow().get(&t) {
return existing.clone();
match self.environments.borrow().get(&t) {
None => (),
Some(Incremental::Complete(e)) => return e.clone(),
Some(Incremental::InProgress) => {
// TODO: Rewrite as complete with empty after reporting error.
// eprintln!("environment_of circular => {t:?}");
self.report_error_tree_ref(
t,
"INTERNAL COMPILER ERROR: Circular dependency detected: environment",
);
return self.empty_environment.clone();
}
}
self.environments
.borrow_mut()
.insert(t, Incremental::InProgress);
let tree = &self.syntax_tree[t];
// eprintln!("environment_of => {tree:?}");
let parent = match self.logical_parents[t.index()] {
Some(t) => self.environment_of(t),
None => self.empty_environment.clone(),
@ -340,12 +374,13 @@ impl<'a> Semantics<'a> {
_ => parent,
};
self.environments.borrow_mut().insert(t, result.clone());
self.environments
.borrow_mut()
.insert(t, Incremental::Complete(result.clone()));
result
}
fn environment_of_let(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
// 0 is the ... let keyword
let Some(name) = tree.nth_token(1) else {
return parent; // Error is already reported?
};
@ -370,11 +405,24 @@ impl<'a> Semantics<'a> {
}
pub fn type_of(&self, t: TreeRef) -> Option<Type> {
if let Some(existing) = self.types.borrow().get(&t) {
return Some(existing.clone());
match self.types.borrow().get(&t) {
None => (),
Some(Incremental::Complete(existing)) => return Some(existing.clone()),
Some(Incremental::InProgress) => {
// TODO: Rewrite as complete with error after reporting error.
// eprintln!("type_of circular => {t:?}");
self.report_error_tree_ref(
t,
"INTERNAL COMPILER ERROR: Circular dependency detected: type",
);
return Some(Type::Error);
}
}
self.types.borrow_mut().insert(t, Incremental::InProgress);
let tree = &self.syntax_tree[t];
// eprintln!("type_of => {tree:?}");
let result = match tree.kind {
TreeKind::Error => Some(Type::Error),
TreeKind::UnaryExpression => self.type_of_unary(tree),
@ -391,13 +439,22 @@ impl<'a> Semantics<'a> {
TreeKind::ReturnStatement => Some(Type::Unreachable),
TreeKind::ExpressionStatement => self.type_of_expression_statement(tree),
TreeKind::Identifier => self.type_of_identifier(tree),
_ => return None,
// TODO: Previously I had short-circuited here and not put anything
// in the table if this node isn't the kind that I would
// normally compute a type for. I should keep doing that to
// detect nonsense without blowing out the hash table. If
// we're going to be computing a type for every node it
// should just be an array instead of a hash table.
_ => None,
};
// NOTE: These return `None` if they encounter some problem.
let result = result.unwrap_or(Type::Error);
self.types.borrow_mut().insert(t, result.clone());
self.types
.borrow_mut()
.insert(t, Incremental::Complete(result.clone()));
Some(result)
}

View file

@ -107,6 +107,7 @@ fn report_semantic_error(semantics: &Semantics, tr: Option<TreeRef>, message: &s
}
if let Some(tr) = tr {
println!("About the tree: {:?}", &tree[tr]);
println!("The logical parent chain of the tree was:\n");
let mut current = Some(tr);
while let Some(c) = current {

View file

@ -7,7 +7,10 @@
// | LiteralExpression
// | Number:'"23"'
// | Semicolon:'";"'
// | ExpressionStatement
// | LetStatement
// | Let:'"let"'
// | Identifier:'"y"'
// | Equal:'"="'
// | BinaryExpression
// | Identifier
// | Identifier:'"x"'
@ -15,9 +18,14 @@
// | LiteralExpression
// | Number:'"2"'
// | Semicolon:'";"'
// | ExpressionStatement
// | Identifier
// | Identifier:'"y"'
// | Semicolon:'";"'
// |
let x = 23;
x * 2;
let y = x * 2;
y;
// @type: 416 f64
// @type: 590 f64