Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "technique"
version = "0.4.1"
version = "0.4.2"
edition = "2021"
description = "A domain specific language for procedures."
authors = [ "Andrew Cowie" ]
Expand Down
84 changes: 71 additions & 13 deletions src/formatting/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,12 @@ pub fn render_invocation<'i>(invocation: &'i Invocation, renderer: &dyn Render)
render_fragments(&sub.fragments, renderer)
}

pub fn render_descriptive<'i>(descriptive: &'i Descriptive, renderer: &dyn Render) -> String {
let mut sub = Formatter::new(78);
sub.append_descriptive(descriptive);
render_fragments(&sub.fragments, renderer)
}

pub fn render_function<'i>(function: &'i Function, renderer: &dyn Render) -> String {
let mut sub = Formatter::new(78);
sub.append_function(function);
Expand Down Expand Up @@ -322,16 +328,41 @@ impl<'i> Formatter<'i> {
sub.add_fragment_reference(Syntax::Neutral, " ");
sub.add_fragment_reference(Syntax::Structure, "}");
sub.flush_current();
sub.fragments

let mut combined = String::new();
for (_syntax, content) in &sub.fragments {
combined.push_str(&content);
}

vec![(Syntax::Structure, Cow::Owned(combined))]
}
}
}

fn render_string_interpolation(&self, expr: &'i Expression) -> Vec<(Syntax, Cow<'i, str>)> {
let mut sub = self.subformatter();
sub.add_fragment_reference(Syntax::Structure, "{");
sub.add_fragment_reference(Syntax::Neutral, " ");
sub.append_expression(expr);
sub.add_fragment_reference(Syntax::Neutral, " ");
sub.add_fragment_reference(Syntax::Structure, "}");
sub.flush_current();
sub.fragments
}

fn render_application(&self, invocation: &'i Invocation) -> Vec<(Syntax, Cow<'i, str>)> {
let mut sub = self.subformatter();
sub.append_application(invocation);
sub.flush_current();
sub.fragments

// Combine all fragments into a single atomic fragment to prevent wrapping
let mut combined = String::new();
for (_syntax, content) in &sub.fragments {
combined.push_str(&content);
}

// Return as a single fragment
vec![(Syntax::Invocation, Cow::Owned(combined))]
}

