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
4 changes: 2 additions & 2 deletions examples/abs.zy
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const std = import("std");
const std = import("std")

fn abs(x: i32): i32 {
return if x < 0 then -x else x
}

fn main(): void {
std.debug.print(abs(-42));
std.debug.print(abs(-42))
}
2 changes: 1 addition & 1 deletion examples/get-readme.zy
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const std = import("std")
const fs = std.fs

fn getReadme(filename: string): !string {
const data = fs.readTextFile(filename)?;
const data = fs.readTextFile(filename)?
return data
}

Expand Down
12 changes: 6 additions & 6 deletions examples/switch.zy
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
const std = import("std");
const std = import("std")

fn label(n: i32): string {
return switch n {
1 => "one",
2 => "two",
3 => "three",
else => "other",
};
}
}

fn main(): void {
std.debug.print(label(1));
std.debug.print(label(2));
std.debug.print(label(3));
std.debug.print(label(99));
std.debug.print(label(1))
std.debug.print(label(2))
std.debug.print(label(3))
std.debug.print(label(99))
}
33 changes: 33 additions & 0 deletions src/colors.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,39 @@
const RESET: &str = "\x1b[0m";
const BOLD_RED: &str = "\x1b[1;31m";
const RED: &str = "\x1b[31m";
const GREEN: &str = "\x1b[32m";
const RED_BG: &str = "\x1b[97;41m"; // bright white on red bg
const GREEN_BG: &str = "\x1b[97;42m"; // bright white on green bg

fn wrap(color: &str, s: &str) -> String {
format!("{}{}{}", color, s, RESET)
}

fn wrap_diff(fg: &str, bg: &str, prefix: &str, mid: &str, suffix: &str) -> String {
format!(
"{}{}{}{}{}{}{}{}",
fg, prefix, bg, mid, RESET, fg, suffix, RESET
)
}

pub fn error(s: &str) -> String {
format!("{}error{}: {}", BOLD_RED, RESET, s)
}

pub fn red(s: &str) -> String {
wrap(RED, s)
}

pub fn green(s: &str) -> String {
wrap(GREEN, s)
}

/// Red line with background highlight on the changed portion.
pub fn red_diff(prefix: &str, mid: &str, suffix: &str) -> String {
wrap_diff(RED, RED_BG, prefix, mid, suffix)
}

/// Green line with background highlight on the changed portion.
pub fn green_diff(prefix: &str, mid: &str, suffix: &str) -> String {
wrap_diff(GREEN, GREEN_BG, prefix, mid, suffix)
}
180 changes: 158 additions & 22 deletions src/commands/check.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,89 @@
use super::{abort, render_error};
use super::abort;
use std::path::Path;

