Skip to content

Commit

Permalink
More things
Browse files Browse the repository at this point in the history
- Specification options
- Add `duplicate_block` example
- Formatting fixes
  • Loading branch information
kaleidawave committed Nov 10, 2024
1 parent c34761d commit e4f3a7d
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 78 deletions.
92 changes: 66 additions & 26 deletions checker/specification/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@ fn main() -> Result<(), Box<dyn Error>> {

if cfg!(feature = "staging") {
let staging = read_to_string("./staging.md")?;
writeln!(&mut out, "mod staging {{ use super::check_errors; ").unwrap();
writeln!(&mut out, "mod staging {{ ").unwrap();
writeln!(&mut out, "use super::{{check_expected_diagnostics, TypeCheckOptions}}; ")
.unwrap();
markdown_lines_append_test_to_rust(staging.lines().enumerate(), &mut out)?;
writeln!(&mut out, "}}").unwrap();
}

if cfg!(feature = "all") {
let to_implement = read_to_string("./to_implement.md")?;
writeln!(&mut out, "mod to_implement {{ use super::check_errors; ").unwrap();
writeln!(&mut out, "mod to_implement {{ ").unwrap();
writeln!(&mut out, "use super::{{check_expected_diagnostics, TypeCheckOptions}}; ")
.unwrap();
markdown_lines_append_test_to_rust(to_implement.lines().enumerate(), &mut out)?;
writeln!(&mut out, "}}").unwrap();
}
Expand Down Expand Up @@ -60,8 +64,20 @@ fn markdown_lines_append_test_to_rust(
let heading = line.strip_prefix("####").unwrap().trim_start();
let test_title = heading_to_rust_identifier(heading);

let blocks = {
let mut blocks = Vec::new();
pub struct File<'a> {
path: &'a str,
code: String,
}

// pub struct Block {
// /// Vec for FS tests
// files: Vec<File>,
// expected_diagnostics: Vec<String>,
// options: Vec<String>
// }

let files = {
let mut files = Vec::<File>::new();
let mut current_filename = None;
for (_, line) in lines.by_ref() {
// Also handles TSX
Expand All @@ -74,10 +90,10 @@ fn markdown_lines_append_test_to_rust(
for (_, line) in lines.by_ref() {
if let Some(path) = line.strip_prefix("// in ") {
if !code.trim().is_empty() {
blocks.push((
current_filename.unwrap_or(DEFAULT_FILE_PATH),
mem::take(&mut code),
));
files.push(File {
path: current_filename.unwrap_or(DEFAULT_FILE_PATH),
code: mem::take(&mut code),
});
}
current_filename = Some(path);
continue;
Expand All @@ -88,40 +104,64 @@ fn markdown_lines_append_test_to_rust(
code.push_str(line);
code.push('\n')
}
blocks.push((current_filename.unwrap_or(DEFAULT_FILE_PATH), code));
blocks
files.push(File { path: current_filename.unwrap_or(DEFAULT_FILE_PATH), code });
files
};
let errors = {
let mut errors = Vec::new();

let (expected_diagnostics, options) = {
let mut expected_diagnostics = Vec::new();
let mut options = None::<Vec<&str>>;
for (_, line) in lines.by_ref() {
if line.starts_with("#") {
if let (Some(args), false) = (line.strip_prefix("With "), options.is_some()) {
options = Some(args.split(',').collect());
} else if line.starts_with("#") {
panic!("block with no diagnostics or break between in {test_title}")
} else if line.starts_with('-') {
let error =
line.strip_prefix("- ").unwrap().replace('\\', "").replace('"', "\\\"");
errors.push(format!("\"{}\"", error))
} else if !errors.is_empty() {
} else if let Some(diagnostic) = line.strip_prefix("-") {
let error = diagnostic.trim().replace('\\', "").replace('"', "\\\"");
expected_diagnostics.push(format!("\"{}\"", error))
} else if !expected_diagnostics.is_empty() {
break;
}
}
errors
(expected_diagnostics, options)
};

let errors = errors.join(", ");
let expected_diagnostics = expected_diagnostics.join(", ");

let heading_idx = heading_idx + 1;
let code = blocks
// TODO don't allocate
let code_as_list = files
.into_iter()
.map(|(path, content)| format!("(\"{path}\",r#\"{content}\"#),"))
.fold(String::new(), |mut acc, cur| {
acc.push_str(&cur);
.map(|File { path, code }| format!("(\"{path}\",r#\"{code}\"#),"))
.reduce(|mut acc, slice| {
acc.push_str(&slice);
acc
});
})
.unwrap();

let options = if let Some(options) = options {
let arguments = options
.into_iter()
.map(|value| format!("{value}: true"))
.reduce(|mut acc, slice| {
acc.push_str(&slice);
acc.push_str(", ");
acc
})
.unwrap();
format!("Some(super::TypeCheckOptions {{ {arguments}, ..super::TypeCheckOptions::default() }})")
} else {
format!("None")
};

writeln!(
out,
"#[test] fn {test_title}() {{
super::check_errors(\"{heading}\", {heading_idx}, &[{code}], &[{errors}])
super::check_expected_diagnostics(
\"{heading}\", {heading_idx},
&[{code_as_list}], &[{expected_diagnostics}],
{options}
)
}}",
)?;
}
Expand Down
15 changes: 7 additions & 8 deletions checker/specification/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ use checker::{
diagnostics,
source_map::{Nullable, SourceId},
synthesis::EznoParser,
TypeCheckOptions,
};

// This is here as it is used in the included `/specification.rs`
use parser::ASTNode;

mod specification {
use super::check_errors;
use super::{check_expected_diagnostics, TypeCheckOptions};

// from build.rs
include!(concat!(env!("OUT_DIR"), "/specification.rs"));
Expand All @@ -37,12 +38,13 @@ const SIMPLE_DTS: Option<&str> = None;
const IN_CI: bool = option_env!("CI").is_some();

/// Called by each test
fn check_errors(
fn check_expected_diagnostics(
heading: &'static str,
_line: usize,
line: usize,
// (Path, Content)
code: &[(&'static str, &'static str)],
expected_diagnostics: &[&'static str],
type_check_options: Option<TypeCheckOptions>,
) {
// let global_buffer = Arc::new(Mutex::new(String::new()));
// let old_panic_hook = panic::take_hook();
Expand All @@ -59,10 +61,7 @@ fn check_errors(
// })
// });

// TODO could test these
let type_check_options = Default::default();

// eprintln!("{:?}", code);
let type_check_options = type_check_options.unwrap_or_default();

// let result = panic::catch_unwind(|| {

Expand Down Expand Up @@ -125,7 +124,7 @@ fn check_errors(

if diagnostics != expected_diagnostics {
panic!(
"{}",
"In '{heading}' on line {line}, found\n{}",
pretty_assertions::Comparison::new(expected_diagnostics, &diagnostics).to_string()
)
}
Expand Down
135 changes: 135 additions & 0 deletions parser/examples/duplicate_block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use ezno_parser::{
declarations::VariableDeclaration,
visiting::{Chain, ImmutableVariableOrProperty, VisitOptions, Visitor, Visitors},
ASTNode, Declaration, Expression, Module, StatementOrDeclaration, VariableField,
};
use std::collections::{HashMap, HashSet};

struct Offsets {
pub offsets: Vec<u32>,
/// TODO use &str references
pub top_level_variables: HashSet<String>,
pub top_level_types: HashSet<String>,
}

/// TODO this could use visting right?
/// TODO abstract to library
/// TODO do for funtions and types
fn get_top_level_identifiers(m: &Module) -> (HashSet<String>, HashSet<String>) {
let (mut variables, mut types): (HashSet<_>, HashSet<_>) = Default::default();
for item in &m.items {
match item {
StatementOrDeclaration::Declaration(Declaration::Variable(variable)) => {
match variable {
VariableDeclaration::ConstDeclaration { declarations, position: _ } => {
for declaration in declarations {
if let VariableField::Name(identifier) = declaration.name.get_ast_ref()
{
variables.insert(identifier.as_option_str().unwrap().to_owned());
}
}
}
VariableDeclaration::LetDeclaration { declarations, position: _ } => {
for declaration in declarations {
if let VariableField::Name(identifier) = declaration.name.get_ast_ref()
{
variables.insert(identifier.as_option_str().unwrap().to_owned());
}
}
}
}
}
StatementOrDeclaration::Declaration(Declaration::Function(function)) => {
variables.insert(function.on.name.identifier.as_option_str().unwrap().to_owned());
}
_ => {}
}
}
(variables, types)
}

fn main() {
let code = "
let x = 2;
let y = x + 2;
let z = 6;
"
.trim();

// function func() {{ return [x, z] }}
let module = Module::from_string(code.into(), Default::default()).unwrap();

let (top_level_variables, top_level_types) = get_top_level_identifiers(&module);

let mut visitors = Visitors {
expression_visitors: vec![Box::new(NameReferenceFinder)],
statement_visitors: Default::default(),
variable_visitors: vec![Box::new(NameIndexFinder)],
block_visitors: Default::default(),
};

// eprintln!("variables={:#?}", (&top_level_variables, &top_level_types));

let mut offsets: Offsets =
Offsets { offsets: Default::default(), top_level_variables, top_level_types };

module.visit::<Offsets>(
&mut visitors,
&mut offsets,
&VisitOptions { visit_nested_blocks: true, reverse_statements: false },
source_map::Nullable::NULL,
);

// TODO why is this backwards
// eprintln!("offsets={:#?}", offsets);

offsets.offsets.sort_unstable();
let mut rest = code.to_owned();
for (idx, offset) in offsets.offsets.iter_mut().enumerate().rev() {
let current_offset = *offset as usize;
rest.insert_str(current_offset, "000");
// need to ammed offset now string has been changed
*offset += ("000".len() * idx) as u32;
}
rest.push('\n');

let mut total = rest.clone();
const SIZE: usize = 10;
total.reserve(rest.len() * (SIZE - 1));

for i in 1..SIZE {
let name = format!("{:03}", i);
for offset in offsets.offsets.iter().copied() {
let range = offset as usize..(offset as usize + 3);
rest.replace_range(range, &name);
}

total.push_str(&rest);
}

eprintln!("{}", total);
}

/// TODO this could be collected in the same process as above
struct NameIndexFinder;

impl<'a> Visitor<ImmutableVariableOrProperty<'a>, Offsets> for NameIndexFinder {
fn visit(&mut self, item: &ImmutableVariableOrProperty<'a>, data: &mut Offsets, chain: &Chain) {
if chain.len() == 1 && item.get_variable_name().is_some() {
data.offsets.push(item.get_position().end);
// data.insert(name.to_owned());
}
}
}

struct NameReferenceFinder;

impl Visitor<Expression, Offsets> for NameReferenceFinder {
fn visit(&mut self, item: &Expression, data: &mut Offsets, _chain: &Chain) {
if let Expression::VariableReference(name, position) = item {
if data.top_level_variables.contains(name) {
data.offsets.push(position.end);
}
}
}
}
Loading

0 comments on commit e4f3a7d

Please sign in to comment.