Skip to content

Commit

Permalink
Add notation sets
Browse files Browse the repository at this point in the history
  • Loading branch information
justinpombrio committed Mar 17, 2024
1 parent 6f539bc commit 22d7bba
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 39 deletions.
175 changes: 161 additions & 14 deletions src/language/language_set.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
use super::LanguageError;
use crate::infra::bug;
use crate::style::ValidNotation;
use crate::style::{Notation, StyleLabel, ValidNotation, HOLE_STYLE};
use bit_set::BitSet;
use partial_pretty_printer as ppp;
use std::collections::HashMap;

// TODO: split up this file (language_set.rs)

const HOLE_NAME: &str = "$hole";

// Other options: ✵ ✶ ✦ ✳ ✪ ✺ ⍟ ❂ ★ ◯ ☐ ☉ ◼
pub const HOLE_LITERAL: &str = "☐";

// NOTE: Why all the wrapper types, instead of using indexes? Two reasons:
//
// 1. It simplifies the caller. For example, instead of having to pass around a
Expand Down Expand Up @@ -50,12 +55,29 @@ pub enum AritySpec {
/// in which positions.
#[derive(Debug, Clone)]
pub struct GrammarSpec {
pub language_name: String,
pub constructs: Vec<ConstructSpec>,
pub sorts: Vec<(String, SortSpec)>,
pub root_sort: SortSpec,
}

/// Describes how to display every construct in a language.
#[derive(Debug, Clone)]
pub struct NotationSetSpec {
/// A unqiue name for this set of notations
pub name: String,
/// Maps `Construct.name` to that construct's notation.
pub notations: Vec<(String, Notation)>,
}

/// A single notation, with a grammar describing its structure and a notation describing how to
/// display it.
#[derive(Debug, Clone)]
pub struct LanguageSpec {
pub name: String,
pub grammar: GrammarSpec,
pub default_notation_set: NotationSetSpec,
}

/********************************************
* Compiled Grammar *
********************************************/
Expand Down Expand Up @@ -85,7 +107,8 @@ enum ArityCompiled {
struct SortCompiled(BitSet);

struct GrammarCompiled {
language_name: String,
/// Construct_name -> ConstructId
constructs_by_name: HashMap<String, ConstructId>,
/// ConstructId -> ConstructCompiled
constructs: Vec<ConstructCompiled>,
/// SortId -> SortCompiled
Expand All @@ -97,8 +120,8 @@ struct GrammarCompiled {
keymap: HashMap<char, ConstructId>,
}

// TODO: need to be able to add a NotationSet! It should inject notation for holes.
struct LanguageCompiled {
name: String,
grammar: GrammarCompiled,
notation_sets_by_name: HashMap<String, NotationSetId>,
current_notation_set: NotationSetId,
Expand All @@ -107,6 +130,7 @@ struct LanguageCompiled {
}

struct NotationSetCompiled {
name: String,
/// ConstructId -> ValidNotation
notations: Vec<ValidNotation>,
}
Expand Down Expand Up @@ -172,11 +196,9 @@ pub struct NotationSet {

impl Language {
pub fn name(self, l: &LanguageSet) -> &str {
&l.grammar(self.language).language_name
&l.languages[self.language].name
}

// TODO do we need all_sorts or all_constructs?

pub fn keymap(self, l: &LanguageSet) -> impl ExactSizeIterator<Item = (char, Construct)> + '_ {
l.grammar(self.language).keymap.iter().map(move |(key, c)| {
(
Expand Down Expand Up @@ -332,25 +354,148 @@ impl FixedSorts {
}

impl LanguageSet {
pub fn new() -> LanguageSet {
LanguageSet {
languages_by_name: HashMap::new(),
languages: Vec::new(),
}
}

pub fn add_language(&mut self, language_spec: LanguageSpec) -> Result<(), LanguageError> {
let language = language_spec.compile()?;
let id = self.languages.len();
if self
.languages_by_name
.insert(language.name.clone(), id)
.is_some()
{
return Err(LanguageError::DuplicateLanguage(language.name));
}

self.languages.push(language);
Ok(())
}

pub fn add_notation_set(
&mut self,
language_name: &str,
notation_set: NotationSetSpec,
) -> Result<(), LanguageError> {
if let Some(language_id) = self.languages_by_name.get(language_name) {
self.languages[*language_id].add_notation_set(notation_set)
} else {
Err(LanguageError::UndefinedLanguage(language_name.to_owned()))
}
}

fn grammar(&self, language_id: LanguageId) -> &GrammarCompiled {
&self.languages[language_id].grammar
}
}

impl LanguageCompiled {
fn add_notation_set(&mut self, notation_set: NotationSetSpec) -> Result<(), LanguageError> {
let notation_set = notation_set.compile(&self.grammar)?;
let id = self.notation_sets.len();
if self
.notation_sets_by_name
.insert(notation_set.name.clone(), id)
.is_some()
{
return Err(LanguageError::DuplicateNotationSet(
self.name.clone(),
notation_set.name.clone(),
));
}
self.notation_sets.push(notation_set);
Ok(())
}
}

/********************************************
* Grammar Builder *
* Builders *
********************************************/

impl LanguageSpec {
fn compile(self) -> Result<LanguageCompiled, LanguageError> {
let grammar = self.grammar.compile()?;

let notation_set = self.default_notation_set.compile(&grammar)?;
let mut notation_sets_by_name = HashMap::new();
notation_sets_by_name.insert(notation_set.name.to_owned(), 0);

Ok(LanguageCompiled {
name: self.name,
grammar,
notation_sets_by_name,
current_notation_set: 0,
notation_sets: vec![notation_set],
})
}
}

impl NotationSetSpec {
fn inject_builtins(&mut self) {
use ppp::notation_constructors::{lit, style};
let hole_notation = style(StyleLabel::Hole, lit(HOLE_LITERAL));
self.notations.push((HOLE_NAME.to_owned(), hole_notation));
}

fn compile(mut self, grammar: &GrammarCompiled) -> Result<NotationSetCompiled, LanguageError> {
self.inject_builtins();

// Put notations in a HashMap, checking for duplicate entries.
let mut notations_map = HashMap::new();
for (construct_name, notation) in self.notations {
if notations_map
.insert(construct_name.clone(), notation)
.is_some()
{
return Err(LanguageError::DuplicateNotation(
self.name,
construct_name.clone(),
));
}
}

// Look up the notation of every construct in the grammar,
// putting them in a Vec ordered by ConstructId.
let mut notations = Vec::new();
for construct in &grammar.constructs {
if let Some(notation) = notations_map.remove(&construct.name) {
let valid_notation = notation.validate().map_err(|err| {
LanguageError::InvalidNotation(self.name.clone(), construct.name.clone(), err)
})?;
notations.push(valid_notation);
} else {
return Err(LanguageError::MissingNotation(
self.name,
construct.name.clone(),
));
}
}

// Any remaining notations don't name any construct in the grammar!
if let Some(construct_name) = notations_map.into_keys().next() {
return Err(LanguageError::UndefinedNotation(self.name, construct_name));
}

Ok(NotationSetCompiled {
name: self.name,
notations,
})
}
}

struct GrammarBuilder {
language_name: String,
constructs: HashMap<String, (ConstructId, ConstructSpec)>,
sorts: HashMap<String, SortSpec>,
root_sort: SortSpec,
}

impl GrammarSpec {
fn compile(mut self) -> Result<GrammarCompiled, LanguageError> {
let mut builder = GrammarBuilder::new(self.language_name, self.root_sort);
let mut builder = GrammarBuilder::new(self.root_sort);
for construct in self.constructs {
builder.add_construct(construct)?;
}
Expand All @@ -362,9 +507,8 @@ impl GrammarSpec {
}

impl GrammarBuilder {
fn new(language_name: String, root_sort: SortSpec) -> GrammarBuilder {
fn new(root_sort: SortSpec) -> GrammarBuilder {
GrammarBuilder {
language_name,
constructs: HashMap::new(),
sorts: HashMap::new(),
root_sort,
Expand Down Expand Up @@ -420,7 +564,7 @@ impl GrammarBuilder {
let hole_id = self.inject_builtins()?;

let mut grammar = GrammarCompiled {
language_name: self.language_name.clone(),
constructs_by_name: HashMap::new(),
constructs: Vec::new(),
sorts: Vec::new(),
root_sort: 0,
Expand Down Expand Up @@ -483,7 +627,7 @@ impl GrammarBuilder {
.map(|sort_spec| {
Ok((self.compile_sort(grammar, sort_spec)?, sort_spec.clone()))
})
.collect::<Result<Vec<_>, _>>()?,
.collect::<Result<Vec<_>, LanguageError>>()?,
),
AritySpec::Listy(sort_spec) => {
ArityCompiled::Listy(self.compile_sort(grammar, sort_spec)?, sort_spec.clone())
Expand All @@ -508,6 +652,9 @@ impl GrammarBuilder {
is_comment_or_ws: construct.is_comment_or_ws,
key: construct.key,
});
grammar
.constructs_by_name
.insert(construct.name.clone(), construct_id);
Ok(())
}
}
24 changes: 22 additions & 2 deletions src/language/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ mod location;
mod node;
mod text;

use partial_pretty_printer as ppp;
use std::fmt;

pub use location::Location;
pub use node::{DocStorage, Node, NodeId};

#[derive(thiserror::Error, fmt::Debug)]
pub enum LanguageError {
#[error("Missing notation for construct '{0}'")]
MissingNotation(String),
// Grammar errors
#[error("Duplicate key '{0}' used for both construct '{1}' and construct '{2}")]
DuplicateKey(char, String, String),
#[error("Duplicate name '{0}' used for two constructs")]
Expand All @@ -26,4 +26,24 @@ pub enum LanguageError {
// TODO: Check for cycles
// #[error("Sort '{0}' refers to itself")]
// InfiniteSort(String),

// Notation sets
#[error("The language '{0}' already has a notation set named '{1}'")]
DuplicateNotationSet(String, String),
#[error(
"Notation set '{0}' gives a notation for '{1}', but there is no construct with that name"
)]
UndefinedNotation(String, String),
#[error("Notation set '{0}' does not give a notation for construct '{1}'")]
MissingNotation(String, String),
#[error("Notation set '{0}' gives two notations for construct '{1}'")]
DuplicateNotation(String, String),
#[error("Invalid notation for construct '{1}' in notation set '{0}':\n{2}")]
InvalidNotation(String, String, ppp::NotationError),

// Languages
#[error("Duplicate name '{0}' used for two languages")]
DuplicateLanguage(String),
#[error("Name '{0}' is not a known language")]
UndefinedLanguage(String),
}
29 changes: 18 additions & 11 deletions src/pretty_doc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::infra::{bug, SynlessBug};
use crate::language::{DocStorage, Location, Node, NodeId};
use crate::style::{Condition, CursorHalf, Style, StyleLabel, ValidNotation};
use crate::style::{
Condition, CursorHalf, Style, StyleLabel, ValidNotation, HOLE_STYLE, LEFT_CURSOR_STYLE,
RIGHT_CURSOR_STYLE,
};
use partial_pretty_printer as ppp;
use std::fmt;
use std::sync::OnceLock;
Expand Down Expand Up @@ -102,15 +105,15 @@ impl<'d> ppp::PrettyDoc<'d> for DocRef<'d> {

fn lookup_style(self, style_label: StyleLabel) -> Style {
match style_label {
StyleLabel::Hole => HOLE_STYLE,
StyleLabel::Open => {
let mut style = Style::default();
if self.cursor_pos == Location::BeforeFirstChild(self.node) {
style.cursor = Some(CursorHalf::Left)
LEFT_CURSOR_STYLE
} else {
Style::default()
}
style
}
StyleLabel::Close => {
let mut style = Style::default();
let at_end = match self.cursor_pos {
Location::InText(..) => todo!(),
Location::BeforeFirstChild(parent) => {
Expand All @@ -121,22 +124,26 @@ impl<'d> ppp::PrettyDoc<'d> for DocRef<'d> {
// TODO: perhaps rewrite as:
// if self.node.gap_after_children(self.storage) == self.cursor_pos
if at_end {
style.cursor = Some(CursorHalf::Right)
RIGHT_CURSOR_STYLE
} else {
Style::default()
}
style
}
StyleLabel::Properties {
color,
fg_color,
bg_color,
bold,
italic,
underlined,
priority,
} => Style {
color: color.map(|x| (x, priority)),
fg_color: fg_color.map(|x| (x, priority)),
bg_color: bg_color.map(|x| (x, priority)),
bold: bold.map(|x| (x, priority)),
italic: italic.map(|x| (x, priority)),
underlined: underlined.map(|x| (x, priority)),
cursor: None,
is_hole: false,
},
}
}
Expand All @@ -145,9 +152,9 @@ impl<'d> ppp::PrettyDoc<'d> for DocRef<'d> {
if self.text_pos.is_some() {
Style::default()
} else if self.left_cursor == Some(self.node) {
Style::cursor(CursorHalf::Left)
LEFT_CURSOR_STYLE
} else if self.right_cursor == Some(self.node) {
Style::cursor(CursorHalf::Right)
RIGHT_CURSOR_STYLE
} else {
Style::default()
}
Expand Down
Loading

0 comments on commit 22d7bba

Please sign in to comment.