ycl

Artifact [9deef7092b]
Login

Artifact [9deef7092b]

Artifact 9deef7092bc84cf8272532be12b43716df493629:


#! /usr/bin/env tclsh


namespace eval doc {}

package require {ycl ns join}
interp alias {} [[yclprefix] ns join [
	namespace current] nsjoin] {} [[yclprefix] ns join [yclprefix] ns join]

package require {ycl ns absolute}
namespace import [nsjoin [yclprefix] ns absolute?]
namespace import [nsjoin [yclprefix] ns globalns]
package require {ycl ns normalize}
namespace import [nsjoin [yclprefix] ns normalize]

package require {ycl ns absolute}
interp alias {} [nsjoin [namespace current] absolute?] {} [
	yclprefix] ns absolute?


variable doc {
	description {
		A set of useful arguments to be copied into documentation for other
		procedures.

	}
	args {
		dry {
			description {
				If true, don't actually perform operations that modify storage
			}
			constrain {[string is boolean $dry]}
			default {lindex false}
			process {
				if {$dry} {
					proc [nsjoin $_ dry] args {}
				} else {
					proc [nsjoin $_ dry] args [
						list [nsjoin {} tailcall] {*}$args]
				}
			}
		}
	}
}


# out of alphabetic order because it is used below

variable [nsjoin doc stub] {
	description {
		create a procedure that first evaluates $pre only the first time it is called.
	}
}

variable apply_ [nsjoin {} apply]
variable dict_ [nsjoin {} dict]
variable error_ [nsjoin {} error]
variable expr_ [nsjoin {} expr]
variable foreach_ [nsjoin {} foreach]
variable info_ [nsjoin {} info]
variable interp_ [nsjoin {} interp]
variable list_ [nsjoin {} list]
variable proc_ [nsjoin {} proc]
variable set_ [nsjoin {} set]
variable switch_ [nsjoin {} switch]
variable tailcall_ [nsjoin {} tailcall]
variable try [nsjoin {} try]
variable uplevel_ [nsjoin {} uplevel]
variable upvar_ [nsjoin {} upvar]


proc stub {name argspec pre body} [string map [
	list @uplevel@ [list $uplevel_] \
		@proc@ [list $proc_] \
		@upvar@ [list $upvar_] \
		@list@ [list $list_] \
		@tailcall@ [list $tailcall_] \
		@try@ [list $try]
	] {

	variable apply_
	variable proc_
	variable uplevel_
	uplevel 1 [list $proc_ $name args [
		list $apply_ [list {name argspec pre body} {

			@uplevel@ 1 [@list@ @try@ $pre]
			@proc@ $name $argspec $body
			@upvar@ args args
			@tailcall@ @tailcall@ $name {*}$args
		} [uplevel 1 {namespace current}]] $name $argspec $pre $body]]
}]

variable [nsjoin doc alias] {
	description {
		creates an alias to a target command prefix

		if an alias should continue to refer to the new name of the target when
		the target is renamed
			then use [proc import]
				defined below
			instead
	}
}

proc alias {alias args} {
	package require {ycl ns ascall}
	if {![llength $args]} {
		lappend args $alias
		set alias [namespace tail $alias]
	}
	if {![absolute? $alias]} {
		set alias [nsjoin [uplevel 1 {namespace current}] $alias]
	}

	set name [lindex $args 0]

	if {![absolute? $name]} {
		set args [namespace eval [uplevel 1 {namespace current}] [
			list [yclprefix] ns ascall {*}$args[set args {}]]]
	}
	uplevel 1 [list [namespace which interp] alias {} $alias {} {*}$args]
}


alias [nsjoin [yclprefix] ns normalize]

alias apply_ [nsjoin {} apply]


