2026-07-18 18:48:23 +02:00
# include <iostream>
# include "error.h"
Recursion guard and static klammer checking
A klammer that reaches itself, directly or through a cycle, expanded
until the C++ stack was exhausted: the process died from SIGSEGV with no
message and no location. The former limit guarded only the top-level
fixed-point iteration, never the descent through klammer application. A
depth guard now raises a recursion error naming the klammer and where it
was applied. The same loop's termination test moves from "the katom list
stopped growing" to "a pass applied no klammer", since a klammer whose
body expands to nothing is a reduction that adds no katoms; exceeding the
round limit is now an error rather than a message followed by rendering a
document with live klammers still in it.
ktext --check locates every klammer application written in a document or
in a klammer body and checks name existence, argument count, option
names, and target coverage without applying anything, reporting all
problems at once. This is possible because Klammertext has no catcodes:
katom structure is fixed when a file is read, so a klammer body has a
determinate shape before it is expanded. The check therefore reaches what
the engine cannot -- the branch of a @cond that is not selected, and
bodies a given render never enters.
@cond's set of truth values is an open language question, so its meaning
is unchanged here; an unrecognized predicate now warns, giving its value
and location.
tst/ gains recursion_test.sh (7 cases) and check_test.sh (19 cases), and
this snapshot's test Makefile is generated from the shipped suite list so
the two cannot drift apart.
(from dev c27e63802406)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:41:43 +02:00
# include "check.h"
2026-07-18 18:48:23 +02:00
# include "command.h"
# include "log.h"
# include "argv.h"
# include "file.h"
# include "util.h"
# include "machine.h"
# include "show.h"
Klammerset: the @@@klammerset construct, its search path, and const correctness
The @@@klammerset system command formally declares a klammerset — a
named, logically related group of klammer definitions — with an
operative, idempotent declaration (:requires and :files load in order
at the declaration point, relative to the declaring file). A bare
symbol given to ktext -k, kdesc --input, or :requires resolves to
x/x.k on the search path: the document's directory, then
KLAMMERTEXT_KLAMMERSETS, then KLAMMERTEXT_HOME; kdesc --klammerset
lists the available sets. sks/sks.k is the first declared klammerset,
so `-k sks` loads the SKS by name. The engine's lookup classes were
renamed *_set → *_registry to keep the two concepts apart, and the
whole C++ tree now follows standard const-correctness conventions.
tst/ gains klammerset_test.sh (18 cases).
(from dev 64b1abf23e56)
2026-07-30 23:50:07 +02:00
# include "target_registry.h"
# include "klammerset_registry.h"
2026-07-18 18:48:23 +02:00
int main ( int argc , char * argv [ ] )
{
try {
set_verbose_level ( argc , argv ) ;
Argv args { } ;
args . req ( " filenames " , " Input files in Klammertext format. " , " 'list' " ) ;
args . opt ( " s " , " Text processed before input files. " , " input-string " , " " , " 'text' " ) ;
args . opt ( " t " , " Output target; default is general (unspecified) " , " target " ,
Klammerset: the @@@klammerset construct, its search path, and const correctness
The @@@klammerset system command formally declares a klammerset — a
named, logically related group of klammer definitions — with an
operative, idempotent declaration (:requires and :files load in order
at the declaration point, relative to the declaring file). A bare
symbol given to ktext -k, kdesc --input, or :requires resolves to
x/x.k on the search path: the document's directory, then
KLAMMERTEXT_KLAMMERSETS, then KLAMMERTEXT_HOME; kdesc --klammerset
lists the available sets. sks/sks.k is the first declared klammerset,
so `-k sks` loads the SKS by name. The engine's lookup classes were
renamed *_set → *_registry to keep the two concepts apart, and the
whole C++ tree now follows standard const-correctness conventions.
tst/ gains klammerset_test.sh (18 cases).
(from dev 64b1abf23e56)
2026-07-30 23:50:07 +02:00
Target_registry : : general_name , " 'word' " ) ;
2026-07-18 18:48:23 +02:00
args . opt ( " o " , " Output basename; meaning and default defined by target. " , " basename " ,
" " , " 'word' " ) ;
Klammerset: the @@@klammerset construct, its search path, and const correctness
The @@@klammerset system command formally declares a klammerset — a
named, logically related group of klammer definitions — with an
operative, idempotent declaration (:requires and :files load in order
at the declaration point, relative to the declaring file). A bare
symbol given to ktext -k, kdesc --input, or :requires resolves to
x/x.k on the search path: the document's directory, then
KLAMMERTEXT_KLAMMERSETS, then KLAMMERTEXT_HOME; kdesc --klammerset
lists the available sets. sks/sks.k is the first declared klammerset,
so `-k sks` loads the SKS by name. The engine's lookup classes were
renamed *_set → *_registry to keep the two concepts apart, and the
whole C++ tree now follows standard const-correctness conventions.
tst/ gains klammerset_test.sh (18 cases).
(from dev 64b1abf23e56)
2026-07-30 23:50:07 +02:00
args . opt ( " k " , " Klammerset symbol (resolved on the klammerset search path) or the pathname of a klammerset definition file; default is the Standard Klammer Set. With a value of \" none \" , no klammerset is loaded. " ,
2026-07-18 18:48:23 +02:00
" pathname " , " " , " 'word' " ) ;
args . flag ( " d " , " Display the output to the screen, rather than writing files. " ) ;
args . flag ( " m " , " Show the Klammermachine state at the beginning of processing. " ) ;
Recursion guard and static klammer checking
A klammer that reaches itself, directly or through a cycle, expanded
until the C++ stack was exhausted: the process died from SIGSEGV with no
message and no location. The former limit guarded only the top-level
fixed-point iteration, never the descent through klammer application. A
depth guard now raises a recursion error naming the klammer and where it
was applied. The same loop's termination test moves from "the katom list
stopped growing" to "a pass applied no klammer", since a klammer whose
body expands to nothing is a reduction that adds no katoms; exceeding the
round limit is now an error rather than a message followed by rendering a
document with live klammers still in it.
ktext --check locates every klammer application written in a document or
in a klammer body and checks name existence, argument count, option
names, and target coverage without applying anything, reporting all
problems at once. This is possible because Klammertext has no catcodes:
katom structure is fixed when a file is read, so a klammer body has a
determinate shape before it is expanded. The check therefore reaches what
the engine cannot -- the branch of a @cond that is not selected, and
bodies a given render never enters.
@cond's set of truth values is an open language question, so its meaning
is unchanged here; an unrecognized predicate now warns, giving its value
and location.
tst/ gains recursion_test.sh (7 cases) and check_test.sh (19 cases), and
this snapshot's test Makefile is generated from the shipped suite list so
the two cannot drift apart.
(from dev c27e63802406)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:41:43 +02:00
args . flag ( " check " , " Check every klammer application in the input and in the "
" body of every defined klammer, report all problems found, and exit "
" without producing output. " ) ;
2026-07-18 18:48:23 +02:00
args . opt ( " v " , " 'verbosity' " , " degree " , " 0 " , " 'verbosity' " ) ;
if ( show_usage ( argc , argv ) ) {
args . usage ( file_basename ( argv [ 0 ] ) ) ;
exit ( 1 ) ;
}
args . parse ( argc , argv ) ;
if ( verbose_level > 0 ) {
args . describe ( ) ;
}
std : : string input_text = args . as_string ( " s " ) ;
2026-07-26 23:22:59 +02:00
// argv boundaries are authoritative (a shell-quoted "my file.kt" is
// one element); group_filename_tokens adds the standalone-"/" list
// form, the existence rescue for unquoted spaces, and ~ expansion.
std : : vector < std : : string > input_filenames =
group_filename_tokens ( args . as_vector ( " filenames " ) ) ;
2026-07-18 18:48:23 +02:00
if ( input_text . empty ( ) & & input_filenames . empty ( ) ) {
throw Argument_error (
" You must specify input filenames and/or text " , Locator ( ) ) ;
}
auto [ target , output_dir , output_basename , output_filename ,
write_files , display_only ] =
2026-07-26 23:22:59 +02:00
parse_args ( input_filenames , args . as_string ( " t " ) ,
expand_tilde ( args . as_string ( " o " ) ) , args . as_bool ( " d " ) ) ;
2026-07-18 18:48:23 +02:00
Recursion guard and static klammer checking
A klammer that reaches itself, directly or through a cycle, expanded
until the C++ stack was exhausted: the process died from SIGSEGV with no
message and no location. The former limit guarded only the top-level
fixed-point iteration, never the descent through klammer application. A
depth guard now raises a recursion error naming the klammer and where it
was applied. The same loop's termination test moves from "the katom list
stopped growing" to "a pass applied no klammer", since a klammer whose
body expands to nothing is a reduction that adds no katoms; exceeding the
round limit is now an error rather than a message followed by rendering a
document with live klammers still in it.
ktext --check locates every klammer application written in a document or
in a klammer body and checks name existence, argument count, option
names, and target coverage without applying anything, reporting all
problems at once. This is possible because Klammertext has no catcodes:
katom structure is fixed when a file is read, so a klammer body has a
determinate shape before it is expanded. The check therefore reaches what
the engine cannot -- the branch of a @cond that is not selected, and
bodies a given render never enters.
@cond's set of truth values is an open language question, so its meaning
is unchanged here; an unrecognized predicate now warns, giving its value
and location.
tst/ gains recursion_test.sh (7 cases) and check_test.sh (19 cases), and
this snapshot's test Makefile is generated from the shipped suite list so
the two cannot drift apart.
(from dev c27e63802406)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:41:43 +02:00
// --check produces no output, so it needs neither a target nor -d.
// With no -t it checks every defined target, which is the more useful
// default here: "does this document hold together at all?"
bool check_only = args . as_bool ( " check " ) ;
2026-07-18 18:48:23 +02:00
if ( * ( output_filename . end ( ) - 1 ) = = ' * '
Recursion guard and static klammer checking
A klammer that reaches itself, directly or through a cycle, expanded
until the C++ stack was exhausted: the process died from SIGSEGV with no
message and no location. The former limit guarded only the top-level
fixed-point iteration, never the descent through klammer application. A
depth guard now raises a recursion error naming the klammer and where it
was applied. The same loop's termination test moves from "the katom list
stopped growing" to "a pass applied no klammer", since a klammer whose
body expands to nothing is a reduction that adds no katoms; exceeding the
round limit is now an error rather than a message followed by rendering a
document with live klammers still in it.
ktext --check locates every klammer application written in a document or
in a klammer body and checks name existence, argument count, option
names, and target coverage without applying anything, reporting all
problems at once. This is possible because Klammertext has no catcodes:
katom structure is fixed when a file is read, so a klammer body has a
determinate shape before it is expanded. The check therefore reaches what
the engine cannot -- the branch of a @cond that is not selected, and
bodies a given render never enters.
@cond's set of truth values is an open language question, so its meaning
is unchanged here; an unrecognized predicate now warns, giving its value
and location.
tst/ gains recursion_test.sh (7 cases) and check_test.sh (19 cases), and
this snapshot's test Makefile is generated from the shipped suite list so
the two cannot drift apart.
(from dev c27e63802406)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:41:43 +02:00
& & ! display_only & & ! check_only ) {
2026-07-18 18:48:23 +02:00
throw Argument_error (
" You must specify an output target or "
" display the results with the \" -d \" flag. " ,
Locator ( ) ) ;
}
Machine M ;
M . m_state . open_frame ( " ktext " ) ;
M . m_state . set ( " K_target " , target ) ;
M . m_state . set ( " K_output_dir " , output_dir ) ;
M . m_state . set ( " K_output_basename " , output_basename ) ;
M . m_state . set ( " K_stdout_only " , display_only ? " true " : " false " ) ;
2026-07-26 23:22:59 +02:00
// The list form uses the standalone-"/" separator (filenames may
// contain spaces); K_input_filename is the single root input file.
M . m_state . set ( " K_input_filenames " , join ( input_filenames , " / " ) ) ;
M . m_state . set ( " K_input_filename " ,
input_filenames . empty ( ) ? " " : input_filenames [ 0 ] ) ;
2026-07-18 18:48:23 +02:00
M . m_state . set ( " K_verbose_level " , std : : to_string ( verbose_level ) ) ;
if ( ! input_filenames . empty ( ) ) {
M . m_state . set (
" K_input_dir " , absolute_pathname ( file_directory ( input_filenames [ 0 ] ) ) ) ;
} else {
M . m_state . set ( " K_input_dir " , fs : : current_path ( ) . string ( ) ) ;
}
std : : string klammerset_filename = args . as_string ( " k " ) ;
if ( klammerset_filename ! = " none " ) {
if ( klammerset_filename . empty ( ) ) {
klammerset_filename = M . m_state . value ( " KLAMMERTEXT_HOME " ) + " /sks/sks.k " ;
Klammerset: the @@@klammerset construct, its search path, and const correctness
The @@@klammerset system command formally declares a klammerset — a
named, logically related group of klammer definitions — with an
operative, idempotent declaration (:requires and :files load in order
at the declaration point, relative to the declaring file). A bare
symbol given to ktext -k, kdesc --input, or :requires resolves to
x/x.k on the search path: the document's directory, then
KLAMMERTEXT_KLAMMERSETS, then KLAMMERTEXT_HOME; kdesc --klammerset
lists the available sets. sks/sks.k is the first declared klammerset,
so `-k sks` loads the SKS by name. The engine's lookup classes were
renamed *_set → *_registry to keep the two concepts apart, and the
whole C++ tree now follows standard const-correctness conventions.
tst/ gains klammerset_test.sh (18 cases).
(from dev 64b1abf23e56)
2026-07-30 23:50:07 +02:00
} else if ( is_klammerset_symbol ( klammerset_filename ) ) {
// A bare symbol resolves on the klammerset search path;
// the local stage is the input document's directory (the
// cwd when the input is a string).
klammerset_filename = resolve_klammerset_symbol (
klammerset_filename , M . m_state . value ( " K_input_dir " ) , Locator ( ) ) . string ( ) ;
2026-07-18 18:48:23 +02:00
}
K : : log ( 1 , " Reading klammerset filename: " + klammerset_filename ) ;
M . read ( fs : : path ( klammerset_filename ) ) ;
}
if ( ! input_text . empty ( ) ) {
M . read ( input_text + " \n " ) ;
}
for ( auto p : input_filenames ) {
M . read ( fs : : path ( p ) ) ;
}
if ( args . as_bool ( " m " ) ) {
std : : cout < < M < < " \n " ;
}
Recursion guard and static klammer checking
A klammer that reaches itself, directly or through a cycle, expanded
until the C++ stack was exhausted: the process died from SIGSEGV with no
message and no location. The former limit guarded only the top-level
fixed-point iteration, never the descent through klammer application. A
depth guard now raises a recursion error naming the klammer and where it
was applied. The same loop's termination test moves from "the katom list
stopped growing" to "a pass applied no klammer", since a klammer whose
body expands to nothing is a reduction that adds no katoms; exceeding the
round limit is now an error rather than a message followed by rendering a
document with live klammers still in it.
ktext --check locates every klammer application written in a document or
in a klammer body and checks name existence, argument count, option
names, and target coverage without applying anything, reporting all
problems at once. This is possible because Klammertext has no catcodes:
katom structure is fixed when a file is read, so a klammer body has a
determinate shape before it is expanded. The check therefore reaches what
the engine cannot -- the branch of a @cond that is not selected, and
bodies a given render never enters.
@cond's set of truth values is an open language question, so its meaning
is unchanged here; an unrecognized predicate now warns, giving its value
and location.
tst/ gains recursion_test.sh (7 cases) and check_test.sh (19 cases), and
this snapshot's test Makefile is generated from the shipped suite list so
the two cannot drift apart.
(from dev c27e63802406)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:41:43 +02:00
// --check reports statically, before anything is applied: all problems
// at once, including ones in @cond branches that are not selected and
// in klammer bodies that this render would never reach.
if ( check_only ) {
int errors = report_diagnostics ( check_machine ( M , target ) , std : : cout ) ;
return errors > 0 ? 1 : 0 ;
}
2026-07-18 18:48:23 +02:00
std : : string result = trim ( M . apply ( target ) ) ;
if ( display_only & & ! result . empty ( ) ) {
std : : cout < < result < < " \n " ;
} else if ( ! result . empty ( ) ) {
msg ( ) < < " Output filename: " < < output_filename < < " \n " ;
string_to_file ( output_filename , result + " \n " ) ;
( void ) K : : log ( 1 , " Wrote file: " + output_filename ) ;
}
}
catch ( Error & err ) {
err . print_message ( ) ;
std : : cout < < " \n " ;
return 1 ;
}
return 0 ;
}