pub fn run(input_path: Option<&str>) {
pub fn run(input_path: Option<&str>, fix: bool) {
match input_path {
None => {
let files = collect_zy_files(Path::new("."));
let total_errors: usize = files.iter().map(|p| check_file(p)).sum();
if total_errors > 0 {
std::process::exit(1);
let total = files.len();
let mut type_error_count = 0usize;
let mut not_formatted = 0usize;
for p in &files {
let (type_errors, fmt_diff, _changed) = check_file(p, fix);
if !type_errors.is_empty() {
eprintln!("{}", super::full_path(p));
for msg in &type_errors {
eprintln!("{}", msg);
}
type_error_count += type_errors.len();
}
if let Some(diff) = &fmt_diff {
eprintln!("from {}:", super::full_path(p));
eprintln!("{}", diff);
not_formatted += 1;
}
}
print_summary(total, type_error_count, not_formatted);
}
Some(path) => {
check_source(&super::read_file(path), path);
let source = super::read_file(path);
let (_, type_errors, fmt_diff, _changed) = check_and_report(&source, path, fix);
if !type_errors.is_empty() {
eprintln!("{}", super::full_path(Path::new(path)));
for msg in &type_errors {
eprintln!("{}", msg);
}
}
let not_formatted = if let Some(diff) = &fmt_diff {
eprintln!("from {}:", super::full_path(Path::new(path)));
eprintln!("{}", diff);
1
} else {
0
};
print_summary(1, type_errors.len(), not_formatted);
if !type_errors.is_empty() {
abort(type_errors.len());
}
}
}
}

fn plural(n: usize) -> &'static str {
if n == 1 { "" } else { "s" }
}

fn print_summary(total: usize, type_errors: usize, not_formatted: usize) {
if not_formatted > 0 {
eprintln!(
"{}",
crate::colors::error(&format!(
"Found {} not formatted file{} in {} file{}",
not_formatted,
plural(not_formatted),
total,
plural(total),
))
);
if type_errors == 0 {
std::process::exit(1);
}
} else if type_errors == 0 {
eprintln!("Checked {} file{}", total, plural(total));
}
if type_errors > 0 {
std::process::exit(1);
}
}

/// Parses and type-checks source, aborting on errors. Returns the AST.
pub fn check_source(source: &str, path: &str) -> crate::parser::Program {
let (ast, error_count) = check_and_report(source, path);
if error_count > 0 {
abort(error_count);
let (ast, type_errors, _, _) = check_and_report(source, path, false);
if !type_errors.is_empty() {
for msg in &type_errors {
eprintln!("{}", msg);
}
abort(type_errors.len());
}
ast
}
Expand All @@ -41,35 +104,108 @@ fn collect_zy_files(dir: &Path) -> Vec<std::path::PathBuf> {
result
}

fn check_file(path: &Path) -> usize {
fn check_file(path: &Path, fix: bool) -> (Vec<String>, Option<String>, bool) {
let path_str = path.to_string_lossy();
let source = match std::fs::read_to_string(path) {
Ok(s) => s,
Err(e) => {
eprintln!("{}: {}", path_str, e);
return 1;
return (vec![format!("{}: {}", path_str, e)], None, false);
}
};
let (_, error_count) = check_and_report(&source, &path_str);
error_count
let (_, type_errors, fmt_diff, changed) = check_and_report(&source, &path_str, fix);
(type_errors, fmt_diff, changed)
}

fn diff_highlight(a: &str, b: &str) -> (String, String) {
let ac: Vec<char> = a.chars().collect();
let bc: Vec<char> = b.chars().collect();
let prefix = ac.iter().zip(bc.iter()).take_while(|(x, y)| x == y).count();
let suffix = ac[prefix..]
.iter()
.rev()
.zip(bc[prefix..].iter().rev())
.take_while(|(x, y)| x == y)
.count();
let a_end = ac.len() - suffix;
let b_end = bc.len() - suffix;
let ap: String = ac[..prefix].iter().collect();
let am: String = ac[prefix..a_end].iter().collect();
let as_: String = ac[a_end..].iter().collect();
let bp: String = bc[..prefix].iter().collect();
let bm: String = bc[prefix..b_end].iter().collect();
let bs: String = bc[b_end..].iter().collect();
(
crate::colors::red_diff(&ap, &am, &as_),
crate::colors::green_diff(&bp, &bm, &bs),
)
}

fn check_and_report(source: &str, path: &str) -> (crate::parser::Program, usize) {
fn fmt_diff(source: &str, formatted: &str) -> String {
let src_lines: Vec<&str> = source.lines().collect();
let fmt_lines: Vec<&str> = formatted.lines().collect();
let mut out = String::new();
let max_len = src_lines.len().max(fmt_lines.len());
let mut shown = 0;
for i in 0..max_len {
let a = src_lines.get(i).copied().unwrap_or("");
let b = fmt_lines.get(i).copied().unwrap_or("");
if a != b {
let (hl_a, hl_b) = diff_highlight(a, b);
let ln = i + 1;
out.push_str(&crate::colors::red(&format!("{} | -", ln)));
out.push_str(&hl_a);
out.push('\n');
out.push_str(&crate::colors::green(&format!("{} | +", ln)));
out.push_str(&hl_b);
out.push('\n');
shown += 1;
if shown >= 8 {
out.push_str("...\n");
break;
}
}
}
let trimmed_len = out.trim_end().len();
out.truncate(trimmed_len);
out
}

fn check_and_report(
source: &str,
path: &str,
fix: bool,
) -> (crate::parser::Program, Vec<String>, Option<String>, bool) {
let tokens = crate::lexer::tokenize(source);
let (ast, parse_errors) = crate::parser::parse(tokens);
let mut error_count = parse_errors.len();
let (ast, parse_errors, blanks) = crate::parser::parse(tokens);
let mut type_errors: Vec<String> = Vec::new();

for e in &parse_errors {
render_error(&e.message, e.span, source, path);
type_errors.push(super::format_error(&e.message, e.span, source, path));
}
let source_dir = Path::new(path).parent().unwrap_or(Path::new("."));
let zy_errors = crate::typechecker::check_with_diagnostics(&ast, source_dir);
error_count += zy_errors.len();
for e in &zy_errors {
if let Some(span) = e.span {
render_error(&e.message, span, source, path);
type_errors.push(super::format_error(&e.message, span, source, path));
} else {
eprintln!("{}", crate::colors::error(&e.message));
type_errors.push(crate::colors::error(&e.message).to_string());
}
}
(ast, error_count)

let mut fmt_diff_out: Option<String> = None;
let mut changed = false;
if parse_errors.is_empty() {
let formatted = crate::fmt::format_program(&ast, &blanks);
if formatted.trim() != source.trim() {
if fix {
std::fs::write(path, &formatted)
.unwrap_or_else(|e| panic!("Failed to write '{}': {}", path, e));
changed = true;
} else {
fmt_diff_out = Some(fmt_diff(source, &formatted));
}
}
}

(ast, type_errors, fmt_diff_out, changed)
}
22 changes: 14 additions & 8 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,23 @@ pub fn read_file(path: &str) -> String {
std::fs::read_to_string(path).unwrap_or_else(|e| panic!("Failed to read '{}': {}", path, e))
}

pub fn render_error(message: &str, span: (usize, usize), source: &str, path: &str) {
pub fn full_path(p: &std::path::Path) -> String {
let s = p.canonicalize().unwrap_or_else(|_| p.to_path_buf());
let s = s.to_string_lossy();
s.strip_prefix(r"\\?\").unwrap_or(&s).to_string()
}

pub fn format_error(message: &str, span: (usize, usize), source: &str, path: &str) -> String {
let (line, col, source_line, caret) = locate(span, source);
eprintln!(
"{}:{}:{}: {}",
format!(
"{}:{}:{}: {}\n {}\n {}",
path,
line,
col,
crate::colors::error(message)
);
eprintln!(" {}", source_line);
eprintln!(" {}", caret);
crate::colors::error(message),
source_line,
caret
)
}

pub fn locate(span: (usize, usize), source: &str) -> (usize, usize, &str, String) {
Expand Down Expand Up @@ -108,7 +114,7 @@ fn compile_module_inner(
}
let source = read_file(source_path);
let tokens = crate::lexer::tokenize(&source);
let (ast, _) = crate::parser::parse(tokens);
let (ast, _, _) = crate::parser::parse(tokens);
let zig_code = crate::codegen::generate(&ast);

let cache_dir = Path::new(cache_path).parent().unwrap();
Expand Down
Loading
Loading