proc aliases script {
	package require {ycl parse tcl commands}
	alias [nsjoin [yclprefix] parse tcl commands commands]
	foreach command [commands $script] {
		set length [llength $command]
		switch $length {
			1 {
				set package [lindex $command 0]
				set version {}
				if {[lindex $package 0] eq {ycl}} {
					set parts [lrange $package 1 end]
				} else {
					set parts $package
				}
				set alias [list [lindex $package end] [
					nsjoin [yclprefix] {*}$parts]]
			}
			2 {
				lassign $command[set command {}] package alias
				set version {}
			}
			default {
				set alias [lindex $comand end]
				set package [lindex $command 0]
				set version [lrange $command[set command {}] 1 end-1]
			}
		}
		uplevel 1 [list [namespace which package] require $package {*}$version]

		if {
			(
				[string is list $alias] && ![llength $alias]
			)
			||
			$alias eq {}
		} {
			set alias [list [nsjoin [yclprefix] {*}k]]
		}

		foreach command [commands $alias] {
			set length [llength $command]
			switch $length {
				1 {
					set command [lassign $command[set command {}] alias]
					if {[absolute? $alias]} {
						set target [list $alias]
						set alias [namespace tail $alias]
					} else {
						set target $alias
					}
				}
				default {
					set command [lassign $command[set command {}] alias target]
				}
			}

			if {![absolute? $target]} {
				if {[lindex $package 0] eq {ycl}} {
					set target [nsjoin [yclprefix] {*}[lrange $package 1 end] $target]
				} else {
					set target [nsjoin [yclprefix] {*}[$package] $target]
				}
			}
			
			uplevel 1 [list [namespace which alias] $alias $target {*}$command]
		}
	}
}


