init.tcl 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. # evaluate in internal openmsx namespace
  2. namespace eval openmsx {
  3. variable init_tcl_executed false
  4. variable tabcompletion_proc_sensitive
  5. variable tabcompletion_proc_insensitive
  6. variable help_text
  7. variable help_proc
  8. variable lazy [dict create]
  9. # Only execute this script once. Below we source other Tcl script,
  10. # so this makes sure we don't get in an infinite loop.
  11. if {$init_tcl_executed} return
  12. set init_tcl_executed true
  13. # Debug pints (disabled by default).
  14. proc dbg {msg} {
  15. #puts stderr $msg
  16. }
  17. # Helpers to handle on-demand (lazy) loading of Tcl scripts
  18. # Register 'script' to be loaded on-demand when one of the proc names in
  19. # 'procs' is about to be executed. See also 'lazy.tcl'.
  20. proc register_lazy {script procs} {
  21. variable lazy
  22. dict set lazy $script $procs
  23. }
  24. # Lookup the script associated with the given proc name. If found that script
  25. # is executed (and the script+proc-names are removed from the list of
  26. # yet-to-be-executed lazy scripts).
  27. proc lazy_handler {name} {
  28. variable lazy
  29. set name [namespace tail $name]
  30. dict for {script procs} $lazy {
  31. if {[lsearch -exact $procs $name] == -1} continue
  32. dict unset lazy $script
  33. dbg "start executing script $script (via lazy_handler)"
  34. if {[catch {namespace eval :: [list source [data_file scripts/$script]]}]} {
  35. puts stderr "Error while (lazily) loading Tcl script: $script\n$::errorInfo"
  36. error $::errorInfo
  37. }
  38. dbg "done executing script $script"
  39. return true
  40. }
  41. return false
  42. }
  43. # Execute all not yet executed lazy-scripts. ATM this is (only) required for
  44. # the 'about' command which has to search through the help text of all the
  45. # scripts.
  46. proc lazy_execute_all {} {
  47. variable lazy
  48. # cannot simply iterate because the 'source' command below might
  49. # trigger a load of a script later in the collection
  50. while {[dict size $lazy] != 0} {
  51. set script [lindex [dict keys $lazy] 0]
  52. dict unset lazy $script
  53. dbg "start executing script $script (via lazy_execute_all)"
  54. if {[catch {namespace eval :: [list source [data_file scripts/$script]]}]} {
  55. puts stderr "Error while (lazily) loading Tcl script: $script\n$::errorInfo"
  56. error $::errorInfo
  57. }
  58. dbg "done executing script $script"
  59. }
  60. }
  61. # Return a list of all command names. This includes:
  62. # builtin Tcl commands,
  63. # procs defined in Tcl scripts,
  64. # procs from not yet loaded lazy-scripts (see register_lazy).
  65. # This helper proc is used for tab-completion in the openMSX console.
  66. proc all_command_names {} {
  67. variable lazy
  68. set result [info commands]
  69. foreach procs [dict values $lazy] {
  70. lappend result {*}$procs
  71. }
  72. # only one level deep, good enough for machineN::*
  73. foreach ns [namespace children ::] {
  74. lappend result {*}[info commands ${ns}::*]
  75. }
  76. return $result
  77. }
  78. # Returns a list of Tcl commands that provide extra help (text or proc).
  79. # Note: there may also be commands implemented in c++ which provide help.
  80. proc all_command_names_with_help {} {
  81. lazy_execute_all
  82. set result [array names ::openmsx::help_text]
  83. lappend result {*}[array names ::openmsx::help_proc]
  84. return $result
  85. }
  86. # Is the given name a name of a proc, possibly a name defined in a not-yet
  87. # loaded script. This helper proc is used for syntax-highlighting in the
  88. # openMSX console.
  89. proc is_command_name {name} {
  90. if {[info commands ::$name] ne ""} {return 1}
  91. expr {[lsearch -exact [all_command_names] [namespace tail $name]] != -1}
  92. }
  93. # Override the builtin Tcl proc 'unknown'. This is called when the Tcl
  94. # interpreter is about to execute an undefined command.
  95. proc ::unknown {args} {
  96. #puts stderr "unknown: $args"
  97. set name [lindex $args 0]
  98. if {[openmsx::lazy_handler $name]} {
  99. return [uplevel 1 $args]
  100. }
  101. return -code error "invalid command name \"$name\""
  102. }
  103. # internal proc to make help function available to Tcl procs
  104. proc help {args} {
  105. variable help_text
  106. variable help_proc
  107. set command [lindex $args 0]
  108. lazy_handler $command
  109. if {[info exists help_proc($command)]} {
  110. return [namespace eval :: $help_proc($command) $args]
  111. } elseif {[info exists help_text($command)]} {
  112. return $help_text($command)
  113. } elseif {[info commands $command] ne ""} {
  114. error "No help for command: $command"
  115. } else {
  116. error "Unknown command: $command"
  117. }
  118. }
  119. proc set_help_text {command help} {
  120. variable help_text
  121. set help_text($command) $help
  122. }
  123. set_help_text set_help_text \
  124. {Associate a help-text with a Tcl proc. This is normally only used in Tcl scripts.}
  125. proc set_help_proc {command procname} {
  126. variable help_proc
  127. set help_proc($command) $procname
  128. }
  129. set_help_text set_help_proc \
  130. {Associate a help-proc with a Tcl proc. This is normally only used in Tcl scripts.}
  131. # internal proc to make tabcompletion available to Tcl procs
  132. proc tabcompletion {args} {
  133. variable tabcompletion_proc_sensitive
  134. variable tabcompletion_proc_insensitive
  135. set command [lindex $args 0]
  136. lazy_handler $command
  137. set result ""
  138. if {[info exists tabcompletion_proc_sensitive($command)]} {
  139. set result [namespace eval :: $tabcompletion_proc_sensitive($command) $args]
  140. lappend result true
  141. } elseif {[info exists tabcompletion_proc_insensitive($command)]} {
  142. set result [namespace eval :: $tabcompletion_proc_insensitive($command) $args]
  143. lappend result false
  144. }
  145. return $result
  146. }
  147. proc set_tabcompletion_proc {command proc {case_sensitive true}} {
  148. variable tabcompletion_proc_sensitive
  149. variable tabcompletion_proc_insensitive
  150. if {$case_sensitive} {
  151. set tabcompletion_proc_sensitive($command) $proc
  152. } else {
  153. set tabcompletion_proc_insensitive($command) $proc
  154. }
  155. }
  156. set_help_text set_tabcompletion_proc \
  157. {Provide a way to do tab-completion for a certain Tcl proc. For details look at the numerous examples in the share/scripts directory. This is normally only used in Tcl scripts.}
  158. set_help_text data_file \
  159. "Resolve data file. First try user directory, if the file doesn't exist
  160. there try the system directory."
  161. proc data_file { file } {
  162. global env
  163. set user_file $env(OPENMSX_USER_DATA)/$file
  164. if {[file exists $user_file]} { return $user_file }
  165. return $env(OPENMSX_SYSTEM_DATA)/$file
  166. }
  167. namespace export register_lazy
  168. namespace export set_help_text
  169. namespace export set_help_proc
  170. namespace export set_tabcompletion_proc
  171. namespace export data_file
  172. } ;# namespace openmsx
  173. namespace import openmsx::*
  174. namespace eval openmsx {
  175. # Source all .tcl files in user and system scripts directory. Prefer
  176. # the version in the user directory in case a script exists in both
  177. set user_scripts [glob -dir $env(OPENMSX_USER_DATA)/scripts -tails -nocomplain *.tcl]
  178. set system_scripts [glob -dir $env(OPENMSX_SYSTEM_DATA)/scripts -tails -nocomplain *.tcl]
  179. set profile_list [list]
  180. foreach script [lsort -unique [concat $user_scripts $system_scripts]] {
  181. # Skip scripts that start with a '_' character. (By convention) those
  182. # are loaded on-demand (see 'lazy.tcl').
  183. if {[string index $script 0] eq "_"} continue
  184. set script [data_file scripts/$script]
  185. set t1 [openmsx_info realtime]
  186. dbg "start executing script $script (via startup sequence)"
  187. if {[catch {namespace eval :: [list source $script]}]} {
  188. puts stderr "Error while executing $script\n$errorInfo"
  189. }
  190. dbg "done executing script $script"
  191. set t2 [openmsx_info realtime]
  192. lappend profile_list [list [expr {int(1000000 * ($t2 - $t1))}] $script]
  193. }
  194. if 0 {
  195. foreach e [lsort -integer -index 0 $profile_list] { puts stderr $e }
  196. }
  197. } ;# namespace openmsx