#! /bin/sh # -*- tcl -*- \ exec tclsh "$0" ${1+"$@"} # TMC - Trival Magic Compiler # === = ===================== # Use cases # --------- # (-) Compilation of one or more files in magic(5) syntax into a # list of recognizers performing all the checks and mappings # encoded in them. # # Command syntax # -------------- # # Ad 1) tmc magic-file ?magic-file...? # # Compile all magic files list of recognizers, generate a script which # assigns the recognizers to $tests and $named and # write the script to stdout. # # Ad 2) tmc -merge tclfile magic-file ?magic-file...? # # Same as (1), but does not write to stdout. Instead the part of # the 'tclfile' delineated by marker lines containing "BEGIN # GENERATED CODE" and "END GENERATED CODE" is replaced with the # generated code. package require Tcl 8.5 set auto_path [linsert $auto_path 0 [file dirname [file normalize [info script]]]] ; # This directory set auto_path [linsert $auto_path 0 [file dirname [lindex $auto_path end]]] ; # and the one above #puts *\t[join $auto_path \n*\t] package require fileutil::magic::cfront # ### ### ### ######### ######### ######### ## Internal data and status namespace eval ::tmc { # Path to where the output goes to. An empty string signals that # the output is written to stdout. Otherwise it goes to the # specified file, which has to exist, and is merged into it. # # Specified through the optional option '-merge'. variable output "" # Name of the procedure to generate from the input files. variable proc "" # List of the input files to process. variable magic {} } # ### ### ### ######### ######### ######### ## External data and status # ## Only the file merge mode uses external data, which is explicitly ## specified via the command line. It is a template the generated ## recognizer is merged into, completely replacing an existing ## recognizer. # ### ### ### ######### ######### ######### ## Option processing. ## Validate command line. ## Full command line syntax. ## # tmc ?-merge iofile? procname magic ?magic...? ## proc ::tmc::processCmdline {} { global argv variable output variable magic set output "" set magic {} # Process the options, perform basic validation. while {[llength $argv]} { set opt [lindex $argv 0] if {![string match "-*" $opt]} break if {$opt eq "-merge"} { if {[llength $argv] < 2} Usage set output [lindex $argv 1] set argv [lrange $argv 2 end] } else { Usage } } # Additional validation, and extraction of the non-option # arguments. if {[llength $argv] < 1} Usage set magic [lrange $argv 1 end] # Final validation across the whole configuration. foreach m $magic { CheckInput $m {Magic file} } if {$output ne ""} { CheckTheMerge } return } # ### ### ### ######### ######### ######### ## Option processing. ## Helpers: Generation of error messages. ## I. General usage/help message. ## II. Specific messages. # # Both write their messages to stderr and then # exit the application with status 1. ## proc ::tmc::Usage {} { global argv0 puts stderr "$argv0 wrong#args, expected:\ ?-merge iofile? magic magic..." exit 1 } proc ::tmc::ArgError {text} { global argv0 puts stderr "$argv0: $text" exit 1 } proc in {list item} { expr {([lsearch -exact $list $item] >= 0)} } # ### ### ### ######### ######### ######### ## Check existence and permissions of an input/output file or ## directory. proc ::tmc::CheckInput {f label} { if {![file exists $f]} { ArgError "Unable to find $label \"$f\"" } elseif {![file readable $f]} { ArgError "$label \"$f\" not readable (permission denied)" } return } proc ::tmc::CheckTheMerge {} { variable output if {$output eq ""} { ArgError "No merge file specified" } if {![file exists $output]} { ArgError "Merge file \"$output\" not found" } elseif {![file isfile $output]} { ArgError "Merge file \"$output\" is no such (is a directory)" } elseif {![file readable $output]} { ArgError "Merge file \"$output\" not readable (permission denied)" } elseif {![file writable $output]} { ArgError "Merge file \"$output\" not writable (permission denied)" } return } # ### ### ### ######### ######### ######### ## Helper commands. File reading and writing. proc ::tmc::Get {f} { return [read [set in [open $f r]]][close $in] } proc ::tmc::Write {f data} { while 1 { set tmp $f.tmc_[incr i] if {![file exists $tmp]} break } puts -nonewline [set out [open $tmp w]] $data close $out file rename -force $tmp $f return } # ### ### ### ######### ######### ######### ## Configuation phase, validate command line. ::tmc::processCmdline # ### ### ### ######### ######### ######### ## Helper command implementing the file merge functionality. proc ::tmc::Merge {f script} { set out {} set skip 0 foreach l [split [Get $f] \n] { if {$skip == 0} { lappend out $l if {[string match {*BEGIN GENERATED CODE*} $l]} { set skip 1 lappend out $script } } elseif {$skip == 1} { if {[string match {*END GENERATED CODE*} $l]} { lappend out $l set skip 2 } } else { # Skip == 2 lappend out $l } } Write $f [join $out \n] return } # ### ### ### ######### ######### ######### ## Invoking the functionality. if {[catch { # Read and process all input files. # Write the result either to stdout, or merge # into the specified output file. set tcl [eval [linsert $tmc::magic 0 \ fileutil::magic::cfront::generate compressed 0 --]] if {$tmc::output eq {}} { puts stdout $tcl } else { ::tmc::Merge $tmc::output \n${tcl}\n } } msg]} { puts $::errorInfo ::tmc::ArgError $msg } # ### ### ### ######### ######### ######### exit