proc argsswitch switch {
	set length [llength $switch]
	if {$length % 2} {
		error [list {malformed switch}]
	}
	set switch [lassign $switch[set switch {}] first body]
	lappend res [namespace which if] $first $body
	foreach {expr body} $switch[set switch {}] {
		lappend res elseif $expr $body
	}
	lappend res else {
		error [list {wrong # args}]
	}
	return $res
}


variable [nsjoin doc import] {
	description
		like [namespace import] but the name of the new command may be
		specified

}
proc import args {
	if {[llength $args] == 2} {
		lassign $args alias target
	} elseif {[llength $args] == 1} {
		lassign $args target
		set alias [namespace tail $target]
	} else {
		error [list {wrong # args}]
	}
	set fulltarget [uplevel 1 [list [namespace which namespace] which $target]]
	if {$fulltarget eq {}} {
		return -code error [list {no such command} $target]
	}


	set needsimport 1
	if {[absolute? $alias]} {
		set fullalias $alias
	} else {
		set fullalias [nsjoin [uplevel 1 [
			list [namespace which namespace] current]] $alias]
		if {[namespace which $fullalias] ne {}} {
			set origin [namespace origin $fullalias]
			if {$origin eq $fulltarget} {
				set needsimport 0
			}
		}
	}

	if {$needsimport} {
		set qualifiers [namespace qualifiers $fulltarget]
		if {$qualifiers eq {}} {
			set qualifiers [globalns]
		}
		set save [namespace eval $qualifiers {
			namespace export}]

		namespace eval $qualifiers {namespace export *}
		while {[namespace exists [
			set tmpns [nsjoin [namespace current] [info cmdcount]]]]} {}
		set code [catch {set newcmd [namespace eval $tmpns [
			string map [list @{fulltarget} [list $fulltarget]] {
			namespace import @{fulltarget}
		}]]} cres copts]
		namespace eval $qualifiers [
			list namespace export -clear {*}$save]
		if {$code} {
			return -options $copts $cres
		}

		uplevel 1 [list [namespace which rename] [nsjoin $tmpns [
			namespace tail $target]] $fullalias]
		namespace delete $tmpns 
	}

	return [uplevel 1 [list [namespace which namespace] which $alias]]
}


proc imports {to from list} {
	foreach name $list {
		optswitch [llength $name] {
			1 {
				uplevel 1 [list [namespace which namespace] eval $to [
					list [namespace which import] [
						nsjoin $from [lindex $name 0]]]]
			}
			2 {
				uplevel 1 [list [namespace which namespace] eval $to [
					list [namespace which import] [lindex $name 0] [
						nsjoin $from [lindex $name 1]]]]
			}
		}
	}
}


variable [nsjoin doc argnames] {
	description {
		"argnames" returns the argument names of the procedure that calls it.
	}
}
proc argnames {} {
	variable apply_
	variable info_
	set cmd [uplevel 1 [list $info_ level 0]]

	set args [lassign $cmd rawname]
	set oldname $rawname

	# 2016-05-11: Gymnastics involving [apply], [tailcall] and namespace maps
	# can poke holes in the assumptions this code relies on . Use [uplevel
	# {namespace orgin} ...] instead .
	#if {![absolute? $name]} {
	#	#do [uplevel 2 ...] first in order to catch renamed imported commands.
	#	if {[set name [upcall 2 namespace which $oldname]] eq {}} {
	#		set name [upcall 1 namespace which $oldname]
	#	}
	#}

	# Maybe once
	# http://core.tcl.tk/tcl/tktview/229fa655638ab16d794ea819296cf9f3a9088619
	# is fixed, this can be reworked

	# uplevel 1 handles direct calls
	if {[catch {uplevel 1 [list namespace origin $rawname]} name]} {
		#uplevel 2 handles [rename] calls 
		set name [uplevel 2 [list namespace origin $rawname]]
	}

	if {$name eq {}} {
		error [list {no such command} $oldname]
	}
	if {$name eq $apply_} {
		set args [lindex $args 0 0]
	} else {
		set args [info args $name]
	}
	return $args
}


proc checkarg {level arg givenname argspecname neededname constrainedname} {
	variable expr_
	upvar $argspecname argspec $givenname given $neededname needed \
		$constrainedname constrained

	set seen [dict get $argspec seen]

	if {[dict exists $argspec count]} {
		set count [dict get $argspec count]
	} else {
		set count 1
	}

	if {$count == -1} {
		#no problem

		#{to do} {make negative numbers mean "at least"}
		dict unset needed $arg
	} else {
		if {$seen > $count} {
			return -level [expr {$level + 1}] -code error [
				list {too many occurrences} argument $arg allowed $count \
					occurrences $seen
			]
		} elseif {$seen == $count} {
			dict unset needed $arg
		}
	}

	if {[llength $given]} {
		set given [lassign $given[set given {}] val]
	} else {
		return -level [expr {$level + 1}] -code error [
			list {no value for argument} $arg]
	}

	if {[dict exists $argspec init] && $seen == 1} {
		uplevel $level [dict get $argspec init]
	}

	if {[dict exists $argspec constrain]} {
		lappend constrained $arg [dict get $argspec constrain]
	}


	if {[dict exists $argspec name]} {
		set varname [dict get $argspec name]
	} else {
		set varname $arg
	}
	upvar $level $varname var
	set var $val
	if {[dict exists $argspec validate]} {
		set validate [dict get $argspec validate]
		if {[regexp {[^[:space:]]} $validate]} {
			#validate is not empty
			set vres [uplevel $level [list $expr_ $validate]]
			if {!$vres} {
				return -level 2 -code error [
					list {failed validation} argument $arg \
						expression [concat $validate] value $val
				]
			}
		}
	}
	if {[dict exists $argspec process]} {
		set process [dict get $argspec process]
		upvar $level $varname var
		set var [uplevel $level $process]
	}
	if {[dict exists $argspec trigger]} {
		uplevel $level [dict get $argspec trigger]
	}
	return
}

variable [nsjoin doc checkargs] {
	description {
		check arguments passed to a function against the argument specification
		for that function .

		note that this documentation is "fake" in the sense that it isn't
		parsed by checkargs . We are not Münchhausen !
	}
	args {
		doc {
			description {
				The documentation for the the procedure; a dictionary that may
				contain keys as described here.
			}

			keys {
				description {
					a description of the operation of the command, in a
					natural language , e.g., Ket or English .
				}
				args {
					description {
						A dictionary in which the keys are the names of
						arguments that may be provided, by the same name, when
						calling the function .  Each key specifies an argument
						to the procedure , and is processed as specified in
						"keys" , below .

						Positional arguments and also the contents the
						procedure's $args argument , if one is present , are
						processed .
						
						The procedure's $args argument , is considered to be a
						varname-value dictionary which will be converted in
						variables in the scope of the procedure .
					}
					keys {
						description {
							description {
								A description of the argument .
							}
						}
						automatic {
							description {
								for use by {ycl shelf util configure }
							}
						}
						constrain {
							description {

								Evaluated as an expression .

								Used for inter-argument validation .

								Processed in the order they occur in the
								docspec after all inputs and defaults have been
								processed . Intended to check that processed
								input meets some criteria , as default values
								have already been set and input has been
								validated by this time .

							}
						}
						count {
							description {
								The number of times the argument may appear .

								-1 means unlimited

								#TODO: expand count to include a min and a max
							}
							default {
								set count 1
							}
							validate {
								[string is entier $count]
							}
						}
						init {
							description {
								A script to run for each argument in the
								argspec to make any needed initializations .
								All init scripts are run prior to walking
								through any actual arguments .
							}
							default {
								set init {}
							}
						}
						name {
							description {
								The name of the variable to assign the value to
								.  By default , the variable name is the same
								as the argument name .
							}
						}
						validate {
							description {
								evaluated as an expression

								processed as each argument is encountered.
								Intended primarily to check that input
								matches a certain pattern .

								For inter-argument validation, use "constrain"
							}
						}
						default {
							description {
								Indicates that this argument is optional 

								A script whose result becomes the value of this
								argument when it is not explicitly provided

								Processed after all inputs are processed, and
								in the order of occurrence in $doc .

								If this key is present , the argument is
								optional . Otherwise , it is mandatory.

								As a special case , if default is the empty
								string (as opposed to an expression whose
								result is the empty string) , the argument will
								not be set, and the validate step will be
								skipped, but the constrain step will still run
								. This allows for constraints that take into
								account the non-existence of the variable . For
								example , one might set a constraint to make
								sure that if the variable is not said , some
								other variable is .
							}
						}
						positional {
							description {
								if true
									the argument is positional

									if a positional argument is explicitly
									named in the procedure definition

										it isn't necessary to give it this
										value in the argument dictionary

								positional arguments not explicity named in the
								procedure definition are extracted from $args
								before $args is processed as a dictionary
							}
						}
						process {
							description {
								A script to invoke as the argument is
								encountered . Some potential uses :

									require that some other argument be
									specified first
								
								default arguments are "encountered" as
								described in their documentation

								Returns a value which replaces the value of
								\$arg

							}
						}
						trigger {
							description
								like "process"
									but the returned value is discarded
						}
					}
				}
				effects {
					description {
						A script that serves to check that intended effects
						of the command have actually occurrred . If it is
						empty , the command should be purely functional . If
						it doesn't exist at all , the command author simply
						hasn't specified it . Evaluation of effects can be
						enabled for debugging , or disabled for performance .
						The user semantics of the command the comand should
						not be modified by this script .

						TODO:  Implement this .
					}
				}
				extra {
					description {
						the name of an argument to assign each extra argument
						to

						validation and constraints are applied individually to
						each value
					}
				}
				stop {
					description {
						if an argument == $stop
							stop processing arguments

						if there is no $extra

							$stop is assigned to extra
					}
				}
				value {
					description {
						An expression that evaluated to determine whether
						the return value of the script is valid .

						Any additional non-code description of the value ,
						intended for humans , should go in teh "description"
						element .

						TODO: Implement this .
					}
				}
			}
		}
		given {
			description {
				the name of a variable containing the arguments given for a
				particular call of a function

				a dictionary
					thus
						for keys that occur more than once only the last
						occurance is used
			}
		}
	}
	value {
		A dictionary containing information about the following data

		keys {
			next {
				The index in $given of the next item that would have been
				checked had the function not stopped .
			}
		}
	}
}


proc checkargs {doc args} {
	variable expr_
	variable info_
	variable set_
	set given $args
	set constrained {}
	set positional [uplevel 1 [list [nsjoin [namespace current] argnames]]] 
	set mandatory [dict create]
	set res [dict create]
	set finalres {}
	if {[dict exists $doc stop]} {
		set stop [dict get $doc stop]
	}

	if {{args} eq [lindex $positional end]} {
		set positional [lrange $positional[set positional {}] 0 end-1]
	}

	set docargs [dict get $doc args]
	if 0 {
		to do
			pass the name of the variable containing the arg spec rather than
			the arg spec itself, so that this routine can validate it one time
			and then mark it as validated
	}
	set fakespec [dict create args $docargs] 
	validatespec fakespec
	set needed $docargs 
	set myns [namespace current]
	foreach arg $positional {
		set given [linsert $given[set given {}] 0 $arg [
			uplevel 1 [list $set_ $arg]]]
	}


	while {[llength $given]} {
		set given [lassign $given[set given {}] arg]

		#{to do} make Tcl compile this check away
		if {[info exists stop] && $arg eq $stop} {
			if {![dict exists $doc extra]} {
				dict set doc extra $stop
			}
			break
		}

		if {[dict exists $docargs $arg]} {
			dict update docargs $arg argspec {
				dict incr argspec seen
			}
			checkarg 2 $arg given argspec needed constrained
			continue
		}

		set found 0
		dict for {arg1 argspec} $needed {
			if {[dict exists $argspec positional]} {
				dict update docargs $arg1 argspec {
					dict incr argspec seen
				}
				set given [linsert $given[set given {}] 0 $arg]
				checkarg 2 $arg1 given argspec needed constrained
				set found 1
			}
		}
		if {!$found} {
			set given [linsert $given[set given {}] 0 $arg]
			break 
		}
	}


	if {[llength $given]} {
		if {![dict exists $doc extra]} {
			error [list {unknown argument} [lindex $given 0]]
		}
		set arg [dict get $doc extra]
		set argspec [dict get $docargs $arg]
		while {[llength $given]} {
			dict update docargs $arg argspec {
				dict incr argspec seen
			}
			checkarg 2 $arg given argspec needed constrained
		}
	}

	#process all the defaults before doing any of the constraints
	#otherwise, processing becomes sensitive to the order of arguments in the argspec
	dict for {arg argspec} $needed {
		if {[dict exists $argspec default]} {
			if {[dict get $argspec default] eq {}} {
				dict unset docargs $arg
			} else {
				dict incr argspec seen
				dict set seen $arg $argspec
				dict unset docargs $arg

				set given [list [uplevel 1 [dict get $argspec default]]] 

				checkarg 2 $arg given argspec needed constrained
			}
		}
	}

	dict for {arg argspec} $needed {
		if {!([dict exists $argspec default] 
			&& [dict get $argspec default] eq {})} {
			return -level 2 -code error  [list {missing mandatory argument} $arg] 
		}
	}


	foreach {arg constrain} $constrained[set constrained {}] {
		if {![uplevel 1 [list $info_ exists $arg]]} {
			continue
		}
		set constrainres [uplevel 1 [list $expr_ $constrain]]
		if {!$constrainres} {
			lappend msg {fails constraint} [concat $constrain]
			if {[uplevel 1 [list $info_ exists $arg]]} {
				lappend msg argument $arg
			}
			return -level 2 -code error $msg
		}
	}
	return $finalres 
}


variable [nsjoin doc checkdargs] {
	description {
		Check arguments passed to a function against the argument specification
		for that function .
		
		faster, but with different semantics than checkargs
	}
	args {
		doc {
			description {
				The documentation for a function , which normally contains an
				"args" entry specifying its arguments .
			}
			keys {
				args {
					description {
						A list of arguments that may be provided when
						calling the function .  In contrast to "checkargs" ,
						defaults are processed in the order they occur in
						the specification rather than the order of $given .

					}
					keys {
						constrain {
							description {
								Processed after all defaults , and in the
								order presented in $doc . This allows for
								constraints that depend on other
								constraints .
							}
						}
						default {
							description {
								sets a default value . processed after all
								inputs are processed , and in the order of
								occurance in $doc .

								If this key is not present , the argument is
								mandatory 

							}
						}
					}
				}
			}
		}
		given {
			description {
				The arguments given for a particular call of a function.
				Because it is interpreted as a dictionary , if any key occurs
				more than once in $given , only the last occurence is used .  See
				[proc checkdargs] for an alternative processor .

			}
		}
	}
}


proc checkdargs {doc given} {
	variable dict_
	variable expr_
	variable info_
	uplevel 1 [list $dict_ with $given {}]
	upvar $doc[unset doc] doc
	upvar $given[unset given] given
	#make sure it's a dictionary
	dict info $given
	dict for {opt optspec} [dict get $doc args] {
		dict with optspec {
			if {![dict exists $given $opt]} {
				if {![dict exists $optspec default]} {
					return level 2 -code error [
						list {missing mandatory argument} $opt
					]
				}
				if {[dict exists $optspec default]} {
					uplevel 1 $default
				}
			}
			if {[uplevel 1 [list $info_ exists $opt]]} {
				if {[dict exists $optspec process]} {
					set process [dict get $optspec process]
					uplevel 1 $process
				}
			}
			if {[dict exists $optspec constrain]} {
				#note that constraints are executed in the order presented in the argument specification
				if {[regexp {[^[:space:]]} $constrain]} {
					#constrain is not empty
					set res [uplevel 1 [list $expr_ $constrain]]
					if {!$res} {
						return -level 2 -code error [
							list {failed constraint} argument $opt contraint \
								$constrain value [dict get $given $opt]
						]
					}
				}
			}
		}
	}
}


proc checkspec spec {
	set keys [dict get [set [nsjoin doc checkargs]] args doc keys args keys]
	dict for {arg argspec} [dict get $spec args] {
		dict for {key dummy} $argspec {
			if {![dict exists $keys $key]} {
				return -code error [list $key {not one of} [dict keys $keys]]
			}
		}
	}
}


stub const {name value} {
	package require {ycl eval}
	alias [nsjoin [yclprefix] eval upcall]
} {
	if {![absolute? $name]} {
		set name [upcall 1 normalize $name[set name {}]]
	}
	upcall 1 interp alias {} $name {} [nsjoin {} lindex] $value
}


proc copy {from to} {
	if {![absolute? $from]} {
		set from [uplevel 1 [list namespace which -command $from]]
	}
	if {![absolute? $to]} {
		set to [normalize $to [uplevel {namespace current}]]
	}
	set args [info args $from]
	set newargs [formals $from]
	set parent [namespace qualifiers $to]
	if {$parent eq {}} {
		set parent [globalns]
	}
	if {![namespace exists $parent]} {
		namespace eval $parent {}
	}
	proc $to $newargs [info body $from]
}


variable curried {}
stub curry {name args} {
	package require {ycl eval}
	alias [nsjoin [yclprefix] eval upcall]
} {
	variable curried
	set id [info cmdcount]
	dict set curried $id $args
	upcall 1 proc $name args "
		[list [namespace which docurry]] [list $id] {*}\$args
	"
	upcall 1 trace add command $name delete [
		list [namespace which deletecurry] $id]
	return
}


proc deletecurry {id args} {
	variable curried
	dict unset curried $id
	return
}


proc docurry {id args} {
	variable curried
	tailcall [
		namespace which tailcall] {*}[dict get $curried $id] {*}$args
}


stub dproc {name args body} {
	package require {ycl eval}
	alias [nsjoin [yclprefix] eval upcall]
} {
	upvar 1 [nsjoin doc $name] spec
	checkspec $spec
	upcall 1 proc $name $args $body
}


stub exists name {
	package require {ycl eval}
	alias [nsjoin [yclprefix] eval upcall]
} {
	if {![absolute? $name]} {
		set name [upcall 1 normalize $name]
	}
	expr {[upcall 1 namespace which $name] ne {}}
}


#value: the formal parameters of a procedure
stub formals proc {
	package require {ycl eval}
	alias [nsjoin [yclprefix] eval upcall]
} {
	if {![absolute? $proc]} {
		set proc [upcall 1 namespace which -command $proc]
	}
	set args [info args $proc]
	set newargs {} 
	foreach arg $args {
		if {[info default $proc $arg val]} {
			lappend newargs [list $arg $val]
		} else {
			lappend newargs $arg
		}
	}
	return $newargs
}


variable [nsjoin doc kvargs] {
	description {
		Create a script to process $args as key-value arguments .
	}
	args {
		keys {
			description {
				a list of argument names to accept
			}
		}
	}
}
proc kvargs keys {
	string map [list @spec@ $keys] {
		apply {{spec provided} {
			foreach key [dict keys $provided] {
				if {$key ni $spec} {
					error [list {unknown argument} $key]
				}
			}

		}} @spec@ $args
		dict with args {}
	}
}


proc lambda args {
	variable apply_
	switch [llength $args] {
		1 {
			set spec {}
			set args [lassign $args[set args {}] body]
		}
		0 {
			error [list {wrong # args}]
		}
		default {
			set args [lassign $args[set args {}] spec body]
		}
	}
	list $apply_ [list $spec $body [uplevel 1 {namespace current}]] {*}$args
}


proc lambdacurry args {
	variable tailcall_
	uplevel 1 [list [namespace which lambda] {args0 args} "
		[list $tailcall_] {*}\$args0 {*}\$args
	" $args]
}


proc lbody {args list} [string map [list @apply@ [list $apply_] @foreach@ [
	list $foreach_] @uplevel@ [list $uplevel_]] {
    list @apply@ [
        list [list list {*}$args] {
            @apply@ [list {} {
                upvar list list 
                @foreach@ line $list {
                    if {[catch {@uplevel@ 1 $line} cres copts]} {
                        dict incr copts -level
                        return -options $copts $cres
                    }
                }
				return $cres
            } [namespace current]]
        } [uplevel 1 {namespace current}]
    ] $list
}]


stub lproc {name args list} {
	package require {ycl eval}
	alias [nsjoin [yclprefix] eval upcall]
} {
	variable interp_
    uplevel 1 [list $interp_ alias {} [upcall 1 normalize $name] {} {*}[
		uplevel 1 [list [namespace which lbody] $args $list]]]
}


#like builtin proc, but $vars specifies namespace variables to make available
stub nsproc {name args vars body} {
	package require {ycl eval}
	alias [nsjoin [yclprefix] eval upcall]
} {
	foreach var $vars {
		append pre "variable [list $var]\n"
	}
	append pre $body
	upcall 1 proc $name $args $pre
}


proc vmacro {mspec args} {
	if {[llength $mspec] != 2} {
		return -code error [
			list {wrong # args} allowed 2 received $mspec]
	}
	lassign $mspec margs body
	if {[llength $margs] != [llength $args]} {
		return -code error [
			list {wrong # args} expected [llength $margs] received [
				llength $args] args $args]
		]

	}
	foreach marg $margs arg $args {
		lappend map \${$marg} \${$arg}
		lappend map "{{$marg}}" [list $arg]
	}
	set body [string map $map $body[set body {}]]
	uplevel $body
}

variable [nsjoin doc method] {
	description
		create a procedure
	args
		attributes
			names of variables to link to in the namespace of the object
		vars
			names of variables to link to in the namespace of the procedure 
}


stub method {object name args vars attributes body} {
	package require {ycl eval}
	alias [nsjoin [yclprefix] eval upcall]
} {
	variable proc_
	foreach var $vars {
		append pre "variable [list $var]\n"
	}
	set ns [upcall 1 namespace ensemble configure $object -namespace]
	foreach attribute $attributes {
		append pre "namespace upvar [list $ns]  [
				list $attribute] [list $attribute]\n"
	}

	append pre $body[set body {}]
	namespace eval $ns [list $proc_ $name $args $pre]
}


proc optswitch {opt switch} {
	variable error_
	variable switch_
	lappend switch default [
		list $error_ [list {unknown option} $opt]]
	tailcall $switch_ $opt $switch
}


proc partial {cmd args} {
	variable apply_
	variable tailcall_
	list $apply [list {cmd baked args} {
		$tailcall $cmd {*}$baked {*}$args
	} [uplevel 1 {namespace current}]] $cmd $args
}


variable [nsjoin doc replace] {
	description {
		create a procedure and replace the current routine by calling the new
		procedure with the provided arguments.
	}
}
stub replace {name spec body args} {
	package require {ycl eval}
	alias [nsjoin [yclprefix] eval upcall]
} {
	variable tailcall_
	upcall 1 proc $name $spec $body
	$tailcall_ $tailcall_ $name {*}$args
}


proc validatespec specname  {
	namespace upvar doc checkargs doc
	upvar $specname spec
	set specargs [dict get $spec args]
	dict size $specargs
	foreach {name spec1} $specargs {
		dict size $spec1
		foreach {skey sval} $spec1 {
			set allowed [dict keys [dict get $doc args doc keys args keys]]
			if {$skey ni $allowed} {
				error [list {bad documentation} name $skey]
			}
		}
	}
	dict set spec validated 1
}