2020-08-02 21:17:42 +02:00
use std ::env ;
use std ::ffi ::OsStr ;
2022-02-22 12:42:02 +01:00
use std ::fs ;
2022-01-03 23:18:21 +01:00
use std ::ops ::Range ;
2020-08-17 16:18:55 +02:00
use std ::path ::Path ;
2022-01-31 16:06:44 +01:00
use std ::sync ::Arc ;
2020-08-02 21:17:42 +02:00
2021-07-08 22:33:44 +02:00
use tiny_skia as sk ;
2022-04-16 22:23:57 +02:00
use unscanny ::Scanner ;
2021-01-14 16:47:29 +01:00
use walkdir ::WalkDir ;
2020-08-02 21:17:42 +02:00
2022-04-24 15:42:56 +02:00
use typst ::eval ::{ Smart , Value } ;
2022-01-24 16:48:24 +01:00
use typst ::frame ::{ Element , Frame } ;
2022-05-02 18:48:32 +02:00
use typst ::geom ::{ Length , RgbaColor , Sides } ;
2022-02-28 15:50:48 +01:00
use typst ::library ::layout ::PageNode ;
2022-04-08 15:08:26 +02:00
use typst ::library ::text ::{ TextNode , TextSize } ;
2021-08-09 11:06:37 +02:00
use typst ::loading ::FsLoader ;
2022-04-24 15:42:56 +02:00
use typst ::model ::StyleMap ;
2022-02-22 12:42:02 +01:00
use typst ::source ::SourceFile ;
2022-05-31 13:19:09 +02:00
use typst ::syntax ::SyntaxNode ;
2022-05-25 11:16:03 +02:00
use typst ::{ bail , Config , Context } ;
2020-08-02 21:17:42 +02:00
2021-02-20 17:53:40 +01:00
const TYP_DIR : & str = " ./typ " ;
const REF_DIR : & str = " ./ref " ;
const PNG_DIR : & str = " ./png " ;
const PDF_DIR : & str = " ./pdf " ;
2020-12-10 22:44:35 +01:00
const FONT_DIR : & str = " ../fonts " ;
2020-08-02 21:17:42 +02:00
fn main ( ) {
2020-11-20 16:36:22 +01:00
env ::set_current_dir ( env ::current_dir ( ) . unwrap ( ) . join ( " tests " ) ) . unwrap ( ) ;
2021-03-13 14:18:31 +01:00
let args = Args ::new ( env ::args ( ) . skip ( 1 ) ) ;
2020-08-02 21:17:42 +02:00
let mut filtered = Vec ::new ( ) ;
2022-01-01 12:12:50 +01:00
// Since differents tests can affect each other through the layout cache, a
// deterministic order is very important for reproducibility.
for entry in WalkDir ::new ( " . " ) . sort_by_file_name ( ) {
2021-01-14 16:47:29 +01:00
let entry = entry . unwrap ( ) ;
2021-01-16 15:28:03 +01:00
if entry . depth ( ) < = 1 {
continue ;
}
2021-01-14 16:47:29 +01:00
let src_path = entry . into_path ( ) ;
2020-10-13 12:34:11 +02:00
if src_path . extension ( ) ! = Some ( OsStr ::new ( " typ " ) ) {
2020-08-02 21:17:42 +02:00
continue ;
}
2021-12-15 11:12:38 +01:00
if args . matches ( & src_path ) {
2021-01-14 16:47:29 +01:00
filtered . push ( src_path ) ;
2020-08-02 21:17:42 +02:00
}
}
let len = filtered . len ( ) ;
2021-01-13 21:33:22 +01:00
if len = = 1 {
2020-08-02 21:17:42 +02:00
println! ( " Running test ... " ) ;
2021-01-13 21:33:22 +01:00
} else if len > 1 {
2022-01-13 18:43:18 +01:00
println! ( " Running {len} tests " ) ;
2020-08-02 21:17:42 +02:00
}
2021-12-07 16:36:39 +01:00
// Set page width to 120pt with 10pt margins, so that the inner page is
// exactly 100pt wide. Page height is unbounded and font size is 10pt so
// that it multiplies to nice round numbers.
2021-12-30 12:12:19 +01:00
let mut styles = StyleMap ::new ( ) ;
2022-04-08 15:08:26 +02:00
styles . set ( PageNode ::WIDTH , Smart ::Custom ( Length ::pt ( 120.0 ) . into ( ) ) ) ;
2021-12-07 16:36:39 +01:00
styles . set ( PageNode ::HEIGHT , Smart ::Auto ) ;
2022-05-02 18:48:32 +02:00
styles . set (
PageNode ::MARGINS ,
2022-05-03 11:40:27 +02:00
Sides ::splat ( Some ( Smart ::Custom ( Length ::pt ( 10.0 ) . into ( ) ) ) ) ,
2022-05-02 18:48:32 +02:00
) ;
2022-04-08 15:08:26 +02:00
styles . set ( TextNode ::SIZE , TextSize ( Length ::pt ( 10.0 ) . into ( ) ) ) ;
2021-07-20 20:21:56 +02:00
2022-05-27 14:19:58 +02:00
// Hook up helpers into the global scope.
2021-07-21 11:25:49 +02:00
let mut std = typst ::library ::new ( ) ;
2022-05-26 17:14:44 +02:00
std . define ( " conifer " , RgbaColor ::new ( 0x9f , 0xEB , 0x52 , 0xFF ) ) ;
std . define ( " forest " , RgbaColor ::new ( 0x43 , 0xA1 , 0x27 , 0xFF ) ) ;
2022-03-12 14:24:24 +01:00
std . def_fn ( " test " , move | _ , args | {
2021-07-30 18:04:08 +02:00
let lhs = args . expect ::< Value > ( " left-hand side " ) ? ;
let rhs = args . expect ::< Value > ( " right-hand side " ) ? ;
if lhs ! = rhs {
2022-03-15 11:30:13 +01:00
bail! ( args . span , " Assertion failed: {:?} != {:?} " , lhs , rhs , ) ;
2021-07-30 18:04:08 +02:00
}
Ok ( Value ::None )
} ) ;
2022-05-27 14:19:58 +02:00
std . def_fn ( " print " , move | _ , args | {
print! ( " > " ) ;
for ( i , value ) in args . all ::< Value > ( ) ? . into_iter ( ) . enumerate ( ) {
if i > 0 {
print! ( " , " )
}
print! ( " {value:?} " ) ;
}
println! ( ) ;
Ok ( Value ::None )
} ) ;
2020-08-02 21:17:42 +02:00
2021-07-21 11:25:49 +02:00
// Create loader and context.
2022-05-25 11:16:03 +02:00
let loader = FsLoader ::new ( ) . with_path ( FONT_DIR ) ;
let config = Config ::builder ( ) . std ( std ) . styles ( styles ) . build ( ) ;
let mut ctx = Context ::new ( Arc ::new ( loader ) , config ) ;
2020-08-02 21:17:42 +02:00
2021-07-21 11:25:49 +02:00
// Run all the tests.
2021-12-15 11:11:57 +01:00
let mut ok = 0 ;
2021-01-14 16:47:29 +01:00
for src_path in filtered {
2021-03-13 14:18:31 +01:00
let path = src_path . strip_prefix ( TYP_DIR ) . unwrap ( ) ;
let png_path = Path ::new ( PNG_DIR ) . join ( path ) . with_extension ( " png " ) ;
let ref_path = Path ::new ( REF_DIR ) . join ( path ) . with_extension ( " png " ) ;
let pdf_path =
args . pdf . then ( | | Path ::new ( PDF_DIR ) . join ( path ) . with_extension ( " pdf " ) ) ;
2021-12-15 11:11:57 +01:00
ok + = test (
2021-07-20 20:21:56 +02:00
& mut ctx ,
2021-03-13 14:18:31 +01:00
& src_path ,
& png_path ,
& ref_path ,
pdf_path . as_deref ( ) ,
2022-04-19 13:54:04 +02:00
& args . print ,
2021-12-15 11:11:57 +01:00
) as usize ;
}
if len > 1 {
2022-01-13 18:43:18 +01:00
println! ( " {ok} / {len} tests passed. " ) ;
2021-01-13 14:07:38 +01:00
}
2020-10-13 12:34:11 +02:00
2021-12-15 11:11:57 +01:00
if ok < len {
2020-10-13 12:34:11 +02:00
std ::process ::exit ( 1 ) ;
}
}
2020-08-02 21:17:42 +02:00
2022-04-19 13:54:04 +02:00
/// Parsed command line arguments.
2021-03-13 14:18:31 +01:00
struct Args {
2020-08-02 21:17:42 +02:00
filter : Vec < String > ,
2021-12-15 11:12:38 +01:00
exact : bool ,
2021-03-13 14:18:31 +01:00
pdf : bool ,
2022-04-19 13:54:04 +02:00
print : PrintConfig ,
}
/// Which things to print out for debugging.
#[ derive(Default, Copy, Clone, Eq, PartialEq) ]
struct PrintConfig {
syntax : bool ,
frames : bool ,
2020-08-02 21:17:42 +02:00
}
2021-03-13 14:18:31 +01:00
impl Args {
2020-08-03 16:01:23 +02:00
fn new ( args : impl Iterator < Item = String > ) -> Self {
2020-08-02 21:17:42 +02:00
let mut filter = Vec ::new ( ) ;
2021-12-15 11:12:38 +01:00
let mut exact = false ;
2021-03-13 14:18:31 +01:00
let mut pdf = false ;
2022-04-19 13:54:04 +02:00
let mut print = PrintConfig ::default ( ) ;
2020-08-02 21:17:42 +02:00
for arg in args {
match arg . as_str ( ) {
2021-12-15 11:12:38 +01:00
// Ignore this, its for cargo.
2020-08-03 16:01:23 +02:00
" --nocapture " = > { }
2021-12-15 11:12:38 +01:00
// Match only the exact filename.
" --exact " = > exact = true ,
// Generate PDFs.
2021-03-13 14:18:31 +01:00
" --pdf " = > pdf = true ,
2022-04-19 13:54:04 +02:00
// Debug print the syntax trees.
" --syntax " = > print . syntax = true ,
// Debug print the frames.
" --frames " = > print . frames = true ,
2021-12-15 11:12:38 +01:00
// Everything else is a file filter.
2020-08-02 21:17:42 +02:00
_ = > filter . push ( arg ) ,
}
}
2022-04-19 13:54:04 +02:00
Self { filter , exact , pdf , print }
2020-08-02 21:17:42 +02:00
}
2021-12-15 11:12:38 +01:00
fn matches ( & self , path : & Path ) -> bool {
if self . exact {
let name = path . file_name ( ) . unwrap ( ) . to_string_lossy ( ) ;
self . filter . iter ( ) . any ( | v | v = = & name )
2020-08-02 21:17:42 +02:00
} else {
2021-12-15 11:12:38 +01:00
let path = path . to_string_lossy ( ) ;
self . filter . is_empty ( ) | | self . filter . iter ( ) . any ( | v | path . contains ( v ) )
2020-08-02 21:17:42 +02:00
}
}
}
2020-12-10 22:44:35 +01:00
fn test (
2021-07-20 20:21:56 +02:00
ctx : & mut Context ,
2020-12-10 22:44:35 +01:00
src_path : & Path ,
png_path : & Path ,
2021-02-20 17:53:40 +01:00
ref_path : & Path ,
2021-03-13 14:18:31 +01:00
pdf_path : Option < & Path > ,
2022-04-19 13:54:04 +02:00
print : & PrintConfig ,
2020-12-10 22:44:35 +01:00
) -> bool {
2021-01-14 16:47:29 +01:00
let name = src_path . strip_prefix ( TYP_DIR ) . unwrap_or ( src_path ) ;
println! ( " Testing {} " , name . display ( ) ) ;
2020-12-10 22:44:35 +01:00
let src = fs ::read_to_string ( src_path ) . unwrap ( ) ;
2021-01-13 14:07:38 +01:00
let mut ok = true ;
let mut frames = vec! [ ] ;
2021-07-31 22:59:14 +02:00
let mut line = 0 ;
2021-01-31 22:43:11 +01:00
let mut compare_ref = true ;
2021-03-12 13:57:47 +01:00
let mut compare_ever = false ;
2022-01-02 00:46:19 +01:00
let mut rng = LinearShift ::new ( ) ;
2021-01-31 22:43:11 +01:00
2021-06-29 16:05:05 +02:00
let parts : Vec < _ > = src . split ( " \n --- " ) . collect ( ) ;
2021-07-31 22:59:14 +02:00
for ( i , & part ) in parts . iter ( ) . enumerate ( ) {
2021-01-31 22:43:11 +01:00
let is_header = i = = 0
& & parts . len ( ) > 1
& & part
. lines ( )
. all ( | s | s . starts_with ( " // " ) | | s . chars ( ) . all ( | c | c . is_whitespace ( ) ) ) ;
if is_header {
for line in part . lines ( ) {
if line . starts_with ( " // Ref: false " ) {
compare_ref = false ;
}
}
} else {
2022-01-02 00:46:19 +01:00
let ( part_ok , compare_here , part_frames ) = test_part (
ctx ,
src_path ,
part . into ( ) ,
i ,
compare_ref ,
line ,
2022-04-19 13:54:04 +02:00
print ,
2022-01-02 00:46:19 +01:00
& mut rng ,
) ;
2021-01-31 22:43:11 +01:00
ok & = part_ok ;
2021-03-12 13:57:47 +01:00
compare_ever | = compare_here ;
2021-01-31 22:43:11 +01:00
frames . extend ( part_frames ) ;
}
2021-07-31 22:59:14 +02:00
line + = part . lines ( ) . count ( ) + 1 ;
2021-01-13 14:07:38 +01:00
}
2021-03-12 13:57:47 +01:00
if compare_ever {
2021-03-13 14:18:31 +01:00
if let Some ( pdf_path ) = pdf_path {
2021-07-20 20:21:56 +02:00
let pdf_data = typst ::export ::pdf ( ctx , & frames ) ;
2021-03-13 14:18:31 +01:00
fs ::create_dir_all ( & pdf_path . parent ( ) . unwrap ( ) ) . unwrap ( ) ;
fs ::write ( pdf_path , pdf_data ) . unwrap ( ) ;
}
2021-01-13 14:07:38 +01:00
2022-04-19 13:54:04 +02:00
if print . frames {
for frame in & frames {
println! ( " Frame: {:#?} " , frame ) ;
}
}
2022-01-24 16:48:24 +01:00
let canvas = render ( ctx , & frames ) ;
2021-01-14 16:47:29 +01:00
fs ::create_dir_all ( & png_path . parent ( ) . unwrap ( ) ) . unwrap ( ) ;
2021-03-23 13:17:00 +01:00
canvas . save_png ( png_path ) . unwrap ( ) ;
2021-01-13 23:19:44 +01:00
2021-07-08 22:33:44 +02:00
if let Ok ( ref_pixmap ) = sk ::Pixmap ::load_png ( ref_path ) {
2022-04-19 13:54:04 +02:00
if canvas . width ( ) ! = ref_pixmap . width ( )
| | canvas . height ( ) ! = ref_pixmap . height ( )
| | canvas
. data ( )
. iter ( )
. zip ( ref_pixmap . data ( ) )
. any ( | ( & a , & b ) | a . abs_diff ( b ) > 2 )
{
2021-02-20 17:53:40 +01:00
println! ( " Does not match reference image. ❌ " ) ;
2021-01-13 14:07:38 +01:00
ok = false ;
}
2021-07-30 18:04:08 +02:00
} else if ! frames . is_empty ( ) {
2021-02-20 17:53:40 +01:00
println! ( " Failed to open reference image. ❌ " ) ;
ok = false ;
2021-01-13 14:07:38 +01:00
}
}
if ok {
2022-04-19 13:54:04 +02:00
if * print = = PrintConfig ::default ( ) {
2021-12-15 11:12:38 +01:00
print! ( " \x1b [1A " ) ;
}
println! ( " Testing {} ✔ " , name . display ( ) ) ;
2021-01-13 14:07:38 +01:00
}
ok
}
2021-01-31 22:43:11 +01:00
fn test_part (
2021-07-20 20:21:56 +02:00
ctx : & mut Context ,
2021-08-09 11:06:37 +02:00
src_path : & Path ,
src : String ,
2021-01-31 22:43:11 +01:00
i : usize ,
compare_ref : bool ,
2021-07-31 22:59:14 +02:00
line : usize ,
2022-04-19 13:54:04 +02:00
print : & PrintConfig ,
2022-01-02 00:46:19 +01:00
rng : & mut LinearShift ,
2022-01-31 16:06:44 +01:00
) -> ( bool , bool , Vec < Arc < Frame > > ) {
2022-01-30 12:50:58 +01:00
let mut ok = true ;
2021-08-09 11:06:37 +02:00
let id = ctx . sources . provide ( src_path , src ) ;
let source = ctx . sources . get ( id ) ;
2022-04-19 13:54:04 +02:00
if print . syntax {
2022-02-02 20:33:19 +01:00
println! ( " Syntax Tree: {:#?} " , source . root ( ) )
2022-01-30 11:16:57 +01:00
}
2021-08-09 11:06:37 +02:00
2021-07-31 22:59:14 +02:00
let ( local_compare_ref , mut ref_errors ) = parse_metadata ( & source ) ;
2021-01-31 22:43:11 +01:00
let compare_ref = local_compare_ref . unwrap_or ( compare_ref ) ;
2022-01-30 12:50:58 +01:00
2022-06-01 13:49:02 +02:00
ok & = test_spans ( source . root ( ) ) ;
2022-01-30 12:50:58 +01:00
ok & = test_reparse ( ctx . sources . get ( id ) . src ( ) , i , rng ) ;
2020-12-10 22:44:35 +01:00
2022-05-31 12:37:05 +02:00
let ( mut frames , errors ) = match typst ::typeset ( ctx , id ) {
2022-02-22 12:42:02 +01:00
Ok ( frames ) = > ( frames , vec! [ ] ) ,
2021-07-30 18:04:08 +02:00
Err ( errors ) = > ( vec! [ ] , * errors ) ,
} ;
2021-01-17 13:53:22 +01:00
2022-02-22 12:42:02 +01:00
// Don't retain frames if we don't wanna compare with reference images.
if ! compare_ref {
frames . clear ( ) ;
}
2022-05-31 12:37:05 +02:00
// Map errors to range and message format, discard traces and errors from
// other files.
let mut errors : Vec < _ > = errors
. into_iter ( )
. filter ( | error | error . span . source ( ) = = id )
. map ( | error | {
2022-05-31 13:19:09 +02:00
let range = error . pos . apply ( ctx . sources . range ( error . span ) ) ;
2022-05-31 12:37:05 +02:00
( range , error . message )
} )
. collect ( ) ;
2021-07-31 22:59:14 +02:00
2022-05-31 12:37:05 +02:00
errors . sort_by_key ( | error | error . 0. start ) ;
ref_errors . sort_by_key ( | error | error . 0. start ) ;
2021-07-21 11:25:49 +02:00
2021-07-30 18:04:08 +02:00
if errors ! = ref_errors {
2022-01-13 18:43:18 +01:00
println! ( " Subtest {i} does not match expected errors. ❌ " ) ;
2020-12-10 22:44:35 +01:00
ok = false ;
2021-08-09 11:06:37 +02:00
let source = ctx . sources . get ( id ) ;
2021-07-30 18:04:08 +02:00
for error in errors . iter ( ) {
2022-05-31 12:37:05 +02:00
if ! ref_errors . contains ( error ) {
2021-01-14 17:41:13 +01:00
print! ( " Not annotated | " ) ;
2021-07-31 22:59:14 +02:00
print_error ( & source , line , error ) ;
2020-12-10 22:44:35 +01:00
}
}
2021-07-30 18:04:08 +02:00
for error in ref_errors . iter ( ) {
if ! errors . contains ( error ) {
2021-01-14 17:41:13 +01:00
print! ( " Not emitted | " ) ;
2021-07-31 22:59:14 +02:00
print_error ( & source , line , error ) ;
2020-12-10 22:44:35 +01:00
}
}
}
2021-07-26 00:08:08 +02:00
( ok , compare_ref , frames )
}
2021-06-27 12:28:40 +02:00
2022-05-31 12:37:05 +02:00
fn parse_metadata ( source : & SourceFile ) -> ( Option < bool > , Vec < ( Range < usize > , String ) > ) {
2022-01-24 16:48:24 +01:00
let mut compare_ref = None ;
let mut errors = vec! [ ] ;
2021-06-27 12:28:40 +02:00
2022-01-24 16:48:24 +01:00
let lines : Vec < _ > = source . src ( ) . lines ( ) . map ( str ::trim ) . collect ( ) ;
for ( i , line ) in lines . iter ( ) . enumerate ( ) {
if line . starts_with ( " // Ref: false " ) {
compare_ref = Some ( false ) ;
2021-06-27 12:28:40 +02:00
}
2022-01-24 16:48:24 +01:00
if line . starts_with ( " // Ref: true " ) {
compare_ref = Some ( true ) ;
}
2021-06-27 12:28:40 +02:00
2022-01-24 16:48:24 +01:00
let rest = if let Some ( rest ) = line . strip_prefix ( " // Error: " ) {
rest
} else {
continue ;
} ;
2021-07-26 00:08:08 +02:00
2022-01-24 16:48:24 +01:00
fn num ( s : & mut Scanner ) -> usize {
2022-04-16 22:23:57 +02:00
s . eat_while ( char ::is_numeric ) . parse ( ) . unwrap ( )
2021-07-26 00:08:08 +02:00
}
2022-01-24 16:48:24 +01:00
let comments =
lines [ i .. ] . iter ( ) . take_while ( | line | line . starts_with ( " // " ) ) . count ( ) ;
let pos = | s : & mut Scanner | -> usize {
let first = num ( s ) - 1 ;
let ( delta , column ) =
if s . eat_if ( ':' ) { ( first , num ( s ) - 1 ) } else { ( 0 , first ) } ;
let line = ( i + comments ) + delta ;
source . line_column_to_byte ( line , column ) . unwrap ( )
} ;
let mut s = Scanner ::new ( rest ) ;
let start = pos ( & mut s ) ;
let end = if s . eat_if ( '-' ) { pos ( & mut s ) } else { start } ;
2022-05-31 12:37:05 +02:00
let range = start .. end ;
2022-01-24 16:48:24 +01:00
2022-05-31 12:37:05 +02:00
errors . push ( ( range , s . after ( ) . trim ( ) . to_string ( ) ) ) ;
2021-06-27 18:06:39 +02:00
}
2022-01-24 16:48:24 +01:00
( compare_ref , errors )
}
2021-07-26 00:08:08 +02:00
2022-05-31 12:37:05 +02:00
fn print_error (
source : & SourceFile ,
line : usize ,
( range , message ) : & ( Range < usize > , String ) ,
) {
let start_line = 1 + line + source . byte_to_line ( range . start ) . unwrap ( ) ;
let start_col = 1 + source . byte_to_column ( range . start ) . unwrap ( ) ;
let end_line = 1 + line + source . byte_to_line ( range . end ) . unwrap ( ) ;
let end_col = 1 + source . byte_to_column ( range . end ) . unwrap ( ) ;
println! ( " Error: {start_line} : {start_col} - {end_line} : {end_col} : {message} " ) ;
2020-12-10 22:44:35 +01:00
}
2022-01-02 00:46:19 +01:00
/// Pseudorandomly edit the source file and test whether a reparse produces the
/// same result as a clean parse.
///
/// The method will first inject 10 strings once every 400 source characters
/// and then select 5 leaf node boundries to inject an additional, randomly
/// chosen string from the injection list.
fn test_reparse ( src : & str , i : usize , rng : & mut LinearShift ) -> bool {
let supplements = [
" [ " ,
2022-01-31 13:26:40 +01:00
" ] " ,
" { " ,
" } " ,
" ( " ,
2022-01-02 00:46:19 +01:00
" ) " ,
" #rect() " ,
" a word " ,
" , a: 1 " ,
" 10.0 " ,
" : " ,
" if i == 0 {true} " ,
" for " ,
" * hello * " ,
" // " ,
" /* " ,
" \\ u{12e4} " ,
" ```typst " ,
" " ,
" trees " ,
" \\ " ,
" $ a $ " ,
" 2. " ,
" - " ,
" 5 " ,
] ;
let mut ok = true ;
let apply = | replace : std ::ops ::Range < usize > , with | {
let mut incr_source = SourceFile ::detached ( src ) ;
2022-01-02 14:46:08 +01:00
if incr_source . root ( ) . len ( ) ! = src . len ( ) {
println! (
2022-01-13 18:43:18 +01:00
" Subtest {i} tree length {} does not match string length {} ❌ " ,
2022-01-02 14:46:08 +01:00
incr_source . root ( ) . len ( ) ,
src . len ( ) ,
) ;
return false ;
}
2022-01-02 00:46:19 +01:00
incr_source . edit ( replace . clone ( ) , with ) ;
2022-01-24 16:48:24 +01:00
let edited_src = incr_source . src ( ) ;
2022-01-02 00:46:19 +01:00
let incr_root = incr_source . root ( ) ;
2022-05-31 13:19:09 +02:00
let ref_source = SourceFile ::detached ( edited_src ) ;
2022-01-02 00:46:19 +01:00
let ref_root = ref_source . root ( ) ;
2022-06-01 13:49:02 +02:00
let mut ok = incr_root = = ref_root ;
if ! ok {
2022-01-02 00:46:19 +01:00
println! (
2022-01-13 18:43:18 +01:00
" Subtest {i} reparse differs from clean parse when inserting '{with}' at {}-{} ❌ \n " ,
replace . start , replace . end ,
2022-01-02 00:46:19 +01:00
) ;
2022-01-13 18:43:18 +01:00
println! ( " Expected reference tree: \n {ref_root:#?} \n " ) ;
println! ( " Found incremental tree: \n {incr_root:#?} " ) ;
2022-06-01 13:49:02 +02:00
println! (
" Full source ({}): \n \" {edited_src:?} \" " ,
edited_src . len ( )
) ;
2022-01-02 00:46:19 +01:00
}
2022-01-24 16:48:24 +01:00
2022-06-01 13:49:02 +02:00
ok & = test_spans ( ref_root ) ;
ok & = test_spans ( incr_root ) ;
ok
2022-01-02 00:46:19 +01:00
} ;
2022-01-03 23:18:21 +01:00
let mut pick = | range : Range < usize > | {
let ratio = rng . next ( ) ;
( range . start as f64 + ratio * ( range . end - range . start ) as f64 ) . floor ( ) as usize
2022-01-02 00:46:19 +01:00
} ;
let insertions = ( src . len ( ) as f64 / 400.0 ) . ceil ( ) as usize ;
for _ in 0 .. insertions {
2022-01-03 23:18:21 +01:00
let supplement = supplements [ pick ( 0 .. supplements . len ( ) ) ] ;
let start = pick ( 0 .. src . len ( ) ) ;
let end = pick ( start .. src . len ( ) ) ;
2022-01-02 00:46:19 +01:00
if ! src . is_char_boundary ( start ) | | ! src . is_char_boundary ( end ) {
continue ;
}
2022-01-03 23:18:21 +01:00
ok & = apply ( start .. end , supplement ) ;
2022-01-02 00:46:19 +01:00
}
2022-05-31 12:37:05 +02:00
let source = SourceFile ::detached ( src ) ;
let leafs = source . root ( ) . leafs ( ) ;
let start = source . range ( leafs [ pick ( 0 .. leafs . len ( ) ) ] . span ( ) ) . start ;
2022-01-03 23:18:21 +01:00
let supplement = supplements [ pick ( 0 .. supplements . len ( ) ) ] ;
2022-05-31 12:37:05 +02:00
ok & = apply ( start .. start , supplement ) ;
2022-01-02 00:46:19 +01:00
ok
}
2022-05-31 13:19:09 +02:00
/// Ensure that all spans are properly ordered (and therefore unique).
2022-06-01 13:49:02 +02:00
#[ track_caller ]
fn test_spans ( root : & SyntaxNode ) -> bool {
test_spans_impl ( root , 0 .. u64 ::MAX )
2022-05-31 13:19:09 +02:00
}
2022-06-01 13:49:02 +02:00
#[ track_caller ]
fn test_spans_impl ( node : & SyntaxNode , within : Range < u64 > ) -> bool {
if ! within . contains ( & node . span ( ) . number ( ) ) {
eprintln! ( " Node: {node:#?} " ) ;
eprintln! (
" Wrong span order: {} not in {within:?} ❌ " ,
node . span ( ) . number ( ) ,
) ;
}
let start = node . span ( ) . number ( ) + 1 ;
let mut children = node . children ( ) . peekable ( ) ;
2022-05-31 13:19:09 +02:00
while let Some ( child ) = children . next ( ) {
let end = children . peek ( ) . map_or ( within . end , | next | next . span ( ) . number ( ) ) ;
2022-06-01 13:49:02 +02:00
if ! test_spans_impl ( child , start .. end ) {
return false ;
}
2022-05-31 13:19:09 +02:00
}
2022-06-01 13:49:02 +02:00
true
2022-05-31 13:19:09 +02:00
}
2022-01-24 16:48:24 +01:00
/// Draw all frames into one image with padding in between.
2022-01-31 16:06:44 +01:00
fn render ( ctx : & mut Context , frames : & [ Arc < Frame > ] ) -> sk ::Pixmap {
2022-01-24 16:48:24 +01:00
let pixel_per_pt = 2.0 ;
let pixmaps : Vec < _ > = frames
. iter ( )
. map ( | frame | {
let limit = Length ::cm ( 100.0 ) ;
if frame . size . x > limit | | frame . size . y > limit {
panic! ( " overlarge frame: {:?} " , frame . size ) ;
}
typst ::export ::render ( ctx , frame , pixel_per_pt )
} )
. collect ( ) ;
2020-08-30 22:18:55 +02:00
2022-01-24 16:48:24 +01:00
let pad = ( 5.0 * pixel_per_pt ) . round ( ) as u32 ;
let pxw = 2 * pad + pixmaps . iter ( ) . map ( sk ::Pixmap ::width ) . max ( ) . unwrap_or_default ( ) ;
let pxh = pad + pixmaps . iter ( ) . map ( | pixmap | pixmap . height ( ) + pad ) . sum ::< u32 > ( ) ;
2021-01-13 23:19:44 +01:00
2021-11-16 21:32:29 +01:00
let mut canvas = sk ::Pixmap ::new ( pxw , pxh ) . unwrap ( ) ;
2021-07-08 22:33:44 +02:00
canvas . fill ( sk ::Color ::BLACK ) ;
2020-08-02 21:17:42 +02:00
2022-01-24 16:48:24 +01:00
let [ x , mut y ] = [ pad ; 2 ] ;
for ( frame , mut pixmap ) in frames . iter ( ) . zip ( pixmaps ) {
let ts = sk ::Transform ::from_scale ( pixel_per_pt , pixel_per_pt ) ;
render_links ( & mut pixmap , ts , ctx , frame ) ;
canvas . draw_pixmap (
x as i32 ,
y as i32 ,
pixmap . as_ref ( ) ,
& sk ::PixmapPaint ::default ( ) ,
sk ::Transform ::identity ( ) ,
None ,
) ;
y + = pixmap . height ( ) + pad ;
2020-08-02 21:17:42 +02:00
}
2020-11-25 18:46:47 +01:00
canvas
2020-08-02 21:17:42 +02:00
}
2022-01-24 16:48:24 +01:00
/// Draw extra boxes for links so we can see whether they are there.
fn render_links (
2021-11-16 21:32:29 +01:00
canvas : & mut sk ::Pixmap ,
ts : sk ::Transform ,
ctx : & Context ,
frame : & Frame ,
) {
for ( pos , element ) in & frame . elements {
2022-01-24 16:48:24 +01:00
let ts = ts . pre_translate ( pos . x . to_pt ( ) as f32 , pos . y . to_pt ( ) as f32 ) ;
2021-11-16 21:32:29 +01:00
match * element {
2021-11-22 15:26:56 +01:00
Element ::Group ( ref group ) = > {
2022-01-24 16:48:24 +01:00
let ts = ts . pre_concat ( group . transform . into ( ) ) ;
render_links ( canvas , ts , ctx , & group . frame ) ;
2021-03-23 15:32:36 +01:00
}
2022-01-24 16:48:24 +01:00
Element ::Link ( _ , size ) = > {
let w = size . x . to_pt ( ) as f32 ;
let h = size . y . to_pt ( ) as f32 ;
let rect = sk ::Rect ::from_xywh ( 0.0 , 0.0 , w , h ) . unwrap ( ) ;
let mut paint = sk ::Paint ::default ( ) ;
paint . set_color_rgba8 ( 40 , 54 , 99 , 40 ) ;
canvas . fill_rect ( rect , & paint , ts , None ) ;
2021-04-05 22:32:09 +02:00
}
2022-01-24 16:48:24 +01:00
_ = > { }
2021-03-23 15:32:36 +01:00
}
2021-11-20 15:51:07 +01:00
}
}
2021-11-29 23:22:53 +01:00
2022-04-07 14:32:35 +02:00
/// This is a Linear-feedback shift register using XOR as its shifting
2022-01-02 00:46:19 +01:00
/// function. It can be used as PRNG.
struct LinearShift ( u64 ) ;
impl LinearShift {
/// Initialize the shift register with a pre-set seed.
pub fn new ( ) -> Self {
Self ( 0xACE5 )
}
2022-01-03 23:18:21 +01:00
/// Return a pseudo-random number between `0.0` and `1.0`.
pub fn next ( & mut self ) -> f64 {
2022-01-02 00:46:19 +01:00
self . 0 ^ = self . 0 > > 3 ;
self . 0 ^ = self . 0 < < 14 ;
self . 0 ^ = self . 0 > > 28 ;
self . 0 ^ = self . 0 < < 36 ;
self . 0 ^ = self . 0 > > 52 ;
2022-01-03 23:18:21 +01:00
self . 0 as f64 / u64 ::MAX as f64
2022-01-02 00:46:19 +01:00
}
}