First steps
This commit is contained in:
commit
20cb936e0d
|
@ -0,0 +1,18 @@
|
|||
*.swp
|
||||
*.log
|
||||
test-prefix
|
||||
*.so
|
||||
*.o
|
||||
*.o*
|
||||
*.c
|
||||
*.dep
|
||||
*.zo
|
||||
*.meta
|
||||
compiled
|
||||
srfi-*.scm
|
||||
srfi-*.sld
|
||||
srfi.*.scm
|
||||
srfi.*.sld
|
||||
reports
|
||||
r7rs-tests.scm
|
||||
snow.*
|
|
@ -0,0 +1,489 @@
|
|||
pipeline {
|
||||
|
||||
agent {
|
||||
dockerfile {
|
||||
filename 'Dockerfile.jenkins'
|
||||
dir '.'
|
||||
args '--privileged -v /var/run/docker.sock:/var/run/docker.sock'
|
||||
}
|
||||
}
|
||||
|
||||
options {
|
||||
buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '10'))
|
||||
}
|
||||
|
||||
parameters {
|
||||
choice(name: 'BUILD_IMPLEMENTATION',
|
||||
description: 'Build',
|
||||
choices: [
|
||||
'all',
|
||||
'chibi',
|
||||
'chicken',
|
||||
'cyclone',
|
||||
'gambit',
|
||||
'gauche',
|
||||
'guile',
|
||||
'kawa',
|
||||
'loko',
|
||||
'mit-scheme',
|
||||
'sagittarius',
|
||||
'stklos',
|
||||
'skint',
|
||||
'tr7',
|
||||
])
|
||||
}
|
||||
|
||||
stages {
|
||||
|
||||
stage("Init") {
|
||||
steps {
|
||||
sh 'rm -rf srfi-test && git clone https://github.com/srfi-explorations/srfi-test.git'
|
||||
sh 'mkdir -p reports'
|
||||
sh 'touch reports/placeholder'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
sh 'echo "<h1>Test results</h1>" > reports/results.html'
|
||||
sh '(cd srfi-test && make clean build)'
|
||||
sh 'tree srfi-test'
|
||||
stash name: 'tests', includes: 'srfi-test/*'
|
||||
}
|
||||
}
|
||||
|
||||
stage("chibi") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/chibi'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'chibi'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/chibi-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("chicken") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/chicken'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'chicken'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/chicken-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("cyclone") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/cyclone'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'cyclone'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/cyclone-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("gambit") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/gambit'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'gambit'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/gambit-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("gauche") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/gauche'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'gauche'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/gauche-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("guile") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/guile'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'guile'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/guile-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("kawa") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/kawa'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'kawa'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/kawa-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("loko") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/loko:head'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'loko'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/loko-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("mit-scheme") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/mit-scheme'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'mit-scheme'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/mit-scheme-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("sagittarius") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/sagittarius'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'sagittarius'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/sagittarius-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("stklos") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/stklos'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'stklos'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/stklos-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("skint") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/skint'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'skint'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/skint-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage("tr7") {
|
||||
agent {
|
||||
docker {
|
||||
image 'schemers/tr7'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == 'tr7'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
||||
sh 'for f in *.log; do cp -- "$f" "reports/tr7-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stage("Report") {
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
unstash 'reports'
|
||||
sh './report'
|
||||
archiveArtifacts artifacts: 'reports/*.html'
|
||||
publishHTML (target : [allowMissing: false,
|
||||
alwaysLinkToLastBuild: false,
|
||||
keepAll: true,
|
||||
reportDir: 'reports',
|
||||
reportFiles: '*.html,*.css',
|
||||
reportName: 'R7RS-SRFI Test Report',
|
||||
reportTitles: 'R7RS-SRFI Test Report'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
post {
|
||||
always {
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
archiveArtifacts artifacts: 'reports/*.html'
|
||||
sh 'for f in srfi/*.sld; do snow-chibi package "$f"; done'
|
||||
archiveArtifacts artifacts: '*.tgz'
|
||||
archiveArtifacts artifacts: 'srfi/*.tgz'
|
||||
deleteDir()
|
||||
}
|
||||
failure {
|
||||
archiveArtifacts artifacts: 'reports/*.html'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
test-chibi-r7rs-test:
|
||||
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/chibi bash -c "cd workdir && chibi-scheme -I ./snow r7rs-tests.scm"
|
||||
|
||||
|
||||
test-chicken-r7rs-test:
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/chicken bash -c "cd workdir && ls && cp snow/chibi/optional.sld snow.chibi.optional.sld && csc -include-path ./snow/chibi -X r7rs -R r7rs -s -J snow.chibi.optional.sld && cp snow/chibi/diff.sld snow.chibi.diff.sld && csc -include-path ./snow/chibi -X r7rs -R r7rs -s -J snow.chibi.diff.sld && cp snow/chibi/test.sld snow.chibi.test.sld && csc -include-path ./snow/chibi -X r7rs -R r7rs -s -J snow.chibi.test.sld"
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/chicken bash -c "cd workdir && csc -include-path ./snow/chibi -X r7rs -R r7rs r7rs-tests.scm && ./r7rs-test && rm r7rs-test"
|
||||
|
||||
|
||||
test-cyclone-r7rs-test:
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/cyclone bash -c "cd workdir && ls && cyclone -A . snow/chibi/optional.sld && cyclone -A . snow/chibi/diff.sld && cyclone -A . snow/chibi/test.sld"
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/cyclone bash -c "cd workdir && cyclone -A . r7rs-tests.scm && ./r7rs-test && rm r7rs-test"
|
||||
|
||||
|
||||
test-gambit-r7rs-test:
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/gambit bash -c "cd workdir && ls && gsc . snow/chibi/optional && gsc . snow/chibi/diff && gsc . snow/chibi/test"
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/gambit bash -c "cd workdir && gsc -exe . -nopreload r7rs-tests.scm && ./r7rs-test && rm r7rs-test"
|
||||
|
||||
|
||||
test-gauche-r7rs-test:
|
||||
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/gauche bash -c "cd workdir && gosh -r7 -A ./snow r7rs-tests.scm"
|
||||
|
||||
|
||||
test-guile-r7rs-test:
|
||||
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/guile bash -c "cd workdir && guile --fresh-auto-compile --r7rs -L . -L ./snow r7rs-tests.scm"
|
||||
|
||||
|
||||
test-kawa-r7rs-test:
|
||||
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/kawa bash -c "cd workdir && kawa --r7rs -Dkawa.import.path=..:../snow:*.sld:./snow/chibi/*.sld:./snow/chibi/term/*.sld r7rs-tests.scm"
|
||||
|
||||
|
||||
test-loko-r7rs-test:
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/loko bash -c "cd workdir && ls && ls snow/chibi/optional.sld && ls snow/chibi/diff.sld && ls snow/chibi/test.sld"
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/loko bash -c "cd workdir && LOKO_LIBRARY_PATH=./snow loko -std=r7rs --compile r7rs-tests.scm && ./r7rs-test && rm r7rs-test"
|
||||
|
||||
|
||||
test-mit-scheme-r7rs-test:
|
||||
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/mit-scheme bash -c "cd workdir && mit-scheme --load r7rs-tests.scm"
|
||||
|
||||
|
||||
test-sagittarius-r7rs-test:
|
||||
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/sagittarius bash -c "cd workdir && sash -r7 -L ./snow r7rs-tests.scm > r7rs-test.log && cat r7rs-test.log"
|
||||
|
||||
|
||||
test-stklos-r7rs-test:
|
||||
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/stklos bash -c "cd workdir && stklos -I . r7rs-tests.scm"
|
||||
|
||||
|
||||
test-skint-r7rs-test:
|
||||
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/skint bash -c "cd workdir && skint --program r7rs-tests.scm"
|
||||
|
||||
|
||||
test-tr7-r7rs-test:
|
||||
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/tr7 bash -c "cd workdir && tr7i r7rs-tests.scm"
|
||||
|
||||
|
||||
clean:
|
||||
find . -name "*.so" -delete
|
||||
find . -name "*.c" -delete
|
||||
find . -name "*.o*" -delete
|
||||
find . -name "*.so" -delete
|
||||
find . -name "*.dep" -delete
|
||||
find . -name "*.zo" -delete
|
||||
find . -name "*.meta" -delete
|
||||
find . -name "compiled" -delete
|
||||
find . -name "srfi.*.sld" -delete
|
||||
find . -name "srfi.*.scm" -delete
|
||||
find . -name "srfi-*.sld" -delete
|
||||
find . -name "srfi.*.import.scm" -delete
|
||||
find . -name "srfi-*.import.scm" -delete
|
||||
find . -name "*.log" -delete
|
||||
find . -name "test-prefix.txt" -delete
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
(import (scheme base)
|
||||
(scheme write)
|
||||
(scheme file)
|
||||
(scheme process-context)
|
||||
(scheme file)
|
||||
(arvyy mustache))
|
||||
|
||||
(include "util.scm")
|
||||
(include "implementations.scm")
|
||||
(include "tests.scm")
|
||||
|
||||
(define full-library-command
|
||||
(lambda (implementation test)
|
||||
(let* ((name (symbol->string (cdr (assoc 'name implementation))))
|
||||
(library-command (assoc 'library-command implementation)))
|
||||
(cond ((not library-command) #f)
|
||||
; Note that Chicken needs to have the SRFI library as srfi-N.scm in same folder
|
||||
((string=? name "chicken")
|
||||
(string-append
|
||||
" ls "
|
||||
" && cp snow/chibi/optional.sld snow.chibi.optional.sld"
|
||||
" && " (cdr library-command) " snow.chibi.optional.sld"
|
||||
" && cp snow/chibi/diff.sld snow.chibi.diff.sld"
|
||||
" && " (cdr library-command) " snow.chibi.diff.sld"
|
||||
" && cp snow/chibi/test.sld snow.chibi.test.sld"
|
||||
" && " (cdr library-command) " snow.chibi.test.sld"
|
||||
))
|
||||
(else (string-append
|
||||
" ls "
|
||||
" && " (cdr library-command) " snow/chibi/optional" (if (string=? name "gambit") "" ".sld")
|
||||
" && " (cdr library-command) " snow/chibi/diff" (if (string=? name "gambit") "" ".sld")
|
||||
" && " (cdr library-command) " snow/chibi/test" (if (string=? name "gambit") "" ".sld")
|
||||
))))))
|
||||
|
||||
(define full-command
|
||||
(lambda (implementation test)
|
||||
(let* ((name (symbol->string (cdr (assoc 'name implementation))))
|
||||
(test-name (cdr (assoc 'name test)))
|
||||
(test-file (cdr (assoc 'file test)))
|
||||
(command
|
||||
(string-append
|
||||
(cdr (assoc 'command implementation)) " " test-file))
|
||||
(library-command (assoc 'library-command implementation)))
|
||||
(cond
|
||||
((not library-command)
|
||||
(string-append command
|
||||
; Sagittarius does not make .log file for some reason
|
||||
; Temporary fix to get atleast something out
|
||||
(if (string=? name "sagittarius")
|
||||
(string-append " > " test-name ".log && cat " test-name ".log")
|
||||
"")))
|
||||
(else (string-append command
|
||||
" && ./" test-name
|
||||
" && rm " test-name))))))
|
||||
|
||||
(define jenkinsfile-top (compile (slurp "templates/Jenkinsfile-top")))
|
||||
(define jenkinsfile-job-top (compile (slurp "templates/Jenkinsfile-job-top")))
|
||||
(define jenkinsfile-job (compile (slurp "templates/Jenkinsfile-job")))
|
||||
(define jenkinsfile-job-bottom (compile (slurp "templates/Jenkinsfile-job-bottom")))
|
||||
(define jenkinsfile-bottom (compile (slurp "templates/Jenkinsfile-bottom")))
|
||||
|
||||
(call-with-output-file
|
||||
"Jenkinsfile"
|
||||
(lambda (out)
|
||||
(execute jenkinsfile-top '() out)
|
||||
(newline out)
|
||||
(for-each
|
||||
(lambda (implementation)
|
||||
(let ((name (symbol->string (cdr (assoc 'name implementation)))))
|
||||
(execute jenkinsfile-job-top
|
||||
`((name . ,name)
|
||||
(dockerimage . ,(if (assoc 'docker-image implementation)
|
||||
(cdr (assoc 'docker-image implementation))
|
||||
(string-append "schemers/" name)))) out)
|
||||
(for-each
|
||||
(lambda (test)
|
||||
(execute jenkinsfile-job
|
||||
`((command . ,(full-command implementation test))
|
||||
(library-command . ,(full-library-command implementation test))
|
||||
out)))
|
||||
tests)
|
||||
(execute jenkinsfile-job-bottom `((name . ,(cdr (assoc 'name implementation)))) out)
|
||||
(newline out)))
|
||||
implementations)
|
||||
(execute jenkinsfile-bottom '() out)
|
||||
(newline out)))
|
||||
|
||||
(define makefile-top (compile (slurp "templates/Makefile-top")))
|
||||
(define makefile-job (compile (slurp "templates/Makefile-job")))
|
||||
(define makefile-bottom (compile (slurp "templates/Makefile-bottom")))
|
||||
|
||||
(call-with-output-file
|
||||
"Makefile"
|
||||
(lambda (out)
|
||||
(execute makefile-top '() out)
|
||||
(for-each
|
||||
(lambda (test)
|
||||
(for-each
|
||||
(lambda (implementation)
|
||||
(let* ((name (symbol->string (cdr (assoc 'name implementation)))))
|
||||
(execute makefile-job
|
||||
`((name . ,name)
|
||||
(test-name . ,(cdr (assoc 'name test)))
|
||||
(command . ,(full-command implementation test))
|
||||
(library-command . ,(full-library-command implementation test)))
|
||||
out))
|
||||
(newline out))
|
||||
implementations))
|
||||
tests)
|
||||
(execute makefile-bottom '() out)
|
||||
(newline out)))
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
(define implementations
|
||||
'(((name . chibi) (command . "chibi-scheme -I ./snow"))
|
||||
((name . chicken)
|
||||
(command . "csc -include-path ./snow/chibi -X r7rs -R r7rs")
|
||||
(library-command . "csc -include-path ./snow/chibi -X r7rs -R r7rs -s -J"))
|
||||
((name . cyclone)
|
||||
(command . "cyclone -A .")
|
||||
(library-command . "cyclone -A ."))
|
||||
((name . gambit)
|
||||
(command . "gsc -exe . -nopreload")
|
||||
(library-command . "gsc ."))
|
||||
((name . gauche) (command . "gosh -r7 -A ./snow"))
|
||||
((name . guile) (command . "guile --fresh-auto-compile --r7rs -L . -L ./snow"))
|
||||
((name . kawa) (command . "kawa --r7rs -Dkawa.import.path=..:../snow:*.sld:./snow/chibi/*.sld:./snow/chibi/term/*.sld"))
|
||||
((name . loko)
|
||||
(docker-image . "schemers/loko:head")
|
||||
(command . "LOKO_LIBRARY_PATH=./snow loko -std=r7rs --compile")
|
||||
; Library command so the executable gets run
|
||||
(library-command . "ls"))
|
||||
((name . mit-scheme) (command . "mit-scheme --load"))
|
||||
((name . sagittarius) (command . "sash -r7 -L ./snow"))
|
||||
((name . stklos) (command . "stklos -I ."))
|
||||
((name . skint) (command . "skint --program"))
|
||||
((name . tr7) (command . "tr7i"))))
|
|
@ -0,0 +1,106 @@
|
|||
(define (default-writer obj out)
|
||||
(when obj
|
||||
(display obj out)))
|
||||
|
||||
(define default-lookup
|
||||
(compose-lookups
|
||||
alist-lookup))
|
||||
|
||||
(define default-collection
|
||||
(compose-collections
|
||||
vector-collection
|
||||
stream-collection))
|
||||
|
||||
(define (port->string port)
|
||||
(define str
|
||||
(let loop ((chunks '())
|
||||
(chunk (read-string 2000 port)))
|
||||
(if (eof-object? chunk)
|
||||
(apply string-append (reverse chunks))
|
||||
(loop (cons chunk chunks)
|
||||
(read-string 2000 port)))))
|
||||
(close-input-port port)
|
||||
str)
|
||||
|
||||
(define (template-get-partials template)
|
||||
(define partials
|
||||
(let loop ((template template)
|
||||
(parts '()))
|
||||
(cond
|
||||
((null? template) parts)
|
||||
(else (let ((t (car template))
|
||||
(rest (cdr template)))
|
||||
(cond
|
||||
((partial? t) (loop rest
|
||||
(cons (partial-name t) parts)))
|
||||
((section? t) (loop rest
|
||||
(append (template-get-partials (section-content t))
|
||||
parts)))
|
||||
(else (loop rest
|
||||
parts))))))))
|
||||
(delete-duplicates! partials))
|
||||
|
||||
(define compile
|
||||
(case-lambda
|
||||
((template) (compile/without-partials template))
|
||||
((root partial-locator) (compile/with-partials root partial-locator))))
|
||||
|
||||
(define (compile/without-partials template)
|
||||
(compile/with-partials #f (lambda (partial)
|
||||
(if partial
|
||||
#f
|
||||
template))))
|
||||
|
||||
(define (compile/with-partials root partial-locator)
|
||||
|
||||
;; returns 2 values: missing partials (found in part) and compiled part template
|
||||
(define (compile-part part resolved-partials)
|
||||
(define source (partial-locator part))
|
||||
(define in (cond
|
||||
((not source) "")
|
||||
((string? source) source)
|
||||
((port? source) (port->string source))
|
||||
(else (error "Partial locator returned unrecognized type"))))
|
||||
(define template (parse (read-tokens in)))
|
||||
(define partials (template-get-partials template))
|
||||
(define missing-partials (lset-difference string=? partials resolved-partials))
|
||||
(values missing-partials template))
|
||||
|
||||
(let loop ((unresolved (list root))
|
||||
(resolved-map '())
|
||||
(resolved-lst '()))
|
||||
(cond
|
||||
((null? unresolved) (cons root resolved-map))
|
||||
(else (let ((part (car unresolved)))
|
||||
(define-values (unresolved* template)
|
||||
(compile-part part resolved-lst))
|
||||
(loop (append unresolved* (cdr unresolved))
|
||||
(cons (cons part template) resolved-map)
|
||||
(cons part resolved-lst)))))))
|
||||
|
||||
(define current-lookup (make-parameter default-lookup))
|
||||
(define current-collection (make-parameter default-collection))
|
||||
(define current-writer (make-parameter default-writer))
|
||||
|
||||
(define execute
|
||||
(case-lambda
|
||||
((compilation data)
|
||||
(let ((out (open-output-string)))
|
||||
(execute compilation data out)
|
||||
(get-output-string out)))
|
||||
((compilation data out)
|
||||
(define root (car compilation))
|
||||
(define partials (cdr compilation))
|
||||
(define template (cdr (assoc root partials)))
|
||||
(define lookup (current-lookup))
|
||||
(define collection* (current-collection))
|
||||
(define writer (current-writer))
|
||||
(executor-execute template
|
||||
(list data)
|
||||
partials
|
||||
out
|
||||
lookup
|
||||
(collection-pred-proc collection*)
|
||||
(collection-empty?-proc collection*)
|
||||
(collection-for-each-proc collection*)
|
||||
writer))))
|
|
@ -0,0 +1,73 @@
|
|||
(define-library
|
||||
(arvyy mustache-test)
|
||||
|
||||
(import (scheme base)
|
||||
(scheme write)
|
||||
(arvyy mustache)
|
||||
(srfi 41))
|
||||
|
||||
(export run-tests)
|
||||
|
||||
(cond-expand
|
||||
(chibi
|
||||
(import (rename (except (chibi test) test-equal)
|
||||
(test test-equal))))
|
||||
((library (srfi 64))
|
||||
(import (srfi 64)))
|
||||
(else (error "No testing library found")))
|
||||
|
||||
(begin
|
||||
(define-syntax test-mustache
|
||||
(syntax-rules ()
|
||||
((_ name data template expected)
|
||||
(test-equal name expected (execute (compile "foo" (lambda args template)) data)))
|
||||
((_ name data partials template expected)
|
||||
(let* ((partials* (cons (cons "root" template) partials))
|
||||
(fn (lambda (n)
|
||||
(cond
|
||||
((assoc n partials*) => cdr)
|
||||
(else #f)))))
|
||||
(test-equal name expected (execute (compile "root" fn) data)))))))
|
||||
|
||||
(include "mustache-test/comments.scm"
|
||||
"mustache-test/delimiters.scm"
|
||||
"mustache-test/implementation-specific.scm"
|
||||
"mustache-test/interpolation.scm"
|
||||
"mustache-test/inverted.scm"
|
||||
"mustache-test/partials.scm"
|
||||
"mustache-test/sections.scm")
|
||||
|
||||
(begin
|
||||
|
||||
(define (run-tests)
|
||||
(test-begin "mustache")
|
||||
|
||||
(test-group
|
||||
"comments"
|
||||
(run-tests/comments))
|
||||
|
||||
(test-group
|
||||
"delimiters"
|
||||
(run-tests/delimiters))
|
||||
|
||||
(test-group
|
||||
"interpolation"
|
||||
(run-tests/interpolation))
|
||||
|
||||
(test-group
|
||||
"inverted"
|
||||
(run-tests/inverted))
|
||||
|
||||
(test-group
|
||||
"partials"
|
||||
(run-tests/partials))
|
||||
|
||||
(test-group
|
||||
"sections"
|
||||
(run-tests/sections))
|
||||
|
||||
(test-group
|
||||
"implementation-specific"
|
||||
(run-tests/implementation-specific))
|
||||
|
||||
(test-end))))
|
|
@ -0,0 +1,56 @@
|
|||
(define (run-tests/comments)
|
||||
(test-mustache "Inline"
|
||||
'()
|
||||
"12345{{! Comment Block! }}67890"
|
||||
"1234567890")
|
||||
|
||||
(test-mustache "Multiline"
|
||||
'()
|
||||
"12345{{!\n This is a\n multi-line comment...\n}}67890"
|
||||
"1234567890")
|
||||
|
||||
(test-mustache "Standalone"
|
||||
'()
|
||||
"Begin.\n{{! Comment Block! }}\nEnd."
|
||||
"Begin.\nEnd.")
|
||||
|
||||
(test-mustache "Indented Standalone"
|
||||
'()
|
||||
"Begin.\n {{! Comment Block! }}\nEnd."
|
||||
"Begin.\nEnd.")
|
||||
|
||||
(test-mustache "Standalone Line Endings"
|
||||
'()
|
||||
"\r\n{{! Standalone Comment }}\r\n"
|
||||
"\r\n")
|
||||
|
||||
(test-mustache "Standalone Without Previous Line"
|
||||
'()
|
||||
" {{! I'm Still Standalone }}\n!"
|
||||
"!")
|
||||
|
||||
(test-mustache "Standalone Without Newline"
|
||||
'()
|
||||
"!\n {{! I'm Still Standalone }}"
|
||||
"!\n")
|
||||
|
||||
(test-mustache "Multiline Standalone"
|
||||
'()
|
||||
"Begin.\n{{!\nSomething's going on here...\n}}\nEnd."
|
||||
"Begin.\nEnd.")
|
||||
|
||||
(test-mustache "Indented Multiline Standalone"
|
||||
'()
|
||||
"Begin.\n {{!\n Something's going on here...\n }}\nEnd."
|
||||
"Begin.\nEnd.")
|
||||
|
||||
(test-mustache "Indented Inline"
|
||||
'()
|
||||
" 12 {{! 34 }}\n"
|
||||
" 12 \n")
|
||||
|
||||
(test-mustache "Surrounding Whitespace"
|
||||
'()
|
||||
"12345 {{! Comment Block! }} 67890"
|
||||
"12345 67890"))
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
(define (run-tests/delimiters)
|
||||
|
||||
(test-mustache "Pair Behavior"
|
||||
'((text . "Hey!"))
|
||||
"{{=<% %>=}}(<%text%>)"
|
||||
"(Hey!)")
|
||||
|
||||
(test-mustache "Special Characters"
|
||||
'((text . "It worked!"))
|
||||
"({{=[ ]=}}[text])"
|
||||
"(It worked!)")
|
||||
|
||||
(test-mustache "Sections"
|
||||
'((section . #t)
|
||||
(data . "I got interpolated."))
|
||||
"[\n{{#section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= | | =}}\n|#section|\n {{data}}\n |data|\n|/section|\n]\n"
|
||||
"[\n I got interpolated.\n |data|\n\n {{data}}\n I got interpolated.\n]\n")
|
||||
|
||||
(test-mustache "Inverted Sections"
|
||||
'((section . #f)
|
||||
(data . "I got interpolated."))
|
||||
"[\n{{^section}}\n {{data}}\n |data|\n{{/section}}\n\n{{= | | =}}\n|^section|\n {{data}}\n |data|\n|/section|\n]\n"
|
||||
"[\n I got interpolated.\n |data|\n\n {{data}}\n I got interpolated.\n]\n")
|
||||
|
||||
(test-mustache "Partial Inheritence"
|
||||
'((value . "yes"))
|
||||
'(("include" . ".{{value}}."))
|
||||
"[ {{>include}} ]\n{{= | | =}}\n[ |>include| ]\n"
|
||||
"[ .yes. ]\n[ .yes. ]\n")
|
||||
|
||||
(test-mustache "Post-Partial Behavior"
|
||||
'((value . "yes"))
|
||||
'(("include" . ".{{value}}. {{= | | =}} .|value|."))
|
||||
"[ {{>include}} ]\n[ .{{value}}. .|value|. ]\n"
|
||||
"[ .yes. .yes. ]\n[ .yes. .|value|. ]\n")
|
||||
|
||||
(test-mustache "Surrounding Whitespace"
|
||||
'()
|
||||
"| {{=@ @=}} |"
|
||||
"| |")
|
||||
|
||||
(test-mustache "Outlying Whitespace (Inline)"
|
||||
'()
|
||||
" | {{=@ @=}}\n"
|
||||
" | \n")
|
||||
|
||||
(test-mustache "Standalone Tag"
|
||||
'()
|
||||
"Begin.\n{{=@ @=}}\nEnd.\n"
|
||||
"Begin.\nEnd.\n")
|
||||
|
||||
(test-mustache "Indented Standalone Tag"
|
||||
'()
|
||||
"Begin.\n {{=@ @=}}\nEnd.\n"
|
||||
"Begin.\nEnd.\n")
|
||||
|
||||
(test-mustache "Standalone Line Endings"
|
||||
'()
|
||||
"|\r\n{{= @ @ =}}\r\n|"
|
||||
"|\r\n|")
|
||||
|
||||
(test-mustache "Standalone Without Previous Line"
|
||||
'()
|
||||
" {{=@ @=}}\n="
|
||||
"=")
|
||||
|
||||
(test-mustache "Standalone Without Newline"
|
||||
'()
|
||||
"=\n {{=@ @=}}"
|
||||
"=\n")
|
||||
|
||||
(test-mustache "Pair with Padding"
|
||||
'()
|
||||
"|{{= @ @ =}}|"
|
||||
"||"))
|
|
@ -0,0 +1,71 @@
|
|||
(define-record-type <foo> (foo bar) foo? (bar foo-bar))
|
||||
|
||||
(define (run-tests/implementation-specific)
|
||||
(define (foo-lookup obj name found not-found)
|
||||
(cond
|
||||
((not (foo? obj)) (not-found))
|
||||
((string=? "bar" name) (found (foo-bar obj)))
|
||||
(else (not-found))))
|
||||
|
||||
(define alist+foo (compose-lookups alist-lookup foo-lookup))
|
||||
|
||||
(define (write-foo obj out)
|
||||
(write-string "(foo " out)
|
||||
(display (foo-bar obj) out)
|
||||
(write-string ")" out))
|
||||
|
||||
(define-record-type <num-lst> (num-lst count) num-lst? (count num-lst-count))
|
||||
(define num-lst-collection
|
||||
(collection
|
||||
num-lst?
|
||||
(lambda (obj) (= 0 (num-lst-count obj)))
|
||||
(lambda (proc obj)
|
||||
(define target (num-lst-count obj))
|
||||
(let loop ((i 0))
|
||||
(when (< i target)
|
||||
(begin
|
||||
(proc i)
|
||||
(loop (+ 1 i))))))))
|
||||
|
||||
(parameterize
|
||||
((current-writer (lambda (obj out)
|
||||
(cond
|
||||
((not obj) #t)
|
||||
((foo? obj) (write-foo obj out))
|
||||
(else (display obj out))))))
|
||||
(test-mustache "Custom writer"
|
||||
`((obj . ,(foo "baz")))
|
||||
"Test {{obj}}"
|
||||
"Test (foo baz)"))
|
||||
|
||||
(parameterize
|
||||
((current-lookup alist+foo))
|
||||
(test-mustache "Custom lookup"
|
||||
`((a . ((bar . "baz1")))
|
||||
(b . ,(foo "baz2")))
|
||||
"{{a.bar}}, {{b.bar}}"
|
||||
"baz1, baz2"))
|
||||
|
||||
(parameterize
|
||||
((current-collection num-lst-collection))
|
||||
(test-mustache "Custom collection"
|
||||
`((a . ,(num-lst 3)))
|
||||
"{{#a}}{{.}};{{/a}}"
|
||||
"0;1;2;"))
|
||||
|
||||
(parameterize
|
||||
((current-collection list-collection)
|
||||
(current-lookup foo-lookup))
|
||||
(test-mustache "List collection"
|
||||
(foo '(0 1 2))
|
||||
"{{#bar}}{{.}};{{/bar}}"
|
||||
"0;1;2;"))
|
||||
|
||||
(parameterize
|
||||
((current-collection stream-collection)
|
||||
(current-lookup foo-lookup))
|
||||
(test-mustache "Stream collection"
|
||||
(foo (list->stream '(0 1 2)))
|
||||
"{{#bar}}{{.}};{{/bar}}"
|
||||
"0;1;2;")))
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
(define (run-tests/interpolation)
|
||||
|
||||
(test-mustache "No Interpolation"
|
||||
'()
|
||||
"Hello from {Mustache}!"
|
||||
"Hello from {Mustache}!")
|
||||
|
||||
(test-mustache "Basic Interpolation"
|
||||
'((subject . "world"))
|
||||
"Hello, {{subject}}!"
|
||||
"Hello, world!")
|
||||
|
||||
(test-mustache "HTML Escaping"
|
||||
'((forbidden . "& \" < >"))
|
||||
"These characters should be HTML escaped: {{forbidden}}"
|
||||
"These characters should be HTML escaped: & " < >")
|
||||
|
||||
(test-mustache "Triple Mustache"
|
||||
'((forbidden . "& \" < >"))
|
||||
"These characters should not be HTML escaped: {{{forbidden}}}"
|
||||
"These characters should not be HTML escaped: & \" < >")
|
||||
|
||||
(test-mustache "Ampersand"
|
||||
'((forbidden . "& \" < >"))
|
||||
"These characters should not be HTML escaped: {{&forbidden}}"
|
||||
"These characters should not be HTML escaped: & \" < >")
|
||||
|
||||
(test-mustache "Basic Integer Interpolation"
|
||||
'((mph . 85))
|
||||
"\"{{mph}} miles an hour!\""
|
||||
"\"85 miles an hour!\"")
|
||||
|
||||
(test-mustache "Triple Mustache Integer Interpolation"
|
||||
'((mph . 85))
|
||||
"\"{{{mph}}} miles an hour!\""
|
||||
"\"85 miles an hour!\"")
|
||||
|
||||
(test-mustache "Ampersand Mustache Integer Interpolation"
|
||||
'((mph . 85))
|
||||
"\"{{&mph}} miles an hour!\""
|
||||
"\"85 miles an hour!\"")
|
||||
|
||||
(test-mustache "Basic Decimal Interpolation"
|
||||
'((power . 1.210))
|
||||
"\"{{power}} jiggawatts!\""
|
||||
"\"1.21 jiggawatts!\"")
|
||||
|
||||
(test-mustache "Triple Mustache Decimal Interpolation"
|
||||
'((power . 1.210))
|
||||
"\"{{{power}}} jiggawatts!\""
|
||||
"\"1.21 jiggawatts!\"")
|
||||
|
||||
(test-mustache "Ampersand Mustache Decimal Interpolation"
|
||||
'((power . 1.210))
|
||||
"\"{{&power}} jiggawatts!\""
|
||||
"\"1.21 jiggawatts!\"")
|
||||
|
||||
(test-mustache "Basic Null Interpolation"
|
||||
'((cannot . #f))
|
||||
"I ({{cannot}}) be seen!"
|
||||
"I () be seen!")
|
||||
|
||||
(test-mustache "Triple Mustache Null Interpolation"
|
||||
'((cannot . #f))
|
||||
"I ({{{cannot}}}) be seen!"
|
||||
"I () be seen!")
|
||||
|
||||
(test-mustache "Ampersand Null Interpolation"
|
||||
'((cannot . #f))
|
||||
"I ({{&cannot}}) be seen!"
|
||||
"I () be seen!")
|
||||
|
||||
(test-mustache "Basic Context Miss Interpolation"
|
||||
'()
|
||||
"I ({{cannot}}) be seen!"
|
||||
"I () be seen!")
|
||||
|
||||
(test-mustache "Triple Mustache Context Miss Interpolation"
|
||||
'()
|
||||
"I ({{{cannot}}}) be seen!"
|
||||
"I () be seen!")
|
||||
|
||||
(test-mustache "Ampersand Context Miss Interpolation"
|
||||
'()
|
||||
"I ({{&cannot}}) be seen!"
|
||||
"I () be seen!")
|
||||
|
||||
(test-mustache "Dotted Names - Basic Interpolation"
|
||||
'((person . ((name . "Joe"))))
|
||||
"\"{{person.name}}\" == \"{{#person}}{{name}}{{/person}}\""
|
||||
"\"Joe\" == \"Joe\"")
|
||||
|
||||
(test-mustache "Dotted Names - Triple Mustache Interpolation"
|
||||
'((person . ((name . "Joe"))))
|
||||
"\"{{{person.name}}}\" == \"{{#person}}{{{name}}}{{/person}}\""
|
||||
"\"Joe\" == \"Joe\"")
|
||||
|
||||
(test-mustache "Dotted Names - Ampersand Interpolation"
|
||||
'((person . ((name . "Joe"))))
|
||||
"\"{{&person.name}}\" == \"{{#person}}{{&name}}{{/person}}\""
|
||||
"\"Joe\" == \"Joe\"")
|
||||
|
||||
(test-mustache "Dotted Names - Arbitrary Depth"
|
||||
'((a . ((b . ((c . ((d . ((e . ((name . "Phil"))))))))))))
|
||||
"\"{{a.b.c.d.e.name}}\" == \"Phil\""
|
||||
"\"Phil\" == \"Phil\"")
|
||||
|
||||
(test-mustache "Dotted Names - Broken Chains"
|
||||
'((a . ()))
|
||||
"\"{{a.b.c}}\" == \"\""
|
||||
"\"\" == \"\"")
|
||||
|
||||
(test-mustache "Dotted Names - Broken Chain Resolution"
|
||||
'((a . ((b . ())))
|
||||
(c . ((name . "Jim"))))
|
||||
"\"{{a.b.c.name}}\" == \"\""
|
||||
"\"\" == \"\"")
|
||||
|
||||
(test-mustache "Dotted Names - Initial Resolution"
|
||||
'((a . ((b . ((c . ((d . ((e . ((name . "Phil")))))))))))
|
||||
(b . ((c . ((d . ((e . ((name . "Wrong"))))))))))
|
||||
"\"{{#a}}{{b.c.d.e.name}}{{/a}}\" == \"Phil\""
|
||||
"\"Phil\" == \"Phil\"")
|
||||
|
||||
(test-mustache "Dotted Names - Context Precedence"
|
||||
'((a . ((b . ())))
|
||||
(b . ((c . "ERROR"))))
|
||||
"{{#a}}{{b.c}}{{/a}}"
|
||||
"")
|
||||
|
||||
(test-mustache "Implicit Iterators - Basic Interpolation"
|
||||
"world"
|
||||
"Hello, {{.}}!"
|
||||
"Hello, world!")
|
||||
|
||||
(test-mustache "Implicit Iterators - HTML Escaping"
|
||||
"& \" < >"
|
||||
"These characters should be HTML escaped: {{.}}"
|
||||
"These characters should be HTML escaped: & " < >")
|
||||
|
||||
(test-mustache "Implicit Iterators - Triple Mustache"
|
||||
"& \" < >"
|
||||
"These characters should not be HTML escaped: {{{.}}}"
|
||||
"These characters should not be HTML escaped: & \" < >")
|
||||
|
||||
(test-mustache "Implicit Iterators - Ampersand"
|
||||
"& \" < >"
|
||||
"These characters should not be HTML escaped: {{&.}}"
|
||||
"These characters should not be HTML escaped: & \" < >")
|
||||
|
||||
(test-mustache "Implicit Iterators - Basic Integer Interpolation"
|
||||
85
|
||||
"\"{{.}} miles an hour!\""
|
||||
"\"85 miles an hour!\"")
|
||||
|
||||
(test-mustache "Interpolation - Surrounding Whitespace"
|
||||
'((string . "---"))
|
||||
"| {{string}} |"
|
||||
"| --- |")
|
||||
|
||||
(test-mustache "Triple Mustache - Surrounding Whitespace"
|
||||
'((string . "---"))
|
||||
"| {{{string}}} |"
|
||||
"| --- |")
|
||||
|
||||
(test-mustache "Ampersand - Surrounding Whitespace"
|
||||
'((string . "---"))
|
||||
"| {{&string}} |"
|
||||
"| --- |")
|
||||
|
||||
(test-mustache "Interpolation - Standalone"
|
||||
'((string . "---"))
|
||||
" {{string}}\n"
|
||||
" ---\n")
|
||||
|
||||
(test-mustache "Triple Mustache - Standalone"
|
||||
'((string . "---"))
|
||||
" {{{string}}}\n"
|
||||
" ---\n")
|
||||
|
||||
(test-mustache "Ampersand - Standalone"
|
||||
'((string . "---"))
|
||||
" {{&string}}\n"
|
||||
" ---\n")
|
||||
|
||||
(test-mustache "Interpolation With Padding"
|
||||
'((string . "---"))
|
||||
"|{{ string }}|"
|
||||
"|---|")
|
||||
|
||||
(test-mustache "Triple Mustache With Padding"
|
||||
'((string . "---"))
|
||||
"|{{{ string }}}|"
|
||||
"|---|")
|
||||
|
||||
(test-mustache "Ampersand With Padding"
|
||||
'((string . "---"))
|
||||
"|{{& string }}|"
|
||||
"|---|"))
|
|
@ -0,0 +1,147 @@
|
|||
(define (run-tests/inverted)
|
||||
|
||||
(test-mustache "Falsey"
|
||||
'((boolean . #f))
|
||||
"\"{{^boolean}}This should be rendered.{{/boolean}}\""
|
||||
"\"This should be rendered.\"")
|
||||
|
||||
(test-mustache "Truthy"
|
||||
'((boolean . #t))
|
||||
"\"{{^boolean}}This should not be rendered.{{/boolean}}\""
|
||||
"\"\"")
|
||||
|
||||
;; "Null is falsey" test is skipped; no meaningful value for null
|
||||
|
||||
(test-mustache "Context"
|
||||
'((context . ((name . "Joe"))))
|
||||
"\"{{^context}}Hi {{name}}.{{/context}}\""
|
||||
"\"\"")
|
||||
|
||||
(test-mustache "List"
|
||||
'(list . #(((n . 1))
|
||||
((n . 2))
|
||||
((n . 3))))
|
||||
"\"{{^list}}{{n}}{{/list}}\""
|
||||
"\"\"")
|
||||
|
||||
(test-mustache "Empty List"
|
||||
'(list . #())
|
||||
"\"{{^list}}Yay lists!{{/list}}\""
|
||||
"\"Yay lists!\"")
|
||||
|
||||
(test-mustache "Doubled"
|
||||
'((bool . #f) (two . "second"))
|
||||
"
|
||||
{{^bool}}
|
||||
* first
|
||||
{{/bool}}
|
||||
* {{two}}
|
||||
{{^bool}}
|
||||
* third
|
||||
{{/bool}}
|
||||
"
|
||||
"
|
||||
* first
|
||||
* second
|
||||
* third
|
||||
")
|
||||
|
||||
(test-mustache "Nested (Falsey)"
|
||||
'((bool . #f))
|
||||
"| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |"
|
||||
"| A B C D E |")
|
||||
|
||||
(test-mustache "Nested (Truthy)"
|
||||
'((bool . #t))
|
||||
"| A {{^bool}}B {{^bool}}C{{/bool}} D{{/bool}} E |"
|
||||
"| A E |")
|
||||
|
||||
(test-mustache "Context Misses"
|
||||
'(())
|
||||
"[{{^missing}}Cannot find key 'missing'!{{/missing}}]"
|
||||
"[Cannot find key 'missing'!]")
|
||||
|
||||
(test-mustache "Dotted Names - Truthy"
|
||||
'((a . ((b . ((c . #t))))))
|
||||
"\"{{^a.b.c}}Not Here{{/a.b.c}}\" == \"\""
|
||||
"\"\" == \"\"")
|
||||
|
||||
(test-mustache "Dotted Names - Falsey"
|
||||
'((a . ((b . ((c . #f))))))
|
||||
"\"{{^a.b.c}}Not Here{{/a.b.c}}\" == \"Not Here\""
|
||||
"\"Not Here\" == \"Not Here\"")
|
||||
|
||||
(test-mustache "Dotted Names - Broken Chains"
|
||||
'((a . ()))
|
||||
"\"{{^a.b.c}}Not Here{{/a.b.c}}\" == \"Not Here\""
|
||||
"\"Not Here\" == \"Not Here\"")
|
||||
|
||||
(test-mustache "Surrounding Whitespace"
|
||||
'((boolean . #f))
|
||||
" | {{^boolean}}\t|\t{{/boolean}} | \n"
|
||||
" | \t|\t | \n")
|
||||
|
||||
(test-mustache "Internal Whitespace"
|
||||
'((boolean . #f))
|
||||
" | {{^boolean}} {{! Important Whitespace }}\n {{/boolean}} | \n"
|
||||
" | \n | \n")
|
||||
|
||||
(test-mustache "Indented Inline Sections"
|
||||
'((boolean . #f))
|
||||
" {{^boolean}}NO{{/boolean}}\n {{^boolean}}WAY{{/boolean}}\n"
|
||||
" NO\n WAY\n")
|
||||
|
||||
(test-mustache "Standalone Lines"
|
||||
'((boolean . #f))
|
||||
"
|
||||
|
|
||||
| This Is
|
||||
{{^boolean}}
|
||||
|
|
||||
{{/boolean}}
|
||||
| A Line
|
||||
"
|
||||
"
|
||||
|
|
||||
| This Is
|
||||
|
|
||||
| A Line
|
||||
")
|
||||
|
||||
(test-mustache "Standalone Indented Lines"
|
||||
'((boolean . #f))
|
||||
"
|
||||
|
|
||||
| This Is
|
||||
{{^boolean}}
|
||||
|
|
||||
{{/boolean}}
|
||||
| A Line
|
||||
"
|
||||
"
|
||||
|
|
||||
| This Is
|
||||
|
|
||||
| A Line
|
||||
")
|
||||
|
||||
(test-mustache "Standalone Line Endings"
|
||||
'((boolean . #f))
|
||||
"|\r\n{{^boolean}}\r\n{{/boolean}}\r\n|"
|
||||
"|\r\n|")
|
||||
|
||||
(test-mustache "Standalone Without Previous Line"
|
||||
'((boolean . #f))
|
||||
" {{^boolean}}\n^{{/boolean}}\n/"
|
||||
"^\n/")
|
||||
|
||||
(test-mustache "Standalone Without Newline"
|
||||
'((boolean . #f))
|
||||
"^{{^boolean}}\n/\n {{/boolean}}"
|
||||
"^\n/\n")
|
||||
|
||||
(test-mustache "Padding"
|
||||
'((boolean . #f))
|
||||
"|{{^ boolean }}={{/ boolean }}|"
|
||||
"|=|"))
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
(define (run-tests/partials)
|
||||
|
||||
(test-mustache "Basic Behavior"
|
||||
'()
|
||||
'(("text" . "from partial"))
|
||||
"\"{{>text}}\""
|
||||
"\"from partial\"")
|
||||
|
||||
(test-mustache "Failed Lookup"
|
||||
'()
|
||||
'()
|
||||
"\"{{>text}}\""
|
||||
"\"\"")
|
||||
|
||||
(test-mustache "Context"
|
||||
'((text . "content"))
|
||||
'(("partial" . "*{{text}}*"))
|
||||
"\"{{>partial}}\""
|
||||
"\"*content*\"")
|
||||
|
||||
(test-mustache "Recursion"
|
||||
'((content . "X")
|
||||
(nodes . #(((content . "Y")
|
||||
(nodes . #())))))
|
||||
'(("node" . "{{content}}<{{#nodes}}{{>node}}{{/nodes}}>"))
|
||||
"{{>node}}"
|
||||
"X<Y<>>")
|
||||
|
||||
(test-mustache "Surrounding Whitespace"
|
||||
'()
|
||||
'(("partial" . "\t|\t"))
|
||||
"| {{>partial}} |"
|
||||
"| \t|\t |")
|
||||
|
||||
(test-mustache "Inline Indentation"
|
||||
'((data . "|"))
|
||||
'(("partial" . ">\n>"))
|
||||
" {{data}} {{> partial}}\n"
|
||||
" | >\n>\n")
|
||||
|
||||
(test-mustache "Standalone Line Endings"
|
||||
'()
|
||||
'(("partial" . ">"))
|
||||
"|\r\n{{>partial}}\r\n|"
|
||||
"|\r\n>|"))
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
(define (run-tests/sections)
|
||||
(test-mustache "Truthy"
|
||||
'((boolean . #t))
|
||||
"\"{{#boolean}}This should be rendered.{{/boolean}}\""
|
||||
"\"This should be rendered.\"")
|
||||
|
||||
(test-mustache "Falsey"
|
||||
'((boolean . #f))
|
||||
"\"{{#boolean}}This should not be rendered.{{/boolean}}\""
|
||||
"\"\"")
|
||||
|
||||
;; "Null is falsey" test is skipped; no meaningful value for null
|
||||
|
||||
(test-mustache "Context"
|
||||
'((context . ((name . "Joe"))))
|
||||
"\"{{#context}}Hi {{name}}.{{/context}}\""
|
||||
"\"Hi Joe.\"")
|
||||
|
||||
(test-mustache "Parent contexts"
|
||||
'((a . "foo")
|
||||
(b . "wrong")
|
||||
(sec . ((b . "bar")))
|
||||
(c . ((d . "baz"))))
|
||||
"\"{{#sec}}{{a}}, {{b}}, {{c.d}}{{/sec}}\""
|
||||
"\"foo, bar, baz\"")
|
||||
|
||||
(test-mustache "Variable test"
|
||||
'((foo . "bar"))
|
||||
"\"{{#foo}}{{.}} is {{foo}}{{/foo}}\""
|
||||
"\"bar is bar\"")
|
||||
|
||||
(test-mustache "List Contexts"
|
||||
'((tops . #(((tname . ((upper . "A")
|
||||
(lower . "a")))
|
||||
(middles . #(((mname . "1")
|
||||
(bottoms . #(((bname . "x"))
|
||||
((bname . "y")))))))))))
|
||||
"{{#tops}}{{#middles}}{{tname.lower}}{{mname}}.{{#bottoms}}{{tname.upper}}{{mname}}{{bname}}.{{/bottoms}}{{/middles}}{{/tops}}"
|
||||
"a1.A1x.A1y.")
|
||||
|
||||
(test-mustache "Deeply Nested Contexts"
|
||||
'((a . ((one . 1)))
|
||||
(b . ((two . 2)))
|
||||
(c . ((three . 3)
|
||||
(d . ((four . 4)
|
||||
(five . 5))))))
|
||||
"
|
||||
{{#a}}
|
||||
{{one}}
|
||||
{{#b}}
|
||||
{{one}}{{two}}{{one}}
|
||||
{{#c}}
|
||||
{{one}}{{two}}{{three}}{{two}}{{one}}
|
||||
{{#d}}
|
||||
{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}
|
||||
{{#five}}
|
||||
{{one}}{{two}}{{three}}{{four}}{{five}}{{four}}{{three}}{{two}}{{one}}
|
||||
{{one}}{{two}}{{three}}{{four}}{{.}}6{{.}}{{four}}{{three}}{{two}}{{one}}
|
||||
{{one}}{{two}}{{three}}{{four}}{{five}}{{four}}{{three}}{{two}}{{one}}
|
||||
{{/five}}
|
||||
{{one}}{{two}}{{three}}{{four}}{{three}}{{two}}{{one}}
|
||||
{{/d}}
|
||||
{{one}}{{two}}{{three}}{{two}}{{one}}
|
||||
{{/c}}
|
||||
{{one}}{{two}}{{one}}
|
||||
{{/b}}
|
||||
{{one}}
|
||||
{{/a}}
|
||||
"
|
||||
"
|
||||
1
|
||||
121
|
||||
12321
|
||||
1234321
|
||||
123454321
|
||||
12345654321
|
||||
123454321
|
||||
1234321
|
||||
12321
|
||||
121
|
||||
1
|
||||
"
|
||||
))
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
(define-library
|
||||
(arvyy mustache)
|
||||
(import (scheme base)
|
||||
(scheme case-lambda)
|
||||
(scheme write)
|
||||
(arvyy mustache lookup)
|
||||
(arvyy mustache collection)
|
||||
(prefix (arvyy mustache executor) executor-)
|
||||
(arvyy mustache parser)
|
||||
(arvyy mustache tokenizer)
|
||||
(srfi 1))
|
||||
(export
|
||||
execute
|
||||
compile
|
||||
current-lookup
|
||||
current-collection
|
||||
current-writer
|
||||
|
||||
compose-lookups
|
||||
alist-lookup
|
||||
|
||||
collection
|
||||
compose-collections
|
||||
vector-collection
|
||||
list-collection
|
||||
stream-collection)
|
||||
(include "mustache-impl.scm"))
|
|
@ -0,0 +1,64 @@
|
|||
(define-library
|
||||
(arvyy mustache collection)
|
||||
(import (scheme base)
|
||||
(srfi 41))
|
||||
(export
|
||||
collection
|
||||
collection-pred-proc
|
||||
collection-empty?-proc
|
||||
collection-for-each-proc
|
||||
|
||||
compose-collections
|
||||
vector-collection
|
||||
stream-collection
|
||||
list-collection)
|
||||
(begin
|
||||
|
||||
(define-record-type <collection>
|
||||
(collection pred-proc empty?-proc for-each-proc)
|
||||
collection?
|
||||
(pred-proc collection-pred-proc)
|
||||
(empty?-proc collection-empty?-proc)
|
||||
(for-each-proc collection-for-each-proc))
|
||||
|
||||
(define vector-collection
|
||||
(collection vector?
|
||||
(lambda (v) (= 0 (vector-length v)))
|
||||
vector-for-each))
|
||||
|
||||
(define list-collection
|
||||
(collection list?
|
||||
null?
|
||||
for-each))
|
||||
|
||||
(define stream-collection
|
||||
(collection stream?
|
||||
stream-null?
|
||||
stream-for-each))
|
||||
|
||||
(define (compose-collections . collections)
|
||||
(define (find-collection object)
|
||||
(let loop ((collections collections))
|
||||
(cond
|
||||
((null? collections)
|
||||
#f)
|
||||
(((collection-pred-proc (car collections)) object)
|
||||
(car collections))
|
||||
(else (loop (cdr collections))))))
|
||||
|
||||
(collection
|
||||
;; predicate
|
||||
(lambda (object)
|
||||
(cond
|
||||
((find-collection object) #t)
|
||||
(else #f)))
|
||||
;; empty proc
|
||||
(lambda (object)
|
||||
(cond
|
||||
((find-collection object) => (lambda (c) ((collection-empty?-proc c) object)))
|
||||
(else (error "Collection not found"))))
|
||||
;; for-each proc
|
||||
(lambda (proc object)
|
||||
(cond
|
||||
((find-collection object) => (lambda (c) ((collection-for-each-proc c) proc object)))
|
||||
(else (error "Collection not found"))))))))
|
|
@ -0,0 +1,94 @@
|
|||
(define (html-escape writer value)
|
||||
(define str-value
|
||||
(let ((out (open-output-string)))
|
||||
(writer value out)
|
||||
(get-output-string out)))
|
||||
(define out (open-output-string))
|
||||
(string-for-each
|
||||
(lambda (char)
|
||||
(case char
|
||||
((#\&) (write-string "&" out))
|
||||
((#\<) (write-string "<" out))
|
||||
((#\>) (write-string ">" out))
|
||||
((#\") (write-string """ out))
|
||||
(else (write-char char out))))
|
||||
str-value)
|
||||
(get-output-string out))
|
||||
|
||||
(define (lookup-in-stack-single name objs-stack lookup)
|
||||
(let loop ((objs objs-stack))
|
||||
(if (null? objs)
|
||||
(values objs #f)
|
||||
(lookup (car objs)
|
||||
name
|
||||
(lambda (value) (values objs value))
|
||||
(lambda () (loop (cdr objs)))))))
|
||||
|
||||
(define (lookup-in-stack name-lst objs-stack lookup)
|
||||
(define-values (objs value)
|
||||
(lookup-in-stack-single (car name-lst) objs-stack lookup))
|
||||
(cond
|
||||
((not value) #f)
|
||||
((null? (cdr name-lst)) value)
|
||||
(else (lookup-in-stack (cdr name-lst)
|
||||
(list value)
|
||||
lookup))))
|
||||
|
||||
(define (execute template objs-stack partials out lookup collection? collection-empty? collection-for-each writer)
|
||||
(define (execute-h template indent objs-stack)
|
||||
(for-each
|
||||
(lambda (fragment)
|
||||
(cond
|
||||
((string? fragment)
|
||||
(write-string fragment out))
|
||||
((new-line? fragment)
|
||||
(begin
|
||||
(write-string (new-line-content fragment) out)
|
||||
(write-string (make-string indent #\space) out)))
|
||||
((interp? fragment)
|
||||
(let* ((name (interp-ref fragment))
|
||||
(value (if (equal? '(".") name)
|
||||
(car objs-stack)
|
||||
(lookup-in-stack name
|
||||
objs-stack
|
||||
lookup))))
|
||||
(if (interp-escape? fragment)
|
||||
(write-string (html-escape writer value) out)
|
||||
(writer value out))))
|
||||
|
||||
((section? fragment)
|
||||
(let ((value (lookup-in-stack (section-ref fragment)
|
||||
objs-stack
|
||||
lookup))
|
||||
(inner-template (section-content fragment)))
|
||||
|
||||
(cond
|
||||
((not value)
|
||||
(when (section-invert? fragment)
|
||||
(execute-h inner-template indent objs-stack)))
|
||||
((not (collection? value))
|
||||
(unless (section-invert? fragment)
|
||||
(execute-h inner-template indent (cons value objs-stack))))
|
||||
(else
|
||||
(if (section-invert? fragment)
|
||||
(when (collection-empty? value)
|
||||
(execute-h inner-template indent objs-stack))
|
||||
(collection-for-each
|
||||
(lambda (el)
|
||||
(execute-h inner-template indent (cons el objs-stack)))
|
||||
value))))))
|
||||
|
||||
((partial? fragment)
|
||||
(let ()
|
||||
(define partial-tpl
|
||||
(cond
|
||||
((assoc (partial-name fragment) partials) => cdr)
|
||||
(else #f)))
|
||||
(when partial-tpl
|
||||
(execute-h partial-tpl
|
||||
(+ indent (partial-indent fragment))
|
||||
objs-stack) )))
|
||||
|
||||
(else (error "Unknown fragment"))))
|
||||
template))
|
||||
(execute-h template 0 objs-stack))
|
|
@ -0,0 +1,6 @@
|
|||
(define-library
|
||||
(arvyy mustache executor)
|
||||
(import (scheme base)
|
||||
(arvyy mustache parser))
|
||||
(export execute)
|
||||
(include "executor-impl.scm"))
|
|
@ -0,0 +1,27 @@
|
|||
(define-library
|
||||
(arvyy mustache lookup)
|
||||
(import (scheme base))
|
||||
(export
|
||||
compose-lookups
|
||||
alist-lookup)
|
||||
(begin
|
||||
|
||||
(define (compose-lookups . lookups)
|
||||
(lambda (obj name found not-found)
|
||||
(let loop ((lookups lookups))
|
||||
(if (null? lookups)
|
||||
(not-found)
|
||||
(let ((l (car lookups)))
|
||||
(l obj name found (lambda ()
|
||||
(loop (cdr lookups)))))))))
|
||||
|
||||
(define (alist-lookup obj name found not-found)
|
||||
(define key (string->symbol name))
|
||||
(define alist? (and (list? obj)
|
||||
(or (null? obj)
|
||||
(pair? (car obj)))))
|
||||
(if alist?
|
||||
(cond
|
||||
((assoc key obj) => (lambda (pair) (found (cdr pair))))
|
||||
(else (not-found)))
|
||||
(not-found)))))
|
|
@ -0,0 +1,296 @@
|
|||
(define-record-type <interp>
|
||||
(interp ref escape?)
|
||||
interp?
|
||||
(ref interp-ref)
|
||||
(escape? interp-escape?) ;; should html be escaped
|
||||
)
|
||||
|
||||
(define-record-type <section>
|
||||
(section ref invert? content raw-content)
|
||||
section?
|
||||
(ref section-ref)
|
||||
(invert? section-invert?) ;; normal section if false, {{^ section if true
|
||||
(content section-content) ;; compiled inner content
|
||||
(raw-content section-raw-content) ;; uncompiled inner content as a string; used for lambdas
|
||||
)
|
||||
|
||||
(define-record-type <partial>
|
||||
(partial name indent)
|
||||
partial?
|
||||
(name partial-name)
|
||||
(indent partial-indent))
|
||||
|
||||
(define-record-type <newline>
|
||||
(new-line content)
|
||||
new-line?
|
||||
(content new-line-content))
|
||||
|
||||
(define (parse tokens)
|
||||
(let* ((tokens (replace-standalone tokens))
|
||||
(tokens (remove-non-visible tokens))
|
||||
(tokens (convert-string-tokens tokens))
|
||||
(tokens (parse-interp+sections tokens)))
|
||||
tokens))
|
||||
|
||||
(define (tpl->string tokens)
|
||||
(define (->string item out)
|
||||
(cond
|
||||
((string? item) (write-string item out))
|
||||
((new-line? item) (write-string (new-line-content item) out))
|
||||
((section? item)
|
||||
(let ((tagname (list->tagname (section-ref item))))
|
||||
(write-string (if (section-invert? item) "{{^" "{{#") out)
|
||||
(write-string tagname out)
|
||||
(write-string "}}" out)
|
||||
(for-each
|
||||
(lambda (item*)
|
||||
(->string item* out))
|
||||
(section-content item))
|
||||
(write-string "{{/" out)
|
||||
(write-string tagname out)
|
||||
(write-string "}}" out)))
|
||||
((interp? item)
|
||||
(let ((tagname (list->tagname (interp-ref item))))
|
||||
(write-string (if (interp-escape? item) "{{" "{{&") out)
|
||||
(write-string tagname out)
|
||||
(write-string "}}" out)))))
|
||||
(define out (open-output-string))
|
||||
(for-each
|
||||
(lambda (item) (->string item out))
|
||||
tokens)
|
||||
(get-output-string out))
|
||||
|
||||
;;TODO remove this
|
||||
(define (debug-tokens tokens)
|
||||
(for-each
|
||||
(lambda (t)
|
||||
(cond
|
||||
((token-str? t) (display (string-append "#<<token-str> " (token-str-content t) "> ")))
|
||||
((token-nl? t) (display "#<<token-nl>> "))
|
||||
((token-section-open? t) (display (string-append "#<<token-open> " (token-section-open-tag t) "> ")))
|
||||
((token-section-close? t) (display "#<<token-close>> "))
|
||||
((token-ws? t) (display (string-append "#<<token-ws> " (number->string (token-ws-count t)) "> ")))
|
||||
((token-interp? t) (display (string-append "#<<token-interp> " (token-interp-tag t) "> ")))
|
||||
(else (display t))))
|
||||
tokens
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
(define (standalone/remove? token)
|
||||
(or (token-comment? token)
|
||||
(token-delimchager? token)))
|
||||
|
||||
(define (standalone/trim? token)
|
||||
(or (token-section-open? token)
|
||||
(token-section-close? token)))
|
||||
|
||||
(define (replace-standalone tokens)
|
||||
(let loop ((tokens tokens)
|
||||
(result/inv '())
|
||||
(first #t))
|
||||
|
||||
(cond
|
||||
((null? tokens) (reverse result/inv))
|
||||
|
||||
((and first
|
||||
(or (match-follows tokens standalone/remove? token-ws? token-nl?)
|
||||
(match-follows tokens standalone/remove? token-nl?)
|
||||
(match-follows tokens token-ws? standalone/remove? token-ws? token-nl?)
|
||||
(match-follows tokens token-ws? standalone/remove? token-nl?))) =>
|
||||
(lambda (tokens*)
|
||||
(loop tokens*
|
||||
result/inv
|
||||
#t)))
|
||||
|
||||
((and first
|
||||
(or (match-follows tokens token-ws? standalone/remove? token-ws? eof-object?)
|
||||
(match-follows tokens token-ws? standalone/remove? eof-object?)
|
||||
(match-follows tokens standalone/remove? token-ws? eof-object?)
|
||||
(match-follows tokens standalone/remove? eof-object?))) =>
|
||||
(lambda (tokens*)
|
||||
(loop '()
|
||||
result/inv
|
||||
#t)))
|
||||
|
||||
((and first
|
||||
(or (match-follows tokens token-ws? standalone/trim? token-ws? token-nl?)
|
||||
(match-follows tokens token-ws? standalone/trim? token-nl?)
|
||||
(match-follows tokens token-ws? standalone/trim? token-ws? eof-object?)
|
||||
(match-follows tokens token-ws? standalone/trim? eof-object?))) =>
|
||||
(lambda (tokens*)
|
||||
(loop tokens*
|
||||
(append (list (cadr tokens))
|
||||
result/inv)
|
||||
#t)))
|
||||
|
||||
((and first
|
||||
(or (match-follows tokens standalone/trim? token-ws? token-nl?)
|
||||
(match-follows tokens standalone/trim? token-nl?)
|
||||
(match-follows tokens standalone/trim? token-ws? eof-object?)
|
||||
(match-follows tokens standalone/trim? eof-object?))) =>
|
||||
(lambda (tokens*)
|
||||
(loop tokens*
|
||||
(append (list (car tokens))
|
||||
result/inv)
|
||||
#t)))
|
||||
|
||||
((and first
|
||||
(or (match-follows tokens token-ws? token-partial? token-ws? token-nl?)
|
||||
(match-follows tokens token-ws? token-partial? token-nl?)
|
||||
(match-follows tokens token-ws? token-partial? token-ws? eof-object?)
|
||||
(match-follows tokens token-ws? token-partial? eof-object?))) =>
|
||||
(lambda (tokens*)
|
||||
(loop tokens*
|
||||
(append (list (partial (token-partial-tag (cadr tokens))
|
||||
(token-ws-count (car tokens))))
|
||||
result/inv)
|
||||
#t)))
|
||||
|
||||
((and first
|
||||
(or (match-follows tokens token-partial? token-ws? token-nl?)
|
||||
(match-follows tokens token-partial? token-nl?)
|
||||
(match-follows tokens token-partial? token-ws? eof-object?)
|
||||
(match-follows tokens token-partial? eof-object?))) =>
|
||||
(lambda (tokens*)
|
||||
(loop tokens*
|
||||
(append (list (partial (token-partial-tag (car tokens))
|
||||
0))
|
||||
result/inv)
|
||||
#t)))
|
||||
|
||||
((match-follows tokens token-partial?) => (lambda (tokens*)
|
||||
(loop tokens*
|
||||
(cons (partial (token-partial-tag (car tokens))
|
||||
0)
|
||||
result/inv)
|
||||
#f)))
|
||||
|
||||
(else (loop (cdr tokens)
|
||||
(cons (car tokens) result/inv)
|
||||
(token-nl? (car tokens)))))))
|
||||
|
||||
(define (convert-string-tokens tokens)
|
||||
(let loop ((tokens tokens)
|
||||
(out #f)
|
||||
(result/inv '()))
|
||||
(cond
|
||||
((null? tokens)
|
||||
(let ((result-final/inv (if out
|
||||
(cons (get-output-string out)
|
||||
result/inv)
|
||||
result/inv)))
|
||||
(reverse result-final/inv)))
|
||||
((or (token-str? (car tokens))
|
||||
(token-ws? (car tokens)))
|
||||
(let* ((token (car tokens))
|
||||
(out* (if out
|
||||
out
|
||||
(open-output-string)))
|
||||
(str (if (token-str? token)
|
||||
(token-str-content token)
|
||||
(make-string (token-ws-count token) #\space))))
|
||||
(write-string str out*)
|
||||
(loop (cdr tokens)
|
||||
out*
|
||||
result/inv)))
|
||||
(else (let* ((token (car tokens))
|
||||
(value (cond
|
||||
((token-nl? token) (new-line (list->string (token-nl-chars token))))
|
||||
(else token)))
|
||||
(new-result/inv (if out
|
||||
(cons (get-output-string out)
|
||||
result/inv)
|
||||
result/inv)))
|
||||
(loop (cdr tokens)
|
||||
#f
|
||||
(cons value new-result/inv)))))))
|
||||
|
||||
(define (parse-interp+sections tokens)
|
||||
(define (parse-interp+sections* tokens expected-close-tag)
|
||||
(let loop ((tokens tokens)
|
||||
(result/inv '()))
|
||||
(cond
|
||||
((null? tokens)
|
||||
(if expected-close-tag
|
||||
(error "Unexpected eof")
|
||||
(values '() (reverse result/inv))))
|
||||
((token-section-close? (car tokens))
|
||||
(if (equal? expected-close-tag (token-section-close-tag (car tokens)))
|
||||
(values (cdr tokens) (reverse result/inv))
|
||||
(error "Closing token mismatch")))
|
||||
((token-section-open? (car tokens))
|
||||
(let* ((token (car tokens))
|
||||
(tag (token-section-open-tag token))
|
||||
(ref (tagname->list tag)))
|
||||
(define-values (tokens* result*)
|
||||
(parse-interp+sections* (cdr tokens)
|
||||
tag))
|
||||
(define value (section ref
|
||||
(token-section-open-inverted? token)
|
||||
result*
|
||||
#f))
|
||||
(loop tokens*
|
||||
(cons value result/inv))))
|
||||
((token-interp? (car tokens))
|
||||
(let* ((token (car tokens))
|
||||
(tag (token-interp-tag token))
|
||||
(ref (tagname->list tag)))
|
||||
(define value (interp ref (token-interp-escape? token)))
|
||||
(loop (cdr tokens)
|
||||
(cons value result/inv))))
|
||||
(else (loop (cdr tokens)
|
||||
(cons (car tokens)
|
||||
result/inv))))))
|
||||
(define-values (tokens* result)
|
||||
(parse-interp+sections* tokens #f))
|
||||
result)
|
||||
|
||||
(define (remove-non-visible tokens)
|
||||
(filter
|
||||
(lambda (token)
|
||||
(not (or (token-comment? token)
|
||||
(token-delimchager? token))))
|
||||
tokens))
|
||||
|
||||
(define (match-follows in . preds)
|
||||
(let loop ((in* in)
|
||||
(preds* preds))
|
||||
(cond
|
||||
((null? preds*) in*)
|
||||
((null? in*) (and (null? (cdr preds*))
|
||||
(eq? (car preds*) eof-object?)
|
||||
'()))
|
||||
(((car preds*) (car in*))
|
||||
(loop (cdr in*)
|
||||
(cdr preds*)))
|
||||
(else #f))))
|
||||
|
||||
(define (tagname->list str)
|
||||
(define (prepend-part parts part)
|
||||
(when (null? part)
|
||||
(error "Trailing period in tag name"))
|
||||
(cons (list->string (reverse part))
|
||||
parts))
|
||||
(if (equal? "." str)
|
||||
'(".")
|
||||
(let loop ((in (string->list str))
|
||||
(parts '())
|
||||
(part '()))
|
||||
(cond
|
||||
((null? in)
|
||||
(reverse (prepend-part parts part)))
|
||||
((char=? #\. (car in))
|
||||
(loop (cdr in)
|
||||
(prepend-part parts part)
|
||||
'()))
|
||||
(else (loop (cdr in)
|
||||
parts
|
||||
(cons (car in) part)))))))
|
||||
|
||||
(define (list->tagname lst)
|
||||
(apply string-append
|
||||
(cdr (apply append
|
||||
(map
|
||||
(lambda (el) (list "." el))
|
||||
lst)))))
|
|
@ -0,0 +1,14 @@
|
|||
(define-library
|
||||
(arvyy mustache parser)
|
||||
(import (scheme base)
|
||||
(scheme write)
|
||||
(scheme cxr)
|
||||
(arvyy mustache tokenizer)
|
||||
(srfi 1))
|
||||
(export
|
||||
parse
|
||||
interp? interp-ref interp-escape?
|
||||
section? section-ref section-invert? section-content section-raw-content
|
||||
partial? partial-name partial-indent
|
||||
new-line? new-line-content)
|
||||
(include "parser-impl.scm"))
|
|
@ -0,0 +1,237 @@
|
|||
(define-record-type <token-ws>
|
||||
(token-ws count)
|
||||
token-ws?
|
||||
(count token-ws-count))
|
||||
|
||||
(define-record-type <token-nl>
|
||||
(token-nl chars)
|
||||
token-nl?
|
||||
(chars token-nl-chars))
|
||||
|
||||
(define-record-type <token-comment>
|
||||
(token-comment)
|
||||
token-comment?)
|
||||
|
||||
(define-record-type <token-str>
|
||||
(token-str content)
|
||||
token-str?
|
||||
(content token-str-content))
|
||||
|
||||
(define-record-type <token-delimchanger>
|
||||
(token-delimchager open close)
|
||||
token-delimchager?
|
||||
(open token-delimchager-open)
|
||||
(close token-delimchager-close))
|
||||
|
||||
(define-record-type <token-interp>
|
||||
(token-interp tag escape?)
|
||||
token-interp?
|
||||
(tag token-interp-tag)
|
||||
(escape? token-interp-escape?))
|
||||
|
||||
(define-record-type <token-section-open>
|
||||
(token-section-open tag inverted?)
|
||||
token-section-open?
|
||||
(tag token-section-open-tag)
|
||||
(inverted? token-section-open-inverted?))
|
||||
|
||||
(define-record-type <token-section-close>
|
||||
(token-section-close tag)
|
||||
token-section-close?
|
||||
(tag token-section-close-tag))
|
||||
|
||||
(define-record-type <token-partial>
|
||||
(token-partial tag)
|
||||
token-partial?
|
||||
(tag token-partial-tag))
|
||||
|
||||
(define (read-tokens str)
|
||||
(let loop ((in (string->list str))
|
||||
(ws-count 0)
|
||||
(str-value '())
|
||||
(open-delim '(#\{ #\{))
|
||||
(close-delim '(#\} #\}))
|
||||
(result/inv '()))
|
||||
|
||||
(define (resolve-ws/str)
|
||||
(cond
|
||||
;; unflushed ws and str info
|
||||
((and (not (null? str-value))
|
||||
(> ws-count 0))
|
||||
(append (list (token-ws ws-count)
|
||||
(token-str (list->string (reverse str-value))))
|
||||
result/inv))
|
||||
|
||||
;; unflushed str info
|
||||
((not (null? str-value))
|
||||
(cons (token-str (list->string (reverse str-value)))
|
||||
result/inv))
|
||||
|
||||
;; unflushed ws info
|
||||
((> ws-count 0)
|
||||
(cons (token-ws ws-count)
|
||||
result/inv))
|
||||
|
||||
;; no unflushed info
|
||||
(else result/inv)))
|
||||
|
||||
;; handle when in is null; ie final function return
|
||||
(define (return)
|
||||
(define final-result/inv (resolve-ws/str))
|
||||
(reverse final-result/inv))
|
||||
|
||||
;; handle after tag read
|
||||
(define (continue-after-tag in token)
|
||||
(loop
|
||||
in
|
||||
0
|
||||
'()
|
||||
open-delim
|
||||
close-delim
|
||||
(cons token (resolve-ws/str))))
|
||||
|
||||
(define (process-interp in)
|
||||
(define-values (in* tag)
|
||||
(read-tag in close-delim))
|
||||
(continue-after-tag in* (token-interp tag #t)))
|
||||
|
||||
(define (process-triple-mustache in)
|
||||
(define-values (in* tag)
|
||||
(read-tag in '(#\} #\} #\})))
|
||||
(continue-after-tag in* (token-interp tag #f)))
|
||||
|
||||
(define (process-ampersand in)
|
||||
(define-values (in* tag)
|
||||
(read-tag in close-delim))
|
||||
(continue-after-tag in* (token-interp tag #f)))
|
||||
|
||||
(define (process-inverted in)
|
||||
(define-values (in* tag)
|
||||
(read-tag in close-delim))
|
||||
(continue-after-tag in* (token-section-open tag #t)))
|
||||
|
||||
(define (process-section in)
|
||||
(define-values (in* tag)
|
||||
(read-tag in close-delim))
|
||||
(continue-after-tag in* (token-section-open tag #f)))
|
||||
|
||||
(define (process-close in)
|
||||
(define-values (in* tag)
|
||||
(read-tag in close-delim))
|
||||
(continue-after-tag in* (token-section-close tag)))
|
||||
|
||||
(define (process-partial in)
|
||||
(define-values (in* tag)
|
||||
(read-tag in close-delim))
|
||||
(continue-after-tag in* (token-partial tag)))
|
||||
|
||||
(define (process-comment in)
|
||||
(let loop* ((in in))
|
||||
(cond
|
||||
((null? in) (error "Unexpected EOF"))
|
||||
((match-follows in close-delim) => (lambda (in*)
|
||||
(continue-after-tag in* (token-comment))))
|
||||
(else (loop* (cdr in))))))
|
||||
|
||||
(define (process-delim-change in)
|
||||
(let*-values (((in new-open) (read-tag in #f))
|
||||
((in new-close) (read-tag in (cons #\= close-delim))))
|
||||
(loop in
|
||||
0
|
||||
'()
|
||||
(string->list new-open)
|
||||
(string->list new-close)
|
||||
(cons (token-delimchager new-open new-close)
|
||||
(resolve-ws/str)))))
|
||||
|
||||
(define (process-open-delim in*)
|
||||
(cond
|
||||
((match-follows in* '(#\&)) => process-ampersand)
|
||||
((match-follows in* '(#\^)) => process-inverted)
|
||||
((match-follows in* '(#\#)) => process-section)
|
||||
((match-follows in* '(#\/)) => process-close)
|
||||
((match-follows in* '(#\>)) => process-partial)
|
||||
((match-follows in* '(#\=)) => process-delim-change)
|
||||
((match-follows in* '(#\!)) => process-comment)
|
||||
(else (process-interp in*))))
|
||||
|
||||
(define (process-space in*)
|
||||
(loop in*
|
||||
(+ 1 ws-count)
|
||||
str-value
|
||||
open-delim
|
||||
close-delim
|
||||
result/inv))
|
||||
|
||||
(define (process-eol in* chars)
|
||||
(loop in*
|
||||
0
|
||||
'()
|
||||
open-delim
|
||||
close-delim
|
||||
(cons (token-nl chars)
|
||||
(resolve-ws/str))))
|
||||
|
||||
(define (process-nl in*)
|
||||
(process-eol in* '(#\newline)))
|
||||
|
||||
(define (process-crnl in*)
|
||||
(process-eol in* '(#\return #\newline)))
|
||||
|
||||
(define (process-char)
|
||||
(loop (cdr in)
|
||||
0
|
||||
(append (list (car in))
|
||||
(make-list ws-count #\space)
|
||||
str-value)
|
||||
open-delim
|
||||
close-delim
|
||||
result/inv))
|
||||
|
||||
;; loop handler
|
||||
(cond
|
||||
((null? in) (return))
|
||||
((match-follows in '(#\{ #\{ #\{)) => process-triple-mustache)
|
||||
((match-follows in open-delim) => process-open-delim)
|
||||
((match-follows in '(#\space)) => process-space)
|
||||
((match-follows in '(#\newline)) => process-nl)
|
||||
((match-follows in '(#\return #\newline)) => process-crnl)
|
||||
(else (process-char)))))
|
||||
|
||||
(define (match-follows in chars)
|
||||
(let loop ((in* in)
|
||||
(chars* chars))
|
||||
(cond
|
||||
((null? chars*) in*)
|
||||
((null? in*) #f)
|
||||
((char=? (car in*) (car chars*))
|
||||
(loop (cdr in*)
|
||||
(cdr chars*)))
|
||||
(else #f))))
|
||||
|
||||
(define (skip-spaces in)
|
||||
(cond
|
||||
((null? in) '())
|
||||
((char=? (car in) #\space) (skip-spaces (cdr in)))
|
||||
(else in)))
|
||||
|
||||
(define (read-tag in close-delim)
|
||||
(define-values
|
||||
(tag in*)
|
||||
(let loop ((in (skip-spaces in))
|
||||
(result '()))
|
||||
(define (return)
|
||||
(values (list->string (reverse result))
|
||||
in))
|
||||
(cond
|
||||
((null? in) (error "Unexpected EOF"))
|
||||
((char=? (car in) #\space) (return))
|
||||
((and close-delim (match-follows in close-delim))
|
||||
(return))
|
||||
(else (loop (cdr in)
|
||||
(cons (car in) result))))))
|
||||
(cond
|
||||
((not close-delim) (values in* tag))
|
||||
((match-follows (skip-spaces in*) close-delim) => (lambda (in**)
|
||||
(values in** tag)))
|
||||
(else (error "Bad tag"))))
|
|
@ -0,0 +1,15 @@
|
|||
(define-library
|
||||
(arvyy mustache tokenizer)
|
||||
(import (scheme base))
|
||||
(export
|
||||
read-tokens
|
||||
token-ws? token-ws-count
|
||||
token-nl token-nl? token-nl-chars
|
||||
token-comment?
|
||||
token-str? token-str-content
|
||||
token-delimchager? token-delimchager-open token-delimchager-close
|
||||
token-interp? token-interp-tag token-interp-escape?
|
||||
token-section-open? token-section-open-tag token-section-open-inverted?
|
||||
token-section-close? token-section-close-tag
|
||||
token-partial? token-partial-tag)
|
||||
(include "tokenizer-impl.scm"))
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
(define-library (chibi diff-test)
|
||||
(import (scheme base) (chibi diff))
|
||||
(export run-tests)
|
||||
(cond-expand
|
||||
(chibi (import (chibi test)))
|
||||
(else
|
||||
(import (scheme write))
|
||||
;; inline (chibi test) to avoid circular dependencies in snow
|
||||
;; installations
|
||||
(begin
|
||||
(define-syntax test
|
||||
(syntax-rules ()
|
||||
((test expect expr)
|
||||
(test 'expr expect expr))
|
||||
((test name expect expr)
|
||||
(guard (exn (else (display "!\nERROR: ") (write name) (newline)
|
||||
(write exn) (newline)))
|
||||
(let* ((res expr)
|
||||
(pass? (equal? expect expr)))
|
||||
(display (if pass? "." "x"))
|
||||
(cond
|
||||
((not pass?)
|
||||
(display "\nFAIL: ") (write name) (newline))))))))
|
||||
(define (test-begin name)
|
||||
(display name))
|
||||
(define (test-end)
|
||||
(newline)))))
|
||||
(begin
|
||||
(define (run-tests)
|
||||
(test-begin "diff")
|
||||
(test '((#\A 1 0) (#\C 2 2))
|
||||
(lcs-with-positions '(#\G #\A #\C) '(#\A #\G #\C #\A #\T)))
|
||||
(test '(#\A #\C)
|
||||
(lcs '(#\G #\A #\C) '(#\A #\G #\C #\A #\T)))
|
||||
(test '((#\G #\A #\C) (#\A #\G #\C #\A #\T) ((#\A 1 0) (#\C 2 2)))
|
||||
(diff "GAC" "AGCAT" read-char))
|
||||
(let ((d (diff "GAC" "AGCAT" read-char)))
|
||||
(test " »G« AC"
|
||||
(edits->string (car d) (car (cddr d)) 1))
|
||||
(test "A «G» C «AT» "
|
||||
(edits->string (cadr d) (car (cddr d)) 2))
|
||||
(test "\x1b;[31mG\x1b;[39mAC"
|
||||
(edits->string/color (car d) (car (cddr d)) 1))
|
||||
(test "A\x1b;[32mG\x1b;[39mC\x1b;[32mAT\x1b;[39m"
|
||||
(edits->string/color (cadr d) (car (cddr d)) 2)))
|
||||
(test-end))))
|
|
@ -0,0 +1,75 @@
|
|||
<html><head>
|
||||
<style type="text/css">
|
||||
body {color: #000; background-color: #FFFFF8;}
|
||||
div#menu {font-size: smaller; position: absolute; top: 50px; left: 0; width: 250px; height: 100%}
|
||||
div#menu a:link {text-decoration: none}
|
||||
div#main {font-size: large; position: absolute; top: 0; left: 260px; max-width: 590px; height: 100%}
|
||||
div#notes {position: relative; top: 2em; left: 620px; width: 200px; height: 0px; font-size: smaller;}
|
||||
div#footer {padding-bottom: 50px}
|
||||
div#menu ol {list-style-position:inside; padding-left: 5px; margin-left: 5px}
|
||||
div#menu ol ol {list-style: lower-alpha; padding-left: 15px; margin-left: 15px}
|
||||
div#menu ol ol ol {list-style: decimal; padding-left: 5px; margin-left: 5px}
|
||||
h2 { color: #888888; border-top: 3px solid #4588ba; }
|
||||
h3 { color: #666666; border-top: 2px solid #4588ba; }
|
||||
h4 { color: #222288; border-top: 1px solid #4588ba; }
|
||||
.result { color: #000; background-color: #FFEADF; width: 100%; padding: 3px}
|
||||
.output { color: #000; background-color: beige; width: 100%; padding: 3px}
|
||||
.error { color: #000; background-color: #F0B0B0; width: 100%; padding: 3px}
|
||||
.command { color: #000; background-color: #FFEADF; width: 100%; padding: 5px}
|
||||
.keyword { color: #800080; background-color: inherit; }
|
||||
.type { color: #008000; background-color: inherit; }
|
||||
.function { color: #0000FF; background-color: inherit; }
|
||||
.variable { color: #B8860B; background-color: inherit; }
|
||||
.comment { color: #FF0000; background-color: inherit; }
|
||||
.string { color: #BC8F8F; background-color: inherit; }
|
||||
.attribute { color: #FF5000; background-color: inherit; }
|
||||
.preprocessor { color: #FF00FF; background-color: inherit; }
|
||||
.builtin { color: #FF00FF; background-color: inherit; }
|
||||
.character { color: #0055AA; background-color: inherit; }
|
||||
.syntaxerror { color: #FF0000; background-color: inherit; }
|
||||
.diff-deleted { color: #5F2121; background-color: inherit; }
|
||||
.diff-added { color: #215F21; background-color: inherit; }
|
||||
span.paren1 { color: #AAAAAA; background-color: inherit; }
|
||||
span.paren2 { color: #888888; background-color: inherit; }
|
||||
span.paren3 { color: #666666; background-color: inherit; }
|
||||
span.paren4 { color: #444444; background-color: inherit; }
|
||||
span.paren5 { color: #222222; background-color: inherit; }
|
||||
span.paren6 { color: #000000; background-color: inherit; }
|
||||
</style>
|
||||
</head><body><div id="menu"><ol><li><a href="#h4_lcs">lcs</a></li><li><a href="#h4_lcs-with-positions">lcs-with-positions</a></li><li><a href="#h4_diff">diff</a></li><li><a href="#h4_write-diff">write-diff</a></li><li><a href="#h4_diff->string">diff->string</a></li><li><a href="#h4_write-line-diffs">write-line-diffs</a></li><li><a href="#h4_write-line-diffs/color">write-line-diffs/color</a></li><li><a href="#h4_write-char-diffs">write-char-diffs</a></li><li><a href="#h4_write-char-diffs/color">write-char-diffs/color</a></li><li><a href="#h4_write-edits">write-edits</a></li><li><a href="#h4_edits->string">edits->string</a></li><li><a href="#h4_edits->string/color">edits->string/color</a></li></ol></div><div id="main"><div><a name="h1_(chibidiff)"></a><h1>(chibi diff)</h1></div><div><a name="h4_lcs"></a><h4><code>(lcs a-ls b-ls [eq])</code></h4></div>Finds the Longest Common Subsequence between <code>a-ls</code> and
|
||||
<code>b-ls</code>, comparing elements with <code>eq</code> (default
|
||||
<code><span>equal?</span></code>. Returns this sequence as a list, using the
|
||||
elements from <code>a-ls</code>. Uses quadratic time and space.<div><a name="h4_lcs-with-positions"></a><h4><code>(lcs-with-positions a-ls b-ls [eq])</code></h4></div>Variant of <code><span>lcs</span></code> which returns the annotated sequence. The
|
||||
result is a list of the common elements, each represented as a
|
||||
list of 3 values: the element, the zero-indexed position in
|
||||
<code>a-ls</code> where the element occurred, and the position in
|
||||
<code>b-ls</code>.<div><a name="h4_diff"></a><h4><code>(diff a b [reader eq])</code></h4></div>Utility to run lcs on text. <code>a</code> and <code>b</code> can be strings or
|
||||
ports, which are tokenized into a sequence by calling <code>reader</code>
|
||||
until <code>eof-object</code> is found. Returns a list of three values,
|
||||
the sequences read from <code>a</code> and <code>b</code>, and the <code><span>lcs</span></code>
|
||||
result.<div><a name="h4_write-diff"></a><h4><code>(write-diff diff [writer out])</code></h4></div>Utility to format the result of a <code>diff</code> to output port
|
||||
<code>out</code> (default <code>(current-output-port)</code>). Applies
|
||||
<code>writer</code> to successive diff chunks. <code>writer</code> should be a
|
||||
procedure of three arguments: <code>(writer <span>subsequence</span> <span>type</span>
|
||||
<span>out</span>)<span>.</span> <code><span>subsequence</span></code> <span>is</span> <span>a</span> <span>subsequence</span> <span>from</span> <span>the</span> <span>original</span> <span>input</span>,
|
||||
<code><span>type</span></code> <span>is</span> <span>a</span> <span>symbol</span> <span>indicating</span> <span>the</span> <span>type</span> <span>of</span> <span>diff:</span> <code>'<span><span>same</span></span></code>
|
||||
<span>if</span> <span>this</span> <span>is</span> <span>part</span> <span>of</span> <span>the</span> <span>lcs</span>, <code>'<span><span>add</span></span></code> <span>if</span> <span>it</span> <span>is</span> <span>unique</span> <span>to</span> <span>the</span>
|
||||
<span>second</span> <span>input</span>, <span>or</span> <code>'<span><span>remove</span></span></code> <span>if</span> <span>it</span> <span>is</span> <span>unique</span> <span>to</span> <span>the</span> <span>first</span>
|
||||
<span>input.</span> <code><span>writer</span></code> <span>defaults</span> <span>to</span> <code><span><span>write-line-diffs</span></span></code>,
|
||||
<span>assuming</span> <span>the</span> <span>default</span> <span>line</span> <span>diffs.</span></code><div><a name="h4_diff->string"></a><h4><code>(diff->string diff . o)</code></h4></div>Equivalent to <code><span>write-diff</span></code> but collects the output to a string.<div><a name="h4_write-line-diffs"></a><h4><code>(write-line-diffs lines type out)</code></h4></div>The default writer for <code><span>write-diff</span></code>, annotates simple +/-
|
||||
prefixes for added/removed lines.<div><a name="h4_write-line-diffs/color"></a><h4><code>(write-line-diffs/color lines type out)</code></h4></div>A variant of <code><span>write-line-diffs</span></code> which adds red/green ANSI
|
||||
coloring to the +/- prefix.<div><a name="h4_write-char-diffs"></a><h4><code>(write-char-diffs chars type out)</code></h4></div>A diff writer for sequences of characters (when a diff was
|
||||
generated with <code><span>read-char</span></code>), enclosing added characters in
|
||||
«...» brackets and removed characters in »...«.<div><a name="h4_write-char-diffs/color"></a><h4><code>(write-char-diffs/color chars type out)</code></h4></div>A diff writer for sequences of characters (when a diff was
|
||||
generated with <code><span>read-char</span></code>), formatting added characters in
|
||||
green and removed characters in red.<div><a name="h4_write-edits"></a><h4><code>(write-edits ls lcs [index writer out])</code></h4></div>Utility to format the result of a <code><span>diff</span></code> with respect to a
|
||||
single input sequence <code>ls</code>. <code>lcs</code> is the annotated common
|
||||
sequence from <code><span>diff</span></code> or <code><span>lcs-with-positions</span></code>, and
|
||||
<code>index</code> is the index (0 or 1, default 1) of <code>ls</code> in the
|
||||
original call. Since we have no information about the other
|
||||
input, we can only format what is the same and what is different,
|
||||
formatting the differences as either added (if <code>index</code> is 0)
|
||||
or removed (if <code>index</code> is 1).<div><a name="h4_edits->string"></a><h4><code>(edits->string ls lcs [type writer])</code></h4></div>Equivalent to <code><span>write-edits</span></code> but collects the output to a string.<div><a name="h4_edits->string/color"></a><h4><code>(edits->string/color ls lcs [type writer])</code></h4></div>Equivalent to <code><span>write-edits</span></code> but collects the output to a
|
||||
string and uses a color-aware writer by default. Note with a
|
||||
character diff this returns the original input string as-is, with
|
||||
only ANSI escapes indicating what changed.<div id="footer"></div></div></body></html>
|
|
@ -0,0 +1,239 @@
|
|||
|
||||
;; utility for lcs-with-positions
|
||||
(define (max-seq . o)
|
||||
(if (null? o)
|
||||
(list 0 '())
|
||||
(let loop ((a (car o)) (ls (cdr o)))
|
||||
(if (null? ls)
|
||||
a
|
||||
(let ((b (car ls)))
|
||||
(if (>= (car a) (car b))
|
||||
(loop a (cdr ls))
|
||||
(loop b (cdr ls))))))))
|
||||
|
||||
;;> Finds the Longest Common Subsequence between \var{a-ls} and
|
||||
;;> \var{b-ls}, comparing elements with \var{eq} (default
|
||||
;;> \scheme{equal?}. Returns this sequence as a list, using the
|
||||
;;> elements from \var{a-ls}. Uses quadratic time and space.
|
||||
(define (lcs a-ls b-ls . o)
|
||||
(let ((eq (if (pair? o) (car o) equal?)))
|
||||
(map car (lcs-with-positions a-ls b-ls eq))))
|
||||
|
||||
;;> Variant of \scheme{lcs} which returns the annotated sequence. The
|
||||
;;> result is a list of the common elements, each represented as a
|
||||
;;> list of 3 values: the element, the zero-indexed position in
|
||||
;;> \var{a-ls} where the element occurred, and the position in
|
||||
;;> \var{b-ls}.
|
||||
(define (lcs-with-positions a-ls b-ls . o)
|
||||
(let* ((eq (if (pair? o) (car o) equal?))
|
||||
(a-len (+ 1 (length a-ls)))
|
||||
(b-len (+ 1 (length b-ls)))
|
||||
(results (make-vector (* a-len b-len) #f)))
|
||||
(let loop ((a a-ls) (a-pos 0) (b b-ls) (b-pos 0))
|
||||
;; cache this step if not already done
|
||||
(let ((i (+ (* a-pos b-len) b-pos)))
|
||||
(or (vector-ref results i)
|
||||
(let ((res
|
||||
(if (or (null? a) (null? b))
|
||||
(list 0 '()) ;; base case
|
||||
(let ((a1 (car a))
|
||||
(b1 (car b))
|
||||
(a-tail (loop (cdr a) (+ a-pos 1) b b-pos))
|
||||
(b-tail (loop a a-pos (cdr b) (+ b-pos 1))))
|
||||
(cond
|
||||
((eq a1 b1)
|
||||
;; match found, we either use it or we don't
|
||||
(let* ((a-b-tail (loop (cdr a) (+ a-pos 1)
|
||||
(cdr b) (+ b-pos 1)))
|
||||
(a-b-res (list (+ 1 (car a-b-tail))
|
||||
(cons (list a1 a-pos b-pos)
|
||||
(cadr a-b-tail)))))
|
||||
(max-seq a-b-res a-tail b-tail)))
|
||||
(else
|
||||
;; not a match
|
||||
(max-seq a-tail b-tail)))))))
|
||||
(vector-set! results i res)
|
||||
res))))
|
||||
(cadr (vector-ref results 0))))
|
||||
|
||||
(define (source->list x reader)
|
||||
(port->list
|
||||
reader
|
||||
(cond ((port? x) x)
|
||||
((string? x) (open-input-string x))
|
||||
(else (error "don't know how to diff from:" x)))))
|
||||
|
||||
;;> Utility to run lcs on text. \var{a} and \var{b} can be strings or
|
||||
;;> ports, which are tokenized into a sequence by calling \var{reader}
|
||||
;;> until \var{eof-object} is found. Returns a list of three values,
|
||||
;;> the sequences read from \var{a} and \var{b}, and the \scheme{lcs}
|
||||
;;> result.
|
||||
(define (diff a b . o)
|
||||
(let-optionals o ((reader read-line)
|
||||
(eq equal?))
|
||||
(let ((a-ls (source->list a reader))
|
||||
(b-ls (source->list b reader)))
|
||||
(list a-ls b-ls (lcs-with-positions a-ls b-ls eq)))))
|
||||
|
||||
;;> Utility to format the result of a \var{diff} to output port
|
||||
;;> \var{out} (default \scheme{(current-output-port)}). Applies
|
||||
;;> \var{writer} to successive diff chunks. \var{writer} should be a
|
||||
;;> procedure of three arguments: \scheme{(writer subsequence type
|
||||
;;> out). \var{subsequence} is a subsequence from the original input,
|
||||
;;> \var{type} is a symbol indicating the type of diff: \scheme{'same}
|
||||
;;> if this is part of the lcs, \scheme{'add} if it is unique to the
|
||||
;;> second input, or \scheme{'remove} if it is unique to the first
|
||||
;;> input. \var{writer} defaults to \scheme{write-line-diffs},
|
||||
;;> assuming the default line diffs.
|
||||
(define (write-diff diff . o)
|
||||
(let-optionals o ((writer write-line-diffs)
|
||||
(out (current-output-port)))
|
||||
(let* ((a-ls (car diff))
|
||||
(b-ls (cadr diff))
|
||||
(d-ls (car (cddr diff))))
|
||||
;; context diff
|
||||
(let lp ((d d-ls) (a a-ls) (a-pos 0) (b b-ls) (b-pos 0))
|
||||
(unless (null? d)
|
||||
(let* ((d1 (car d))
|
||||
(a-off (cadr d1))
|
||||
(a-skip (- a-off a-pos))
|
||||
(b-off (car (cddr d1)))
|
||||
(b-skip (- b-off b-pos)))
|
||||
(let-values (((a-head a-tail) (split-at a a-skip))
|
||||
((b-head b-tail) (split-at b b-skip)))
|
||||
;; elements only in a have been removed
|
||||
(if (pair? a-head)
|
||||
(writer (cdr a-head) 'remove out))
|
||||
;; elements only in b have been added
|
||||
(if (pair? b-head)
|
||||
(writer (cdr b-head) 'add out))
|
||||
;; reprint this common element
|
||||
(writer (list (car d1)) 'same out)
|
||||
;; recurse
|
||||
(lp (cdr d) a-tail a-off b-tail b-off))))))))
|
||||
|
||||
;;> Equivalent to \scheme{write-diff} but collects the output to a string.
|
||||
(define (diff->string diff . o)
|
||||
(let ((out (open-output-string)))
|
||||
(write-diff diff (if (pair? o) (car o) write-line-diffs) out)
|
||||
(get-output-string out)))
|
||||
|
||||
;;> The default writer for \scheme{write-diff}, annotates simple +/-
|
||||
;;> prefixes for added/removed lines.
|
||||
(define (write-line-diffs lines type out)
|
||||
(for-each
|
||||
(lambda (line)
|
||||
(case type
|
||||
((add)
|
||||
(write-char #\+ out))
|
||||
((remove)
|
||||
(write-char #\- out))
|
||||
((same)
|
||||
(write-char #\space out))
|
||||
(else (error "unknown diff type:" type)))
|
||||
(write-string line out)
|
||||
(newline out))
|
||||
lines))
|
||||
|
||||
;;> A variant of \scheme{write-line-diffs} which adds red/green ANSI
|
||||
;;> coloring to the +/- prefix.
|
||||
(define (write-line-diffs/color lines type out)
|
||||
(for-each
|
||||
(lambda (line)
|
||||
(case type
|
||||
((add)
|
||||
(write-string (green "+") out)
|
||||
(write-string (green line) out))
|
||||
((remove)
|
||||
(write-string (red "-") out)
|
||||
(write-string (red line out)))
|
||||
((same)
|
||||
(write-char #\space out)
|
||||
(write-string line out))
|
||||
(else (error "unknown diff type:" type)))
|
||||
(newline out))
|
||||
lines))
|
||||
|
||||
;;> A diff writer for sequences of characters (when a diff was
|
||||
;;> generated with \scheme{read-char}), enclosing added characters in
|
||||
;;> «...» brackets and removed characters in »...«.
|
||||
(define (write-char-diffs chars type out)
|
||||
(case type
|
||||
((add)
|
||||
(write-string " «" out)
|
||||
(write-string (list->string chars) out)
|
||||
(write-string "» " out))
|
||||
((remove)
|
||||
(write-string " »" out)
|
||||
(write-string (list->string chars) out)
|
||||
(write-string "« " out))
|
||||
((same)
|
||||
(write-string (list->string chars) out))
|
||||
(else (error "unknown diff type:" type))))
|
||||
|
||||
;;> A diff writer for sequences of characters (when a diff was
|
||||
;;> generated with \scheme{read-char}), formatting added characters in
|
||||
;;> green and removed characters in red.
|
||||
(define (write-char-diffs/color chars type out)
|
||||
(case type
|
||||
((add)
|
||||
(write-string (green (list->string chars)) out))
|
||||
((remove)
|
||||
(write-string (red (list->string chars)) out))
|
||||
((same)
|
||||
(write-string (list->string chars) out))
|
||||
(else (error "unknown diff type:" type))))
|
||||
|
||||
;;> Utility to format the result of a \scheme{diff} with respect to a
|
||||
;;> single input sequence \var{ls}. \var{lcs} is the annotated common
|
||||
;;> sequence from \scheme{diff} or \scheme{lcs-with-positions}, and
|
||||
;;> \var{index} is the index (0 or 1, default 1) of \var{ls} in the
|
||||
;;> original call. Since we have no information about the other
|
||||
;;> input, we can only format what is the same and what is different,
|
||||
;;> formatting the differences as either added (if \var{index} is 0)
|
||||
;;> or removed (if \var{index} is 1).
|
||||
(define (write-edits ls lcs . o)
|
||||
(let-optionals o ((index 1)
|
||||
(writer write-line-diffs)
|
||||
(out (current-output-port)))
|
||||
(let ((type (if (eq? index 1) 'remove 'add)))
|
||||
(let lp ((ls ls) (lcs lcs) (buf '(#f)) (i 0))
|
||||
(define (output ch type)
|
||||
(cond
|
||||
((eq? type (car buf))
|
||||
(cons type (cons ch (cdr buf))))
|
||||
(else
|
||||
(if (car buf)
|
||||
(writer (reverse (cdr buf)) (car buf) out))
|
||||
(list type ch))))
|
||||
(cond
|
||||
((null? ls) (output #f 'done))
|
||||
((null? lcs)
|
||||
(lp (cdr ls) lcs (output (car ls) type) (+ i 1)))
|
||||
((= i (list-ref (car lcs) index))
|
||||
(lp (cdr ls) (cdr lcs) (output (car ls) 'same) (+ i 1)))
|
||||
(else
|
||||
(lp (cdr ls) lcs (output (car ls) type) (+ i 1))))))))
|
||||
|
||||
;;> Equivalent to \scheme{write-edits} but collects the output to a string.
|
||||
(define (edits->string ls lcs . o)
|
||||
(let-optionals o ((type 'add)
|
||||
(writer (if (and (pair? ls) (char? (car ls)))
|
||||
write-char-diffs
|
||||
write-line-diffs)))
|
||||
(let ((out (open-output-string)))
|
||||
(write-edits ls lcs type writer out)
|
||||
(get-output-string out))))
|
||||
|
||||
;;> Equivalent to \scheme{write-edits} but collects the output to a
|
||||
;;> string and uses a color-aware writer by default. Note with a
|
||||
;;> character diff this returns the original input string as-is, with
|
||||
;;> only ANSI escapes indicating what changed.
|
||||
(define (edits->string/color ls lcs . o)
|
||||
(let-optionals o ((type 'add)
|
||||
(writer (if (and (pair? ls) (char? (car ls)))
|
||||
write-char-diffs/color
|
||||
write-line-diffs/color)))
|
||||
(let ((out (open-output-string)))
|
||||
(write-edits ls lcs type writer out)
|
||||
(get-output-string out))))
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
(define-library (chibi diff)
|
||||
(import (scheme base) (srfi 1) (chibi optional) (chibi term ansi))
|
||||
(export lcs lcs-with-positions
|
||||
diff write-diff diff->string
|
||||
write-edits edits->string edits->string/color
|
||||
write-line-diffs
|
||||
write-line-diffs/color
|
||||
write-char-diffs
|
||||
write-char-diffs/color)
|
||||
(cond-expand
|
||||
(chibi (import (only (chibi io) port->list)))
|
||||
(else
|
||||
(begin
|
||||
(define (port->list reader port)
|
||||
(let lp ((res '()))
|
||||
(let ((x (reader port)))
|
||||
(if (eof-object? x)
|
||||
(reverse res)
|
||||
(lp (cons x res)))))))))
|
||||
(include "diff.scm"))
|
|
@ -0,0 +1,72 @@
|
|||
|
||||
(define-library (chibi optional-test)
|
||||
(import (scheme base) (chibi optional))
|
||||
(cond-expand
|
||||
(chibi (import (chibi test)))
|
||||
(else
|
||||
(import (scheme write))
|
||||
;; inline (chibi test) to avoid circular dependencies in snow
|
||||
;; installations
|
||||
(begin
|
||||
(define-syntax test
|
||||
(syntax-rules ()
|
||||
((test expect expr)
|
||||
(test 'expr expect expr))
|
||||
((test name expect expr)
|
||||
(guard (exn (else (display "!\nERROR: ") (write name) (newline)
|
||||
(write exn) (newline)))
|
||||
(let* ((res expr)
|
||||
(pass? (equal? expect expr)))
|
||||
(display (if pass? "." "x"))
|
||||
(cond
|
||||
((not pass?)
|
||||
(display "\nFAIL: ") (write name) (newline))))))))
|
||||
(define-syntax test-assert
|
||||
(syntax-rules ()
|
||||
((test-assert expr) (test #t expr))))
|
||||
(define-syntax test-error
|
||||
(syntax-rules ()
|
||||
((test-error expr)
|
||||
(test-assert (guard (exn (else #t)) expr #f)))))
|
||||
(define (test-begin name)
|
||||
(display name))
|
||||
(define (test-end)
|
||||
(newline)))))
|
||||
(export run-tests)
|
||||
(begin
|
||||
(define (run-tests)
|
||||
(test-begin "optional")
|
||||
(test '(0 11 12)
|
||||
(let-optionals '(0) ((a 10) (b 11) (c 12))
|
||||
(list a b c)))
|
||||
(test '(0 11 12)
|
||||
((opt-lambda ((a 10) (b 11) (c 12))
|
||||
(list a b c))
|
||||
0))
|
||||
(test '(0 11 12)
|
||||
((opt-lambda (a (b 11) (c 12))
|
||||
(list a b c))
|
||||
0))
|
||||
(test '(0 1 (2 3 4))
|
||||
(let-optionals* '(0 1 2 3 4) ((a 10) (b 11) . c)
|
||||
(list a b c)))
|
||||
(test '(0 1 (2 3 4))
|
||||
(let-optionals '(0 1 2 3 4) ((a 10) (b 11) . c)
|
||||
(list a b c)))
|
||||
(cond-expand
|
||||
(gauche) ; gauche detects this at compile-time, can't catch
|
||||
(else (test-error '(0 11 12)
|
||||
((opt-lambda (a (b 11) (c 12))
|
||||
(list a b c))))))
|
||||
(let ()
|
||||
(define-opt (f a (b 11) (c 12))
|
||||
(list a b c))
|
||||
(cond-expand
|
||||
(gauche)
|
||||
(else
|
||||
(test-error (f))))
|
||||
(test '(0 11 12) (f 0))
|
||||
(test '(0 1 12) (f 0 1))
|
||||
(test '(0 1 2) (f 0 1 2))
|
||||
(test '(0 1 2) (f 0 1 2 3)))
|
||||
(test-end))))
|
|
@ -0,0 +1,137 @@
|
|||
<html><head>
|
||||
<style type="text/css">
|
||||
body {color: #000; background-color: #FFFFF8;}
|
||||
div#menu {font-size: smaller; position: absolute; top: 50px; left: 0; width: 250px; height: 100%}
|
||||
div#menu a:link {text-decoration: none}
|
||||
div#main {font-size: large; position: absolute; top: 0; left: 260px; max-width: 590px; height: 100%}
|
||||
div#notes {position: relative; top: 2em; left: 620px; width: 200px; height: 0px; font-size: smaller;}
|
||||
div#footer {padding-bottom: 50px}
|
||||
div#menu ol {list-style-position:inside; padding-left: 5px; margin-left: 5px}
|
||||
div#menu ol ol {list-style: lower-alpha; padding-left: 15px; margin-left: 15px}
|
||||
div#menu ol ol ol {list-style: decimal; padding-left: 5px; margin-left: 5px}
|
||||
h2 { color: #888888; border-top: 3px solid #4588ba; }
|
||||
h3 { color: #666666; border-top: 2px solid #4588ba; }
|
||||
h4 { color: #222288; border-top: 1px solid #4588ba; }
|
||||
.result { color: #000; background-color: #FFEADF; width: 100%; padding: 3px}
|
||||
.output { color: #000; background-color: beige; width: 100%; padding: 3px}
|
||||
.error { color: #000; background-color: #F0B0B0; width: 100%; padding: 3px}
|
||||
.command { color: #000; background-color: #FFEADF; width: 100%; padding: 5px}
|
||||
.keyword { color: #800080; background-color: inherit; }
|
||||
.type { color: #008000; background-color: inherit; }
|
||||
.function { color: #0000FF; background-color: inherit; }
|
||||
.variable { color: #B8860B; background-color: inherit; }
|
||||
.comment { color: #FF0000; background-color: inherit; }
|
||||
.string { color: #BC8F8F; background-color: inherit; }
|
||||
.attribute { color: #FF5000; background-color: inherit; }
|
||||
.preprocessor { color: #FF00FF; background-color: inherit; }
|
||||
.builtin { color: #FF00FF; background-color: inherit; }
|
||||
.character { color: #0055AA; background-color: inherit; }
|
||||
.syntaxerror { color: #FF0000; background-color: inherit; }
|
||||
.diff-deleted { color: #5F2121; background-color: inherit; }
|
||||
.diff-added { color: #215F21; background-color: inherit; }
|
||||
span.paren1 { color: #AAAAAA; background-color: inherit; }
|
||||
span.paren2 { color: #888888; background-color: inherit; }
|
||||
span.paren3 { color: #666666; background-color: inherit; }
|
||||
span.paren4 { color: #444444; background-color: inherit; }
|
||||
span.paren5 { color: #222222; background-color: inherit; }
|
||||
span.paren6 { color: #000000; background-color: inherit; }
|
||||
</style>
|
||||
</head><body><div id="menu"><ol><li><a href="#h4_(let-optionalsls((vardefault)...[rest])body...)">let-optionals</a></li><li><a href="#h4_(let-optionals*ls((vardefault)...[rest])body...)">let-optionals*</a></li><li><a href="#h4_(opt-lambda((vardefault)...[rest])body...)">opt-lambda</a></li><li><a href="#h4_(define-opt(name(vardefault)...[rest])body...)">define-opt</a></li><li><a href="#h4_(keyword-reflskey[default])">keyword-ref</a></li><li><a href="#h4_(keyword-ref*lskeydefault)">keyword-ref*</a></li><li><a href="#h4_(let-keywordsls((var[keyword]default)...[rest])body...)">let-keywords</a></li><li><a href="#h4_(let-keywords*ls((var[keyword]default)...[rest])body...)">let-keywords*</a></li></ol></div><div id="main"><div><a name="h1_(chibioptional)"></a><h1>(chibi optional)</h1></div><p>Syntax to support optional and named keyword arguments.
|
||||
<code><span>let-optionals</span>[<span>*</span>]</code> is originally from SCSH, and
|
||||
<code><span>let-keywords</span>[<span>*</span>]</code> derived from Gauche.<div><a name="h4_(let-optionalsls((vardefault)...[rest])body...)"></a><h4><code>(let-optionals ls ((var default) ... [rest]) body ...)</code></h4></div>
|
||||
|
||||
Binding construct similar to <code><span>let</span></code>. The <code>var</code>s are
|
||||
bound to fresh locations holding values taken in order from the
|
||||
list <code>ls</code>, <code>body</code> is evaluated in the resulting
|
||||
environment, and the value(s) of the last expression of <code>body</code>
|
||||
returned. If the length of <code>ls</code> is shorter than the number of
|
||||
<code>var</code>s, then the remaining <code>var</code>s taken their values from
|
||||
their corresponding <code>default</code>s, evaluated in an unspecified
|
||||
order. Unused <code>default</code>s are not evaluated. If a final
|
||||
<code>rest</code> var is specified, then it is bound to any remaining
|
||||
elements of <code>ls</code> beyond the length of <code>ls</code>, otherwise any
|
||||
extra values are unused.
|
||||
|
||||
Typically used on the dotted rest list at the start of a lambda,
|
||||
<code><span>let-optionals</span></code> is more concise and more efficient than
|
||||
<code><span>case-lambda</span></code> for simple optional argument uses.
|
||||
|
||||
<em>Example:</em>
|
||||
<pre><code>(<span class="keyword">define</span> (<span class="function">copy-port</span> <span>.</span> <span>o</span>)
|
||||
(<span class="keyword">let-optionals</span> <span>o</span> ((in (current-input-port))
|
||||
(out (current-output-port))
|
||||
(n-bytes #f))
|
||||
(<span class="keyword">do</span> ((i <span>0</span> (+ <span>i</span> <span>1</span>))
|
||||
(n (read-u8 <span>in</span>) (read-u8 <span>in</span>)))
|
||||
((or (and <span>n-bytes</span> (>= <span>i</span> <span>n-bytes</span>))
|
||||
(eof-object? <span>b</span>)))
|
||||
(write-u8 <span>b</span> <span>out</span>)))</code></pre>
|
||||
|
||||
<em>Example:</em>
|
||||
<div><pre><code>(<span class="keyword">let-optionals</span> '(0) ((a <span>10</span>) (b <span>11</span>) (c <span>12</span>))
|
||||
(list <span>a</span> <span>b</span> <span>c</span>))</code></pre><div class="result"><code>=> (0 11 12)</code></div></div><div><a name="h4_(let-optionals*ls((vardefault)...[rest])body...)"></a><h4><code>(let-optionals* ls ((var default) ... [rest]) body ...)</code></h4></div>
|
||||
|
||||
<code><span>let*</span></code> equivalent to <code><span>let-optionals</span></code>. Any required
|
||||
<code>default</code> values are evaluated in left-to-right order, with
|
||||
all preceding <code>var</code>s in scope.
|
||||
<div><a name="h4_(opt-lambda((vardefault)...[rest])body...)"></a><h4><code>(opt-lambda ((var default) ... [rest]) body ...)</code></h4></div>
|
||||
|
||||
Shorthand for
|
||||
<pre><code>(<span class="keyword">lambda</span> (required <span>...</span> <span>.</span> <span>o</span>)
|
||||
(<span class="keyword">let-optionals</span> <span>o</span> ((var <span>default</span>) <span>...</span> [<span>rest</span>])
|
||||
<span>body</span> <span>...</span>))</code></pre><div><a name="h4_(define-opt(name(vardefault)...[rest])body...)"></a><h4><code>(define-opt (name (var default) ... [rest]) body ...)</code></h4></div>
|
||||
|
||||
Shorthand for
|
||||
<pre><code>(<span class="keyword">define</span> <span class="function">name</span> (opt-lambda (var <span>default</span>) <span>...</span> [<span>rest</span>]) <span>body</span> <span>...</span>)</code></pre><div><a name="h4_(keyword-reflskey[default])"></a><h4><code>(keyword-ref ls key [default])</code></h4></div>
|
||||
|
||||
Search for the identifier <code>key</code> in the list <code>ls</code>, treating
|
||||
it as a property list of the form <code>(key1 <span>val1</span> <span>key2</span> <span>val2</span>
|
||||
<span>...</span>)</code>, and return the associated <code>val</code>. If not found, return
|
||||
<code>default</code>, or <code>#f</code>.<div><a name="h4_(keyword-ref*lskeydefault)"></a><h4><code>(keyword-ref* ls key default)</code></h4></div>
|
||||
|
||||
Macro equivalent of <code><span>keyword-ref</span></code>, where <code>default</code> is
|
||||
only evaluated if <code>key</code> is not found.<div><a name="h4_(let-keywordsls((var[keyword]default)...[rest])body...)"></a><h4><code>(let-keywords ls ((var [keyword] default) ... [rest]) body ...)</code></h4></div>
|
||||
|
||||
Analogous to <code><span>let-optionals</span></code>, except instead of binding the
|
||||
<code>var</code>s by position they are bound by name, by searching in
|
||||
<code>ls</code> with <code><span>keyword-ref*</span></code>. If an optional <code>keyword</code>
|
||||
argument is provided it must be an identifier to use as the name,
|
||||
otherwise <code>var</code> is used, appending a ":" (colon). If the name
|
||||
is not found, <code>var</code> is bound to <code>default</code>, even if unused
|
||||
names remain in <code>ls</code>.
|
||||
|
||||
If an optional trailing identifier <code>rest</code> is provided, it is
|
||||
bound to the list of unused arguments not bound to any <code>var</code>.
|
||||
|
||||
Note R7RS does not have a disjoint keyword type or auto-quoting
|
||||
syntax for keywords - they are simply identifiers. Thus when
|
||||
passing keyword arguments they must be quoted (or otherwise
|
||||
dynamically evaluated).
|
||||
|
||||
<em>Example:</em>
|
||||
<div><pre><code>(<span class="keyword">define</span> (<span class="function">make-person</span> <span>.</span> <span>o</span>)
|
||||
(<span class="keyword">let-keywords</span> <span>o</span> ((name <span class="string">"John Doe"</span>)
|
||||
(age <span>0</span>)
|
||||
(occupation <span>job:</span> '<span>unemployed</span>))
|
||||
(vector <span>name</span> <span>age</span> <span>occupation</span>))<span class="syntaxerror">)</span>
|
||||
|
||||
(list (make-person)
|
||||
(make-person '<span>name:</span> <span class="string">"Methuselah"</span> '<span>age:</span> <span>969</span>)
|
||||
(make-person '<span>name:</span> <span class="string">"Dr. Who"</span> '<span>job:</span> '<span>time-lord</span> '<span>age:</span> <span>1500</span>))
|
||||
</code></pre><div class="result"><code>=> (#("John Doe" 0 unemployed) #("Methuselah" 969 unemployed) #("Dr. Who" 1500 time-lord))</code></div></div>
|
||||
|
||||
<em>Example:</em>
|
||||
<div><pre><code>(<span class="keyword">let-keywords</span> '(b: <span>2</span> <span>a:</span> <span>1</span> <span>other:</span> <span>9</span>)
|
||||
((a <span>0</span>) (b <span>0</span>) (c <span>0</span>) <span>rest</span>)
|
||||
(list <span>a</span> <span>b</span> <span>c</span> <span>rest</span>))
|
||||
</code></pre><div class="result"><code>=> (1 2 0 (other: 9))</code></div></div><div><a name="h4_(let-keywords*ls((var[keyword]default)...[rest])body...)"></a><h4><code>(let-keywords* ls ((var [keyword] default) ... [rest]) body ...)</code></h4></div>
|
||||
|
||||
<code><span>let*</span></code> equivalent to <code><span>let-keywords*</span></code>. Any required
|
||||
<code>default</code> values are evaluated in left-to-right order, with
|
||||
all preceding <code>var</code>s in scope.
|
||||
|
||||
<em>Example:</em>
|
||||
<div><pre><code>(<span class="keyword">let-keywords*</span> '(b: <span>5</span>)
|
||||
((a <span>1</span>) (b (* <span>a</span> <span>2</span>)) (c (* <span>b</span> <span>3</span>)))
|
||||
(list <span>a</span> <span>b</span> <span>c</span>))
|
||||
</code></pre><div class="result"><code>=> (1 5 15)</code></div></div></p><div id="footer"></div></div></body></html>
|
|
@ -0,0 +1,227 @@
|
|||
|
||||
;;> Syntax to support optional and named keyword arguments.
|
||||
;;> \scheme{let-optionals[*]} is originally from SCSH, and
|
||||
;;> \scheme{let-keywords[*]} derived from Gauche.
|
||||
|
||||
;; Wrap bindings in temp variables to convert a let* definition to a
|
||||
;; let definition.
|
||||
|
||||
(define-syntax let*-to-let
|
||||
(syntax-rules ()
|
||||
((let*-to-let letstar ls (vars ...) ((v . d) . rest) . body)
|
||||
(let*-to-let letstar ls (vars ... (v tmp . d)) rest . body))
|
||||
((let*-to-let letstar ls ((var tmp . d) ...) rest . body)
|
||||
(letstar ls ((tmp . d) ... . rest)
|
||||
(let ((var tmp) ...) . body)))))
|
||||
|
||||
;;> \macro{(let-optionals ls ((var default) ... [rest]) body ...)}
|
||||
;;>
|
||||
;;> Binding construct similar to \scheme{let}. The \var{var}s are
|
||||
;;> bound to fresh locations holding values taken in order from the
|
||||
;;> list \var{ls}, \var{body} is evaluated in the resulting
|
||||
;;> environment, and the value(s) of the last expression of \var{body}
|
||||
;;> returned. If the length of \var{ls} is shorter than the number of
|
||||
;;> \var{var}s, then the remaining \var{var}s taken their values from
|
||||
;;> their corresponding \var{default}s, evaluated in an unspecified
|
||||
;;> order. Unused \var{default}s are not evaluated. If a final
|
||||
;;> \var{rest} var is specified, then it is bound to any remaining
|
||||
;;> elements of \var{ls} beyond the length of \var{ls}, otherwise any
|
||||
;;> extra values are unused.
|
||||
;;>
|
||||
;;> Typically used on the dotted rest list at the start of a lambda,
|
||||
;;> \scheme{let-optionals} is more concise and more efficient than
|
||||
;;> \scheme{case-lambda} for simple optional argument uses.
|
||||
;;>
|
||||
;;> \emph{Example:}
|
||||
;;> \schemeblock{
|
||||
;;> (define (copy-port . o)
|
||||
;;> (let-optionals o ((in (current-input-port))
|
||||
;;> (out (current-output-port))
|
||||
;;> (n-bytes #f))
|
||||
;;> (do ((i 0 (+ i 1))
|
||||
;;> (n (read-u8 in) (read-u8 in)))
|
||||
;;> ((or (and n-bytes (>= i n-bytes))
|
||||
;;> (eof-object? b)))
|
||||
;;> (write-u8 b out)))}
|
||||
;;>
|
||||
;;> \emph{Example:}
|
||||
;;> \example{
|
||||
;;> (let-optionals '(0) ((a 10) (b 11) (c 12))
|
||||
;;> (list a b c))}
|
||||
|
||||
(define-syntax let-optionals
|
||||
(syntax-rules ()
|
||||
((let-optionals ls ((var default) ... . rest) body ...)
|
||||
(let*-to-let let-optionals* ls () ((var default) ... . rest) body ...))))
|
||||
|
||||
;;> \macro{(let-optionals* ls ((var default) ... [rest]) body ...)}
|
||||
;;>
|
||||
;;> \scheme{let*} equivalent to \scheme{let-optionals}. Any required
|
||||
;;> \var{default} values are evaluated in left-to-right order, with
|
||||
;;> all preceding \var{var}s in scope.
|
||||
|
||||
;;> \macro{(opt-lambda ((var default) ... [rest]) body ...)}
|
||||
;;>
|
||||
;;> Shorthand for
|
||||
;;> \schemeblock{
|
||||
;;> (lambda (required ... . o)
|
||||
;;> (let-optionals o ((var default) ... [rest])
|
||||
;;> body ...))}
|
||||
|
||||
(define-syntax opt-lambda
|
||||
(syntax-rules ()
|
||||
((opt-lambda vars . body)
|
||||
(opt-lambda/aux () vars . body))))
|
||||
|
||||
(define-syntax opt-lambda/aux
|
||||
(syntax-rules ()
|
||||
((opt-lambda/aux (args ...) ((var . default) . vars) . body)
|
||||
(lambda (args ... . o)
|
||||
(let-optionals o ((var . default) . vars) . body)))
|
||||
((opt-lambda/aux (args ...) (var . vars) . body)
|
||||
(opt-lambda/aux (args ... var) vars . body))
|
||||
((opt-lambda/aux (args ...) () . body)
|
||||
(lambda (args ... . o)
|
||||
. body))))
|
||||
|
||||
;;> \macro{(define-opt (name (var default) ... [rest]) body ...)}
|
||||
;;>
|
||||
;;> Shorthand for
|
||||
;;> \schemeblock{
|
||||
;;> (define name (opt-lambda (var default) ... [rest]) body ...)}
|
||||
|
||||
(define-syntax define-opt
|
||||
(syntax-rules ()
|
||||
((define-opt (name . vars) . body)
|
||||
(define name (opt-lambda vars . body)))))
|
||||
|
||||
;;> \procedure{(keyword-ref ls key [default])}
|
||||
;;>
|
||||
;;> Search for the identifier \var{key} in the list \var{ls}, treating
|
||||
;;> it as a property list of the form \scheme{(key1 val1 key2 val2
|
||||
;;> ...)}, and return the associated \var{val}. If not found, return
|
||||
;;> \var{default}, or \scheme{#f}.
|
||||
|
||||
(define (keyword-ref ls key . o)
|
||||
(let lp ((ls ls))
|
||||
(if (and (pair? ls) (pair? (cdr ls)))
|
||||
(if (eq? key (car ls))
|
||||
(cadr ls)
|
||||
(lp (cddr ls)))
|
||||
(and (pair? o) (car o)))))
|
||||
|
||||
;;> \macro{(keyword-ref* ls key default)}
|
||||
;;>
|
||||
;;> Macro equivalent of \scheme{keyword-ref}, where \var{default} is
|
||||
;;> only evaluated if \var{key} is not found.
|
||||
|
||||
(define-syntax keyword-ref*
|
||||
(syntax-rules ()
|
||||
((keyword-ref* ls key default)
|
||||
(cond ((memq key ls) => cadr) (else default)))))
|
||||
|
||||
(define (symbol->keyword sym)
|
||||
(string->symbol (string-append (symbol->string sym) ":")))
|
||||
|
||||
(define-syntax let-key*-to-let
|
||||
(syntax-rules ()
|
||||
((let-key*-to-let ls (vars ...) ((v d) . rest) . body)
|
||||
(let-key*-to-let ls (vars ... (v tmp ,(symbol->keyword 'v) d)) rest
|
||||
. body))
|
||||
((let-key*-to-let ls (vars ...) ((v k d) . rest) . body)
|
||||
(let-key*-to-let ls (vars ... (v tmp k d)) rest . body))
|
||||
((let-key*-to-let ls ((var tmp k d) ...) rest . body)
|
||||
(let-keywords* ls ((tmp k d) ... . rest)
|
||||
(let ((var tmp) ...) . body)))))
|
||||
|
||||
;;> \macro{(let-keywords ls ((var [keyword] default) ... [rest]) body ...)}
|
||||
;;>
|
||||
;;> Analogous to \scheme{let-optionals}, except instead of binding the
|
||||
;;> \var{var}s by position they are bound by name, by searching in
|
||||
;;> \var{ls} with \scheme{keyword-ref*}. If an optional \var{keyword}
|
||||
;;> argument is provided it must be an identifier to use as the name,
|
||||
;;> otherwise \var{var} is used, appending a ":" (colon). If the name
|
||||
;;> is not found, \var{var} is bound to \var{default}, even if unused
|
||||
;;> names remain in \var{ls}.
|
||||
;;>
|
||||
;;> If an optional trailing identifier \var{rest} is provided, it is
|
||||
;;> bound to the list of unused arguments not bound to any \var{var}.
|
||||
;;>
|
||||
;;> Note R7RS does not have a disjoint keyword type or auto-quoting
|
||||
;;> syntax for keywords - they are simply identifiers. Thus when
|
||||
;;> passing keyword arguments they must be quoted (or otherwise
|
||||
;;> dynamically evaluated).
|
||||
;;>
|
||||
;;> \emph{Example:}
|
||||
;;> \example{
|
||||
;;> (define (make-person . o)
|
||||
;;> (let-keywords o ((name "John Doe")
|
||||
;;> (age 0)
|
||||
;;> (occupation job: 'unemployed))
|
||||
;;> (vector name age occupation)))
|
||||
;;>
|
||||
;;> (list (make-person)
|
||||
;;> (make-person 'name: "Methuselah" 'age: 969)
|
||||
;;> (make-person 'name: "Dr. Who" 'job: 'time-lord 'age: 1500))
|
||||
;;> }
|
||||
;;>
|
||||
;;> \emph{Example:}
|
||||
;;> \example{
|
||||
;;> (let-keywords '(b: 2 a: 1 other: 9)
|
||||
;;> ((a 0) (b 0) (c 0) rest)
|
||||
;;> (list a b c rest))
|
||||
;;> }
|
||||
|
||||
(define-syntax let-keywords
|
||||
(syntax-rules ()
|
||||
((let-keywords ls vars . body)
|
||||
(let-key*-to-let ls () vars . body))))
|
||||
|
||||
(define (remove-keywords ls keywords)
|
||||
(let lp ((ls ls) (res '()))
|
||||
(if (and (pair? ls) (pair? (cdr ls)))
|
||||
(if (memq (car ls) keywords)
|
||||
(lp (cddr ls) res)
|
||||
(lp (cddr ls) (cons (cadr ls) (cons (car ls) res))))
|
||||
(reverse res))))
|
||||
|
||||
(define-syntax remove-keywords*
|
||||
(syntax-rules ()
|
||||
((remove-keywords* opt-ls (keys ...) ((var key default) . rest))
|
||||
(remove-keywords* opt-ls (keys ... key) rest))
|
||||
((remove-keywords* opt-ls (keys ...) ((var default) . rest))
|
||||
(remove-keywords* opt-ls (keys ... ,(symbol->keyword* 'var)) rest))
|
||||
((remove-keywords* opt-ls (keys ...) ())
|
||||
(remove-keywords opt-ls `(keys ...)))))
|
||||
|
||||
;;> \macro{(let-keywords* ls ((var [keyword] default) ... [rest]) body ...)}
|
||||
;;>
|
||||
;;> \scheme{let*} equivalent to \scheme{let-keywords*}. Any required
|
||||
;;> \var{default} values are evaluated in left-to-right order, with
|
||||
;;> all preceding \var{var}s in scope.
|
||||
;;>
|
||||
;;> \emph{Example:}
|
||||
;;> \example{
|
||||
;;> (let-keywords* '(b: 5)
|
||||
;;> ((a 1) (b (* a 2)) (c (* b 3)))
|
||||
;;> (list a b c))
|
||||
;;> }
|
||||
|
||||
(define-syntax let-keywords*
|
||||
(syntax-rules ()
|
||||
((let-keywords* opt-ls () . body)
|
||||
(begin . body))
|
||||
((let-keywords* (op . args) vars . body)
|
||||
(let ((tmp (op . args)))
|
||||
(let-keywords* tmp vars . body)))
|
||||
((let-keywords* opt-ls ((var) (vars . x) ...) . body)
|
||||
(let-keywords* opt-ls ((var #f) (vars . x) ...) . body))
|
||||
((let-keywords* opt-ls ((var default) (vars . x) ...) . body)
|
||||
(let ((var (keyword-ref* opt-ls (symbol->keyword* 'var) default)))
|
||||
(let-keywords* opt-ls ((vars . x) ...) . body)))
|
||||
((let-keywords* opt-ls ((var key default) (vars . x) ...) . body)
|
||||
(let ((var (keyword-ref* opt-ls `key default)))
|
||||
(let-keywords* opt-ls ((vars . x) ...) . body)))
|
||||
((let-keywords* opt-ls ((vars . x) ... tail) . body)
|
||||
(let ((tail (remove-keywords* opt-ls () ((vars . x) ...))))
|
||||
(let-keywords* opt-ls ((vars . x) ...) . body)))))
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
(define-library (chibi optional)
|
||||
(export let-optionals let-optionals* opt-lambda define-opt
|
||||
let-keywords let-keywords* keyword-ref keyword-ref*)
|
||||
(cond-expand
|
||||
(chibi
|
||||
(import (chibi))
|
||||
(begin
|
||||
(define-syntax symbol->keyword*
|
||||
(er-macro-transformer
|
||||
(lambda (expr rename compare)
|
||||
(if (and (pair? (cdr expr)) (pair? (cadr expr))
|
||||
(compare 'quote (car (cadr expr))))
|
||||
`(,(rename 'quote)
|
||||
,(string->symbol
|
||||
(string-append
|
||||
(symbol->string
|
||||
(identifier->symbol (cadr (cadr expr)))) ":")))
|
||||
`(string->symbol
|
||||
(string-append (symbol->string ,(cadr expr)) ":"))))))))
|
||||
(else
|
||||
(import (scheme base))
|
||||
(begin
|
||||
(define-syntax let-optionals*
|
||||
(syntax-rules ()
|
||||
((let-optionals* opt-ls () . body)
|
||||
(begin . body))
|
||||
((let-optionals* (op . args) vars . body)
|
||||
(let ((tmp (op . args)))
|
||||
(let-optionals* tmp vars . body)))
|
||||
((let-optionals* tmp ((var default) . rest) . body)
|
||||
(let ((var (if (pair? tmp) (car tmp) default))
|
||||
(tmp2 (if (pair? tmp) (cdr tmp) '())))
|
||||
(let-optionals* tmp2 rest . body)))
|
||||
((let-optionals* tmp tail . body)
|
||||
(let ((tail tmp)) . body))))
|
||||
(define-syntax symbol->keyword*
|
||||
(syntax-rules ()
|
||||
((symbol->keyword* sym)
|
||||
(string->symbol (string-append (symbol->string sym) ":")))
|
||||
)))))
|
||||
(include "optional.scm"))
|
|
@ -0,0 +1,219 @@
|
|||
(define-library (chibi term ansi-test)
|
||||
(export run-tests)
|
||||
(import (scheme base)
|
||||
(scheme write)
|
||||
(chibi term ansi))
|
||||
(begin
|
||||
;; inline (chibi test) to avoid circular dependencies in snow
|
||||
;; installations
|
||||
(define-syntax test
|
||||
(syntax-rules ()
|
||||
((test expect expr)
|
||||
(test 'expr expect expr))
|
||||
((test name expect expr)
|
||||
(guard (exn
|
||||
(else
|
||||
(display "!\nERROR: ")
|
||||
(write name)
|
||||
(newline)
|
||||
(write exn)
|
||||
(newline)))
|
||||
(let* ((res expr)
|
||||
(pass? (equal? expect expr)))
|
||||
(display (if pass? "." "x"))
|
||||
(cond
|
||||
((not pass?)
|
||||
(display "\nFAIL: ")
|
||||
(write name)
|
||||
(newline))))))))
|
||||
(define-syntax test-assert
|
||||
(syntax-rules ()
|
||||
((test-assert expr) (test #t expr))))
|
||||
(define-syntax test-error
|
||||
(syntax-rules ()
|
||||
((test-error expr)
|
||||
(test-assert (guard (exn (else #t)) expr #f)))))
|
||||
(define-syntax test-escape-procedure
|
||||
(syntax-rules ()
|
||||
((test-escape-procedure p s)
|
||||
(begin
|
||||
(test-assert (procedure? p))
|
||||
;;(test-error (p #f))
|
||||
(test s (p))))))
|
||||
(define-syntax test-wrap-procedure
|
||||
(syntax-rules ()
|
||||
((test-wrap-procedure p s)
|
||||
(begin
|
||||
(test-assert (procedure? p))
|
||||
;; (test-error (p))
|
||||
;; (test-error (p #f))
|
||||
;; (test-error (p "" #f))
|
||||
(test (p "FOO")
|
||||
"FOO"
|
||||
(parameterize ((ansi-escapes-enabled? #f)) (p "FOO")))
|
||||
(test (p "FOO")
|
||||
s
|
||||
(parameterize ((ansi-escapes-enabled? #t)) (p "FOO")))))))
|
||||
(define (test-begin name)
|
||||
(display name))
|
||||
(define (test-end)
|
||||
(newline))
|
||||
(define (run-tests)
|
||||
(test-begin "term.ansi")
|
||||
|
||||
(test-assert (procedure? ansi-escapes-enabled?))
|
||||
(test-assert
|
||||
(let ((tag (cons #t #t)))
|
||||
(eqv? tag
|
||||
(parameterize ((ansi-escapes-enabled? tag))
|
||||
(ansi-escapes-enabled?)))))
|
||||
|
||||
(test-escape-procedure black-escape "\x1b;[30m")
|
||||
(test-escape-procedure red-escape "\x1b;[31m")
|
||||
(test-escape-procedure green-escape "\x1b;[32m")
|
||||
(test-escape-procedure yellow-escape "\x1b;[33m")
|
||||
(test-escape-procedure blue-escape "\x1b;[34m")
|
||||
(test-escape-procedure cyan-escape "\x1b;[36m")
|
||||
(test-escape-procedure magenta-escape "\x1b;[35m")
|
||||
(test-escape-procedure white-escape "\x1b;[37m")
|
||||
(test-escape-procedure reset-color-escape "\x1b;[39m")
|
||||
|
||||
(test-assert (procedure? rgb-escape))
|
||||
(test-error (rgb-escape))
|
||||
(test-error (rgb-escape 0))
|
||||
(test-error (rgb-escape 0 0))
|
||||
(test-error (rgb-escape 0 0 0 0))
|
||||
(test-error (rgb-escape 0.0 0 0))
|
||||
(test-error (rgb-escape 0 0.0 0))
|
||||
(test-error (rgb-escape 0 0 0.0))
|
||||
(test-error (rgb-escape -1 0 0))
|
||||
(test-error (rgb-escape 0 -1 0))
|
||||
(test-error (rgb-escape 0 0 -1))
|
||||
(test-error (rgb-escape 6 0 0))
|
||||
(test-error (rgb-escape 0 6 0))
|
||||
(test-error (rgb-escape 0 0 6))
|
||||
(test-escape-procedure (lambda () (rgb-escape 0 0 0)) "\x1B;[38;5;16m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 5 0 0)) "\x1B;[38;5;196m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 0 5 0)) "\x1B;[38;5;46m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 0 0 5)) "\x1B;[38;5;21m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 1 1 1)) "\x1B;[38;5;59m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 2 2 2)) "\x1B;[38;5;102m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 3 3 3)) "\x1B;[38;5;145m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 4 4 4)) "\x1B;[38;5;188m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 5 5 5)) "\x1B;[38;5;231m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 1 3 5)) "\x1B;[38;5;75m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 5 1 3)) "\x1B;[38;5;205m")
|
||||
(test-escape-procedure (lambda () (rgb-escape 3 5 1)) "\x1B;[38;5;155m")
|
||||
|
||||
(test-assert (procedure? gray-escape))
|
||||
(test-error (gray-escape))
|
||||
(test-error (gray-escape 0 0))
|
||||
(test-error (gray-escape 0.0))
|
||||
(test-error (gray-escape -1))
|
||||
(test-error (gray-escape 24))
|
||||
(test-escape-procedure (lambda () (gray-escape 0)) "\x1B;[38;5;232m")
|
||||
(test-escape-procedure (lambda () (gray-escape 23)) "\x1B;[38;5;255m")
|
||||
(test-escape-procedure (lambda () (gray-escape 12)) "\x1B;[38;5;244m")
|
||||
|
||||
(test-wrap-procedure black "\x1b;[30mFOO\x1b;[39m")
|
||||
(test-wrap-procedure red "\x1b;[31mFOO\x1b;[39m")
|
||||
(test-wrap-procedure green "\x1b;[32mFOO\x1b;[39m")
|
||||
(test-wrap-procedure yellow "\x1b;[33mFOO\x1b;[39m")
|
||||
(test-wrap-procedure blue "\x1b;[34mFOO\x1b;[39m")
|
||||
(test-wrap-procedure cyan "\x1b;[36mFOO\x1b;[39m")
|
||||
(test-wrap-procedure magenta "\x1b;[35mFOO\x1b;[39m")
|
||||
(test-wrap-procedure white "\x1b;[37mFOO\x1b;[39m")
|
||||
(test-wrap-procedure (rgb 0 0 0) "\x1B;[38;5;16mFOO\x1b;[39m")
|
||||
(test-wrap-procedure (rgb 5 5 5) "\x1B;[38;5;231mFOO\x1b;[39m")
|
||||
(test-wrap-procedure (gray 0) "\x1B;[38;5;232mFOO\x1b;[39m")
|
||||
(test-wrap-procedure (gray 23) "\x1B;[38;5;255mFOO\x1b;[39m")
|
||||
(test-wrap-procedure (rgb24 #xA6 #x7B #x5B) "\x1B;[38;2;166;123;91mFOO\x1b;[39m")
|
||||
|
||||
(test-escape-procedure black-background-escape "\x1b;[40m")
|
||||
(test-escape-procedure red-background-escape "\x1b;[41m")
|
||||
(test-escape-procedure green-background-escape "\x1b;[42m")
|
||||
(test-escape-procedure yellow-background-escape "\x1b;[43m")
|
||||
(test-escape-procedure blue-background-escape "\x1b;[44m")
|
||||
(test-escape-procedure cyan-background-escape "\x1b;[46m")
|
||||
(test-escape-procedure magenta-background-escape "\x1b;[45m")
|
||||
(test-escape-procedure white-background-escape "\x1b;[47m")
|
||||
(test-escape-procedure reset-background-color-escape "\x1b;[49m")
|
||||
|
||||
(test-assert (procedure? rgb-background-escape))
|
||||
(test-error (rgb-background-escape))
|
||||
(test-error (rgb-background-escape 0))
|
||||
(test-error (rgb-background-escape 0 0))
|
||||
(test-error (rgb-background-escape 0 0 0 0))
|
||||
(test-error (rgb-background-escape 0.0 0 0))
|
||||
(test-error (rgb-background-escape 0 0.0 0))
|
||||
(test-error (rgb-background-escape 0 0 0.0))
|
||||
(test-error (rgb-background-escape -1 0 0))
|
||||
(test-error (rgb-background-escape 0 -1 0))
|
||||
(test-error (rgb-background-escape 0 0 -1))
|
||||
(test-error (rgb-background-escape 6 0 0))
|
||||
(test-error (rgb-background-escape 0 6 0))
|
||||
(test-error (rgb-background-escape 0 0 6))
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 0 0 0)) "\x1B;[48;5;16m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 5 0 0)) "\x1B;[48;5;196m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 0 5 0)) "\x1B;[48;5;46m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 0 0 5)) "\x1B;[48;5;21m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 1 1 1)) "\x1B;[48;5;59m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 2 2 2)) "\x1B;[48;5;102m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 3 3 3)) "\x1B;[48;5;145m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 4 4 4)) "\x1B;[48;5;188m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 5 5 5)) "\x1B;[48;5;231m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 1 3 5)) "\x1B;[48;5;75m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 5 1 3)) "\x1B;[48;5;205m")
|
||||
(test-escape-procedure
|
||||
(lambda () (rgb-background-escape 3 5 1)) "\x1B;[48;5;155m")
|
||||
|
||||
(test-assert (procedure? gray-background-escape))
|
||||
(test-error (gray-background-escape))
|
||||
(test-error (gray-background-escape 0 0))
|
||||
(test-error (gray-background-escape 0.0))
|
||||
(test-error (gray-background-escape -1))
|
||||
(test-error (gray-background-escape 24))
|
||||
(test-escape-procedure
|
||||
(lambda () (gray-background-escape 0)) "\x1B;[48;5;232m")
|
||||
(test-escape-procedure
|
||||
(lambda () (gray-background-escape 23)) "\x1B;[48;5;255m")
|
||||
(test-escape-procedure
|
||||
(lambda () (gray-background-escape 12)) "\x1B;[48;5;244m")
|
||||
|
||||
(test-wrap-procedure black-background "\x1b;[40mFOO\x1b;[49m")
|
||||
(test-wrap-procedure red-background "\x1b;[41mFOO\x1b;[49m")
|
||||
(test-wrap-procedure green-background "\x1b;[42mFOO\x1b;[49m")
|
||||
(test-wrap-procedure yellow-background "\x1b;[43mFOO\x1b;[49m")
|
||||
(test-wrap-procedure blue-background "\x1b;[44mFOO\x1b;[49m")
|
||||
(test-wrap-procedure cyan-background "\x1b;[46mFOO\x1b;[49m")
|
||||
(test-wrap-procedure magenta-background "\x1b;[45mFOO\x1b;[49m")
|
||||
(test-wrap-procedure white-background "\x1b;[47mFOO\x1b;[49m")
|
||||
(test-wrap-procedure (rgb-background 0 0 0) "\x1B;[48;5;16mFOO\x1b;[49m")
|
||||
(test-wrap-procedure (rgb-background 5 5 5) "\x1B;[48;5;231mFOO\x1b;[49m")
|
||||
(test-wrap-procedure (gray-background 0) "\x1B;[48;5;232mFOO\x1b;[49m")
|
||||
(test-wrap-procedure (gray-background 23) "\x1B;[48;5;255mFOO\x1b;[49m")
|
||||
|
||||
(test-escape-procedure bold-escape "\x1b;[1m")
|
||||
(test-escape-procedure reset-bold-escape "\x1b;[22m")
|
||||
(test-wrap-procedure bold "\x1b;[1mFOO\x1b;[22m")
|
||||
|
||||
(test-escape-procedure underline-escape "\x1b;[4m")
|
||||
(test-escape-procedure reset-underline-escape "\x1b;[24m")
|
||||
(test-wrap-procedure underline "\x1b;[4mFOO\x1b;[24m")
|
||||
|
||||
(test-escape-procedure negative-escape "\x1b;[7m")
|
||||
(test-escape-procedure reset-negative-escape "\x1b;[27m")
|
||||
(test-wrap-procedure negative "\x1b;[7mFOO\x1b;[27m")
|
||||
|
||||
(test-end))))
|
|
@ -0,0 +1,206 @@
|
|||
<html><head>
|
||||
<style type="text/css">
|
||||
body {color: #000; background-color: #FFF}
|
||||
div#menu {font-size: smaller; position: absolute; top: 50px; left: 0; width: 190px; height: 100%}
|
||||
div#main {position: absolute; top: 0; left: 200px; width: 540px; height: 100%}
|
||||
div#notes {position: relative; top: 2em; left: 570px; max-width: 200px; height: 0px; font-size: smaller;}
|
||||
div#footer {padding-bottom: 50px}
|
||||
.result { color: #000; background-color: #FFEADF; width: 100%; padding: 3px}
|
||||
.output { color: #000; background-color: beige; width: 100%; padding: 3px}
|
||||
.error { color: #000; background-color: #F0B0B0; width: 100%; padding: 3px}
|
||||
.command { color: #000; background-color: #FFEADF; width: 100%; padding: 5px}
|
||||
.keyword { color: #800080; background-color: inherit; }
|
||||
.type { color: #008000; background-color: inherit; }
|
||||
.function { color: #0000FF; background-color: inherit; }
|
||||
.variable { color: #B8860B; background-color: inherit; }
|
||||
.comment { color: #FF0000; background-color: inherit; }
|
||||
.string { color: #BC8F8F; background-color: inherit; }
|
||||
.attribute { color: #FF5000; background-color: inherit; }
|
||||
.preprocessor { color: #FF00FF; background-color: inherit; }
|
||||
.builtin { color: #FF00FF; background-color: inherit; }
|
||||
.character { color: #0055AA; background-color: inherit; }
|
||||
.syntaxerror { color: #FF0000; background-color: inherit; }
|
||||
.diff-deleted { color: #5F2121; background-color: inherit; }
|
||||
.diff-added { color: #215F21; background-color: inherit; }
|
||||
span.paren1 { color: #AAAAAA; background-color: inherit; }
|
||||
span.paren2 { color: #888888; background-color: inherit; }
|
||||
span.paren3 { color: #666666; background-color: inherit; }
|
||||
span.paren4 { color: #444444; background-color: inherit; }
|
||||
span.paren5 { color: #222222; background-color: inherit; }
|
||||
span.paren6 { color: #000000; background-color: inherit; }
|
||||
</style>
|
||||
</head><body><div id="menu"><ol><li><a href="#h2_Library">Library</a><ol><li><a href="#h3_black-escape">black-escape</a></li><li><a href="#h3_red-escape">red-escape</a></li><li><a href="#h3_green-escape">green-escape</a></li><li><a href="#h3_yellow-escape">yellow-escape</a></li><li><a href="#h3_blue-escape">blue-escape</a></li><li><a href="#h3_magenta-escape">magenta-escape</a></li><li><a href="#h3_cyan-escape">cyan-escape</a></li><li><a href="#h3_white-escape">white-escape</a></li><li><a href="#h3_rgb-escape">rgb-escape</a></li><li><a href="#h3_gray-escape">gray-escape</a></li><li><a href="#h3_rgb24-escape">rgb24-escape</a></li><li><a href="#h3_reset-color-escape">reset-color-escape</a></li><li><a href="#h3_black">black</a></li><li><a href="#h3_red">red</a></li><li><a href="#h3_green">green</a></li><li><a href="#h3_yellow">yellow</a></li><li><a href="#h3_blue">blue</a></li><li><a href="#h3_magenta">magenta</a></li><li><a href="#h3_cyan">cyan</a></li><li><a href="#h3_white">white</a></li><li><a href="#h3_rgb">rgb</a></li><li><a href="#h3_gray">gray</a></li><li><a href="#h3_rgb24">rgb24</a></li><li><a href="#h3_black-background-escape">black-background-escape</a></li><li><a href="#h3_red-background-escape">red-background-escape</a></li><li><a href="#h3_green-background-escape">green-background-escape</a></li><li><a href="#h3_yellow-background-escape">yellow-background-escape</a></li><li><a href="#h3_blue-background-escape">blue-background-escape</a></li><li><a href="#h3_magenta-background-escape">magenta-background-escape</a></li><li><a href="#h3_cyan-background-escape">cyan-background-escape</a></li><li><a href="#h3_white-background-escape">white-background-escape</a></li><li><a href="#h3_rgb-background-escape">rgb-background-escape</a></li><li><a href="#h3_gray-background-escape">gray-background-escape</a></li><li><a href="#h3_rgb24-background-escape">rgb24-background-escape</a></li><li><a href="#h3_(reset-background-color-escape)">reset-background-color-escape</a></li><li><a href="#h3_black-background">black-background</a></li><li><a href="#h3_red-background">red-background</a></li><li><a href="#h3_green-background">green-background</a></li><li><a href="#h3_yellow-background">yellow-background</a></li><li><a href="#h3_blue-background">blue-background</a></li><li><a href="#h3_magenta-background">magenta-background</a></li><li><a href="#h3_cyan-background">cyan-background</a></li><li><a href="#h3_white-background">white-background</a></li><li><a href="#h3_rgb-background">rgb-background</a></li><li><a href="#h3_gray-background">gray-background</a></li><li><a href="#h3_rgb24-background">rgb24-background</a></li><li><a href="#h3_bold-escape">bold-escape</a></li><li><a href="#h3_reset-bold-escape">reset-bold-escape</a></li><li><a href="#h3_bold">bold</a></li><li><a href="#h3_underline-escape">underline-escape</a></li><li><a href="#h3_reset-underline-escape">reset-underline-escape</a></li><li><a href="#h3_underline">underline</a></li><li><a href="#h3_italic-escape">italic-escape</a></li><li><a href="#h3_reset-italic-escape">reset-italic-escape</a></li><li><a href="#h3_italic">italic</a></li><li><a href="#h3_strikethrough-escape">strikethrough-escape</a></li><li><a href="#h3_reset-strikethrough-escape">reset-strikethrough-escape</a></li><li><a href="#h3_strikethrough">strikethrough</a></li><li><a href="#h3_negative-escape">negative-escape</a></li><li><a href="#h3_reset-negative-escape">reset-negative-escape</a></li><li><a href="#h3_negative">negative</a></li><li><a href="#h3_ansi-escapes-enabled?">ansi-escapes-enabled?</a></li></ol></li><li><a href="#h2_Notes">Notes</a></li></ol></div><div id="main"><div><a name="h1_(chibitermansi)"></a><h1>(chibi term ansi)</h1></div><p>A library to use ANSI escape codes to format text and background
|
||||
color, font weigh, and underlining.<div><a name="h2_Library"></a><h2>Library</h2></div><div><a name="h3_black-escape"></a><h3><code>(black-escape)</code></h3></div><div><a name="h3_red-escape"></a><h3><code>(red-escape)</code></h3></div><div><a name="h3_green-escape"></a><h3><code>(green-escape)</code></h3></div><div><a name="h3_yellow-escape"></a><h3><code>(yellow-escape)</code></h3></div><div><a name="h3_blue-escape"></a><h3><code>(blue-escape)</code></h3></div><div><a name="h3_magenta-escape"></a><h3><code>(magenta-escape)</code></h3></div><div><a name="h3_cyan-escape"></a><h3><code>(cyan-escape)</code></h3></div><div><a name="h3_white-escape"></a><h3><code>(white-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select the
|
||||
specified text color.<div><a name="h3_rgb-escape"></a><h3><code>(rgb-escape red-level green-level blue-level)</code></h3></div>Return a string consisting of an ANSI escape code to select the
|
||||
text color specified by the <code>red-level</code>, <code>green-level</code>,
|
||||
and <code>blue-level</code> arguments, each of which must be an exact
|
||||
integer in the range [0, 5].
|
||||
|
||||
The caller is resonsible for verifying that the terminal supports
|
||||
256 colors.<div><a name="h3_gray-escape"></a><h3><code>(gray-escape gray-level)</code></h3></div>Return a string consisting of an ANSI escape code to select the
|
||||
text color specified by the <code>gray-level</code> argument, which must
|
||||
be an exact integer in the range [0, 23].
|
||||
|
||||
The caller is resonsible for verifying that the terminal supports
|
||||
256 colors.<div><a name="h3_rgb24-escape"></a><h3><code>(rgb24-escape red-level green-level blue-level)</code></h3></div>The true-color equivalent of <code><span>rgb-escape</span></code>. Return a string
|
||||
consisting of an ANSI escape code to select the text color
|
||||
specified by the <code>red-level</code>, <code>green-level</code>, and
|
||||
<code>blue-level</code> arguments, each of which must be an exact integer
|
||||
in the range [0, 255].<div><a name="h3_reset-color-escape"></a><h3><code>(reset-color-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select the
|
||||
default text color.<div><a name="h3_black"></a><h3><code>(black str)</code></h3></div><div><a name="h3_red"></a><h3><code>(red str)</code></h3></div><div><a name="h3_green"></a><h3><code>(green str)</code></h3></div><div><a name="h3_yellow"></a><h3><code>(yellow str)</code></h3></div><div><a name="h3_blue"></a><h3><code>(blue str)</code></h3></div><div><a name="h3_magenta"></a><h3><code>(magenta str)</code></h3></div><div><a name="h3_cyan"></a><h3><code>(cyan str)</code></h3></div><div><a name="h3_white"></a><h3><code>(white str)</code></h3></div>If ANSI escapes are enabled, return a string consisting of the
|
||||
string <code>str</code> with a prefix that selects specified text color
|
||||
and a suffix that selects the default text color.
|
||||
|
||||
If ANSI escapes are not enabled, return <code>str</code>.<div><a name="h3_rgb"></a><h3><code>(rgb red-level green-level blue-level)</code></h3></div>Returns a procedure which takes a single argument, a string, and
|
||||
which when called behaves as follows.
|
||||
|
||||
If ANSI escapes are enabled, the procedure returns a string
|
||||
consisting of its argument with a prefix that selects specified
|
||||
text color (obtained by calling the <code><span>rgb-escape</span></code> procedure
|
||||
with the values of the <code>red-level</code>, <code>green-level</code>, and
|
||||
<code>blue-level</code> arguments) and a suffix that selects the default
|
||||
text color.
|
||||
|
||||
If ANSI escapes are not enabled, the procedure returns its argument.
|
||||
|
||||
The caller is resonsible for verifying that the terminal supports
|
||||
256 colors.<div><a name="h3_gray"></a><h3><code>(gray gray-level)</code></h3></div>Returns a procedure which takes a single argument, a string, and
|
||||
which when called behaves as follows.
|
||||
|
||||
If ANSI escapes are enabled, the procedure returns a string
|
||||
consisting of its argument with a prefix that selects specified
|
||||
text color (obtained by calling the <code><span>gray-escape</span></code> procedure
|
||||
with the values of the <code>gray-level</code> argument) and a suffix
|
||||
that selects the default text color.
|
||||
|
||||
If ANSI escapes are not enabled, the procedure returns its argument.
|
||||
|
||||
The caller is resonsible for verifying that the terminal supports
|
||||
256 colors.<div><a name="h3_rgb24"></a><h3><code>(rgb24 red-level green-level blue-level)</code></h3></div>The true-color equivalent of <code><span>rbg</span></code>, extending the ranges
|
||||
to [0, 255].<div><a name="h3_black-background-escape"></a><h3><code>(black-background-escape)</code></h3></div><div><a name="h3_red-background-escape"></a><h3><code>(red-background-escape)</code></h3></div><div><a name="h3_green-background-escape"></a><h3><code>(green-background-escape)</code></h3></div><div><a name="h3_yellow-background-escape"></a><h3><code>(yellow-background-escape)</code></h3></div><div><a name="h3_blue-background-escape"></a><h3><code>(blue-background-escape)</code></h3></div><div><a name="h3_magenta-background-escape"></a><h3><code>(magenta-background-escape)</code></h3></div><div><a name="h3_cyan-background-escape"></a><h3><code>(cyan-background-escape)</code></h3></div><div><a name="h3_white-background-escape"></a><h3><code>(white-background-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select the
|
||||
specified background color.<div><a name="h3_rgb-background-escape"></a><h3><code>(rgb-background-escape red-level green-level blue-level)</code></h3></div>Return a string consisting of an ANSI escape code to select the
|
||||
background color specified by the <code>red-level</code>, <code>green-level</code>,
|
||||
and <code>blue-level</code> arguments, each of which must be an exact
|
||||
integer in the range [0, 5].
|
||||
|
||||
The caller is resonsible for verifying that the terminal supports
|
||||
256 colors.<div><a name="h3_gray-background-escape"></a><h3><code>(gray-background-escape gray-level)</code></h3></div>Return a string consisting of an ANSI escape code to select the
|
||||
background color specified by the <code>gray-level</code> argument, which
|
||||
must be an exact integer in the range [0, 23].
|
||||
|
||||
The caller is resonsible for verifying that the terminal supports
|
||||
256 colors.<div><a name="h3_rgb24-background-escape"></a><h3><code>(rgb24-background-escape red-level green-level blue-level)</code></h3></div>The true-color equivalent of <code><span>rgb-background-escape</span></code>.
|
||||
Return a string consisting of an ANSI escape code to select the
|
||||
text color specified by the <code>red-level</code>, <code>green-level</code>,
|
||||
and <code>blue-level</code> arguments, each of which must be an exact
|
||||
integer in the range [0, 255].<div><a name="h3_(reset-background-color-escape)"></a><h3><code>(reset-background-color-escape)</code></h3></div>
|
||||
|
||||
Return a string consisting of an ANSI escape code to select the
|
||||
default background color.<div><a name="h3_black-background"></a><h3><code>(black-background str)</code></h3></div><div><a name="h3_red-background"></a><h3><code>(red-background str)</code></h3></div><div><a name="h3_green-background"></a><h3><code>(green-background str)</code></h3></div><div><a name="h3_yellow-background"></a><h3><code>(yellow-background str)</code></h3></div><div><a name="h3_blue-background"></a><h3><code>(blue-background str)</code></h3></div><div><a name="h3_magenta-background"></a><h3><code>(magenta-background str)</code></h3></div><div><a name="h3_cyan-background"></a><h3><code>(cyan-background str)</code></h3></div><div><a name="h3_white-background"></a><h3><code>(white-background str)</code></h3></div>If ANSI escapes are enabled, return a string consisting of the
|
||||
string <code>str</code> with a prefix that selects specified background
|
||||
color and a suffix that selects the default background color.
|
||||
|
||||
If ANSI escapes are not enabled, return <code>str</code>.<div><a name="h3_rgb-background"></a><h3><code>(rgb-background red-level green-level blue-level)</code></h3></div>Returns a procedure which takes a single argument, a string, and
|
||||
which when called behaves as follows.
|
||||
|
||||
If ANSI escapes are enabled, the procedure returns a string
|
||||
consisting of its argument with a prefix that selects specified
|
||||
background color (obtained by calling the <code><span>rgb-background-escape</span></code>
|
||||
procedure with the values of the <code>red-level</code>, <code>green-level</code>,
|
||||
and <code>blue-level</code> arguments) and a suffix that selects the
|
||||
default background color.
|
||||
|
||||
If ANSI escapes are not enabled, the procedure returns its argument.
|
||||
|
||||
The caller is resonsible for verifying that the terminal supports
|
||||
256 colors.<div><a name="h3_gray-background"></a><h3><code>(gray-background gray-level)</code></h3></div>Returns a procedure which takes a single argument, a string, and
|
||||
which when called behaves as follows.
|
||||
|
||||
If ANSI escapes are enabled, the procedure returns a string
|
||||
consisting of its argument with a prefix that selects specified
|
||||
background color (obtained by calling the <code><span>gray-background-escape</span></code>
|
||||
procedure with the values of the <code>gray-level</code> argument) and a
|
||||
suffix that selects the default background color.
|
||||
|
||||
If ANSI escapes are not enabled, the procedure returns its argument.
|
||||
|
||||
The caller is resonsible for verifying that the terminal supports
|
||||
256 colors.<div><a name="h3_rgb24-background"></a><h3><code>(rgb24-background red-level green-level blue-level)</code></h3></div>The true-color equivalent of <code><span>rbg-background</span></code>, extending
|
||||
the ranges to [0, 255].<div><a name="h3_bold-escape"></a><h3><code>(bold-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select bold
|
||||
style.<div><a name="h3_reset-bold-escape"></a><h3><code>(reset-bold-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select non-bold
|
||||
style.<div><a name="h3_bold"></a><h3><code>(bold str)</code></h3></div>If ANSI escapes are enabled, return a string consisting of the
|
||||
string <code>str</code> with a prefix that selects bold style and a suffix
|
||||
that selects non-bold style.
|
||||
|
||||
If ANSI escapes are not enabled, return <code>str</code>.<div><a name="h3_underline-escape"></a><h3><code>(underline-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select
|
||||
underlined style.<div><a name="h3_reset-underline-escape"></a><h3><code>(reset-underline-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select
|
||||
non-underlined style.<div><a name="h3_underline"></a><h3><code>(underline str)</code></h3></div>If ANSI escapes are enabled, return a string consisting of the
|
||||
string <code>str</code> with a prefix that selects underlined style and
|
||||
a suffix that selects non-underlined style.
|
||||
|
||||
If ANSI escapes are not enabled, return <code>str</code>.<div><a name="h3_italic-escape"></a><h3><code>(italic-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select
|
||||
italic style.<div><a name="h3_reset-italic-escape"></a><h3><code>(reset-italic-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select
|
||||
non-italic style.<div><a name="h3_italic"></a><h3><code>(italic str)</code></h3></div>Returns <code>str</code> optionally wrapped in italic escapes.<div><a name="h3_strikethrough-escape"></a><h3><code>(strikethrough-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select
|
||||
strikethrough style.<div><a name="h3_reset-strikethrough-escape"></a><h3><code>(reset-strikethrough-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select
|
||||
non-strikethrough style.<div><a name="h3_strikethrough"></a><h3><code>(strikethrough str)</code></h3></div>Returns <code>str</code> optionally wrapped in strikethrough escapes.<div><a name="h3_negative-escape"></a><h3><code>(negative-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select negative
|
||||
style (text in the background color and background in the text
|
||||
color).<div><a name="h3_reset-negative-escape"></a><h3><code>(reset-negative-escape)</code></h3></div>Return a string consisting of an ANSI escape code to select positive
|
||||
style (text in the text color and background in the background
|
||||
color).<div><a name="h3_negative"></a><h3><code>(negative str)</code></h3></div>If ANSI escapes are enabled, return a string consisting of the
|
||||
string <code>str</code> with a prefix that selects negative style (text
|
||||
in the background color and background in the text color) and a
|
||||
suffix that selects positive style (text in the text color and
|
||||
background in the background color).
|
||||
|
||||
If ANSI escapes are not enabled, return <code>str</code>.<div><a name="h3_ansi-escapes-enabled?"></a><h3><code>ansi-escapes-enabled?</code></h3></div>A parameter object that determines whether ANSI escapes are enabled
|
||||
in some of the preceding procedures. They are disabled if
|
||||
<code>(ansi-escapes-enabled?)</code> returns <code>#f</code>, and otherwise
|
||||
they are enabled.
|
||||
|
||||
The initial value returned by <code>(ansi-escapes-enabled?)</code> is
|
||||
determined by the environment.
|
||||
|
||||
If the environment variable <code><span>ANSI_ESCAPES_ENABLED</span></code> is set,
|
||||
its value determines the initial value returned by
|
||||
<code>(ansi-escapes-enabled?)</code>. If the value of
|
||||
<code><span>ANSI_ESCAPES_ENABLED</span></code> is <code><span class="string">"0"</span></code>, the initial value
|
||||
is <code>#f</code>, otherwise the initial value is <code>#t</code>.
|
||||
|
||||
If the environment variable <code><span>ANSI_ESCAPES_ENABLED</span></code> is not
|
||||
set, but the environment variable <code><span>TERM</span></code> is set, the value
|
||||
of the latter determines the initial value returned by
|
||||
<code>(ansi-escapes-enabled?)</code>. If the value of <code><span>TERM</span></code>
|
||||
is <code><span class="string">"xterm"</span></code>, <code><span class="string">"xterm-color"</span></code>, <code><span class="string">"xterm-256color"</span></code>,
|
||||
<code><span class="string">"rxvt"</span></code>, <code><span class="string">"rxvt-unicode-256color"</span></code>, <code><span class="string">"kterm"</span></code>,
|
||||
<code><span class="string">"linux"</span></code>, <code><span class="string">"screen"</span></code>, <code><span class="string">"screen-256color"</span></code>,
|
||||
or <code><span class="string">"vt100"</span></code>, the initial value is <code>#t</code>, otherwise
|
||||
the initial value is <code>#f</code>.
|
||||
|
||||
If neither of the environment variables <code><span>ANSI_ESCAPES_ENABLED</span></code>
|
||||
and <code><span>TERM</span></code> are set, the initial value returned by
|
||||
<code>(ansi-escapes-enabled?)</code> is <code>#f</code>.<div><a name="h2_Notes"></a><h2>Notes</h2></div>
|
||||
|
||||
It is important to remember that the formatting procedures apply
|
||||
a prefix to set a particular graphics parameter and a suffix to
|
||||
reset the parameter to its default value. This can lead to surprises.
|
||||
For example, on an ANSI terminal, one might mistakenly expect the
|
||||
following to display GREEN in green text and then RED in red text:
|
||||
|
||||
<pre><code>(display (red (string-append (green <span class="string">"GREEN"</span>) <span class="string">"RED"</span>)))</code></pre>
|
||||
|
||||
However, it will actually display GREEN in green text and then RED
|
||||
in the default text color. This is a limitation of ANSI control
|
||||
codes; graphics attributes are not saved to and restored from a
|
||||
stack, but instead are simply set. One way to display GREEN in
|
||||
green text and then RED in red text is:
|
||||
|
||||
<pre><code>(display (string-append (green <span class="string">"GREEN"</span>) (red <span class="string">"RED"</span>)))</code></pre>
|
||||
|
||||
On the other hand, text color, background color, font weight (bold
|
||||
or default), underline (on or off), image (positive or negative)
|
||||
are orthogonal. So, for example, on an ANSI terminal the following
|
||||
should display GREEN in green text and then RED in red text, with
|
||||
both in bold and GREEN underlined.
|
||||
|
||||
<pre><code>(display (bold (string-append (underline (green <span class="string">"GREEN"</span>)) (red <span class="string">"RED"</span>))))</code></pre>
|
||||
</p><div id="footer"></div></div></body></html>
|
|
@ -0,0 +1,524 @@
|
|||
;; Copyright (c) 2010-2014 Alex Shinn. All rights reserved. BSD-style
|
||||
;; license: http://synthcode.com/license.txt
|
||||
|
||||
;;> A library to use ANSI escape codes to format text and background
|
||||
;;> color, font weigh, and underlining.
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define (make-simple-escape-procedure parameter)
|
||||
(let ((code (string-append "\x1B;[" (number->string parameter) "m")))
|
||||
(lambda () code)))
|
||||
|
||||
(define (make-wrap-procedure start-escape end-escape)
|
||||
(lambda (str)
|
||||
(if (not (string? str))
|
||||
(error "argument must be a string" str))
|
||||
(if (ansi-escapes-enabled?)
|
||||
(string-append start-escape str end-escape)
|
||||
str)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;; Some definitions are wrapped in begin in order to avoid Scribble
|
||||
;; generating duplicate signatures.
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;> \section{Library}
|
||||
|
||||
(define black-escape
|
||||
(make-simple-escape-procedure 30))
|
||||
(define red-escape
|
||||
(make-simple-escape-procedure 31))
|
||||
(define green-escape
|
||||
(make-simple-escape-procedure 32))
|
||||
(define yellow-escape
|
||||
(make-simple-escape-procedure 33))
|
||||
(define blue-escape
|
||||
(make-simple-escape-procedure 34))
|
||||
(define magenta-escape
|
||||
(make-simple-escape-procedure 35))
|
||||
(define cyan-escape
|
||||
(make-simple-escape-procedure 36))
|
||||
(define white-escape
|
||||
(make-simple-escape-procedure 37))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select the
|
||||
;;> specified text color.
|
||||
;;/
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select the
|
||||
;;> text color specified by the \var{red-level}, \var{green-level},
|
||||
;;> and \var{blue-level} arguments, each of which must be an exact
|
||||
;;> integer in the range [0, 5].
|
||||
;;>
|
||||
;;> The caller is resonsible for verifying that the terminal supports
|
||||
;;> 256 colors.
|
||||
|
||||
(define (rgb-escape red-level green-level blue-level)
|
||||
(when (not (and (exact-integer? red-level) (<= 0 red-level 5)))
|
||||
(error "invalid red-level value" red-level))
|
||||
(when (not (and (exact-integer? green-level) (<= 0 green-level 5)))
|
||||
(error "invalid green-level value" green-level))
|
||||
(when (not (and (exact-integer? blue-level) (<= 0 blue-level 5)))
|
||||
(error "invalid blue-level value" blue-level))
|
||||
(string-append
|
||||
"\x1B;[38;5;"
|
||||
(number->string (+ (* 36 red-level) (* 6 green-level) blue-level 16))
|
||||
"m"))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select the
|
||||
;;> text color specified by the \var{gray-level} argument, which must
|
||||
;;> be an exact integer in the range [0, 23].
|
||||
;;>
|
||||
;;> The caller is resonsible for verifying that the terminal supports
|
||||
;;> 256 colors.
|
||||
|
||||
(define (gray-escape gray-level)
|
||||
(when (not (and (exact-integer? gray-level) (<= 0 gray-level 23)))
|
||||
(error "invalid gray-level value" gray-level))
|
||||
(string-append "\x1B;[38;5;"
|
||||
(number->string (+ gray-level 232))
|
||||
"m"))
|
||||
|
||||
;;> The true-color equivalent of \scheme{rgb-escape}. Return a string
|
||||
;;> consisting of an ANSI escape code to select the text color
|
||||
;;> specified by the \var{red-level}, \var{green-level}, and
|
||||
;;> \var{blue-level} arguments, each of which must be an exact integer
|
||||
;;> in the range [0, 255].
|
||||
|
||||
(define (rgb24-escape red-level green-level blue-level)
|
||||
(when (not (and (exact-integer? red-level) (<= 0 red-level 255)))
|
||||
(error "invalid red-level value" red-level))
|
||||
(when (not (and (exact-integer? green-level) (<= 0 green-level 255)))
|
||||
(error "invalid green-level value" green-level))
|
||||
(when (not (and (exact-integer? blue-level) (<= 0 blue-level 255)))
|
||||
(error "invalid blue-level value" blue-level))
|
||||
(string-append
|
||||
"\x1B;[38;2;"
|
||||
(number->string red-level) ";"
|
||||
(number->string green-level) ";"
|
||||
(number->string blue-level)
|
||||
"m"))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select the
|
||||
;;> default text color.
|
||||
|
||||
(define reset-color-escape
|
||||
(make-simple-escape-procedure 39))
|
||||
|
||||
(define black
|
||||
(make-wrap-procedure (black-escape)
|
||||
(reset-color-escape)))
|
||||
(define red
|
||||
(make-wrap-procedure (red-escape)
|
||||
(reset-color-escape)))
|
||||
(define green
|
||||
(make-wrap-procedure (green-escape)
|
||||
(reset-color-escape)))
|
||||
(define yellow
|
||||
(make-wrap-procedure (yellow-escape)
|
||||
(reset-color-escape)))
|
||||
(define blue
|
||||
(make-wrap-procedure (blue-escape)
|
||||
(reset-color-escape)))
|
||||
(define magenta
|
||||
(make-wrap-procedure (magenta-escape)
|
||||
(reset-color-escape)))
|
||||
(define cyan
|
||||
(make-wrap-procedure (cyan-escape)
|
||||
(reset-color-escape)))
|
||||
(define white
|
||||
(make-wrap-procedure (white-escape)
|
||||
(reset-color-escape)))
|
||||
|
||||
;;> If ANSI escapes are enabled, return a string consisting of the
|
||||
;;> string \var{str} with a prefix that selects specified text color
|
||||
;;> and a suffix that selects the default text color.
|
||||
;;>
|
||||
;;> If ANSI escapes are not enabled, return \var{str}.
|
||||
;;/
|
||||
|
||||
;;> Returns a procedure which takes a single argument, a string, and
|
||||
;;> which when called behaves as follows.
|
||||
;;>
|
||||
;;> If ANSI escapes are enabled, the procedure returns a string
|
||||
;;> consisting of its argument with a prefix that selects specified
|
||||
;;> text color (obtained by calling the \scheme{rgb-escape} procedure
|
||||
;;> with the values of the \var{red-level}, \var{green-level}, and
|
||||
;;> \var{blue-level} arguments) and a suffix that selects the default
|
||||
;;> text color.
|
||||
;;>
|
||||
;;> If ANSI escapes are not enabled, the procedure returns its argument.
|
||||
;;>
|
||||
;;> The caller is resonsible for verifying that the terminal supports
|
||||
;;> 256 colors.
|
||||
|
||||
(define (rgb red-level green-level blue-level)
|
||||
(make-wrap-procedure (rgb-escape red-level green-level blue-level)
|
||||
(reset-color-escape)))
|
||||
|
||||
;;> Returns a procedure which takes a single argument, a string, and
|
||||
;;> which when called behaves as follows.
|
||||
;;>
|
||||
;;> If ANSI escapes are enabled, the procedure returns a string
|
||||
;;> consisting of its argument with a prefix that selects specified
|
||||
;;> text color (obtained by calling the \scheme{gray-escape} procedure
|
||||
;;> with the values of the \var{gray-level} argument) and a suffix
|
||||
;;> that selects the default text color.
|
||||
;;>
|
||||
;;> If ANSI escapes are not enabled, the procedure returns its argument.
|
||||
;;>
|
||||
;;> The caller is resonsible for verifying that the terminal supports
|
||||
;;> 256 colors.
|
||||
|
||||
(define (gray gray-level)
|
||||
(make-wrap-procedure (gray-escape gray-level)
|
||||
(reset-color-escape)))
|
||||
|
||||
;;> The true-color equivalent of \scheme{rbg}, extending the ranges
|
||||
;;> to [0, 255].
|
||||
|
||||
(define (rgb24 red-level green-level blue-level)
|
||||
(make-wrap-procedure (rgb24-escape red-level green-level blue-level)
|
||||
(reset-color-escape)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define black-background-escape
|
||||
(make-simple-escape-procedure 40))
|
||||
(define red-background-escape
|
||||
(make-simple-escape-procedure 41))
|
||||
(define green-background-escape
|
||||
(make-simple-escape-procedure 42))
|
||||
(define yellow-background-escape
|
||||
(make-simple-escape-procedure 43))
|
||||
(define blue-background-escape
|
||||
(make-simple-escape-procedure 44))
|
||||
(define magenta-background-escape
|
||||
(make-simple-escape-procedure 45))
|
||||
(define cyan-background-escape
|
||||
(make-simple-escape-procedure 46))
|
||||
(define white-background-escape
|
||||
(make-simple-escape-procedure 47))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select the
|
||||
;;> specified background color.
|
||||
;;/
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select the
|
||||
;;> background color specified by the \var{red-level}, \var{green-level},
|
||||
;;> and \var{blue-level} arguments, each of which must be an exact
|
||||
;;> integer in the range [0, 5].
|
||||
;;>
|
||||
;;> The caller is resonsible for verifying that the terminal supports
|
||||
;;> 256 colors.
|
||||
|
||||
(define (rgb-background-escape red-level green-level blue-level)
|
||||
(when (not (and (exact-integer? red-level) (<= 0 red-level 5)))
|
||||
(error "invalid red-level value" red-level))
|
||||
(when (not (and (exact-integer? green-level) (<= 0 green-level 5)))
|
||||
(error "invalid green-level value" green-level))
|
||||
(when (not (and (exact-integer? blue-level) (<= 0 blue-level 5)))
|
||||
(error "invalid blue-level value" blue-level))
|
||||
(string-append
|
||||
"\x1B;[48;5;"
|
||||
(number->string (+ (* 36 red-level) (* 6 green-level) blue-level 16))
|
||||
"m"))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select the
|
||||
;;> background color specified by the \var{gray-level} argument, which
|
||||
;;> must be an exact integer in the range [0, 23].
|
||||
;;>
|
||||
;;> The caller is resonsible for verifying that the terminal supports
|
||||
;;> 256 colors.
|
||||
|
||||
(define (gray-background-escape gray-level)
|
||||
(when (not (and (exact-integer? gray-level) (<= 0 gray-level 23)))
|
||||
(error "invalid gray-level value" gray-level))
|
||||
(string-append "\x1B;[48;5;"
|
||||
(number->string (+ gray-level 232))
|
||||
"m"))
|
||||
|
||||
;;> The true-color equivalent of \scheme{rgb-background-escape}.
|
||||
;;> Return a string consisting of an ANSI escape code to select the
|
||||
;;> text color specified by the \var{red-level}, \var{green-level},
|
||||
;;> and \var{blue-level} arguments, each of which must be an exact
|
||||
;;> integer in the range [0, 255].
|
||||
|
||||
(define (rgb24-background-escape red-level green-level blue-level)
|
||||
(when (not (and (exact-integer? red-level) (<= 0 red-level 255)))
|
||||
(error "invalid red-level value" red-level))
|
||||
(when (not (and (exact-integer? green-level) (<= 0 green-level 255)))
|
||||
(error "invalid green-level value" green-level))
|
||||
(when (not (and (exact-integer? blue-level) (<= 0 blue-level 255)))
|
||||
(error "invalid blue-level value" blue-level))
|
||||
(string-append
|
||||
"\x1B;[48;5;"
|
||||
(number->string red-level) ";"
|
||||
(number->string green-level) ";"
|
||||
(number->string blue-level)
|
||||
"m"))
|
||||
|
||||
;;> \procedure{(reset-background-color-escape)}
|
||||
;;>
|
||||
;;> Return a string consisting of an ANSI escape code to select the
|
||||
;;> default background color.
|
||||
|
||||
(define reset-background-color-escape
|
||||
(make-simple-escape-procedure 49))
|
||||
|
||||
(define black-background
|
||||
(make-wrap-procedure (black-background-escape)
|
||||
(reset-background-color-escape)))
|
||||
(define red-background
|
||||
(make-wrap-procedure (red-background-escape)
|
||||
(reset-background-color-escape)))
|
||||
(define green-background
|
||||
(make-wrap-procedure (green-background-escape)
|
||||
(reset-background-color-escape)))
|
||||
(define yellow-background
|
||||
(make-wrap-procedure (yellow-background-escape)
|
||||
(reset-background-color-escape)))
|
||||
(define blue-background
|
||||
(make-wrap-procedure (blue-background-escape)
|
||||
(reset-background-color-escape)))
|
||||
(define magenta-background
|
||||
(make-wrap-procedure (magenta-background-escape)
|
||||
(reset-background-color-escape)))
|
||||
(define cyan-background
|
||||
(make-wrap-procedure (cyan-background-escape)
|
||||
(reset-background-color-escape)))
|
||||
(define white-background
|
||||
(make-wrap-procedure (white-background-escape)
|
||||
(reset-background-color-escape)))
|
||||
|
||||
;;> If ANSI escapes are enabled, return a string consisting of the
|
||||
;;> string \var{str} with a prefix that selects specified background
|
||||
;;> color and a suffix that selects the default background color.
|
||||
;;>
|
||||
;;> If ANSI escapes are not enabled, return \var{str}.
|
||||
;;/
|
||||
|
||||
;;> Returns a procedure which takes a single argument, a string, and
|
||||
;;> which when called behaves as follows.
|
||||
;;>
|
||||
;;> If ANSI escapes are enabled, the procedure returns a string
|
||||
;;> consisting of its argument with a prefix that selects specified
|
||||
;;> background color (obtained by calling the \scheme{rgb-background-escape}
|
||||
;;> procedure with the values of the \var{red-level}, \var{green-level},
|
||||
;;> and \var{blue-level} arguments) and a suffix that selects the
|
||||
;;> default background color.
|
||||
;;>
|
||||
;;> If ANSI escapes are not enabled, the procedure returns its argument.
|
||||
;;>
|
||||
;;> The caller is resonsible for verifying that the terminal supports
|
||||
;;> 256 colors.
|
||||
|
||||
(define (rgb-background red-level green-level blue-level)
|
||||
(make-wrap-procedure (rgb-background-escape red-level green-level blue-level)
|
||||
(reset-background-color-escape)))
|
||||
|
||||
;;> Returns a procedure which takes a single argument, a string, and
|
||||
;;> which when called behaves as follows.
|
||||
;;>
|
||||
;;> If ANSI escapes are enabled, the procedure returns a string
|
||||
;;> consisting of its argument with a prefix that selects specified
|
||||
;;> background color (obtained by calling the \scheme{gray-background-escape}
|
||||
;;> procedure with the values of the \var{gray-level} argument) and a
|
||||
;;> suffix that selects the default background color.
|
||||
;;>
|
||||
;;> If ANSI escapes are not enabled, the procedure returns its argument.
|
||||
;;>
|
||||
;;> The caller is resonsible for verifying that the terminal supports
|
||||
;;> 256 colors.
|
||||
|
||||
(define (gray-background gray-level)
|
||||
(make-wrap-procedure (gray-background-escape gray-level)
|
||||
(reset-background-color-escape)))
|
||||
|
||||
;;> The true-color equivalent of \scheme{rbg-background}, extending
|
||||
;;> the ranges to [0, 255].
|
||||
|
||||
(define (rgb24-background red-level green-level blue-level)
|
||||
(make-wrap-procedure
|
||||
(rgb24-background-escape red-level green-level blue-level)
|
||||
(reset-background-color-escape)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select bold
|
||||
;;> style.
|
||||
|
||||
(define bold-escape
|
||||
(make-simple-escape-procedure 1))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select non-bold
|
||||
;;> style.
|
||||
|
||||
(define reset-bold-escape
|
||||
(make-simple-escape-procedure 22))
|
||||
|
||||
;;> If ANSI escapes are enabled, return a string consisting of the
|
||||
;;> string \var{str} with a prefix that selects bold style and a suffix
|
||||
;;> that selects non-bold style.
|
||||
;;>
|
||||
;;> If ANSI escapes are not enabled, return \var{str}.
|
||||
|
||||
(define bold (make-wrap-procedure (bold-escape)
|
||||
(reset-bold-escape)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select
|
||||
;;> underlined style.
|
||||
|
||||
(define underline-escape
|
||||
(make-simple-escape-procedure 4))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select
|
||||
;;> non-underlined style.
|
||||
|
||||
(define reset-underline-escape
|
||||
(make-simple-escape-procedure 24))
|
||||
|
||||
;;> If ANSI escapes are enabled, return a string consisting of the
|
||||
;;> string \var{str} with a prefix that selects underlined style and
|
||||
;;> a suffix that selects non-underlined style.
|
||||
;;>
|
||||
;;> If ANSI escapes are not enabled, return \var{str}.
|
||||
|
||||
(define underline
|
||||
(make-wrap-procedure (underline-escape) (reset-underline-escape)))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select
|
||||
;;> italic style.
|
||||
|
||||
(define italic-escape
|
||||
(make-simple-escape-procedure 3))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select
|
||||
;;> non-italic style.
|
||||
|
||||
(define reset-italic-escape
|
||||
(make-simple-escape-procedure 23))
|
||||
|
||||
;;> Returns \var{str} optionally wrapped in italic escapes.
|
||||
|
||||
(define italic
|
||||
(make-wrap-procedure (italic-escape) (reset-italic-escape)))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select
|
||||
;;> strikethrough style.
|
||||
|
||||
(define strikethrough-escape
|
||||
(make-simple-escape-procedure 9))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select
|
||||
;;> non-strikethrough style.
|
||||
|
||||
(define reset-strikethrough-escape
|
||||
(make-simple-escape-procedure 29))
|
||||
|
||||
;;> Returns \var{str} optionally wrapped in strikethrough escapes.
|
||||
|
||||
(define strikethrough
|
||||
(make-wrap-procedure (strikethrough-escape) (reset-strikethrough-escape)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select negative
|
||||
;;> style (text in the background color and background in the text
|
||||
;;> color).
|
||||
|
||||
(define negative-escape
|
||||
(make-simple-escape-procedure 7))
|
||||
|
||||
;;> Return a string consisting of an ANSI escape code to select positive
|
||||
;;> style (text in the text color and background in the background
|
||||
;;> color).
|
||||
|
||||
(define reset-negative-escape
|
||||
(make-simple-escape-procedure 27))
|
||||
|
||||
;;> If ANSI escapes are enabled, return a string consisting of the
|
||||
;;> string \var{str} with a prefix that selects negative style (text
|
||||
;;> in the background color and background in the text color) and a
|
||||
;;> suffix that selects positive style (text in the text color and
|
||||
;;> background in the background color).
|
||||
;;>
|
||||
;;> If ANSI escapes are not enabled, return \var{str}.
|
||||
|
||||
(define negative (make-wrap-procedure (negative-escape)
|
||||
(reset-negative-escape)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;> A parameter object that determines whether ANSI escapes are enabled
|
||||
;;> in some of the preceding procedures. They are disabled if
|
||||
;;> \scheme{(ansi-escapes-enabled?)} returns \scheme{#f}, and otherwise
|
||||
;;> they are enabled.
|
||||
;;>
|
||||
;;> The initial value returned by \scheme{(ansi-escapes-enabled?)} is
|
||||
;;> determined by the environment.
|
||||
;;>
|
||||
;;> If the environment variable \scheme{ANSI_ESCAPES_ENABLED} is set,
|
||||
;;> its value determines the initial value returned by
|
||||
;;> \scheme{(ansi-escapes-enabled?)}. If the value of
|
||||
;;> \scheme{ANSI_ESCAPES_ENABLED} is \scheme{"0"}, the initial value
|
||||
;;> is \scheme{#f}, otherwise the initial value is \scheme{#t}.
|
||||
;;>
|
||||
;;> If the environment variable \scheme{ANSI_ESCAPES_ENABLED} is not
|
||||
;;> set, but the environment variable \scheme{TERM} is set, the value
|
||||
;;> of the latter determines the initial value returned by
|
||||
;;> \scheme{(ansi-escapes-enabled?)}. If the value of \scheme{TERM}
|
||||
;;> is \scheme{"xterm"}, \scheme{"xterm-color"}, \scheme{"xterm-256color"},
|
||||
;;> \scheme{"rxvt"}, \scheme{"rxvt-unicode-256color"}, \scheme{"kterm"},
|
||||
;;> \scheme{"linux"}, \scheme{"screen"}, \scheme{"screen-256color"},
|
||||
;;> or \scheme{"vt100"}, the initial value is \scheme{#t}, otherwise
|
||||
;;> the initial value is \scheme{#f}.
|
||||
;;>
|
||||
;;> If neither of the environment variables \scheme{ANSI_ESCAPES_ENABLED}
|
||||
;;> and \scheme{TERM} are set, the initial value returned by
|
||||
;;> \scheme{(ansi-escapes-enabled?)} is \scheme{#f}.
|
||||
|
||||
(define ansi-escapes-enabled?
|
||||
(make-parameter
|
||||
(cond
|
||||
((get-environment-variable "ANSI_ESCAPES_ENABLED")
|
||||
=> (lambda (s) (not (equal? s "0"))))
|
||||
(else
|
||||
(member (get-environment-variable "TERM")
|
||||
'("xterm" "xterm-color" "xterm-256color" "rxvt" "kterm"
|
||||
"linux" "screen" "screen-256color" "vt100"
|
||||
"tmux-256color" "rxvt-unicode-256color"))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
;;> \section{Notes}
|
||||
;;>
|
||||
;;> It is important to remember that the formatting procedures apply
|
||||
;;> a prefix to set a particular graphics parameter and a suffix to
|
||||
;;> reset the parameter to its default value. This can lead to surprises.
|
||||
;;> For example, on an ANSI terminal, one might mistakenly expect the
|
||||
;;> following to display GREEN in green text and then RED in red text:
|
||||
;;>
|
||||
;;> \codeblock{(display (red (string-append (green "GREEN") "RED")))}
|
||||
;;>
|
||||
;;> However, it will actually display GREEN in green text and then RED
|
||||
;;> in the default text color. This is a limitation of ANSI control
|
||||
;;> codes; graphics attributes are not saved to and restored from a
|
||||
;;> stack, but instead are simply set. One way to display GREEN in
|
||||
;;> green text and then RED in red text is:
|
||||
;;>
|
||||
;;> \codeblock{(display (string-append (green "GREEN") (red "RED")))}
|
||||
;;>
|
||||
;;> On the other hand, text color, background color, font weight (bold
|
||||
;;> or default), underline (on or off), image (positive or negative)
|
||||
;;> are orthogonal. So, for example, on an ANSI terminal the following
|
||||
;;> should display GREEN in green text and then RED in red text, with
|
||||
;;> both in bold and GREEN underlined.
|
||||
;;>
|
||||
;;> \codeblock{(display (bold (string-append (underline (green "GREEN")) (red "RED"))))}
|
||||
;;>
|
|
@ -0,0 +1,42 @@
|
|||
(define-library (chibi term ansi)
|
||||
(export
|
||||
|
||||
black-escape red-escape yellow-escape green-escape
|
||||
blue-escape cyan-escape magenta-escape white-escape
|
||||
rgb-escape
|
||||
gray-escape
|
||||
rgb24-escape
|
||||
reset-color-escape
|
||||
|
||||
black-background-escape red-background-escape
|
||||
yellow-background-escape green-background-escape
|
||||
blue-background-escape cyan-background-escape
|
||||
magenta-background-escape white-background-escape
|
||||
rgb-background-escape
|
||||
gray-background-escape
|
||||
rgb24-background-escape
|
||||
reset-background-color-escape
|
||||
|
||||
black red yellow green
|
||||
blue cyan magenta white
|
||||
black-background red-background yellow-background green-background
|
||||
blue-background cyan-background magenta-background white-background
|
||||
bold
|
||||
underline
|
||||
negative
|
||||
italic
|
||||
strikethrough
|
||||
rgb rgb-background
|
||||
gray gray-background
|
||||
rgb24 rgb24-background
|
||||
bold-escape reset-bold-escape
|
||||
underline-escape reset-underline-escape
|
||||
negative-escape reset-negative-escape
|
||||
italic-escape reset-italic-escape
|
||||
strikethrough-escape reset-strikethrough-escape
|
||||
|
||||
ansi-escapes-enabled?)
|
||||
(import (scheme base)
|
||||
(scheme write)
|
||||
(scheme process-context))
|
||||
(include "ansi.scm"))
|
|
@ -0,0 +1,131 @@
|
|||
<html><head>
|
||||
<style type="text/css">
|
||||
body {color: #000; background-color: #FFF}
|
||||
div#menu {font-size: smaller; position: absolute; top: 50px; left: 0; width: 190px; height: 100%}
|
||||
div#main {position: absolute; top: 0; left: 200px; width: 540px; height: 100%}
|
||||
div#notes {position: relative; top: 2em; left: 570px; max-width: 200px; height: 0px; font-size: smaller;}
|
||||
div#footer {padding-bottom: 50px}
|
||||
.result { color: #000; background-color: #FFEADF; width: 100%; padding: 3px}
|
||||
.output { color: #000; background-color: beige; width: 100%; padding: 3px}
|
||||
.error { color: #000; background-color: #F0B0B0; width: 100%; padding: 3px}
|
||||
.command { color: #000; background-color: #FFEADF; width: 100%; padding: 5px}
|
||||
.keyword { color: #800080; background-color: inherit; }
|
||||
.type { color: #008000; background-color: inherit; }
|
||||
.function { color: #0000FF; background-color: inherit; }
|
||||
.variable { color: #B8860B; background-color: inherit; }
|
||||
.comment { color: #FF0000; background-color: inherit; }
|
||||
.string { color: #BC8F8F; background-color: inherit; }
|
||||
.attribute { color: #FF5000; background-color: inherit; }
|
||||
.preprocessor { color: #FF00FF; background-color: inherit; }
|
||||
.builtin { color: #FF00FF; background-color: inherit; }
|
||||
.character { color: #0055AA; background-color: inherit; }
|
||||
.syntaxerror { color: #FF0000; background-color: inherit; }
|
||||
.diff-deleted { color: #5F2121; background-color: inherit; }
|
||||
.diff-added { color: #215F21; background-color: inherit; }
|
||||
span.paren1 { color: #AAAAAA; background-color: inherit; }
|
||||
span.paren2 { color: #888888; background-color: inherit; }
|
||||
span.paren3 { color: #666666; background-color: inherit; }
|
||||
span.paren4 { color: #444444; background-color: inherit; }
|
||||
span.paren5 { color: #222222; background-color: inherit; }
|
||||
span.paren6 { color: #000000; background-color: inherit; }
|
||||
</style>
|
||||
</head><body><div id="menu"><ol><li><a href="#h2_Testing">Testing</a><ol><li><a href="#h3_(test[name]expectexpr)">test</a></li><li><a href="#h3_(test-equalequal[name]expectexpr)">test-equal</a></li><li><a href="#h3_(test-assert[name]expr)">test-assert</a></li><li><a href="#h3_(test-not[name]expr)">test-not</a></li><li><a href="#h3_(test-values[name]expectexpr)">test-values</a></li><li><a href="#h3_(test-error[name]expr)">test-error</a></li><li><a href="#h3_test-propagate-info">test-propagate-info</a></li><li><a href="#h3_test-run">test-run</a></li><li><a href="#h3_test-equal?">test-equal?</a></li></ol></li><li><a href="#h2_TestGroups">Test Groups</a><ol><li><a href="#h3_test-group">test-group</a></li><li><a href="#h3_test-begin">test-begin</a></li><li><a href="#h3_test-end">test-end</a></li><li><a href="#h3_test-exit">test-exit</a></li><li><a href="#h3_test-syntax-error">test-syntax-error</a></li></ol></li><li><a href="#h2_Accessors">Accessors</a><ol><li><a href="#h3_test-group-name">test-group-name</a></li><li><a href="#h3_test-group-ref">test-group-ref</a></li><li><a href="#h3_test-group-set!">test-group-set!</a></li><li><a href="#h3_test-group-inc!">test-group-inc!</a></li><li><a href="#h3_test-group-push!">test-group-push!</a></li><li><a href="#h3_test-get-name!">test-get-name!</a></li></ol></li><li><a href="#h2_Parameters">Parameters</a><ol><li><a href="#h3_current-test-group">current-test-group</a></li><li><a href="#h3_current-test-verbosity">current-test-verbosity</a></li><li><a href="#h3_current-test-epsilon">current-test-epsilon</a></li><li><a href="#h3_current-test-comparator">current-test-comparator</a></li><li><a href="#h3_current-test-applier">current-test-applier</a></li><li><a href="#h3_current-test-skipper">current-test-skipper</a></li><li><a href="#h3_current-test-reporter">current-test-reporter</a></li><li><a href="#h3_current-test-group-reporter">current-test-group-reporter</a></li><li><a href="#h3_test-failure-count">test-failure-count</a></li><li><a href="#h3_current-test-group-filters">current-test-group-filters</a></li><li><a href="#h3_current-test-group-removers">current-test-group-removers</a></li><li><a href="#h3_current-test-filters">current-test-filters</a></li><li><a href="#h3_current-test-removers">current-test-removers</a></li><li><a href="#h3_current-column-width">current-column-width</a></li></ol></li></ol></div><div id="main"><div><a name="h1_(chibitest)"></a><h1>(chibi test)</h1></div><p>Simple but extensible testing framework with advanced reporting.<div><a name="h2_Testing"></a><h2>Testing</h2></div>
|
||||
<div><a name="h3_(test[name]expectexpr)"></a><h3><code>(test [name] expect expr)</code></h3></div>
|
||||
The primary interface to testing. Evaluate <code>expr</code> and check
|
||||
that it is equal to <code>expect</code>, and report the result, using
|
||||
<code>name</code> or a printed summary of <code>expr</code>.
|
||||
|
||||
If used inside a group this will contribute to the overall group
|
||||
reporting, but can be used standalone:
|
||||
|
||||
<div><pre><code>(test <span>4</span> (+ <span>2</span> <span>2</span>))</code></pre><div class="output"><pre>(+ 2 2) .............................................................. [ PASS]
|
||||
</pre></div><div class="result"><code>=> PASS</code></div></div>
|
||||
<div><pre><code>(test <span class="string">"add two and two"</span> <span>4</span> (+ <span>2</span> <span>2</span>))</code></pre><div class="output"><pre>add two and two ...................................................... [ PASS]
|
||||
</pre></div><div class="result"><code>=> PASS</code></div></div>
|
||||
<div><pre><code>(test <span>3</span> (+ <span>2</span> <span>2</span>))</code></pre><div class="output"><pre><span>(+ 2 2) .............................................................. [ <span style="color:red">FAIL</span>]
|
||||
expected <span style="color:red">3</span> but got <span style="color:green">4</span>
|
||||
</span></pre></div><div class="result"><code>=> FAIL</code></div></div>
|
||||
<div><pre><code>(test <span>4</span> (+ <span>2</span> <span class="string">"2"</span>))</code></pre><div class="output"><pre><span>(+ 2 "2") ............................................................ [<u><span style="color:red">ERROR</span></u>]
|
||||
ERROR: invalid type, expected Number: "2"
|
||||
</span></pre></div><div class="result"><code>=> ERROR</code></div></div>
|
||||
|
||||
The equality comparison is made with
|
||||
<code><span>current-test-comparator</span></code>, defaulting to
|
||||
<code><span>test-equal?</span></code>, which is the same as <code><span>equal?</span></code> but
|
||||
more permissive on floating point comparisons). Returns the
|
||||
status of the test (one of the symbols <code>'<span>PASS</span></code>,
|
||||
<code>'<span>FAIL</span></code>, <code>'<span>SKIP</span></code>, <code>'<span>ERROR</span></code>).<div><a name="h3_(test-equalequal[name]expectexpr)"></a><h3><code>(test-equal equal [name] expect expr)</code></h3></div>
|
||||
Equivalent to test, using <code>equal</code> for comparison instead of
|
||||
<code><span>equal?</span></code>.<div><a name="h3_(test-assert[name]expr)"></a><h3><code>(test-assert [name] expr)</code></h3></div>
|
||||
Like <code><span>test</span></code> but evaluates <code>expr</code> and checks that it's true.<div><a name="h3_(test-not[name]expr)"></a><h3><code>(test-not [name] expr)</code></h3></div>
|
||||
Like <code><span>test</span></code> but evaluates <code>expr</code> and checks that it's false.<div><a name="h3_(test-values[name]expectexpr)"></a><h3><code>(test-values [name] expect expr)</code></h3></div>
|
||||
Like <code><span>test</span></code> but <code>expect</code> and <code>expr</code> can both
|
||||
return multiple values.<div><a name="h3_(test-error[name]expr)"></a><h3><code>(test-error [name] expr)</code></h3></div>
|
||||
Like <code><span>test</span></code> but evaluates <code>expr</code> and checks that it
|
||||
raises an error.<div><a name="h3_test-propagate-info"></a><h3><code>(test-propagate-info name expect expr info)</code></h3></div>Low-level macro to pass alist info to the underlying <code>test-run</code>.<div><a name="h3_test-run"></a><h3><code>(test-run expect expr info)</code></h3></div>The procedural interface to testing. <code>expect</code> and <code>expr</code>
|
||||
should be thunks, and <code>info</code> is an alist of properties used in
|
||||
test reporting.<div><a name="h3_test-equal?"></a><h3><code>(test-equal? expect res)</code></h3></div>Returns true if either <code>(equal? <span>expect</span> <span>res</span>)</code>, or
|
||||
<code>expect</code> is inexact and <code>res</code> is within
|
||||
<code><span>current-test-epsilon</span></code> of <code>expect</code>.<div><a name="h2_TestGroups"></a><h2>Test Groups</h2></div><div><a name="h3_test-group"></a><h3><code>(test-group name-expr body ...)</code></h3></div>
|
||||
Tests can be collected in groups for
|
||||
Wraps <code>body</code> as a single test group, which can be filtered
|
||||
and summarized separately.
|
||||
<div><pre><code>(test-group <span class="string">"pi"</span>
|
||||
(test <span>3.14159</span> (acos <span>-1</span>))
|
||||
(test <span>3</span> (acos <span>-1</span>))
|
||||
(test <span>3.14159</span> (acos <span class="string">"-1"</span>)))
|
||||
</code></pre><div class="output"><pre><span><b>pi: </b>.<span style="color:red">x</span><u><span style="color:red">!</span></u>
|
||||
1 out of 3 (33.3%) test passed in 0.00030422210693359375 seconds.
|
||||
<span style="color:red">1 failure (33.3%).
|
||||
</span><u><span style="color:red">1 error (33.3%).
|
||||
</span></u><span style="color:red">FAIL: </span>(acos -1)
|
||||
expected 3 but got 3<span style="color:green">.141592653589793</span>
|
||||
<span style="color:red">ERROR: </span>(acos "-1")
|
||||
ERROR in "acos": invalid type, expected Number: "-1"
|
||||
</span></pre></div></div><div><a name="h3_test-begin"></a><h3><code>(test-begin [name])</code></h3></div>Begin testing a new group until the closing <code>(test-end)</code>.<div><a name="h3_test-end"></a><h3><code>(test-end [name])</code></h3></div>Ends testing group introduced with <code>(test-begin)</code>, and
|
||||
summarizes the results. The <code>name</code> is optional, but if
|
||||
present should match the corresponding <code><span>test-begin</span></code> name,
|
||||
or a warning is printed.<div><a name="h3_test-exit"></a><h3><code>(test-exit)</code></h3></div>Exits with a failure status if any tests have failed,
|
||||
and a successful status otherwise.<div><a name="h3_test-syntax-error"></a><h3><code>(test-syntax-error)</code></h3></div><div><a name="h2_Accessors"></a><h2>Accessors</h2></div><div><a name="h3_test-group-name"></a><h3><code>(test-group-name group)</code></h3></div>Returns the name of a test group info object.<div><a name="h3_test-group-ref"></a><h3><code>(test-group-ref group field . o)</code></h3></div>Returns the value of a <code>field</code> in a test var{group} info
|
||||
object. <code>field</code> should be a symbol, and predefined fields
|
||||
include <code><span>parent</span></code>, <code><span>verbose</span></code>, <code><span>level</span></code>,
|
||||
<code><span>start-time</span></code>, <code><span>skip-group?</span></code>, <code><span>count</span></code>,
|
||||
<code><span>total-pass</span></code>, <code><span>total-fail</span></code>, <code><span>total-error</span></code>.<div><a name="h3_test-group-set!"></a><h3><code>(test-group-set! group field value)</code></h3></div>Sets the value of a <code>field</code> in a test <code>group</code> info object.<div><a name="h3_test-group-inc!"></a><h3><code>(test-group-inc! group field [amount])</code></h3></div>Increments the value of a <code>field</code> in a test <code>group</code> info
|
||||
object by <code>amount</code>, defaulting to 1.<div><a name="h3_test-group-push!"></a><h3><code>(test-group-push! group field value)</code></h3></div>Updates a <code>field</code> in a test group info object by consing
|
||||
<code>value</code> onto it.<div><a name="h3_test-get-name!"></a><h3><code>(test-get-name! info)</code></h3></div><div><a name="h2_Parameters"></a><h2>Parameters</h2></div><div><a name="h3_current-test-group"></a><h3><code>current-test-group</code></h3></div>
|
||||
The current test group as started by <code><span>test-group</span></code> or
|
||||
<code><span>test-begin</span></code>.<div><a name="h3_current-test-verbosity"></a><h3><code>current-test-verbosity</code></h3></div>If true, show more verbose output per test. Inferred from the
|
||||
environment variable TEST_VERBOSE.<div><a name="h3_current-test-epsilon"></a><h3><code>current-test-epsilon</code></h3></div>The epsilon used for floating point comparisons.<div><a name="h3_current-test-comparator"></a><h3><code>current-test-comparator</code></h3></div>The underlying comparator used in testing, defaults to
|
||||
<code><span>test-equal?</span></code>.<div><a name="h3_current-test-applier"></a><h3><code>current-test-applier</code></h3></div>The test applier - what we do with non-skipped tests. Takes the
|
||||
same signature as <code><span>test-run</span></code>, should be responsible for
|
||||
evaluating the thunks, determining the status of the test, and
|
||||
passing this information to <code><span>current-test-reporter</span></code>.<div><a name="h3_current-test-skipper"></a><h3><code>current-test-skipper</code></h3></div>The test skipper - what we do with non-skipped tests. This should
|
||||
not evaluate the thunks and simply pass off to
|
||||
<code><span>current-test-reporter</span></code>.<div><a name="h3_current-test-reporter"></a><h3><code>current-test-reporter</code></h3></div>Takes two arguments, the symbol status of the test and the info
|
||||
alist. Reports the result of the test and updates bookkeeping in
|
||||
the current test group for reporting.<div><a name="h3_current-test-group-reporter"></a><h3><code>current-test-group-reporter</code></h3></div>Takes one argument, a test group, and prints a summary of the test
|
||||
results for that group.<div><a name="h3_test-failure-count"></a><h3><code>test-failure-count</code></h3></div>A running count of all test failures and errors across all groups
|
||||
(and threads). Used by <code><span>test-exit</span></code>.<div><a name="h3_current-test-group-filters"></a><h3><code>current-test-group-filters</code></h3></div><div><a name="h3_current-test-group-removers"></a><h3><code>current-test-group-removers</code></h3></div>Parameters controlling which test groups are skipped. Each
|
||||
parameter is a list of procedures of one argument, a test group
|
||||
info, which can be queried with <code>test-group-name</code> and
|
||||
<code>test-group-ref</code>. Analogous to SRFI 1, a filter selects a
|
||||
group for inclusion and a removers for exclusion. The defaults
|
||||
are set automatically from the environment variables
|
||||
TEST_GROUP_FILTER and TEST_GROUP_REMOVE, which should be
|
||||
comma-delimited lists of strings which are checked for a substring
|
||||
match in the test group name. A test group is skipped if it does
|
||||
not match any filter and:
|
||||
<ul><li>its parent group is skipped, or</li><li>it matches a remover, or</li><li>no removers are specified but some filters are</li></ul><div><a name="h3_current-test-filters"></a><h3><code>current-test-filters</code></h3></div><div><a name="h3_current-test-removers"></a><h3><code>current-test-removers</code></h3></div>Parameters controlling which tests are skipped. Each parameter is
|
||||
a list of procedures of one argument, a test info alist, which can
|
||||
be queried with <code><span>test-get-name!</span></code> or <code><span>assq</span></code>.
|
||||
Analogous to SRFI 1, a filter selects a test for inclusion and a
|
||||
removers for exclusion. The defaults are set automatically from
|
||||
the environment variables TEST_FILTER and TEST_REMOVE, which
|
||||
should be comma-delimited lists of strings which are checked for a
|
||||
substring match in the test name. A test is skipped if its group
|
||||
is skipped, or if it does not match a filter and:
|
||||
<ul><li>it matches a remover, or</li><li>no removers are specified but some filters are</li></ul><div><a name="h3_current-column-width"></a><h3><code>current-column-width</code></h3></div>Parameter controlling the current column width for test output,
|
||||
can be set from the environment variable TEST_COLUMN_WIDTH,
|
||||
otherwise defaults to 78. For portability of implementation (and
|
||||
resulting output), does not attempt to use termios to determine
|
||||
the actual available width.</p><div id="footer"></div></div></body></html>
|
|
@ -0,0 +1,985 @@
|
|||
;; Copyright (c) 2010-2020 Alex Shinn. All rights reserved.
|
||||
;; BSD-style license: http://synthcode.com/license.txt
|
||||
|
||||
;;> Simple but extensible testing framework with advanced reporting.
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; list utilities
|
||||
|
||||
;; Simplified version of SRFI-1 any.
|
||||
(define (any pred ls)
|
||||
(and (pair? ls)
|
||||
(or (pred (car ls))
|
||||
(any pred (cdr ls)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; exception utilities
|
||||
|
||||
(define (warning msg . args)
|
||||
(display msg (current-error-port))
|
||||
(for-each (lambda (x)
|
||||
(write-char #\space (current-error-port))
|
||||
(write x (current-error-port)))
|
||||
args)
|
||||
(newline (current-error-port)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; string utilities
|
||||
|
||||
(define (string-search pat str)
|
||||
(let* ((pat-len (string-length pat))
|
||||
(limit (- (string-length str) pat-len)))
|
||||
(let lp1 ((i 0))
|
||||
(cond
|
||||
((>= i limit) #f)
|
||||
(else
|
||||
(let lp2 ((j i) (k 0))
|
||||
(cond ((>= k pat-len) #t)
|
||||
((not (eqv? (string-ref str j) (string-ref pat k)))
|
||||
(lp1 (+ i 1)))
|
||||
(else (lp2 (+ j 1) (+ k 1))))))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; test interface
|
||||
|
||||
;;> \section{Testing}
|
||||
|
||||
;;> \macro{(test [name] expect expr)}
|
||||
|
||||
;;> The primary interface to testing. Evaluate \var{expr} and check
|
||||
;;> that it is equal to \var{expect}, and report the result, using
|
||||
;;> \var{name} or a printed summary of \var{expr}.
|
||||
;;>
|
||||
;;> If used inside a group this will contribute to the overall group
|
||||
;;> reporting, but can be used standalone:
|
||||
;;>
|
||||
;;> \example{(test 4 (+ 2 2))}
|
||||
;;> \example{(test "add two and two" 4 (+ 2 2))}
|
||||
;;> \example{(test 3 (+ 2 2))}
|
||||
;;> \example{(test 4 (+ 2 "2"))}
|
||||
;;>
|
||||
;;> The equality comparison is made with
|
||||
;;> \scheme{current-test-comparator}, defaulting to
|
||||
;;> \scheme{test-equal?}, which is the same as \scheme{equal?} but
|
||||
;;> more permissive on floating point comparisons). Returns the
|
||||
;;> status of the test (one of the symbols \scheme{'PASS},
|
||||
;;> \scheme{'FAIL}, \scheme{'SKIP}, \scheme{'ERROR}).
|
||||
|
||||
(define-syntax test
|
||||
(syntax-rules (quote)
|
||||
((test expect expr)
|
||||
(test #f expect expr))
|
||||
((test name expect (expr ...))
|
||||
(test-propagate-info name expect (expr ...) ()))
|
||||
((test name 'expect expr)
|
||||
(test-propagate-info name 'expect expr ()))
|
||||
((test name (expect ...) expr)
|
||||
(test-syntax-error
|
||||
'test
|
||||
"the test expression should come last: (test <expected> (<expr> ...)) "
|
||||
(test name (expect ...) expr)))
|
||||
((test name expect expr)
|
||||
(test-propagate-info name expect expr ()))
|
||||
((test a ...)
|
||||
(test-syntax-error 'test "test requires 2 or 3 arguments" (test a ...)))))
|
||||
|
||||
;;> \macro{(test-equal equal [name] expect expr)}
|
||||
|
||||
;;> Equivalent to test, using \var{equal} for comparison instead of
|
||||
;;> \scheme{equal?}.
|
||||
|
||||
(define-syntax test-equal
|
||||
(syntax-rules ()
|
||||
((test-equal equal . args)
|
||||
(parameterize ((current-test-comparator equal))
|
||||
(test . args)))))
|
||||
|
||||
;;> \macro{(test-assert [name] expr)}
|
||||
|
||||
;;> Like \scheme{test} but evaluates \var{expr} and checks that it's true.
|
||||
|
||||
(define-syntax test-assert
|
||||
(syntax-rules ()
|
||||
((_ expr)
|
||||
(test-assert #f expr))
|
||||
((_ name expr)
|
||||
(test-propagate-info name #f expr ((assertion . #t))))
|
||||
((test a ...)
|
||||
(test-syntax-error 'test-assert "1 or 2 arguments required"
|
||||
(test a ...)))))
|
||||
|
||||
;;> \macro{(test-not [name] expr)}
|
||||
|
||||
;;> Like \scheme{test} but evaluates \var{expr} and checks that it's false.
|
||||
|
||||
(define-syntax test-not
|
||||
(syntax-rules ()
|
||||
((_ expr) (test-assert (not expr)))
|
||||
((_ name expr) (test-assert name (not expr)))))
|
||||
|
||||
;;> \macro{(test-values [name] expect expr)}
|
||||
|
||||
;;> Like \scheme{test} but \var{expect} and \var{expr} can both
|
||||
;;> return multiple values.
|
||||
|
||||
(define-syntax test-values
|
||||
(syntax-rules ()
|
||||
((_ expect expr)
|
||||
(test-values #f expect expr))
|
||||
((_ name expect expr)
|
||||
(test name (call-with-values (lambda () expect) (lambda results results))
|
||||
(call-with-values (lambda () expr) (lambda results results))))))
|
||||
|
||||
;;> \macro{(test-error [name] expr)}
|
||||
|
||||
;;> Like \scheme{test} but evaluates \var{expr} and checks that it
|
||||
;;> raises an error.
|
||||
|
||||
(define-syntax test-error
|
||||
(syntax-rules ()
|
||||
((_ expr)
|
||||
(test-error #f expr))
|
||||
((_ name expr)
|
||||
(test-propagate-info name #f expr ((expect-error . #t))))
|
||||
((test a ...)
|
||||
(test-syntax-error 'test-error "1 or 2 arguments required"
|
||||
(test a ...)))))
|
||||
|
||||
;;> Low-level macro to pass alist info to the underlying \var{test-run}.
|
||||
|
||||
(define-syntax test-propagate-info
|
||||
(syntax-rules ()
|
||||
;; TODO: Extract interesting variables so we can show their values
|
||||
;; on failure. Vars are empty for now.
|
||||
((test-propagate-info name expect expr info)
|
||||
(test-vars () name expect expr info))))
|
||||
|
||||
(define-syntax test-vars
|
||||
(syntax-rules ()
|
||||
((_ (vars ...) n expect expr ((key . val) ...))
|
||||
(test-run (lambda () expect)
|
||||
(lambda () expr)
|
||||
`((name . ,n)
|
||||
(source . expr)
|
||||
(var-names . (vars ...))
|
||||
(var-values . ,(list vars ...))
|
||||
(key . val) ...)))))
|
||||
|
||||
;;> The procedural interface to testing. \var{expect} and \var{expr}
|
||||
;;> should be thunks, and \var{info} is an alist of properties used in
|
||||
;;> test reporting.
|
||||
|
||||
(define (test-run expect expr info)
|
||||
(let ((info (test-expand-info info)))
|
||||
(if (and (cond ((current-test-group)
|
||||
=> (lambda (g) (not (test-group-ref g 'skip-group?))))
|
||||
(else #t))
|
||||
(or (and (not (any (lambda (f) (f info)) (current-test-removers)))
|
||||
(or (pair? (current-test-removers))
|
||||
(null? (current-test-filters))))
|
||||
(any (lambda (f) (f info)) (current-test-filters))))
|
||||
((current-test-applier) expect expr info)
|
||||
((current-test-skipper) info))))
|
||||
|
||||
;;> Returns true if either \scheme{(equal? expect res)}, or
|
||||
;;> \var{expect} is inexact and \var{res} is within
|
||||
;;> \scheme{current-test-epsilon} of \var{expect}.
|
||||
|
||||
(define (test-equal? expect res)
|
||||
(or (equal? expect res)
|
||||
(if (real? expect)
|
||||
(and (inexact? expect)
|
||||
(real? res)
|
||||
;; tests which expect an inexact value can
|
||||
;; accept an equivalent exact value
|
||||
;; (inexact? res)
|
||||
(approx-equal? expect res (current-test-epsilon)))
|
||||
(and (complex? res)
|
||||
(complex? expect)
|
||||
(test-equal? (real-part expect) (real-part res))
|
||||
(test-equal? (imag-part expect) (imag-part res))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; group interface
|
||||
|
||||
;;> \section{Test Groups}
|
||||
|
||||
;;> Tests can be collected in groups for
|
||||
|
||||
;;> Wraps \var{body} as a single test group, which can be filtered
|
||||
;;> and summarized separately.
|
||||
|
||||
;;> \example{
|
||||
;;> (test-group "pi"
|
||||
;;> (test 3.14159 (acos -1))
|
||||
;;> (test 3 (acos -1))
|
||||
;;> (test 3.14159 (acos "-1")))
|
||||
;;> }
|
||||
|
||||
(define-syntax test-group
|
||||
(syntax-rules ()
|
||||
((_ name-expr body ...)
|
||||
(let ((name name-expr)
|
||||
(old-group (current-test-group)))
|
||||
(when (not (string? name))
|
||||
(error "a name is required, got " 'name-expr name))
|
||||
(test-begin name)
|
||||
(guard
|
||||
(exn
|
||||
(else
|
||||
(warning "error in group outside of tests")
|
||||
(print-exception exn (current-error-port))
|
||||
(test-group-inc! (current-test-group) 'count)
|
||||
(test-group-inc! (current-test-group) 'ERROR)
|
||||
(test-failure-count (+ 1 (test-failure-count)))))
|
||||
body ...)
|
||||
(test-end name)
|
||||
(current-test-group old-group)))))
|
||||
|
||||
;;> Begin testing a new group until the closing \scheme{(test-end)}.
|
||||
|
||||
(define (test-begin . o)
|
||||
(let* ((name (if (pair? o) (car o) ""))
|
||||
(parent (current-test-group))
|
||||
(group (make-test-group name parent)))
|
||||
;; include a newline if we are directly nested in a parent with no
|
||||
;; tests yet
|
||||
(when (and parent
|
||||
(zero? (test-group-ref parent 'subgroups-count 0))
|
||||
(not (test-group-ref parent 'verbose)))
|
||||
(newline))
|
||||
;; header
|
||||
(cond
|
||||
((test-group-ref group 'skip-group?)
|
||||
(display (make-string (or (test-group-indent-width group) 0) #\space))
|
||||
(display (strikethrough (bold (string-append name ":"))))
|
||||
(display " SKIP"))
|
||||
((test-group-ref group 'verbose)
|
||||
(display
|
||||
(test-header-line
|
||||
(string-append "testing " name)
|
||||
(or (test-group-indent-width group) 0))))
|
||||
(else
|
||||
(display
|
||||
(string-append
|
||||
(make-string (or (test-group-indent-width group) 0)
|
||||
#\space)
|
||||
(bold (string-append name ": "))))))
|
||||
;; set the current test group
|
||||
(current-test-group group)))
|
||||
|
||||
;;> Ends testing group introduced with \scheme{(test-begin)}, and
|
||||
;;> summarizes the results. The \var{name} is optional, but if
|
||||
;;> present should match the corresponding \scheme{test-begin} name,
|
||||
;;> or a warning is printed.
|
||||
|
||||
(define (test-end . o)
|
||||
(let ((name (and (pair? o) (car o))))
|
||||
(cond
|
||||
((current-test-group)
|
||||
=> (lambda (group)
|
||||
(when (and name (not (equal? name (test-group-name group))))
|
||||
(warning "mismatched test-end:" name (test-group-name group)))
|
||||
(let ((parent (test-group-ref group 'parent)))
|
||||
(when (and (test-group-ref group 'skip-group?)
|
||||
(zero? (test-group-ref group 'subgroups-count 0)))
|
||||
(newline))
|
||||
;; only report if there's something to say
|
||||
((current-test-group-reporter) group)
|
||||
(when parent
|
||||
(test-group-inc! parent 'subgroups-count)
|
||||
(cond
|
||||
((test-group-ref group 'skip-group?)
|
||||
(test-group-inc! parent 'subgroups-skip))
|
||||
((and (zero? (test-group-ref group 'FAIL 0))
|
||||
(zero? (test-group-ref group 'ERROR 0))
|
||||
(= (test-group-ref group 'subgroups-pass 0)
|
||||
(test-group-ref group 'subgroups-count 0)))
|
||||
(test-group-inc! parent 'subgroups-pass))))
|
||||
(current-test-group parent)
|
||||
group))))))
|
||||
|
||||
;;> Exits with a failure status if any tests have failed,
|
||||
;;> and a successful status otherwise.
|
||||
|
||||
(define (test-exit)
|
||||
(when (current-test-group)
|
||||
(warning "calling test-exit with unfinished test group:"
|
||||
(test-group-name (current-test-group))))
|
||||
(exit (zero? (test-failure-count))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; utilities
|
||||
|
||||
(define-syntax test-syntax-error
|
||||
(syntax-rules ()
|
||||
((_) (syntax-error "invalid use of test-syntax-error"))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; test-group representation
|
||||
|
||||
;;> \section{Accessors}
|
||||
|
||||
;; (name (prop value) ...)
|
||||
(define (make-test-group name . o)
|
||||
(let ((parent (and (pair? o) (car o)))
|
||||
(group (list name (cons 'start-time (current-second)))))
|
||||
(test-group-set! group 'parent parent)
|
||||
(test-group-set! group 'verbose
|
||||
(if parent
|
||||
(test-group-ref parent 'verbose)
|
||||
(current-test-verbosity)))
|
||||
(test-group-set! group 'level
|
||||
(if parent
|
||||
(+ 1 (test-group-ref parent 'level 0))
|
||||
0))
|
||||
(test-group-set!
|
||||
group
|
||||
'skip-group?
|
||||
(and (or (and parent (test-group-ref parent 'skip-group?))
|
||||
(any (lambda (f) (f group)) (current-test-group-removers))
|
||||
(and (null? (current-test-group-removers))
|
||||
(pair? (current-test-group-filters))))
|
||||
(not (any (lambda (f) (f group)) (current-test-group-filters)))))
|
||||
group))
|
||||
|
||||
;;> Returns the name of a test group info object.
|
||||
|
||||
(define (test-group-name group) (car group))
|
||||
|
||||
;;> Returns the value of a \var{field} in a test var{group} info
|
||||
;;> object. \var{field} should be a symbol, and predefined fields
|
||||
;;> include \scheme{parent}, \scheme{verbose}, \scheme{level},
|
||||
;;> \scheme{start-time}, \scheme{skip-group?}, \scheme{count},
|
||||
;;> \scheme{total-pass}, \scheme{total-fail}, \scheme{total-error}.
|
||||
|
||||
(define (test-group-ref group field . o)
|
||||
(if group
|
||||
(apply assq-ref (cdr group) field o)
|
||||
(and (pair? o) (car o))))
|
||||
|
||||
;;> Sets the value of a \var{field} in a test \var{group} info object.
|
||||
|
||||
(define (test-group-set! group field value)
|
||||
(cond
|
||||
((assq field (cdr group))
|
||||
=> (lambda (x) (set-cdr! x value)))
|
||||
(else (set-cdr! group (cons (cons field value) (cdr group))))))
|
||||
|
||||
;;> Increments the value of a \var{field} in a test \var{group} info
|
||||
;;> object by \var{amount}, defaulting to 1.
|
||||
|
||||
(define (test-group-inc! group field . o)
|
||||
(let ((amount (if (pair? o) (car o) 1)))
|
||||
(cond
|
||||
((assq field (cdr group))
|
||||
=> (lambda (x) (set-cdr! x (+ amount (cdr x)))))
|
||||
(else (set-cdr! group (cons (cons field amount) (cdr group)))))))
|
||||
|
||||
;;> Updates a \var{field} in a test group info object by consing
|
||||
;;> \var{value} onto it.
|
||||
|
||||
(define (test-group-push! group field value)
|
||||
(cond
|
||||
((assq field (cdr group))
|
||||
=> (lambda (x) (set-cdr! x (cons value (cdr x)))))
|
||||
(else (set-cdr! group (cons (cons field (list value)) (cdr group))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; utilities
|
||||
|
||||
(define (assq-ref ls key . o)
|
||||
(cond ((assq key ls) => cdr)
|
||||
((pair? o) (car o))
|
||||
(else #f)))
|
||||
|
||||
(define (approx-equal? a b epsilon)
|
||||
(cond
|
||||
((> (abs a) (abs b))
|
||||
(approx-equal? b a epsilon))
|
||||
((zero? a)
|
||||
(< (abs b) epsilon))
|
||||
(else
|
||||
(< (abs (/ (- a b) b)) epsilon))))
|
||||
|
||||
(define (call-with-output-string proc)
|
||||
(let ((out (open-output-string)))
|
||||
(proc out)
|
||||
(get-output-string out)))
|
||||
|
||||
;; partial pretty printing to abbreviate `quote' forms and the like
|
||||
(define (write-to-string x)
|
||||
(call-with-output-string
|
||||
(lambda (out)
|
||||
(let wr ((x x))
|
||||
(if (pair? x)
|
||||
(cond
|
||||
((and (symbol? (car x)) (pair? (cdr x)) (null? (cddr x))
|
||||
(assq (car x)
|
||||
'((quote . "'") (quasiquote . "`")
|
||||
(unquote . ",") (unquote-splicing . ",@"))))
|
||||
=> (lambda (s) (display (cdr s) out) (wr (cadr x))))
|
||||
(else
|
||||
(display "(" out)
|
||||
(wr (car x))
|
||||
(let lp ((ls (cdr x)))
|
||||
(cond ((pair? ls)
|
||||
(display " " out)
|
||||
(wr (car ls))
|
||||
(lp (cdr ls)))
|
||||
((not (null? ls))
|
||||
(display " . " out)
|
||||
(write ls out))))
|
||||
(display ")" out)))
|
||||
(write x out))))))
|
||||
|
||||
(define (display-to-string x)
|
||||
(if (string? x) x (call-with-output-string (lambda (out) (display x out)))))
|
||||
|
||||
;; if we need to truncate, try first dropping let's to get at the
|
||||
;; heart of the expression
|
||||
(define (truncate-source x width . o)
|
||||
(let* ((str (write-to-string x))
|
||||
(len (string-length str)))
|
||||
(cond
|
||||
((<= len width)
|
||||
str)
|
||||
((and (pair? x) (eq? 'let (car x)))
|
||||
(if (and (pair? o) (car o))
|
||||
(truncate-source (car (reverse x)) width #t)
|
||||
(string-append "..."
|
||||
(truncate-source (car (reverse x)) (- width 3) #t))))
|
||||
((and (pair? x) (eq? 'call-with-current-continuation (car x)))
|
||||
(truncate-source (cons 'call/cc (cdr x)) width (and (pair? o) (car o))))
|
||||
((and (pair? x) (eq? 'call-with-values (car x)))
|
||||
(string-append
|
||||
"..."
|
||||
(truncate-source (if (and (pair? (cadr x)) (eq? 'lambda (car (cadr x))))
|
||||
(car (reverse (cadr x)))
|
||||
(cadr x))
|
||||
(- width 3)
|
||||
#t)))
|
||||
(else
|
||||
(string-append
|
||||
(substring str 0 (min (max 0 (- width 3)) (string-length str)))
|
||||
"...")))))
|
||||
|
||||
(define (test-get-name! info)
|
||||
(or
|
||||
(assq-ref info 'name)
|
||||
(assq-ref info 'gen-name)
|
||||
(let ((name
|
||||
(cond
|
||||
((assq 'source info)
|
||||
=> (lambda (src)
|
||||
(truncate-source (cdr src) (- (current-column-width) 12))))
|
||||
((current-test-group)
|
||||
=> (lambda (g)
|
||||
(display "no source in: " (current-error-port))
|
||||
(write info (current-error-port))
|
||||
(display "\n" (current-error-port))
|
||||
(string-append
|
||||
"test-"
|
||||
(number->string (test-group-ref g 'count 0)))))
|
||||
(else ""))))
|
||||
(if (pair? info)
|
||||
(set-cdr! info (cons (cons 'gen-name name) (cdr info))))
|
||||
name)))
|
||||
|
||||
(define (test-print-name info . indent)
|
||||
(let ((width (- (current-column-width)
|
||||
(or (and (pair? indent) (car indent)) 0)))
|
||||
(name (test-get-name! info)))
|
||||
(display name)
|
||||
(display " ")
|
||||
(let ((diff (- width 9 (string-length name))))
|
||||
(cond
|
||||
((positive? diff)
|
||||
(display (make-string diff #\.)))))
|
||||
(display " ")
|
||||
(flush-output-port)))
|
||||
|
||||
(define (test-group-indent-width group)
|
||||
(let ((level (max 0 (+ 1 (- (test-group-ref group 'level 0)
|
||||
(test-first-indentation))))))
|
||||
(* 4 (min level (test-max-indentation)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define (test-expand-info info)
|
||||
(let ((expr (assq-ref info 'source)))
|
||||
(if (and (pair? expr)
|
||||
(pair-source expr)
|
||||
(not (assq-ref info 'line-number)))
|
||||
`((file-name . ,(car (pair-source expr)))
|
||||
(line-number . ,(cdr (pair-source expr)))
|
||||
,@info)
|
||||
info)))
|
||||
|
||||
(define (test-default-applier expect expr info)
|
||||
(let* ((group (current-test-group))
|
||||
(indent (and group (test-group-indent-width group))))
|
||||
(cond
|
||||
((or (not group) (test-group-ref group 'verbose))
|
||||
(if (and indent (positive? indent))
|
||||
(display (make-string indent #\space)))
|
||||
(test-print-name info indent)))
|
||||
(let ((expect-val
|
||||
(guard
|
||||
(exn
|
||||
(else
|
||||
(warning "bad expect value")
|
||||
(print-exception exn (current-error-port))
|
||||
#f))
|
||||
(expect))))
|
||||
(guard
|
||||
(exn
|
||||
(else
|
||||
((current-test-reporter)
|
||||
(if (assq-ref info 'expect-error) 'PASS 'ERROR)
|
||||
(append `((exception . ,exn)) info))))
|
||||
(let ((res (expr)))
|
||||
(let ((status
|
||||
(if (and (not (assq-ref info 'expect-error))
|
||||
(if (assq-ref info 'assertion)
|
||||
res
|
||||
((current-test-comparator) expect-val res)))
|
||||
'PASS
|
||||
'FAIL))
|
||||
(info `((result . ,res) (expected . ,expect-val) ,@info)))
|
||||
((current-test-reporter) status info)))))))
|
||||
|
||||
(define (test-default-skipper info)
|
||||
((current-test-reporter) 'SKIP info))
|
||||
|
||||
(define (test-status-color status)
|
||||
(case status
|
||||
((ERROR) (lambda (x) (underline (red x))))
|
||||
((FAIL) red)
|
||||
((SKIP) yellow)
|
||||
(else (lambda (x) x))))
|
||||
|
||||
(define (test-status-message status)
|
||||
((test-status-color status) (symbol->string status)))
|
||||
|
||||
(define (test-status-code status)
|
||||
((test-status-color status)
|
||||
;; alternatively: ❗, ✗, ‒, ✓
|
||||
;; unfortunately, these have ambiguous width
|
||||
(case status
|
||||
((ERROR) "!")
|
||||
((FAIL) "x")
|
||||
((SKIP) "-")
|
||||
(else "."))))
|
||||
|
||||
(define (display-expected/actual expected actual)
|
||||
(let* ((e-str (write-to-string expected))
|
||||
(a-str (write-to-string actual))
|
||||
(diff (diff e-str a-str read-char)))
|
||||
(write-string "expected ")
|
||||
(write-string (edits->string/color (car diff) (car (cddr diff)) 1))
|
||||
(write-string " but got ")
|
||||
(write-string (edits->string/color (cadr diff) (car (cddr diff)) 2))))
|
||||
|
||||
(define (test-print-explanation indent status info)
|
||||
(cond
|
||||
((eq? status 'ERROR)
|
||||
(display indent)
|
||||
(cond ((assq 'exception info)
|
||||
=> (lambda (e)
|
||||
(print-exception (cdr e) (current-output-port))))))
|
||||
((and (eq? status 'FAIL) (assq-ref info 'assertion))
|
||||
(display indent)
|
||||
(display "assertion failed\n"))
|
||||
((and (eq? status 'FAIL) (assq-ref info 'expect-error))
|
||||
(display indent)
|
||||
(display "expected an error but got ")
|
||||
(write (assq-ref info 'result)) (newline))
|
||||
((eq? status 'FAIL)
|
||||
(display indent)
|
||||
(display-expected/actual (assq-ref info 'expected) (assq-ref info 'result))
|
||||
(newline)))
|
||||
;; print variables
|
||||
(cond
|
||||
((and (memq status '(FAIL ERROR)) (assq-ref info 'var-names))
|
||||
=> (lambda (names)
|
||||
(let ((values (assq-ref info 'var-values)))
|
||||
(if (and (pair? names)
|
||||
(pair? values)
|
||||
(= (length names) (length values)))
|
||||
(let ((indent2
|
||||
(string-append indent (make-string 2 #\space))))
|
||||
(for-each
|
||||
(lambda (name value)
|
||||
(display indent2) (write name) (display ": ")
|
||||
(write value) (newline))
|
||||
names values))))))))
|
||||
|
||||
(define (test-print-source indent status info)
|
||||
(case status
|
||||
((FAIL ERROR)
|
||||
(cond
|
||||
((assq-ref info 'line-number)
|
||||
=> (lambda (line)
|
||||
(display " on line ")
|
||||
(write line)
|
||||
(cond ((assq-ref info 'file-name)
|
||||
=> (lambda (file) (display " of file ") (write file))))
|
||||
(newline))))
|
||||
(cond
|
||||
((assq-ref info 'source)
|
||||
=> (lambda (s)
|
||||
(cond
|
||||
((or (assq-ref info 'name)
|
||||
(> (string-length (write-to-string s))
|
||||
(current-column-width)))
|
||||
(display (write-to-string s))
|
||||
(newline))))))
|
||||
(cond
|
||||
((assq-ref info 'values)
|
||||
=> (lambda (v)
|
||||
(for-each
|
||||
(lambda (v)
|
||||
(display " ") (display (car v))
|
||||
(display ": ") (write (cdr v)) (newline))
|
||||
v)))))))
|
||||
|
||||
(define (test-print-failure indent status info)
|
||||
;; display status explanation
|
||||
(test-print-explanation indent status info)
|
||||
;; display line, source and values info
|
||||
(test-print-source indent status info))
|
||||
|
||||
(define (test-header-line str . indent)
|
||||
(let* ((header (string-append
|
||||
(make-string (if (pair? indent) (car indent) 0) #\space)
|
||||
"-- " str " "))
|
||||
(len (string-length header)))
|
||||
(string-append (bold header)
|
||||
(make-string (max 0 (- (current-column-width) len)) #\-))))
|
||||
|
||||
(define (test-default-handler status info)
|
||||
(define indent
|
||||
(make-string
|
||||
(+ 4 (cond ((current-test-group)
|
||||
=> (lambda (group) (or (test-group-indent-width group) 0)))
|
||||
(else 0)))
|
||||
#\space))
|
||||
;; update group info
|
||||
(cond
|
||||
((current-test-group)
|
||||
=> (lambda (group)
|
||||
(if (not (eq? 'SKIP status))
|
||||
(test-group-inc! group 'count))
|
||||
(test-group-inc! group status)
|
||||
;; maybe wrap long status lines
|
||||
(let ((width (max (- (current-column-width)
|
||||
(or (test-group-indent-width group) 0))
|
||||
4))
|
||||
(column
|
||||
(+ (string-length (or (test-group-name group) ""))
|
||||
(or (test-group-ref group 'count) 0)
|
||||
1)))
|
||||
(if (and (zero? (modulo column width))
|
||||
(not (test-group-ref group 'verbose)))
|
||||
(display (string-append "\n" (string-copy indent 4))))))))
|
||||
;; update global failure count for exit status
|
||||
(cond
|
||||
((or (eq? status 'FAIL) (eq? status 'ERROR))
|
||||
(test-failure-count (+ 1 (test-failure-count)))))
|
||||
(cond
|
||||
((or (not (current-test-group))
|
||||
(test-group-ref (current-test-group) 'verbose))
|
||||
;; display status
|
||||
(display "[")
|
||||
(if (not (eq? status 'ERROR)) (display " ")) ; pad
|
||||
(display (test-status-message status))
|
||||
(display "]")
|
||||
(newline)
|
||||
(test-print-failure indent status info))
|
||||
((eq? status 'SKIP))
|
||||
(else
|
||||
(display (test-status-code status))
|
||||
(cond
|
||||
((and (memq status '(FAIL ERROR)) (current-test-group))
|
||||
=> (lambda (group)
|
||||
(test-group-push! group 'failures (list indent status info)))))
|
||||
(cond ((current-test-group)
|
||||
=> (lambda (group) (test-group-set! group 'trailing #t))))))
|
||||
(flush-output-port)
|
||||
status)
|
||||
|
||||
(define (test-default-group-reporter group)
|
||||
(define (plural word n)
|
||||
(if (= n 1) word (string-append word "s")))
|
||||
(define (percent n d)
|
||||
(string-append " (" (number->string (/ (round (* 1000.0 (/ n d))) 10))
|
||||
"%)"))
|
||||
(let* ((end-time (current-second))
|
||||
(start-time (test-group-ref group 'start-time))
|
||||
(duration (- end-time start-time))
|
||||
(base-count (or (test-group-ref group 'count) 0))
|
||||
(base-pass (or (test-group-ref group 'PASS) 0))
|
||||
(base-fail (or (test-group-ref group 'FAIL) 0))
|
||||
(base-err (or (test-group-ref group 'ERROR) 0))
|
||||
(skip (or (test-group-ref group 'SKIP) 0))
|
||||
(pass (+ base-pass (or (test-group-ref group 'total-pass) 0)))
|
||||
(fail (+ base-fail (or (test-group-ref group 'total-fail) 0)))
|
||||
(err (+ base-err (or (test-group-ref group 'total-error) 0)))
|
||||
(count (+ pass fail err))
|
||||
(subgroups-count (or (test-group-ref group 'subgroups-count) 0))
|
||||
(subgroups-skip (or (test-group-ref group 'subgroups-skip) 0))
|
||||
(subgroups-run (- subgroups-count subgroups-skip))
|
||||
(subgroups-pass (or (test-group-ref group 'subgroups-pass) 0))
|
||||
(indent (make-string (or (test-group-indent-width group) 0) #\space)))
|
||||
(if (and (not (test-group-ref group 'verbose))
|
||||
(test-group-ref group 'trailing))
|
||||
(newline))
|
||||
(cond
|
||||
((or (positive? count) (positive? subgroups-count))
|
||||
(if (not (= base-count (+ base-pass base-fail base-err)))
|
||||
(warning "inconsistent count:"
|
||||
base-count base-pass base-fail base-err))
|
||||
(cond
|
||||
((positive? count)
|
||||
(display indent)
|
||||
(display
|
||||
((if (= pass count) green (lambda (x) x))
|
||||
(string-append
|
||||
(number->string pass) " out of " (number->string count)
|
||||
(percent pass count))))
|
||||
(display
|
||||
(string-append
|
||||
(plural " test" pass) " passed in "
|
||||
(number->string duration) " seconds"
|
||||
(cond
|
||||
((zero? skip) "")
|
||||
(else (string-append " (" (number->string skip)
|
||||
(plural " test" skip) " skipped)")))
|
||||
".\n"))))
|
||||
(cond ((positive? fail)
|
||||
(display indent)
|
||||
(display
|
||||
(red
|
||||
(string-append
|
||||
(number->string fail) (plural " failure" fail)
|
||||
(percent fail count) ".\n")))))
|
||||
(cond ((positive? err)
|
||||
(display indent)
|
||||
(display
|
||||
((lambda (x) (underline (red x)))
|
||||
(string-append
|
||||
(number->string err) (plural " error" err)
|
||||
(percent err count) ".\n")))))
|
||||
(cond
|
||||
((not (test-group-ref group 'verbose))
|
||||
(for-each
|
||||
(lambda (failure)
|
||||
(display indent)
|
||||
(display (red
|
||||
(string-append (display-to-string (cadr failure)) ": ")))
|
||||
(display (test-get-name! (car (cddr failure))))
|
||||
(newline)
|
||||
(apply test-print-failure failure))
|
||||
(reverse (or (test-group-ref group 'failures) '())))))
|
||||
(cond
|
||||
((positive? subgroups-run)
|
||||
(display indent)
|
||||
(display
|
||||
((if (= subgroups-pass subgroups-run)
|
||||
green (lambda (x) x))
|
||||
(string-append
|
||||
(number->string subgroups-pass) " out of "
|
||||
(number->string subgroups-run)
|
||||
(percent subgroups-pass subgroups-run))))
|
||||
(display (plural " subgroup" subgroups-pass))
|
||||
(display " passed.\n")))))
|
||||
(cond
|
||||
((test-group-ref group 'verbose)
|
||||
(display
|
||||
(test-header-line
|
||||
(string-append "done testing " (or (test-group-name group) ""))
|
||||
(or (test-group-indent-width group) 0)))
|
||||
(newline)))
|
||||
(cond
|
||||
((test-group-ref group 'parent)
|
||||
=> (lambda (parent)
|
||||
(test-group-set! parent 'trailing #f)
|
||||
(test-group-inc! parent 'total-pass pass)
|
||||
(test-group-inc! parent 'total-fail fail)
|
||||
(test-group-inc! parent 'total-error err))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; parameters
|
||||
|
||||
;;> \section{Parameters}
|
||||
|
||||
;;> The current test group as started by \scheme{test-group} or
|
||||
;;> \scheme{test-begin}.
|
||||
|
||||
(define current-test-group (make-parameter #f))
|
||||
|
||||
;;> If true, show more verbose output per test. Inferred from the
|
||||
;;> environment variable TEST_VERBOSE.
|
||||
|
||||
(define current-test-verbosity
|
||||
(make-parameter
|
||||
(cond ((get-environment-variable "TEST_VERBOSE")
|
||||
=> (lambda (s) (not (member s '("" "0")))))
|
||||
(else #f))))
|
||||
|
||||
;;> The epsilon used for floating point comparisons.
|
||||
|
||||
(define current-test-epsilon (make-parameter 1e-5))
|
||||
|
||||
;;> The underlying comparator used in testing, defaults to
|
||||
;;> \scheme{test-equal?}.
|
||||
|
||||
(define current-test-comparator (make-parameter test-equal?))
|
||||
|
||||
;;> The test applier - what we do with non-skipped tests. Takes the
|
||||
;;> same signature as \scheme{test-run}, should be responsible for
|
||||
;;> evaluating the thunks, determining the status of the test, and
|
||||
;;> passing this information to \scheme{current-test-reporter}.
|
||||
|
||||
(define current-test-applier (make-parameter test-default-applier))
|
||||
|
||||
;;> The test skipper - what we do with non-skipped tests. This should
|
||||
;;> not evaluate the thunks and simply pass off to
|
||||
;;> \scheme{current-test-reporter}.
|
||||
|
||||
(define current-test-skipper (make-parameter test-default-skipper))
|
||||
|
||||
;;> Takes two arguments, the symbol status of the test and the info
|
||||
;;> alist. Reports the result of the test and updates bookkeeping in
|
||||
;;> the current test group for reporting.
|
||||
|
||||
(define current-test-reporter (make-parameter test-default-handler))
|
||||
|
||||
;;> Takes one argument, a test group, and prints a summary of the test
|
||||
;;> results for that group.
|
||||
|
||||
(define current-test-group-reporter
|
||||
(make-parameter test-default-group-reporter))
|
||||
|
||||
;;> A running count of all test failures and errors across all groups
|
||||
;;> (and threads). Used by \scheme{test-exit}.
|
||||
|
||||
(define test-failure-count (make-parameter 0))
|
||||
|
||||
(define test-first-indentation
|
||||
(make-parameter
|
||||
(or (cond ((get-environment-variable "TEST_FIRST_INDENTATION")
|
||||
=> string->number)
|
||||
(else #f))
|
||||
1)))
|
||||
|
||||
(define test-max-indentation
|
||||
(make-parameter
|
||||
(or (cond ((get-environment-variable "TEST_MAX_INDENTATION")
|
||||
=> string->number)
|
||||
(else #f))
|
||||
5)))
|
||||
|
||||
(define (string->info-matcher str)
|
||||
(lambda (info)
|
||||
(cond ((test-get-name! info)
|
||||
=> (lambda (n) (string-search str n)))
|
||||
(else #f))))
|
||||
|
||||
(define (string->group-matcher str)
|
||||
(lambda (group) (string-search str (test-group-name group))))
|
||||
|
||||
;; simplified version from SRFI 130
|
||||
(define (string-split str ch)
|
||||
(let ((end (string-length str)))
|
||||
(let lp ((from 0) (to 0) (res '()))
|
||||
(cond
|
||||
((>= to end)
|
||||
(reverse (if (> to from) (cons (substring str from to) res) res)))
|
||||
((eqv? ch (string-ref str to))
|
||||
(lp (+ to 1) (+ to 1) (cons (substring str from to) res)))
|
||||
(else
|
||||
(lp from (+ to 1) res))))))
|
||||
|
||||
(define (getenv-filter-list proc name)
|
||||
(cond
|
||||
((get-environment-variable name)
|
||||
=> (lambda (s)
|
||||
(let lp ((ls (string-split s #\,))
|
||||
(res '()))
|
||||
(cond
|
||||
((null? ls) (reverse res))
|
||||
(else
|
||||
(let* ((s (car ls))
|
||||
(f (guard
|
||||
(exn
|
||||
(else
|
||||
(warning
|
||||
(string-append "invalid filter '" s
|
||||
"' from environment variable: "
|
||||
name))
|
||||
(print-exception exn (current-error-port))
|
||||
#f))
|
||||
(proc s))))
|
||||
(lp (cdr ls) (if f (cons f res) res))))))))
|
||||
(else '())))
|
||||
|
||||
(define current-test-group-filters
|
||||
(make-parameter
|
||||
(getenv-filter-list string->group-matcher "TEST_GROUP_FILTER")))
|
||||
|
||||
(define current-test-group-removers
|
||||
(make-parameter
|
||||
(getenv-filter-list string->group-matcher "TEST_GROUP_REMOVE")))
|
||||
|
||||
;;> Parameters controlling which test groups are skipped. Each
|
||||
;;> parameter is a list of procedures of one argument, a test group
|
||||
;;> info, which can be queried with \var{test-group-name} and
|
||||
;;> \var{test-group-ref}. Analogous to SRFI 1, a filter selects a
|
||||
;;> group for inclusion and a removers for exclusion. The defaults
|
||||
;;> are set automatically from the environment variables
|
||||
;;> TEST_GROUP_FILTER and TEST_GROUP_REMOVE, which should be
|
||||
;;> comma-delimited lists of strings which are checked for a substring
|
||||
;;> match in the test group name. A test group is skipped if it does
|
||||
;;> not match any filter and:
|
||||
;;> \itemlist[
|
||||
;;> \item{its parent group is skipped, or}
|
||||
;;> \item{it matches a remover, or}
|
||||
;;> \item{no removers are specified but some filters are}
|
||||
;;> ]
|
||||
;;/
|
||||
|
||||
(define current-test-filters
|
||||
(make-parameter (getenv-filter-list string->info-matcher "TEST_FILTER")))
|
||||
|
||||
(define current-test-removers
|
||||
(make-parameter (getenv-filter-list string->info-matcher "TEST_REMOVE")))
|
||||
|
||||
;;> Parameters controlling which tests are skipped. Each parameter is
|
||||
;;> a list of procedures of one argument, a test info alist, which can
|
||||
;;> be queried with \scheme{test-get-name!} or \scheme{assq}.
|
||||
;;> Analogous to SRFI 1, a filter selects a test for inclusion and a
|
||||
;;> removers for exclusion. The defaults are set automatically from
|
||||
;;> the environment variables TEST_FILTER and TEST_REMOVE, which
|
||||
;;> should be comma-delimited lists of strings which are checked for a
|
||||
;;> substring match in the test name. A test is skipped if its group
|
||||
;;> is skipped, or if it does not match a filter and:
|
||||
;;> \itemlist[
|
||||
;;> \item{it matches a remover, or}
|
||||
;;> \item{no removers are specified but some filters are}
|
||||
;;> ]
|
||||
;;/
|
||||
|
||||
;;> Parameter controlling the current column width for test output,
|
||||
;;> can be set from the environment variable TEST_COLUMN_WIDTH,
|
||||
;;> otherwise defaults to 78. For portability of implementation (and
|
||||
;;> resulting output), does not attempt to use termios to determine
|
||||
;;> the actual available width.
|
||||
|
||||
(define current-column-width
|
||||
(make-parameter
|
||||
(or (cond ((get-environment-variable "TEST_COLUMN_WIDTH")
|
||||
=> string->number)
|
||||
(else #f))
|
||||
78)))
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
(define-library (chibi test)
|
||||
(export
|
||||
;; basic interface
|
||||
test test-equal test-error test-assert test-not test-values
|
||||
test-group current-test-group
|
||||
test-begin test-end test-syntax-error test-propagate-info
|
||||
test-run test-exit test-equal?
|
||||
;; test and group data
|
||||
test-get-name! test-group-name test-group-ref
|
||||
test-group-set! test-group-inc! test-group-push!
|
||||
;; parameters
|
||||
current-test-verbosity
|
||||
current-test-applier current-test-skipper current-test-reporter
|
||||
current-test-group-reporter test-failure-count
|
||||
current-test-epsilon current-test-comparator
|
||||
current-test-filters current-test-removers
|
||||
current-test-group-filters current-test-group-removers
|
||||
current-column-width)
|
||||
(import (scheme base)
|
||||
(scheme write)
|
||||
(scheme complex)
|
||||
(scheme process-context)
|
||||
(scheme time)
|
||||
(chibi diff)
|
||||
(chibi term ansi))
|
||||
(cond-expand
|
||||
(chibi
|
||||
(import (only (chibi) pair-source print-exception)))
|
||||
(chicken
|
||||
(import (only (chicken) print-error-message))
|
||||
(begin
|
||||
(define (pair-source x) #f)
|
||||
(define print-exception print-error-message)))
|
||||
(else
|
||||
(begin
|
||||
(define (pair-source x) #f)
|
||||
(define print-exception write))))
|
||||
(include "test.scm"))
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,156 @@
|
|||
(define-library
|
||||
(srfi 1)
|
||||
(import (scheme base)
|
||||
(scheme cxr)
|
||||
(srfi 8))
|
||||
(export
|
||||
;cons
|
||||
;list
|
||||
xcons
|
||||
cons*
|
||||
;make-list
|
||||
list-tabulate
|
||||
;list-copy
|
||||
circular-list
|
||||
iota
|
||||
;pair?
|
||||
;null?
|
||||
proper-list?
|
||||
circular-list?
|
||||
dotted-list?
|
||||
not-pair?
|
||||
null-list?
|
||||
list=
|
||||
;car
|
||||
;cdr
|
||||
;cdar
|
||||
;cddr
|
||||
;caar
|
||||
;caadr
|
||||
;cadar
|
||||
;caddr
|
||||
;cdaar
|
||||
;cdadr
|
||||
;cddar
|
||||
;cddr
|
||||
;caaaar
|
||||
;caaadr
|
||||
;caadar
|
||||
;caaddr
|
||||
;cadaar
|
||||
;cadadr
|
||||
;caddar
|
||||
;cadddr
|
||||
;cdaaar
|
||||
;cdaadr
|
||||
;cdadar
|
||||
;cdaddr
|
||||
;cddaar
|
||||
;cddadr
|
||||
;cdddar
|
||||
;cddddr
|
||||
;list-ref
|
||||
first
|
||||
second
|
||||
third
|
||||
fourth
|
||||
fifth
|
||||
sixth
|
||||
seventh
|
||||
eighth
|
||||
ninth
|
||||
tenth
|
||||
car+cdr
|
||||
take
|
||||
drop
|
||||
take-right
|
||||
drop-right
|
||||
take!
|
||||
drop-right!
|
||||
split-at
|
||||
split-at!
|
||||
last
|
||||
last-pair
|
||||
;length
|
||||
length+
|
||||
;append
|
||||
concatenate
|
||||
;reverse
|
||||
append!
|
||||
concatenate!
|
||||
reverse!
|
||||
append-reverse
|
||||
append-reverse!
|
||||
zip
|
||||
unzip1
|
||||
unzip2
|
||||
unzip3
|
||||
unzip4
|
||||
unzip5
|
||||
count
|
||||
;map
|
||||
;for-each
|
||||
fold
|
||||
unfold
|
||||
pair-fold
|
||||
reduce
|
||||
fold-right
|
||||
unfold-right
|
||||
pair-fold-right
|
||||
reduce-right
|
||||
append-map
|
||||
append-map!
|
||||
map!
|
||||
pair-for-each
|
||||
filter-map
|
||||
map-in-order
|
||||
filter
|
||||
partition
|
||||
remove
|
||||
filter!
|
||||
partition!
|
||||
remove!
|
||||
;member
|
||||
;memq
|
||||
;memv
|
||||
find
|
||||
find-tail
|
||||
any
|
||||
every
|
||||
list-index
|
||||
take-while
|
||||
drop-while
|
||||
take-while!
|
||||
span
|
||||
break
|
||||
span!
|
||||
break!
|
||||
delete
|
||||
delete-duplicates
|
||||
delete!
|
||||
delete-duplicates!
|
||||
;assoc
|
||||
;assq
|
||||
;assv
|
||||
alist-cons
|
||||
alist-copy
|
||||
alist-delete
|
||||
alist-delete!
|
||||
lset<=
|
||||
lset=
|
||||
lset-adjoin
|
||||
lset-union
|
||||
lset-union!
|
||||
lset-intersection
|
||||
lset-intersection!
|
||||
lset-difference
|
||||
lset-difference!
|
||||
lset-xor
|
||||
lset-xor!
|
||||
lset-diff+intersection
|
||||
lset-diff+intersection!
|
||||
;set-car!
|
||||
;set-cdr!
|
||||
)
|
||||
(begin
|
||||
(include "1.scm")))
|
|
@ -0,0 +1,5 @@
|
|||
(define-syntax receive
|
||||
(syntax-rules ()
|
||||
((receive formals expression body ...)
|
||||
(call-with-values (lambda () expression)
|
||||
(lambda formals body ...)))))
|
|
@ -0,0 +1,6 @@
|
|||
(define-library
|
||||
(srfi 8)
|
||||
(import (scheme base))
|
||||
(export receive)
|
||||
(begin
|
||||
(include "8.scm")))
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
stage("Report") {
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
unstash 'reports'
|
||||
sh './report'
|
||||
archiveArtifacts artifacts: 'reports/*.html'
|
||||
publishHTML (target : [allowMissing: false,
|
||||
alwaysLinkToLastBuild: false,
|
||||
keepAll: true,
|
||||
reportDir: 'reports',
|
||||
reportFiles: '*.html,*.css',
|
||||
reportName: 'R7RS-SRFI Test Report',
|
||||
reportTitles: 'R7RS-SRFI Test Report'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
post {
|
||||
always {
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
archiveArtifacts artifacts: 'reports/*.html'
|
||||
sh 'for f in srfi/*.sld; do snow-chibi package "$f"; done'
|
||||
archiveArtifacts artifacts: '*.tgz'
|
||||
archiveArtifacts artifacts: 'srfi/*.tgz'
|
||||
deleteDir()
|
||||
}
|
||||
failure {
|
||||
archiveArtifacts artifacts: 'reports/*.html'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
deleteDir()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
{{#library-command}}sh '{{{library-command}}}'{{/library-command}}
|
||||
sh '{{{command}}}'
|
|
@ -0,0 +1,7 @@
|
|||
sh 'for f in *.log; do cp -- "$f" "reports/{{name}}-$f"; done'
|
||||
sh 'ls reports'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
archiveArtifacts artifacts: 'reports/*.log'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
stage("{{name}}") {
|
||||
agent {
|
||||
docker {
|
||||
image '{{{dockerimage}}}'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
when {
|
||||
expression {
|
||||
params.BUILD_IMPLEMENTATION == 'all' || params.BUILD_IMPLEMENTATION == '{{name}}'
|
||||
}
|
||||
}
|
||||
environment {
|
||||
MITSCHEME_LIBRARY_PATH = "${env.MITSCHEME_LIBRARY_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
TR7_LIB_PATH = "${env.TR7_LIB_PATH}:${env.PWD}:${env.PWD}/srfi"
|
||||
}
|
||||
steps {
|
||||
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
|
||||
sh 'find . -maxdepth 1 -name "*.log" -delete'
|
||||
sh 'find . -name "*.so" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
sh 'find . -name "*.o" -delete'
|
||||
unstash 'tests'
|
|
@ -0,0 +1,49 @@
|
|||
pipeline {
|
||||
|
||||
agent {
|
||||
dockerfile {
|
||||
filename 'Dockerfile.jenkins'
|
||||
dir '.'
|
||||
args '--privileged -v /var/run/docker.sock:/var/run/docker.sock'
|
||||
}
|
||||
}
|
||||
|
||||
options {
|
||||
buildDiscarder(logRotator(numToKeepStr: '10', artifactNumToKeepStr: '10'))
|
||||
}
|
||||
|
||||
parameters {
|
||||
choice(name: 'BUILD_IMPLEMENTATION',
|
||||
description: 'Build',
|
||||
choices: [
|
||||
'all',
|
||||
'chibi',
|
||||
'chicken',
|
||||
'cyclone',
|
||||
'gambit',
|
||||
'gauche',
|
||||
'guile',
|
||||
'kawa',
|
||||
'loko',
|
||||
'mit-scheme',
|
||||
'sagittarius',
|
||||
'stklos',
|
||||
'skint',
|
||||
'tr7',
|
||||
])
|
||||
}
|
||||
|
||||
stages {
|
||||
|
||||
stage("Init") {
|
||||
steps {
|
||||
sh 'rm -rf srfi-test && git clone https://github.com/srfi-explorations/srfi-test.git'
|
||||
sh 'mkdir -p reports'
|
||||
sh 'touch reports/placeholder'
|
||||
stash name: 'reports', includes: 'reports/*'
|
||||
sh 'echo "<h1>Test results</h1>" > reports/results.html'
|
||||
sh '(cd srfi-test && make clean build)'
|
||||
sh 'tree srfi-test'
|
||||
stash name: 'tests', includes: 'srfi-test/*'
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
clean:
|
||||
find . -name "*.so" -delete
|
||||
find . -name "*.c" -delete
|
||||
find . -name "*.o*" -delete
|
||||
find . -name "*.so" -delete
|
||||
find . -name "*.dep" -delete
|
||||
find . -name "*.zo" -delete
|
||||
find . -name "*.meta" -delete
|
||||
find . -name "compiled" -delete
|
||||
find . -name "srfi.*.sld" -delete
|
||||
find . -name "srfi.*.scm" -delete
|
||||
find . -name "srfi-*.sld" -delete
|
||||
find . -name "srfi.*.import.scm" -delete
|
||||
find . -name "srfi-*.import.scm" -delete
|
||||
find . -name "*.log" -delete
|
||||
find . -name "test-prefix.txt" -delete
|
|
@ -0,0 +1,4 @@
|
|||
test-{{name}}-{{test-name}}:
|
||||
{{#library-command}}docker run -it -v ${PWD}:/workdir:z schemers/{{name}} bash -c "cd workdir && {{{library-command}}}"{{/library-command}}
|
||||
docker run -it -v ${PWD}:/workdir:z schemers/{{name}} bash -c "cd workdir && {{{command}}}"
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
</table>
|
||||
<ul>
|
||||
Numbers
|
||||
<li># of expected passes</li>
|
||||
<li># of expected failures</li>
|
||||
<li># of unexpected failures</li>
|
||||
<li># of skipped tests</li>
|
||||
</ul>
|
||||
<ul>
|
||||
Colors
|
||||
<li>Red: # of unexpected failures > 0</li>
|
||||
<li>Yellow: # of skipped tests > 0</li>
|
||||
<li>Green: none of the above</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,6 @@
|
|||
<td style="background-color:{{color}}">
|
||||
{{expected-passes}}
|
||||
{{expected-failures}}
|
||||
{{unexpected-failures}}
|
||||
{{skipped-tests}}
|
||||
</td>
|
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>R7RS-SRFI Test results</title>
|
||||
<style>
|
||||
tr:nth-child(even) {
|
||||
background-color: #D6EEEE;
|
||||
}
|
||||
td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<table>
|
||||
<caption>R7RS-SRFI Test results</caption>
|
|
@ -0,0 +1,985 @@
|
|||
;; Copyright (c) 2010-2020 Alex Shinn. All rights reserved.
|
||||
;; BSD-style license: http://synthcode.com/license.txt
|
||||
|
||||
;;> Simple but extensible testing framework with advanced reporting.
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; list utilities
|
||||
|
||||
;; Simplified version of SRFI-1 any.
|
||||
(define (any pred ls)
|
||||
(and (pair? ls)
|
||||
(or (pred (car ls))
|
||||
(any pred (cdr ls)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; exception utilities
|
||||
|
||||
(define (warning msg . args)
|
||||
(display msg (current-error-port))
|
||||
(for-each (lambda (x)
|
||||
(write-char #\space (current-error-port))
|
||||
(write x (current-error-port)))
|
||||
args)
|
||||
(newline (current-error-port)))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; string utilities
|
||||
|
||||
(define (string-search pat str)
|
||||
(let* ((pat-len (string-length pat))
|
||||
(limit (- (string-length str) pat-len)))
|
||||
(let lp1 ((i 0))
|
||||
(cond
|
||||
((>= i limit) #f)
|
||||
(else
|
||||
(let lp2 ((j i) (k 0))
|
||||
(cond ((>= k pat-len) #t)
|
||||
((not (eqv? (string-ref str j) (string-ref pat k)))
|
||||
(lp1 (+ i 1)))
|
||||
(else (lp2 (+ j 1) (+ k 1))))))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; test interface
|
||||
|
||||
;;> \section{Testing}
|
||||
|
||||
;;> \macro{(test [name] expect expr)}
|
||||
|
||||
;;> The primary interface to testing. Evaluate \var{expr} and check
|
||||
;;> that it is equal to \var{expect}, and report the result, using
|
||||
;;> \var{name} or a printed summary of \var{expr}.
|
||||
;;>
|
||||
;;> If used inside a group this will contribute to the overall group
|
||||
;;> reporting, but can be used standalone:
|
||||
;;>
|
||||
;;> \example{(test 4 (+ 2 2))}
|
||||
;;> \example{(test "add two and two" 4 (+ 2 2))}
|
||||
;;> \example{(test 3 (+ 2 2))}
|
||||
;;> \example{(test 4 (+ 2 "2"))}
|
||||
;;>
|
||||
;;> The equality comparison is made with
|
||||
;;> \scheme{current-test-comparator}, defaulting to
|
||||
;;> \scheme{test-equal?}, which is the same as \scheme{equal?} but
|
||||
;;> more permissive on floating point comparisons). Returns the
|
||||
;;> status of the test (one of the symbols \scheme{'PASS},
|
||||
;;> \scheme{'FAIL}, \scheme{'SKIP}, \scheme{'ERROR}).
|
||||
|
||||
(define-syntax test
|
||||
(syntax-rules (quote)
|
||||
((test expect expr)
|
||||
(test #f expect expr))
|
||||
((test name expect (expr ...))
|
||||
(test-propagate-info name expect (expr ...) ()))
|
||||
((test name 'expect expr)
|
||||
(test-propagate-info name 'expect expr ()))
|
||||
((test name (expect ...) expr)
|
||||
(test-syntax-error
|
||||
'test
|
||||
"the test expression should come last: (test <expected> (<expr> ...)) "
|
||||
(test name (expect ...) expr)))
|
||||
((test name expect expr)
|
||||
(test-propagate-info name expect expr ()))
|
||||
((test a ...)
|
||||
(test-syntax-error 'test "test requires 2 or 3 arguments" (test a ...)))))
|
||||
|
||||
;;> \macro{(test-equal equal [name] expect expr)}
|
||||
|
||||
;;> Equivalent to test, using \var{equal} for comparison instead of
|
||||
;;> \scheme{equal?}.
|
||||
|
||||
(define-syntax test-equal
|
||||
(syntax-rules ()
|
||||
((test-equal equal . args)
|
||||
(parameterize ((current-test-comparator equal))
|
||||
(test . args)))))
|
||||
|
||||
;;> \macro{(test-assert [name] expr)}
|
||||
|
||||
;;> Like \scheme{test} but evaluates \var{expr} and checks that it's true.
|
||||
|
||||
(define-syntax test-assert
|
||||
(syntax-rules ()
|
||||
((_ expr)
|
||||
(test-assert #f expr))
|
||||
((_ name expr)
|
||||
(test-propagate-info name #f expr ((assertion . #t))))
|
||||
((test a ...)
|
||||
(test-syntax-error 'test-assert "1 or 2 arguments required"
|
||||
(test a ...)))))
|
||||
|
||||
;;> \macro{(test-not [name] expr)}
|
||||
|
||||
;;> Like \scheme{test} but evaluates \var{expr} and checks that it's false.
|
||||
|
||||
(define-syntax test-not
|
||||
(syntax-rules ()
|
||||
((_ expr) (test-assert (not expr)))
|
||||
((_ name expr) (test-assert name (not expr)))))
|
||||
|
||||
;;> \macro{(test-values [name] expect expr)}
|
||||
|
||||
;;> Like \scheme{test} but \var{expect} and \var{expr} can both
|
||||
;;> return multiple values.
|
||||
|
||||
(define-syntax test-values
|
||||
(syntax-rules ()
|
||||
((_ expect expr)
|
||||
(test-values #f expect expr))
|
||||
((_ name expect expr)
|
||||
(test name (call-with-values (lambda () expect) (lambda results results))
|
||||
(call-with-values (lambda () expr) (lambda results results))))))
|
||||
|
||||
;;> \macro{(test-error [name] expr)}
|
||||
|
||||
;;> Like \scheme{test} but evaluates \var{expr} and checks that it
|
||||
;;> raises an error.
|
||||
|
||||
(define-syntax test-error
|
||||
(syntax-rules ()
|
||||
((_ expr)
|
||||
(test-error #f expr))
|
||||
((_ name expr)
|
||||
(test-propagate-info name #f expr ((expect-error . #t))))
|
||||
((test a ...)
|
||||
(test-syntax-error 'test-error "1 or 2 arguments required"
|
||||
(test a ...)))))
|
||||
|
||||
;;> Low-level macro to pass alist info to the underlying \var{test-run}.
|
||||
|
||||
(define-syntax test-propagate-info
|
||||
(syntax-rules ()
|
||||
;; TODO: Extract interesting variables so we can show their values
|
||||
;; on failure. Vars are empty for now.
|
||||
((test-propagate-info name expect expr info)
|
||||
(test-vars () name expect expr info))))
|
||||
|
||||
(define-syntax test-vars
|
||||
(syntax-rules ()
|
||||
((_ (vars ...) n expect expr ((key . val) ...))
|
||||
(test-run (lambda () expect)
|
||||
(lambda () expr)
|
||||
`((name . ,n)
|
||||
(source . expr)
|
||||
(var-names . (vars ...))
|
||||
(var-values . ,(list vars ...))
|
||||
(key . val) ...)))))
|
||||
|
||||
;;> The procedural interface to testing. \var{expect} and \var{expr}
|
||||
;;> should be thunks, and \var{info} is an alist of properties used in
|
||||
;;> test reporting.
|
||||
|
||||
(define (test-run expect expr info)
|
||||
(let ((info (test-expand-info info)))
|
||||
(if (and (cond ((current-test-group)
|
||||
=> (lambda (g) (not (test-group-ref g 'skip-group?))))
|
||||
(else #t))
|
||||
(or (and (not (any (lambda (f) (f info)) (current-test-removers)))
|
||||
(or (pair? (current-test-removers))
|
||||
(null? (current-test-filters))))
|
||||
(any (lambda (f) (f info)) (current-test-filters))))
|
||||
((current-test-applier) expect expr info)
|
||||
((current-test-skipper) info))))
|
||||
|
||||
;;> Returns true if either \scheme{(equal? expect res)}, or
|
||||
;;> \var{expect} is inexact and \var{res} is within
|
||||
;;> \scheme{current-test-epsilon} of \var{expect}.
|
||||
|
||||
(define (test-equal? expect res)
|
||||
(or (equal? expect res)
|
||||
(if (real? expect)
|
||||
(and (inexact? expect)
|
||||
(real? res)
|
||||
;; tests which expect an inexact value can
|
||||
;; accept an equivalent exact value
|
||||
;; (inexact? res)
|
||||
(approx-equal? expect res (current-test-epsilon)))
|
||||
(and (complex? res)
|
||||
(complex? expect)
|
||||
(test-equal? (real-part expect) (real-part res))
|
||||
(test-equal? (imag-part expect) (imag-part res))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; group interface
|
||||
|
||||
;;> \section{Test Groups}
|
||||
|
||||
;;> Tests can be collected in groups for
|
||||
|
||||
;;> Wraps \var{body} as a single test group, which can be filtered
|
||||
;;> and summarized separately.
|
||||
|
||||
;;> \example{
|
||||
;;> (test-group "pi"
|
||||
;;> (test 3.14159 (acos -1))
|
||||
;;> (test 3 (acos -1))
|
||||
;;> (test 3.14159 (acos "-1")))
|
||||
;;> }
|
||||
|
||||
(define-syntax test-group
|
||||
(syntax-rules ()
|
||||
((_ name-expr body ...)
|
||||
(let ((name name-expr)
|
||||
(old-group (current-test-group)))
|
||||
(when (not (string? name))
|
||||
(error "a name is required, got " 'name-expr name))
|
||||
(test-begin name)
|
||||
(guard
|
||||
(exn
|
||||
(else
|
||||
(warning "error in group outside of tests")
|
||||
(print-exception exn (current-error-port))
|
||||
(test-group-inc! (current-test-group) 'count)
|
||||
(test-group-inc! (current-test-group) 'ERROR)
|
||||
(test-failure-count (+ 1 (test-failure-count)))))
|
||||
body ...)
|
||||
(test-end name)
|
||||
(current-test-group old-group)))))
|
||||
|
||||
;;> Begin testing a new group until the closing \scheme{(test-end)}.
|
||||
|
||||
(define (test-begin . o)
|
||||
(let* ((name (if (pair? o) (car o) ""))
|
||||
(parent (current-test-group))
|
||||
(group (make-test-group name parent)))
|
||||
;; include a newline if we are directly nested in a parent with no
|
||||
;; tests yet
|
||||
(when (and parent
|
||||
(zero? (test-group-ref parent 'subgroups-count 0))
|
||||
(not (test-group-ref parent 'verbose)))
|
||||
(newline))
|
||||
;; header
|
||||
(cond
|
||||
((test-group-ref group 'skip-group?)
|
||||
(display (make-string (or (test-group-indent-width group) 0) #\space))
|
||||
(display (strikethrough (bold (string-append name ":"))))
|
||||
(display " SKIP"))
|
||||
((test-group-ref group 'verbose)
|
||||
(display
|
||||
(test-header-line
|
||||
(string-append "testing " name)
|
||||
(or (test-group-indent-width group) 0))))
|
||||
(else
|
||||
(display
|
||||
(string-append
|
||||
(make-string (or (test-group-indent-width group) 0)
|
||||
#\space)
|
||||
(bold (string-append name ": "))))))
|
||||
;; set the current test group
|
||||
(current-test-group group)))
|
||||
|
||||
;;> Ends testing group introduced with \scheme{(test-begin)}, and
|
||||
;;> summarizes the results. The \var{name} is optional, but if
|
||||
;;> present should match the corresponding \scheme{test-begin} name,
|
||||
;;> or a warning is printed.
|
||||
|
||||
(define (test-end . o)
|
||||
(let ((name (and (pair? o) (car o))))
|
||||
(cond
|
||||
((current-test-group)
|
||||
=> (lambda (group)
|
||||
(when (and name (not (equal? name (test-group-name group))))
|
||||
(warning "mismatched test-end:" name (test-group-name group)))
|
||||
(let ((parent (test-group-ref group 'parent)))
|
||||
(when (and (test-group-ref group 'skip-group?)
|
||||
(zero? (test-group-ref group 'subgroups-count 0)))
|
||||
(newline))
|
||||
;; only report if there's something to say
|
||||
((current-test-group-reporter) group)
|
||||
(when parent
|
||||
(test-group-inc! parent 'subgroups-count)
|
||||
(cond
|
||||
((test-group-ref group 'skip-group?)
|
||||
(test-group-inc! parent 'subgroups-skip))
|
||||
((and (zero? (test-group-ref group 'FAIL 0))
|
||||
(zero? (test-group-ref group 'ERROR 0))
|
||||
(= (test-group-ref group 'subgroups-pass 0)
|
||||
(test-group-ref group 'subgroups-count 0)))
|
||||
(test-group-inc! parent 'subgroups-pass))))
|
||||
(current-test-group parent)
|
||||
group))))))
|
||||
|
||||
;;> Exits with a failure status if any tests have failed,
|
||||
;;> and a successful status otherwise.
|
||||
|
||||
(define (test-exit)
|
||||
(when (current-test-group)
|
||||
(warning "calling test-exit with unfinished test group:"
|
||||
(test-group-name (current-test-group))))
|
||||
(exit (zero? (test-failure-count))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; utilities
|
||||
|
||||
(define-syntax test-syntax-error
|
||||
(syntax-rules ()
|
||||
((_) (syntax-error "invalid use of test-syntax-error"))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; test-group representation
|
||||
|
||||
;;> \section{Accessors}
|
||||
|
||||
;; (name (prop value) ...)
|
||||
(define (make-test-group name . o)
|
||||
(let ((parent (and (pair? o) (car o)))
|
||||
(group (list name (cons 'start-time (current-second)))))
|
||||
(test-group-set! group 'parent parent)
|
||||
(test-group-set! group 'verbose
|
||||
(if parent
|
||||
(test-group-ref parent 'verbose)
|
||||
(current-test-verbosity)))
|
||||
(test-group-set! group 'level
|
||||
(if parent
|
||||
(+ 1 (test-group-ref parent 'level 0))
|
||||
0))
|
||||
(test-group-set!
|
||||
group
|
||||
'skip-group?
|
||||
(and (or (and parent (test-group-ref parent 'skip-group?))
|
||||
(any (lambda (f) (f group)) (current-test-group-removers))
|
||||
(and (null? (current-test-group-removers))
|
||||
(pair? (current-test-group-filters))))
|
||||
(not (any (lambda (f) (f group)) (current-test-group-filters)))))
|
||||
group))
|
||||
|
||||
;;> Returns the name of a test group info object.
|
||||
|
||||
(define (test-group-name group) (car group))
|
||||
|
||||
;;> Returns the value of a \var{field} in a test var{group} info
|
||||
;;> object. \var{field} should be a symbol, and predefined fields
|
||||
;;> include \scheme{parent}, \scheme{verbose}, \scheme{level},
|
||||
;;> \scheme{start-time}, \scheme{skip-group?}, \scheme{count},
|
||||
;;> \scheme{total-pass}, \scheme{total-fail}, \scheme{total-error}.
|
||||
|
||||
(define (test-group-ref group field . o)
|
||||
(if group
|
||||
(apply assq-ref (cdr group) field o)
|
||||
(and (pair? o) (car o))))
|
||||
|
||||
;;> Sets the value of a \var{field} in a test \var{group} info object.
|
||||
|
||||
(define (test-group-set! group field value)
|
||||
(cond
|
||||
((assq field (cdr group))
|
||||
=> (lambda (x) (set-cdr! x value)))
|
||||
(else (set-cdr! group (cons (cons field value) (cdr group))))))
|
||||
|
||||
;;> Increments the value of a \var{field} in a test \var{group} info
|
||||
;;> object by \var{amount}, defaulting to 1.
|
||||
|
||||
(define (test-group-inc! group field . o)
|
||||
(let ((amount (if (pair? o) (car o) 1)))
|
||||
(cond
|
||||
((assq field (cdr group))
|
||||
=> (lambda (x) (set-cdr! x (+ amount (cdr x)))))
|
||||
(else (set-cdr! group (cons (cons field amount) (cdr group)))))))
|
||||
|
||||
;;> Updates a \var{field} in a test group info object by consing
|
||||
;;> \var{value} onto it.
|
||||
|
||||
(define (test-group-push! group field value)
|
||||
(cond
|
||||
((assq field (cdr group))
|
||||
=> (lambda (x) (set-cdr! x (cons value (cdr x)))))
|
||||
(else (set-cdr! group (cons (cons field (list value)) (cdr group))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; utilities
|
||||
|
||||
(define (assq-ref ls key . o)
|
||||
(cond ((assq key ls) => cdr)
|
||||
((pair? o) (car o))
|
||||
(else #f)))
|
||||
|
||||
(define (approx-equal? a b epsilon)
|
||||
(cond
|
||||
((> (abs a) (abs b))
|
||||
(approx-equal? b a epsilon))
|
||||
((zero? a)
|
||||
(< (abs b) epsilon))
|
||||
(else
|
||||
(< (abs (/ (- a b) b)) epsilon))))
|
||||
|
||||
(define (call-with-output-string proc)
|
||||
(let ((out (open-output-string)))
|
||||
(proc out)
|
||||
(get-output-string out)))
|
||||
|
||||
;; partial pretty printing to abbreviate `quote' forms and the like
|
||||
(define (write-to-string x)
|
||||
(call-with-output-string
|
||||
(lambda (out)
|
||||
(let wr ((x x))
|
||||
(if (pair? x)
|
||||
(cond
|
||||
((and (symbol? (car x)) (pair? (cdr x)) (null? (cddr x))
|
||||
(assq (car x)
|
||||
'((quote . "'") (quasiquote . "`")
|
||||
(unquote . ",") (unquote-splicing . ",@"))))
|
||||
=> (lambda (s) (display (cdr s) out) (wr (cadr x))))
|
||||
(else
|
||||
(display "(" out)
|
||||
(wr (car x))
|
||||
(let lp ((ls (cdr x)))
|
||||
(cond ((pair? ls)
|
||||
(display " " out)
|
||||
(wr (car ls))
|
||||
(lp (cdr ls)))
|
||||
((not (null? ls))
|
||||
(display " . " out)
|
||||
(write ls out))))
|
||||
(display ")" out)))
|
||||
(write x out))))))
|
||||
|
||||
(define (display-to-string x)
|
||||
(if (string? x) x (call-with-output-string (lambda (out) (display x out)))))
|
||||
|
||||
;; if we need to truncate, try first dropping let's to get at the
|
||||
;; heart of the expression
|
||||
(define (truncate-source x width . o)
|
||||
(let* ((str (write-to-string x))
|
||||
(len (string-length str)))
|
||||
(cond
|
||||
((<= len width)
|
||||
str)
|
||||
((and (pair? x) (eq? 'let (car x)))
|
||||
(if (and (pair? o) (car o))
|
||||
(truncate-source (car (reverse x)) width #t)
|
||||
(string-append "..."
|
||||
(truncate-source (car (reverse x)) (- width 3) #t))))
|
||||
((and (pair? x) (eq? 'call-with-current-continuation (car x)))
|
||||
(truncate-source (cons 'call/cc (cdr x)) width (and (pair? o) (car o))))
|
||||
((and (pair? x) (eq? 'call-with-values (car x)))
|
||||
(string-append
|
||||
"..."
|
||||
(truncate-source (if (and (pair? (cadr x)) (eq? 'lambda (car (cadr x))))
|
||||
(car (reverse (cadr x)))
|
||||
(cadr x))
|
||||
(- width 3)
|
||||
#t)))
|
||||
(else
|
||||
(string-append
|
||||
(substring str 0 (min (max 0 (- width 3)) (string-length str)))
|
||||
"...")))))
|
||||
|
||||
(define (test-get-name! info)
|
||||
(or
|
||||
(assq-ref info 'name)
|
||||
(assq-ref info 'gen-name)
|
||||
(let ((name
|
||||
(cond
|
||||
((assq 'source info)
|
||||
=> (lambda (src)
|
||||
(truncate-source (cdr src) (- (current-column-width) 12))))
|
||||
((current-test-group)
|
||||
=> (lambda (g)
|
||||
(display "no source in: " (current-error-port))
|
||||
(write info (current-error-port))
|
||||
(display "\n" (current-error-port))
|
||||
(string-append
|
||||
"test-"
|
||||
(number->string (test-group-ref g 'count 0)))))
|
||||
(else ""))))
|
||||
(if (pair? info)
|
||||
(set-cdr! info (cons (cons 'gen-name name) (cdr info))))
|
||||
name)))
|
||||
|
||||
(define (test-print-name info . indent)
|
||||
(let ((width (- (current-column-width)
|
||||
(or (and (pair? indent) (car indent)) 0)))
|
||||
(name (test-get-name! info)))
|
||||
(display name)
|
||||
(display " ")
|
||||
(let ((diff (- width 9 (string-length name))))
|
||||
(cond
|
||||
((positive? diff)
|
||||
(display (make-string diff #\.)))))
|
||||
(display " ")
|
||||
(flush-output-port)))
|
||||
|
||||
(define (test-group-indent-width group)
|
||||
(let ((level (max 0 (+ 1 (- (test-group-ref group 'level 0)
|
||||
(test-first-indentation))))))
|
||||
(* 4 (min level (test-max-indentation)))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
(define (test-expand-info info)
|
||||
(let ((expr (assq-ref info 'source)))
|
||||
(if (and (pair? expr)
|
||||
(pair-source expr)
|
||||
(not (assq-ref info 'line-number)))
|
||||
`((file-name . ,(car (pair-source expr)))
|
||||
(line-number . ,(cdr (pair-source expr)))
|
||||
,@info)
|
||||
info)))
|
||||
|
||||
(define (test-default-applier expect expr info)
|
||||
(let* ((group (current-test-group))
|
||||
(indent (and group (test-group-indent-width group))))
|
||||
(cond
|
||||
((or (not group) (test-group-ref group 'verbose))
|
||||
(if (and indent (positive? indent))
|
||||
(display (make-string indent #\space)))
|
||||
(test-print-name info indent)))
|
||||
(let ((expect-val
|
||||
(guard
|
||||
(exn
|
||||
(else
|
||||
(warning "bad expect value")
|
||||
(print-exception exn (current-error-port))
|
||||
#f))
|
||||
(expect))))
|
||||
(guard
|
||||
(exn
|
||||
(else
|
||||
((current-test-reporter)
|
||||
(if (assq-ref info 'expect-error) 'PASS 'ERROR)
|
||||
(append `((exception . ,exn)) info))))
|
||||
(let ((res (expr)))
|
||||
(let ((status
|
||||
(if (and (not (assq-ref info 'expect-error))
|
||||
(if (assq-ref info 'assertion)
|
||||
res
|
||||
((current-test-comparator) expect-val res)))
|
||||
'PASS
|
||||
'FAIL))
|
||||
(info `((result . ,res) (expected . ,expect-val) ,@info)))
|
||||
((current-test-reporter) status info)))))))
|
||||
|
||||
(define (test-default-skipper info)
|
||||
((current-test-reporter) 'SKIP info))
|
||||
|
||||
(define (test-status-color status)
|
||||
(case status
|
||||
((ERROR) (lambda (x) (underline (red x))))
|
||||
((FAIL) red)
|
||||
((SKIP) yellow)
|
||||
(else (lambda (x) x))))
|
||||
|
||||
(define (test-status-message status)
|
||||
((test-status-color status) (symbol->string status)))
|
||||
|
||||
(define (test-status-code status)
|
||||
((test-status-color status)
|
||||
;; alternatively: ❗, ✗, ‒, ✓
|
||||
;; unfortunately, these have ambiguous width
|
||||
(case status
|
||||
((ERROR) "!")
|
||||
((FAIL) "x")
|
||||
((SKIP) "-")
|
||||
(else "."))))
|
||||
|
||||
(define (display-expected/actual expected actual)
|
||||
(let* ((e-str (write-to-string expected))
|
||||
(a-str (write-to-string actual))
|
||||
(diff (diff e-str a-str read-char)))
|
||||
(write-string "expected ")
|
||||
(write-string (edits->string/color (car diff) (car (cddr diff)) 1))
|
||||
(write-string " but got ")
|
||||
(write-string (edits->string/color (cadr diff) (car (cddr diff)) 2))))
|
||||
|
||||
(define (test-print-explanation indent status info)
|
||||
(cond
|
||||
((eq? status 'ERROR)
|
||||
(display indent)
|
||||
(cond ((assq 'exception info)
|
||||
=> (lambda (e)
|
||||
(print-exception (cdr e) (current-output-port))))))
|
||||
((and (eq? status 'FAIL) (assq-ref info 'assertion))
|
||||
(display indent)
|
||||
(display "assertion failed\n"))
|
||||
((and (eq? status 'FAIL) (assq-ref info 'expect-error))
|
||||
(display indent)
|
||||
(display "expected an error but got ")
|
||||
(write (assq-ref info 'result)) (newline))
|
||||
((eq? status 'FAIL)
|
||||
(display indent)
|
||||
(display-expected/actual (assq-ref info 'expected) (assq-ref info 'result))
|
||||
(newline)))
|
||||
;; print variables
|
||||
(cond
|
||||
((and (memq status '(FAIL ERROR)) (assq-ref info 'var-names))
|
||||
=> (lambda (names)
|
||||
(let ((values (assq-ref info 'var-values)))
|
||||
(if (and (pair? names)
|
||||
(pair? values)
|
||||
(= (length names) (length values)))
|
||||
(let ((indent2
|
||||
(string-append indent (make-string 2 #\space))))
|
||||
(for-each
|
||||
(lambda (name value)
|
||||
(display indent2) (write name) (display ": ")
|
||||
(write value) (newline))
|
||||
names values))))))))
|
||||
|
||||
(define (test-print-source indent status info)
|
||||
(case status
|
||||
((FAIL ERROR)
|
||||
(cond
|
||||
((assq-ref info 'line-number)
|
||||
=> (lambda (line)
|
||||
(display " on line ")
|
||||
(write line)
|
||||
(cond ((assq-ref info 'file-name)
|
||||
=> (lambda (file) (display " of file ") (write file))))
|
||||
(newline))))
|
||||
(cond
|
||||
((assq-ref info 'source)
|
||||
=> (lambda (s)
|
||||
(cond
|
||||
((or (assq-ref info 'name)
|
||||
(> (string-length (write-to-string s))
|
||||
(current-column-width)))
|
||||
(display (write-to-string s))
|
||||
(newline))))))
|
||||
(cond
|
||||
((assq-ref info 'values)
|
||||
=> (lambda (v)
|
||||
(for-each
|
||||
(lambda (v)
|
||||
(display " ") (display (car v))
|
||||
(display ": ") (write (cdr v)) (newline))
|
||||
v)))))))
|
||||
|
||||
(define (test-print-failure indent status info)
|
||||
;; display status explanation
|
||||
(test-print-explanation indent status info)
|
||||
;; display line, source and values info
|
||||
(test-print-source indent status info))
|
||||
|
||||
(define (test-header-line str . indent)
|
||||
(let* ((header (string-append
|
||||
(make-string (if (pair? indent) (car indent) 0) #\space)
|
||||
"-- " str " "))
|
||||
(len (string-length header)))
|
||||
(string-append (bold header)
|
||||
(make-string (max 0 (- (current-column-width) len)) #\-))))
|
||||
|
||||
(define (test-default-handler status info)
|
||||
(define indent
|
||||
(make-string
|
||||
(+ 4 (cond ((current-test-group)
|
||||
=> (lambda (group) (or (test-group-indent-width group) 0)))
|
||||
(else 0)))
|
||||
#\space))
|
||||
;; update group info
|
||||
(cond
|
||||
((current-test-group)
|
||||
=> (lambda (group)
|
||||
(if (not (eq? 'SKIP status))
|
||||
(test-group-inc! group 'count))
|
||||
(test-group-inc! group status)
|
||||
;; maybe wrap long status lines
|
||||
(let ((width (max (- (current-column-width)
|
||||
(or (test-group-indent-width group) 0))
|
||||
4))
|
||||
(column
|
||||
(+ (string-length (or (test-group-name group) ""))
|
||||
(or (test-group-ref group 'count) 0)
|
||||
1)))
|
||||
(if (and (zero? (modulo column width))
|
||||
(not (test-group-ref group 'verbose)))
|
||||
(display (string-append "\n" (string-copy indent 4))))))))
|
||||
;; update global failure count for exit status
|
||||
(cond
|
||||
((or (eq? status 'FAIL) (eq? status 'ERROR))
|
||||
(test-failure-count (+ 1 (test-failure-count)))))
|
||||
(cond
|
||||
((or (not (current-test-group))
|
||||
(test-group-ref (current-test-group) 'verbose))
|
||||
;; display status
|
||||
(display "[")
|
||||
(if (not (eq? status 'ERROR)) (display " ")) ; pad
|
||||
(display (test-status-message status))
|
||||
(display "]")
|
||||
(newline)
|
||||
(test-print-failure indent status info))
|
||||
((eq? status 'SKIP))
|
||||
(else
|
||||
(display (test-status-code status))
|
||||
(cond
|
||||
((and (memq status '(FAIL ERROR)) (current-test-group))
|
||||
=> (lambda (group)
|
||||
(test-group-push! group 'failures (list indent status info)))))
|
||||
(cond ((current-test-group)
|
||||
=> (lambda (group) (test-group-set! group 'trailing #t))))))
|
||||
(flush-output-port)
|
||||
status)
|
||||
|
||||
(define (test-default-group-reporter group)
|
||||
(define (plural word n)
|
||||
(if (= n 1) word (string-append word "s")))
|
||||
(define (percent n d)
|
||||
(string-append " (" (number->string (/ (round (* 1000.0 (/ n d))) 10))
|
||||
"%)"))
|
||||
(let* ((end-time (current-second))
|
||||
(start-time (test-group-ref group 'start-time))
|
||||
(duration (- end-time start-time))
|
||||
(base-count (or (test-group-ref group 'count) 0))
|
||||
(base-pass (or (test-group-ref group 'PASS) 0))
|
||||
(base-fail (or (test-group-ref group 'FAIL) 0))
|
||||
(base-err (or (test-group-ref group 'ERROR) 0))
|
||||
(skip (or (test-group-ref group 'SKIP) 0))
|
||||
(pass (+ base-pass (or (test-group-ref group 'total-pass) 0)))
|
||||
(fail (+ base-fail (or (test-group-ref group 'total-fail) 0)))
|
||||
(err (+ base-err (or (test-group-ref group 'total-error) 0)))
|
||||
(count (+ pass fail err))
|
||||
(subgroups-count (or (test-group-ref group 'subgroups-count) 0))
|
||||
(subgroups-skip (or (test-group-ref group 'subgroups-skip) 0))
|
||||
(subgroups-run (- subgroups-count subgroups-skip))
|
||||
(subgroups-pass (or (test-group-ref group 'subgroups-pass) 0))
|
||||
(indent (make-string (or (test-group-indent-width group) 0) #\space)))
|
||||
(if (and (not (test-group-ref group 'verbose))
|
||||
(test-group-ref group 'trailing))
|
||||
(newline))
|
||||
(cond
|
||||
((or (positive? count) (positive? subgroups-count))
|
||||
(if (not (= base-count (+ base-pass base-fail base-err)))
|
||||
(warning "inconsistent count:"
|
||||
base-count base-pass base-fail base-err))
|
||||
(cond
|
||||
((positive? count)
|
||||
(display indent)
|
||||
(display
|
||||
((if (= pass count) green (lambda (x) x))
|
||||
(string-append
|
||||
(number->string pass) " out of " (number->string count)
|
||||
(percent pass count))))
|
||||
(display
|
||||
(string-append
|
||||
(plural " test" pass) " passed in "
|
||||
(number->string duration) " seconds"
|
||||
(cond
|
||||
((zero? skip) "")
|
||||
(else (string-append " (" (number->string skip)
|
||||
(plural " test" skip) " skipped)")))
|
||||
".\n"))))
|
||||
(cond ((positive? fail)
|
||||
(display indent)
|
||||
(display
|
||||
(red
|
||||
(string-append
|
||||
(number->string fail) (plural " failure" fail)
|
||||
(percent fail count) ".\n")))))
|
||||
(cond ((positive? err)
|
||||
(display indent)
|
||||
(display
|
||||
((lambda (x) (underline (red x)))
|
||||
(string-append
|
||||
(number->string err) (plural " error" err)
|
||||
(percent err count) ".\n")))))
|
||||
(cond
|
||||
((not (test-group-ref group 'verbose))
|
||||
(for-each
|
||||
(lambda (failure)
|
||||
(display indent)
|
||||
(display (red
|
||||
(string-append (display-to-string (cadr failure)) ": ")))
|
||||
(display (test-get-name! (car (cddr failure))))
|
||||
(newline)
|
||||
(apply test-print-failure failure))
|
||||
(reverse (or (test-group-ref group 'failures) '())))))
|
||||
(cond
|
||||
((positive? subgroups-run)
|
||||
(display indent)
|
||||
(display
|
||||
((if (= subgroups-pass subgroups-run)
|
||||
green (lambda (x) x))
|
||||
(string-append
|
||||
(number->string subgroups-pass) " out of "
|
||||
(number->string subgroups-run)
|
||||
(percent subgroups-pass subgroups-run))))
|
||||
(display (plural " subgroup" subgroups-pass))
|
||||
(display " passed.\n")))))
|
||||
(cond
|
||||
((test-group-ref group 'verbose)
|
||||
(display
|
||||
(test-header-line
|
||||
(string-append "done testing " (or (test-group-name group) ""))
|
||||
(or (test-group-indent-width group) 0)))
|
||||
(newline)))
|
||||
(cond
|
||||
((test-group-ref group 'parent)
|
||||
=> (lambda (parent)
|
||||
(test-group-set! parent 'trailing #f)
|
||||
(test-group-inc! parent 'total-pass pass)
|
||||
(test-group-inc! parent 'total-fail fail)
|
||||
(test-group-inc! parent 'total-error err))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; parameters
|
||||
|
||||
;;> \section{Parameters}
|
||||
|
||||
;;> The current test group as started by \scheme{test-group} or
|
||||
;;> \scheme{test-begin}.
|
||||
|
||||
(define current-test-group (make-parameter #f))
|
||||
|
||||
;;> If true, show more verbose output per test. Inferred from the
|
||||
;;> environment variable TEST_VERBOSE.
|
||||
|
||||
(define current-test-verbosity
|
||||
(make-parameter
|
||||
(cond ((get-environment-variable "TEST_VERBOSE")
|
||||
=> (lambda (s) (not (member s '("" "0")))))
|
||||
(else #f))))
|
||||
|
||||
;;> The epsilon used for floating point comparisons.
|
||||
|
||||
(define current-test-epsilon (make-parameter 1e-5))
|
||||
|
||||
;;> The underlying comparator used in testing, defaults to
|
||||
;;> \scheme{test-equal?}.
|
||||
|
||||
(define current-test-comparator (make-parameter test-equal?))
|
||||
|
||||
;;> The test applier - what we do with non-skipped tests. Takes the
|
||||
;;> same signature as \scheme{test-run}, should be responsible for
|
||||
;;> evaluating the thunks, determining the status of the test, and
|
||||
;;> passing this information to \scheme{current-test-reporter}.
|
||||
|
||||
(define current-test-applier (make-parameter test-default-applier))
|
||||
|
||||
;;> The test skipper - what we do with non-skipped tests. This should
|
||||
;;> not evaluate the thunks and simply pass off to
|
||||
;;> \scheme{current-test-reporter}.
|
||||
|
||||
(define current-test-skipper (make-parameter test-default-skipper))
|
||||
|
||||
;;> Takes two arguments, the symbol status of the test and the info
|
||||
;;> alist. Reports the result of the test and updates bookkeeping in
|
||||
;;> the current test group for reporting.
|
||||
|
||||
(define current-test-reporter (make-parameter test-default-handler))
|
||||
|
||||
;;> Takes one argument, a test group, and prints a summary of the test
|
||||
;;> results for that group.
|
||||
|
||||
(define current-test-group-reporter
|
||||
(make-parameter test-default-group-reporter))
|
||||
|
||||
;;> A running count of all test failures and errors across all groups
|
||||
;;> (and threads). Used by \scheme{test-exit}.
|
||||
|
||||
(define test-failure-count (make-parameter 0))
|
||||
|
||||
(define test-first-indentation
|
||||
(make-parameter
|
||||
(or (cond ((get-environment-variable "TEST_FIRST_INDENTATION")
|
||||
=> string->number)
|
||||
(else #f))
|
||||
1)))
|
||||
|
||||
(define test-max-indentation
|
||||
(make-parameter
|
||||
(or (cond ((get-environment-variable "TEST_MAX_INDENTATION")
|
||||
=> string->number)
|
||||
(else #f))
|
||||
5)))
|
||||
|
||||
(define (string->info-matcher str)
|
||||
(lambda (info)
|
||||
(cond ((test-get-name! info)
|
||||
=> (lambda (n) (string-search str n)))
|
||||
(else #f))))
|
||||
|
||||
(define (string->group-matcher str)
|
||||
(lambda (group) (string-search str (test-group-name group))))
|
||||
|
||||
;; simplified version from SRFI 130
|
||||
(define (string-split str ch)
|
||||
(let ((end (string-length str)))
|
||||
(let lp ((from 0) (to 0) (res '()))
|
||||
(cond
|
||||
((>= to end)
|
||||
(reverse (if (> to from) (cons (substring str from to) res) res)))
|
||||
((eqv? ch (string-ref str to))
|
||||
(lp (+ to 1) (+ to 1) (cons (substring str from to) res)))
|
||||
(else
|
||||
(lp from (+ to 1) res))))))
|
||||
|
||||
(define (getenv-filter-list proc name)
|
||||
(cond
|
||||
((get-environment-variable name)
|
||||
=> (lambda (s)
|
||||
(let lp ((ls (string-split s #\,))
|
||||
(res '()))
|
||||
(cond
|
||||
((null? ls) (reverse res))
|
||||
(else
|
||||
(let* ((s (car ls))
|
||||
(f (guard
|
||||
(exn
|
||||
(else
|
||||
(warning
|
||||
(string-append "invalid filter '" s
|
||||
"' from environment variable: "
|
||||
name))
|
||||
(print-exception exn (current-error-port))
|
||||
#f))
|
||||
(proc s))))
|
||||
(lp (cdr ls) (if f (cons f res) res))))))))
|
||||
(else '())))
|
||||
|
||||
(define current-test-group-filters
|
||||
(make-parameter
|
||||
(getenv-filter-list string->group-matcher "TEST_GROUP_FILTER")))
|
||||
|
||||
(define current-test-group-removers
|
||||
(make-parameter
|
||||
(getenv-filter-list string->group-matcher "TEST_GROUP_REMOVE")))
|
||||
|
||||
;;> Parameters controlling which test groups are skipped. Each
|
||||
;;> parameter is a list of procedures of one argument, a test group
|
||||
;;> info, which can be queried with \var{test-group-name} and
|
||||
;;> \var{test-group-ref}. Analogous to SRFI 1, a filter selects a
|
||||
;;> group for inclusion and a removers for exclusion. The defaults
|
||||
;;> are set automatically from the environment variables
|
||||
;;> TEST_GROUP_FILTER and TEST_GROUP_REMOVE, which should be
|
||||
;;> comma-delimited lists of strings which are checked for a substring
|
||||
;;> match in the test group name. A test group is skipped if it does
|
||||
;;> not match any filter and:
|
||||
;;> \itemlist[
|
||||
;;> \item{its parent group is skipped, or}
|
||||
;;> \item{it matches a remover, or}
|
||||
;;> \item{no removers are specified but some filters are}
|
||||
;;> ]
|
||||
;;/
|
||||
|
||||
(define current-test-filters
|
||||
(make-parameter (getenv-filter-list string->info-matcher "TEST_FILTER")))
|
||||
|
||||
(define current-test-removers
|
||||
(make-parameter (getenv-filter-list string->info-matcher "TEST_REMOVE")))
|
||||
|
||||
;;> Parameters controlling which tests are skipped. Each parameter is
|
||||
;;> a list of procedures of one argument, a test info alist, which can
|
||||
;;> be queried with \scheme{test-get-name!} or \scheme{assq}.
|
||||
;;> Analogous to SRFI 1, a filter selects a test for inclusion and a
|
||||
;;> removers for exclusion. The defaults are set automatically from
|
||||
;;> the environment variables TEST_FILTER and TEST_REMOVE, which
|
||||
;;> should be comma-delimited lists of strings which are checked for a
|
||||
;;> substring match in the test name. A test is skipped if its group
|
||||
;;> is skipped, or if it does not match a filter and:
|
||||
;;> \itemlist[
|
||||
;;> \item{it matches a remover, or}
|
||||
;;> \item{no removers are specified but some filters are}
|
||||
;;> ]
|
||||
;;/
|
||||
|
||||
;;> Parameter controlling the current column width for test output,
|
||||
;;> can be set from the environment variable TEST_COLUMN_WIDTH,
|
||||
;;> otherwise defaults to 78. For portability of implementation (and
|
||||
;;> resulting output), does not attempt to use termios to determine
|
||||
;;> the actual available width.
|
||||
|
||||
(define current-column-width
|
||||
(make-parameter
|
||||
(or (cond ((get-environment-variable "TEST_COLUMN_WIDTH")
|
||||
=> string->number)
|
||||
(else #f))
|
||||
78)))
|
|
@ -0,0 +1,3 @@
|
|||
(define tests
|
||||
'(((name . "r7rs-test")
|
||||
(file . "r7rs-tests.scm"))))
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -euxo
|
||||
|
||||
rm -rf snow/srfi
|
||||
mkdir -p snow/srfi
|
||||
cd snow/srfi || exit 1
|
||||
wget https://raw.githubusercontent.com/srfi-explorations/r7rs-srfi/refs/heads/main/srfi/1.scm
|
||||
wget https://raw.githubusercontent.com/srfi-explorations/r7rs-srfi/refs/heads/main/srfi/1.sld
|
||||
wget https://raw.githubusercontent.com/srfi-explorations/r7rs-srfi/refs/heads/main/srfi/8.scm
|
||||
wget https://raw.githubusercontent.com/srfi-explorations/r7rs-srfi/refs/heads/main/srfi/8.sld
|
|
@ -0,0 +1,4 @@
|
|||
#/bin/sh
|
||||
|
||||
rm -rf r7rs-tests.scm
|
||||
wget https://raw.githubusercontent.com/ashinn/chibi-scheme/refs/heads/master/tests/r7rs-tests.scm
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
(define slurp-loop
|
||||
(lambda (line result)
|
||||
(if (eof-object? line)
|
||||
result
|
||||
(slurp-loop (read-line) (string-append result line (string #\newline))))))
|
||||
|
||||
(define slurp
|
||||
(lambda (path)
|
||||
(with-input-from-file
|
||||
path
|
||||
(lambda ()
|
||||
(slurp-loop (read-line) "")))))
|
||||
|
||||
(define string-starts-with?
|
||||
(lambda (str prefix)
|
||||
(and (>= (string-length str) (string-length prefix))
|
||||
(string=? (string-copy str 0 (string-length prefix)) prefix))))
|
||||
|
||||
(define number-of-line->number
|
||||
(lambda (str)
|
||||
(letrec
|
||||
((looper
|
||||
(lambda (chars result)
|
||||
(if (and (not (null? chars))
|
||||
(char-whitespace? (car chars)))
|
||||
(begin
|
||||
(string->number result))
|
||||
(looper (cdr chars) (string-append (string (car chars)) result ))))))
|
||||
(looper (reverse (string->list str)) ""))))
|
Loading…
Reference in New Issue