init.tcl 6.5 KB

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