fn render_binding(
Expand Down Expand Up @@ -363,7 +394,14 @@ impl<'i> Formatter<'i> {
sub.add_fragment_reference(Syntax::Neutral, " ");
sub.append_variables(variables);
sub.flush_current();
sub.fragments

// Combine all fragments into a single atomic fragment to prevent wrapping
let mut combined = String::new();
for (_syntax, content) in &sub.fragments {
combined.push_str(&content);
}

vec![(Syntax::Structure, Cow::Owned(combined))]
}

pub fn append_char(&mut self, c: char) {
Expand Down Expand Up @@ -584,7 +622,16 @@ impl<'i> Formatter<'i> {
}
}

fn append_descriptives(&mut self, descriptives: &'i Vec<Descriptive>) {
// This is a helper for rendering a single descriptives in error messages.
// The real method is append_decriptives() below; this method simply
// creates a single element slice that can be passed to it.
fn append_descriptive(&mut self, descriptive: &'i Descriptive) {
use std::slice;
let slice = slice::from_ref(descriptive);
self.append_descriptives(slice);
}

fn append_descriptives(&mut self, descriptives: &'i [Descriptive<'i>]) {
let syntax = self.current;
let mut line = self.builder();

Expand Down Expand Up @@ -894,7 +941,7 @@ impl<'i> Formatter<'i> {
self.add_fragment_reference(Syntax::String, text);
}
Piece::Interpolation(expr) => {
let fragments = self.render_inline_code(expr);
let fragments = self.render_string_interpolation(expr);
for (syntax, content) in fragments {
self.add_fragment(syntax, content);
}
Expand Down Expand Up @@ -1223,30 +1270,41 @@ impl<'a, 'i> Line<'a, 'i> {
let fragments = self
.output
.render_inline_code(expr);
self.add_fragments(fragments);
for (syntax, content) in fragments {
self.add_no_wrap(syntax, content);
}
}

fn add_application(&mut self, invocation: &'i Invocation) {
let fragments = self
.output
.render_application(invocation);
self.add_fragments(fragments);
for (syntax, content) in fragments {
self.add_no_wrap(syntax, content);
}
}

fn add_binding(&mut self, inner_descriptive: &'i Descriptive, variables: &'i Vec<Identifier>) {
let fragments = self
.output
.render_binding(inner_descriptive, variables);
self.add_fragments(fragments);
}

fn add_fragments(&mut self, fragments: Vec<(Syntax, Cow<'i, str>)>) {
// All fragments should be atomic - the formatter is responsible for breaking up content
// Bindings should not wrap - add as a single non-wrapping unit
for (syntax, content) in fragments {
self.add_atomic_cow(syntax, content);
self.add_no_wrap(syntax, content);
}
}

fn add_no_wrap(&mut self, syntax: Syntax, content: Cow<'i, str>) {
// Add content that must never wrap mid-construct (inline code,
// applications, bindings) Unlike add_atomic_cow(), this bypasses
// width checking entirely to preserve the integrity of these language
// constructs on single lines
let len = content.len() as u8;
self.current
.push((syntax, content));
self.position += len;
}

fn wrap_line(&mut self) {
// Emit all current fragments to the output
for (syntax, content) in self
Expand Down
53 changes: 7 additions & 46 deletions src/language/types.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Types representing an Abstract Syntax Tree for the Technique language

use crate::{language::quantity::parse_quantity, regex::*};
use crate::regex::*;

#[derive(Eq, Debug, PartialEq)]
pub struct Document<'i> {
Expand Down Expand Up @@ -214,7 +214,7 @@ pub use crate::language::quantity::Quantity;
// the validate functions all need to have start and end anchors, which seems
// like it should be abstracted away.

pub fn validate_license(input: &str) -> Option<&str> {
pub(crate) fn validate_license(input: &str) -> Option<&str> {
let re = regex!(r"^[A-Za-z0-9.,\-_ \(\)\[\]]+$");

if re.is_match(input) {
Expand All @@ -224,7 +224,7 @@ pub fn validate_license(input: &str) -> Option<&str> {
}
}

pub fn validate_copyright(input: &str) -> Option<&str> {
pub(crate) fn validate_copyright(input: &str) -> Option<&str> {
let re = regex!(r"^[A-Za-z0-9.,\-_ \(\)\[\]]+$");

if re.is_match(input) {
Expand All @@ -234,7 +234,7 @@ pub fn validate_copyright(input: &str) -> Option<&str> {
}
}

pub fn validate_template(input: &str) -> Option<&str> {
pub(crate) fn validate_template(input: &str) -> Option<&str> {
let re = regex!(r"^[A-Za-z0-9.,\-]+$");

if re.is_match(input) {
Expand All @@ -244,7 +244,7 @@ pub fn validate_template(input: &str) -> Option<&str> {
}
}

pub fn validate_identifier(input: &str) -> Option<Identifier<'_>> {
pub(crate) fn validate_identifier(input: &str) -> Option<Identifier<'_>> {
if input.len() == 0 {
return None;
}
Expand All @@ -257,7 +257,7 @@ pub fn validate_identifier(input: &str) -> Option<Identifier<'_>> {
}
}

pub fn validate_forma(input: &str) -> Option<Forma<'_>> {
pub(crate) fn validate_forma(input: &str) -> Option<Forma<'_>> {
if input.len() == 0 {
return None;
}
Expand Down Expand Up @@ -294,7 +294,7 @@ fn parse_tuple(input: &str) -> Option<Vec<Forma<'_>>> {
}

/// This one copes with (and discards) any internal whitespace encountered.
pub fn validate_genus(input: &str) -> Option<Genus<'_>> {
pub(crate) fn validate_genus(input: &str) -> Option<Genus<'_>> {
let first = input
.chars()
.next()
Expand Down Expand Up @@ -371,33 +371,6 @@ pub fn validate_response(input: &str) -> Option<Response<'_>> {
Some(Response { value, condition })
}

fn _validate_decimal(_input: &str) -> Option<Numeric<'_>> {
// Test the regex macro availability within types.rs
let _decimal_regex = regex!(r"^\s*-?[0-9]+\.[0-9]+\s*$");
// For now, just return None since we removed Decimal variant
None
}

pub fn validate_numeric(input: &str) -> Option<Numeric<'_>> {
if input.is_empty() {
return None;
}

let input = input.trim_ascii();

// Try to parse as a simple Integral first
if let Ok(amount) = input.parse::<i64>() {
return Some(Numeric::Integral(amount));
}

// Try to parse as a Quantity (scientific notation with units)
if let Some(quantity) = parse_quantity(input) {
return Some(Numeric::Scientific(quantity));
}

None
}

#[cfg(test)]
mod check {
use super::*;
Expand Down Expand Up @@ -582,18 +555,6 @@ mod check {
t1
}

#[test]
fn numeric_rules() {
// Test simple integers
assert_eq!(validate_numeric("42"), Some(Numeric::Integral(42)));
assert_eq!(validate_numeric("0"), Some(Numeric::Integral(0)));
assert_eq!(validate_numeric("-123"), Some(Numeric::Integral(-123)));
assert_eq!(
validate_numeric("9223372036854775807"),
Some(Numeric::Integral(9223372036854775807))
);
}

#[test]
fn ast_construction() {
let t1 = Metadata {
Expand Down
Loading