Skip to content

Commit

Permalink
refactor[value/format] rewrite formatting functions with visitors
Browse files Browse the repository at this point in the history
A visitor trait was added to the value package, and the formatting
functions were rewritten as visitors.  They were also renamed to more
meaningful names.
  • Loading branch information
clarete committed Sep 26, 2023
1 parent 1594c5a commit 95ba121
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 113 deletions.
11 changes: 6 additions & 5 deletions langlang/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@ type FormattingFunc = fn(v: &Value) -> String;

fn formatter(name: &str) -> FormattingFunc {
match name {
"fmt0" => format::value_fmt0,
"fmt1" => format::value_fmt1,
"fmt2" => format::value_fmt2,
"html" => format::value_html,
"" => format::compact,
"compact" => format::compact,
"html" => format::html,
"indented" => format::indented,
"raw" => format::raw,
_ => {
warn!("oh no! an invalud formatter: {}", name);
format::value_fmt0
format::raw
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion langlang_lib/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,7 @@ impl<'a> VM<'a> {
top.index,
top.values
.iter()
.map(format::value_fmt1)
.map(format::compact)
.collect::<Vec<_>>()
.join(", ")
));
Expand Down
229 changes: 122 additions & 107 deletions langlang_value/src/format.rs
Original file line number Diff line number Diff line change
@@ -1,116 +1,29 @@
use crate::value::Value;
use crate::value::{self, Value};
use crate::visitor::{walk_list, walk_node, Visitor};

pub fn value_fmt0(value: &Value) -> String {
let mut s = String::new();
s.push_str(format!("{:#?}", value).as_str());
s
// The raw formater uses the host language's formatting function
pub fn raw(value: &Value) -> String {
format!("{:#?}", value)
}

pub fn value_fmt1(value: &Value) -> String {
let mut s = String::new();
match value {
Value::Char(ref v) => s.push(v.value),
Value::String(ref v) => s.push_str(&v.value),
Value::Node(ref node) => {
s.push_str(&node.name);
s.push('[');
for i in &node.items {
s.push_str(value_fmt1(i).as_str())
}
s.push(']');
}
Value::List(ref list) => {
s.push('[');
for c in &list.values {
s.push_str(value_fmt1(c).as_str())
}
s.push(']');
}
Value::Error(ref err) => {
s.push_str("Error[");
s.push_str(&err.label);
if let Some(m) = &err.message {
s.push_str(": ");
s.push_str(m);
}
s.push(']');
}
}
s
// compact formatter wraps lists and nodes around square brackets
pub fn compact(value: &Value) -> String {
let mut f = CompactFormatter::default();
f.visit_value(value);
f.output
}

pub fn value_fmt2(value: &Value) -> String {
fn f(value: &Value, indent: u16) -> String {
let mut s = String::new();
match value {
Value::Char(v) => {
for _ in 0..indent {
s.push_str(" ");
}
s.push('"');
match v.value {
'\n' => s.push_str("\\n"),
vv => s.push(vv),
}
s.push('"');
}
Value::String(v) => {
for _ in 0..indent {
s.push_str(" ");
}
s.push_str(format!(r"{:#?}", v.value).as_str());
}
Value::Node(n) => {
for _ in 0..indent {
s.push_str(" ");
}
s.push_str(&n.name);
s.push(':');
s.push(' ');
s.push('[');
s.push('\n');
for i in &n.items {
s.push_str(f(i, indent + 1).as_str());
s.push('\n');
}
for _ in 0..indent {
s.push_str(" ");
}
s.push(']');
}

Value::List(n) => {
for _ in 0..indent {
s.push_str(" ");
}
s.push('{');
for c in &n.values {
s.push_str(f(c, indent + 1).as_str())
}
for _ in 0..indent {
s.push_str(" ");
}
s.push('}');
}
Value::Error(n) => {
for _ in 0..indent {
s.push_str(" ");
}
s.push_str("Error{");
s.push_str(&n.label);
if let Some(m) = &n.message {
s.push_str(": ");
s.push_str(m);
}
s.push('}');
}
}
s
}
f(value, 0)
// The indented formatter will print out values spanning multiple
// lines if container objects like lists or nodes are present
pub fn indented(value: &Value) -> String {
let mut f = IndentedFormatter::default();
f.visit_value(value);
f.output
}

pub fn value_html(value: &Value) -> String {
// The html formatter will wrapp all node objects around a span tag
// with containing a class attribute that's named after the node.
pub fn html(value: &Value) -> String {
let mut s = String::new();
match value {
Value::Char(v) => match v.value {
Expand All @@ -123,11 +36,113 @@ pub fn value_html(value: &Value) -> String {
s.push_str(&node.name);
s.push_str("\">");
for i in &node.items {
s.push_str(value_html(i).as_str());
s.push_str(html(i).as_str());
}
s.push_str("</span>");
}
_ => {}
}
s
}

#[derive(Default)]
struct CompactFormatter {
output: String,
}

impl<'a> Visitor<'a> for CompactFormatter {
fn visit_char(&mut self, n: &'a value::Char) {
self.output.push(n.value);
}

fn visit_string(&mut self, n: &'a value::String) {
self.output.push_str(&n.value);
}

fn visit_list(&mut self, n: &'a value::List) {
self.output.push('[');
walk_list(self, n);
self.output.push(']');
}

fn visit_node(&mut self, n: &'a value::Node) {
self.output.push_str(&n.name);
self.output.push('[');
walk_node(self, n);
self.output.push(']');
}

fn visit_error(&mut self, n: &'a value::Error) {
self.output.push_str("Error[");
self.output.push_str(&n.label);
if let Some(m) = &n.message {
self.output.push_str(": ");
self.output.push_str(m);
}
self.output.push(']');
}
}

#[derive(Default)]
struct IndentedFormatter {
output: String,
depth: usize,
}

impl IndentedFormatter {
fn indent(&mut self) {
self.depth += 1
}

fn unindent(&mut self) {
self.depth -= 1
}

fn write_indent(&mut self) {
for _ in 0..self.depth {
self.output.push_str(" ")
}
}

fn writes(&mut self, v: &str) {
self.write_indent();
self.output.push_str(v)
}
}

impl<'a> Visitor<'a> for IndentedFormatter {
fn visit_char(&mut self, n: &'a value::Char) {
self.writes(&format!("'{}'\n", n.value));
}

fn visit_string(&mut self, n: &'a value::String) {
self.writes(&format!("'{}'\n", n.value));
}

fn visit_list(&mut self, n: &'a value::List) {
self.writes("{\n");
self.indent();
walk_list(self, n);
self.unindent();
self.writes("}\n");
}

fn visit_node(&mut self, n: &'a value::Node) {
self.writes(&n.name);
self.output.push_str(" {\n");
self.indent();
walk_node(self, n);
self.unindent();
self.writes("}\n");
}

fn visit_error(&mut self, n: &'a value::Error) {
self.writes("Error{");
self.output.push_str(&n.label);
if let Some(m) = &n.message {
self.output.push_str(": ");
self.output.push_str(m);
}
self.output.push_str("}");
}
}
1 change: 1 addition & 0 deletions langlang_value/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod format;
pub mod source_map;
pub mod value;
pub mod visitor;
43 changes: 43 additions & 0 deletions langlang_value/src/visitor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::value::*;

pub trait Visitor<'a>: Sized {
fn visit_value(&mut self, n: &'a Value) {
walk_value(self, n);
}

fn visit_list(&mut self, n: &'a List) {
walk_list(self, n);
}

fn visit_node(&mut self, n: &'a Node) {
walk_node(self, n);
}

fn visit_char(&mut self, _: &'a Char) {}

fn visit_string(&mut self, _: &'a String) {}

fn visit_error(&mut self, _: &'a Error) {}
}

pub fn walk_value<'a, V: Visitor<'a>>(visitor: &mut V, n: &'a Value) {
match n {
Value::Char(v) => visitor.visit_char(v),
Value::String(v) => visitor.visit_string(v),
Value::List(v) => visitor.visit_list(v),
Value::Node(v) => visitor.visit_node(v),
Value::Error(v) => visitor.visit_error(v),
}
}

pub fn walk_list<'a, V: Visitor<'a>>(visitor: &mut V, n: &'a List) {
for v in &n.values {
visitor.visit_value(v)
}
}

pub fn walk_node<'a, V: Visitor<'a>>(visitor: &mut V, n: &'a Node) {
for v in &n.items {
visitor.visit_value(v)
}
}

0 comments on commit 95ba121

Please sign in to comment.