summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib/editor.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/idlelib/editor.py')
-rw-r--r--Lib/idlelib/editor.py1653
1 files changed, 1653 insertions, 0 deletions
diff --git a/Lib/idlelib/editor.py b/Lib/idlelib/editor.py
new file mode 100644
index 0000000..7f910e7
--- /dev/null
+++ b/Lib/idlelib/editor.py
@@ -0,0 +1,1653 @@
+import importlib
+import importlib.abc
+import importlib.util
+import os
+import platform
+import re
+import string
+import sys
+from tkinter import *
+from tkinter.ttk import Scrollbar
+import tkinter.simpledialog as tkSimpleDialog
+import tkinter.messagebox as tkMessageBox
+import traceback
+import webbrowser
+
+from idlelib.multicall import MultiCallCreator
+from idlelib import windows
+from idlelib import search
+from idlelib import grep
+from idlelib import replace
+from idlelib import pyparse
+from idlelib.config import idleConf
+from idlelib import help_about, textview, configdialog
+from idlelib import macosx
+from idlelib import help
+
+# The default tab setting for a Text widget, in average-width characters.
+TK_TABWIDTH_DEFAULT = 8
+
+_py_version = ' (%s)' % platform.python_version()
+
+def _sphinx_version():
+ "Format sys.version_info to produce the Sphinx version string used to install the chm docs"
+ major, minor, micro, level, serial = sys.version_info
+ release = '%s%s' % (major, minor)
+ release += '%s' % (micro,)
+ if level == 'candidate':
+ release += 'rc%s' % (serial,)
+ elif level != 'final':
+ release += '%s%s' % (level[0], serial)
+ return release
+
+
+class EditorWindow(object):
+ from idlelib.percolator import Percolator
+ from idlelib.colorizer import ColorDelegator, color_config
+ from idlelib.undo import UndoDelegator
+ from idlelib.iomenu import IOBinding, filesystemencoding, encoding
+ from idlelib import mainmenu
+ from tkinter import Toplevel
+ from idlelib.statusbar import MultiStatusBar
+
+ help_url = None
+
+ def __init__(self, flist=None, filename=None, key=None, root=None):
+ if EditorWindow.help_url is None:
+ dochome = os.path.join(sys.base_prefix, 'Doc', 'index.html')
+ if sys.platform.count('linux'):
+ # look for html docs in a couple of standard places
+ pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
+ if os.path.isdir('/var/www/html/python/'): # "python2" rpm
+ dochome = '/var/www/html/python/index.html'
+ else:
+ basepath = '/usr/share/doc/' # standard location
+ dochome = os.path.join(basepath, pyver,
+ 'Doc', 'index.html')
+ elif sys.platform[:3] == 'win':
+ chmfile = os.path.join(sys.base_prefix, 'Doc',
+ 'Python%s.chm' % _sphinx_version())
+ if os.path.isfile(chmfile):
+ dochome = chmfile
+ elif sys.platform == 'darwin':
+ # documentation may be stored inside a python framework
+ dochome = os.path.join(sys.base_prefix,
+ 'Resources/English.lproj/Documentation/index.html')
+ dochome = os.path.normpath(dochome)
+ if os.path.isfile(dochome):
+ EditorWindow.help_url = dochome
+ if sys.platform == 'darwin':
+ # Safari requires real file:-URLs
+ EditorWindow.help_url = 'file://' + EditorWindow.help_url
+ else:
+ EditorWindow.help_url = "https://docs.python.org/%d.%d/" % sys.version_info[:2]
+ self.flist = flist
+ root = root or flist.root
+ self.root = root
+ try:
+ sys.ps1
+ except AttributeError:
+ sys.ps1 = '>>> '
+ self.menubar = Menu(root)
+ self.top = top = windows.ListedToplevel(root, menu=self.menubar)
+ if flist:
+ self.tkinter_vars = flist.vars
+ #self.top.instance_dict makes flist.inversedict available to
+ #configdialog.py so it can access all EditorWindow instances
+ self.top.instance_dict = flist.inversedict
+ else:
+ self.tkinter_vars = {} # keys: Tkinter event names
+ # values: Tkinter variable instances
+ self.top.instance_dict = {}
+ self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
+ 'recent-files.lst')
+ self.text_frame = text_frame = Frame(top)
+ self.vbar = vbar = Scrollbar(text_frame, name='vbar')
+ self.width = idleConf.GetOption('main', 'EditorWindow',
+ 'width', type='int')
+ text_options = {
+ 'name': 'text',
+ 'padx': 5,
+ 'wrap': 'none',
+ 'highlightthickness': 0,
+ 'width': self.width,
+ 'tabstyle': 'wordprocessor', # new in 8.5
+ 'height': idleConf.GetOption(
+ 'main', 'EditorWindow', 'height', type='int'),
+ }
+ self.text = text = MultiCallCreator(Text)(text_frame, **text_options)
+ self.top.focused_widget = self.text
+
+ self.createmenubar()
+ self.apply_bindings()
+
+ self.top.protocol("WM_DELETE_WINDOW", self.close)
+ self.top.bind("<<close-window>>", self.close_event)
+ if macosx.isAquaTk():
+ # Command-W on editorwindows doesn't work without this.
+ text.bind('<<close-window>>', self.close_event)
+ # Some OS X systems have only one mouse button, so use
+ # control-click for popup context menus there. For two
+ # buttons, AquaTk defines <2> as the right button, not <3>.
+ text.bind("<Control-Button-1>",self.right_menu_event)
+ text.bind("<2>", self.right_menu_event)
+ else:
+ # Elsewhere, use right-click for popup menus.
+ text.bind("<3>",self.right_menu_event)
+ text.bind("<<cut>>", self.cut)
+ text.bind("<<copy>>", self.copy)
+ text.bind("<<paste>>", self.paste)
+ text.bind("<<center-insert>>", self.center_insert_event)
+ text.bind("<<help>>", self.help_dialog)
+ text.bind("<<python-docs>>", self.python_docs)
+ text.bind("<<about-idle>>", self.about_dialog)
+ text.bind("<<open-config-dialog>>", self.config_dialog)
+ text.bind("<<open-module>>", self.open_module)
+ text.bind("<<do-nothing>>", lambda event: "break")
+ text.bind("<<select-all>>", self.select_all)
+ text.bind("<<remove-selection>>", self.remove_selection)
+ text.bind("<<find>>", self.find_event)
+ text.bind("<<find-again>>", self.find_again_event)
+ text.bind("<<find-in-files>>", self.find_in_files_event)
+ text.bind("<<find-selection>>", self.find_selection_event)
+ text.bind("<<replace>>", self.replace_event)
+ text.bind("<<goto-line>>", self.goto_line_event)
+ text.bind("<<smart-backspace>>",self.smart_backspace_event)
+ text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
+ text.bind("<<smart-indent>>",self.smart_indent_event)
+ text.bind("<<indent-region>>",self.indent_region_event)
+ text.bind("<<dedent-region>>",self.dedent_region_event)
+ text.bind("<<comment-region>>",self.comment_region_event)
+ text.bind("<<uncomment-region>>",self.uncomment_region_event)
+ text.bind("<<tabify-region>>",self.tabify_region_event)
+ text.bind("<<untabify-region>>",self.untabify_region_event)
+ text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
+ text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
+ text.bind("<Left>", self.move_at_edge_if_selection(0))
+ text.bind("<Right>", self.move_at_edge_if_selection(1))
+ text.bind("<<del-word-left>>", self.del_word_left)
+ text.bind("<<del-word-right>>", self.del_word_right)
+ text.bind("<<beginning-of-line>>", self.home_callback)
+
+ if flist:
+ flist.inversedict[self] = key
+ if key:
+ flist.dict[key] = self
+ text.bind("<<open-new-window>>", self.new_callback)
+ text.bind("<<close-all-windows>>", self.flist.close_all_callback)
+ text.bind("<<open-class-browser>>", self.open_class_browser)
+ text.bind("<<open-path-browser>>", self.open_path_browser)
+ text.bind("<<open-turtle-demo>>", self.open_turtle_demo)
+
+ self.set_status_bar()
+ vbar['command'] = text.yview
+ vbar.pack(side=RIGHT, fill=Y)
+ text['yscrollcommand'] = vbar.set
+ text['font'] = idleConf.GetFont(self.root, 'main', 'EditorWindow')
+ text_frame.pack(side=LEFT, fill=BOTH, expand=1)
+ text.pack(side=TOP, fill=BOTH, expand=1)
+ text.focus_set()
+
+ # usetabs true -> literal tab characters are used by indent and
+ # dedent cmds, possibly mixed with spaces if
+ # indentwidth is not a multiple of tabwidth,
+ # which will cause Tabnanny to nag!
+ # false -> tab characters are converted to spaces by indent
+ # and dedent cmds, and ditto TAB keystrokes
+ # Although use-spaces=0 can be configured manually in config-main.def,
+ # configuration of tabs v. spaces is not supported in the configuration
+ # dialog. IDLE promotes the preferred Python indentation: use spaces!
+ usespaces = idleConf.GetOption('main', 'Indent',
+ 'use-spaces', type='bool')
+ self.usetabs = not usespaces
+
+ # tabwidth is the display width of a literal tab character.
+ # CAUTION: telling Tk to use anything other than its default
+ # tab setting causes it to use an entirely different tabbing algorithm,
+ # treating tab stops as fixed distances from the left margin.
+ # Nobody expects this, so for now tabwidth should never be changed.
+ self.tabwidth = 8 # must remain 8 until Tk is fixed.
+
+ # indentwidth is the number of screen characters per indent level.
+ # The recommended Python indentation is four spaces.
+ self.indentwidth = self.tabwidth
+ self.set_notabs_indentwidth()
+
+ # If context_use_ps1 is true, parsing searches back for a ps1 line;
+ # else searches for a popular (if, def, ...) Python stmt.
+ self.context_use_ps1 = False
+
+ # When searching backwards for a reliable place to begin parsing,
+ # first start num_context_lines[0] lines back, then
+ # num_context_lines[1] lines back if that didn't work, and so on.
+ # The last value should be huge (larger than the # of lines in a
+ # conceivable file).
+ # Making the initial values larger slows things down more often.
+ self.num_context_lines = 50, 500, 5000000
+ self.per = per = self.Percolator(text)
+ self.undo = undo = self.UndoDelegator()
+ per.insertfilter(undo)
+ text.undo_block_start = undo.undo_block_start
+ text.undo_block_stop = undo.undo_block_stop
+ undo.set_saved_change_hook(self.saved_change_hook)
+ # IOBinding implements file I/O and printing functionality
+ self.io = io = self.IOBinding(self)
+ io.set_filename_change_hook(self.filename_change_hook)
+ self.good_load = False
+ self.set_indentation_params(False)
+ self.color = None # initialized below in self.ResetColorizer
+ if filename:
+ if os.path.exists(filename) and not os.path.isdir(filename):
+ if io.loadfile(filename):
+ self.good_load = True
+ is_py_src = self.ispythonsource(filename)
+ self.set_indentation_params(is_py_src)
+ else:
+ io.set_filename(filename)
+ self.good_load = True
+
+ self.ResetColorizer()
+ self.saved_change_hook()
+ self.update_recent_files_list()
+ self.load_extensions()
+ menu = self.menudict.get('windows')
+ if menu:
+ end = menu.index("end")
+ if end is None:
+ end = -1
+ if end >= 0:
+ menu.add_separator()
+ end = end + 1
+ self.wmenu_end = end
+ windows.register_callback(self.postwindowsmenu)
+
+ # Some abstractions so IDLE extensions are cross-IDE
+ self.askyesno = tkMessageBox.askyesno
+ self.askinteger = tkSimpleDialog.askinteger
+ self.showerror = tkMessageBox.showerror
+
+ def _filename_to_unicode(self, filename):
+ """Return filename as BMP unicode so diplayable in Tk."""
+ # Decode bytes to unicode.
+ if isinstance(filename, bytes):
+ try:
+ filename = filename.decode(self.filesystemencoding)
+ except UnicodeDecodeError:
+ try:
+ filename = filename.decode(self.encoding)
+ except UnicodeDecodeError:
+ # byte-to-byte conversion
+ filename = filename.decode('iso8859-1')
+ # Replace non-BMP char with diamond questionmark.
+ return re.sub('[\U00010000-\U0010FFFF]', '\ufffd', filename)
+
+ def new_callback(self, event):
+ dirname, basename = self.io.defaultfilename()
+ self.flist.new(dirname)
+ return "break"
+
+ def home_callback(self, event):
+ if (event.state & 4) != 0 and event.keysym == "Home":
+ # state&4==Control. If <Control-Home>, use the Tk binding.
+ return
+ if self.text.index("iomark") and \
+ self.text.compare("iomark", "<=", "insert lineend") and \
+ self.text.compare("insert linestart", "<=", "iomark"):
+ # In Shell on input line, go to just after prompt
+ insertpt = int(self.text.index("iomark").split(".")[1])
+ else:
+ line = self.text.get("insert linestart", "insert lineend")
+ for insertpt in range(len(line)):
+ if line[insertpt] not in (' ','\t'):
+ break
+ else:
+ insertpt=len(line)
+ lineat = int(self.text.index("insert").split('.')[1])
+ if insertpt == lineat:
+ insertpt = 0
+ dest = "insert linestart+"+str(insertpt)+"c"
+ if (event.state&1) == 0:
+ # shift was not pressed
+ self.text.tag_remove("sel", "1.0", "end")
+ else:
+ if not self.text.index("sel.first"):
+ # there was no previous selection
+ self.text.mark_set("my_anchor", "insert")
+ else:
+ if self.text.compare(self.text.index("sel.first"), "<",
+ self.text.index("insert")):
+ self.text.mark_set("my_anchor", "sel.first") # extend back
+ else:
+ self.text.mark_set("my_anchor", "sel.last") # extend forward
+ first = self.text.index(dest)
+ last = self.text.index("my_anchor")
+ if self.text.compare(first,">",last):
+ first,last = last,first
+ self.text.tag_remove("sel", "1.0", "end")
+ self.text.tag_add("sel", first, last)
+ self.text.mark_set("insert", dest)
+ self.text.see("insert")
+ return "break"
+
+ def set_status_bar(self):
+ self.status_bar = self.MultiStatusBar(self.top)
+ sep = Frame(self.top, height=1, borderwidth=1, background='grey75')
+ if sys.platform == "darwin":
+ # Insert some padding to avoid obscuring some of the statusbar
+ # by the resize widget.
+ self.status_bar.set_label('_padding1', ' ', side=RIGHT)
+ self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
+ self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
+ self.status_bar.pack(side=BOTTOM, fill=X)
+ sep.pack(side=BOTTOM, fill=X)
+ self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
+ self.text.event_add("<<set-line-and-column>>",
+ "<KeyRelease>", "<ButtonRelease>")
+ self.text.after_idle(self.set_line_and_column)
+
+ def set_line_and_column(self, event=None):
+ line, column = self.text.index(INSERT).split('.')
+ self.status_bar.set_label('column', 'Col: %s' % column)
+ self.status_bar.set_label('line', 'Ln: %s' % line)
+
+ menu_specs = [
+ ("file", "_File"),
+ ("edit", "_Edit"),
+ ("format", "F_ormat"),
+ ("run", "_Run"),
+ ("options", "_Options"),
+ ("windows", "_Window"),
+ ("help", "_Help"),
+ ]
+
+
+ def createmenubar(self):
+ mbar = self.menubar
+ self.menudict = menudict = {}
+ for name, label in self.menu_specs:
+ underline, label = prepstr(label)
+ menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
+ mbar.add_cascade(label=label, menu=menu, underline=underline)
+ if macosx.isCarbonTk():
+ # Insert the application menu
+ menudict['application'] = menu = Menu(mbar, name='apple',
+ tearoff=0)
+ mbar.add_cascade(label='IDLE', menu=menu)
+ self.fill_menus()
+ self.recent_files_menu = Menu(self.menubar, tearoff=0)
+ self.menudict['file'].insert_cascade(3, label='Recent Files',
+ underline=0,
+ menu=self.recent_files_menu)
+ self.base_helpmenu_length = self.menudict['help'].index(END)
+ self.reset_help_menu_entries()
+
+ def postwindowsmenu(self):
+ # Only called when Windows menu exists
+ menu = self.menudict['windows']
+ end = menu.index("end")
+ if end is None:
+ end = -1
+ if end > self.wmenu_end:
+ menu.delete(self.wmenu_end+1, end)
+ windows.add_windows_to_menu(menu)
+
+ rmenu = None
+
+ def right_menu_event(self, event):
+ self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
+ if not self.rmenu:
+ self.make_rmenu()
+ rmenu = self.rmenu
+ self.event = event
+ iswin = sys.platform[:3] == 'win'
+ if iswin:
+ self.text.config(cursor="arrow")
+
+ for item in self.rmenu_specs:
+ try:
+ label, eventname, verify_state = item
+ except ValueError: # see issue1207589
+ continue
+
+ if verify_state is None:
+ continue
+ state = getattr(self, verify_state)()
+ rmenu.entryconfigure(label, state=state)
+
+
+ rmenu.tk_popup(event.x_root, event.y_root)
+ if iswin:
+ self.text.config(cursor="ibeam")
+
+ rmenu_specs = [
+ # ("Label", "<<virtual-event>>", "statefuncname"), ...
+ ("Close", "<<close-window>>", None), # Example
+ ]
+
+ def make_rmenu(self):
+ rmenu = Menu(self.text, tearoff=0)
+ for item in self.rmenu_specs:
+ label, eventname = item[0], item[1]
+ if label is not None:
+ def command(text=self.text, eventname=eventname):
+ text.event_generate(eventname)
+ rmenu.add_command(label=label, command=command)
+ else:
+ rmenu.add_separator()
+ self.rmenu = rmenu
+
+ def rmenu_check_cut(self):
+ return self.rmenu_check_copy()
+
+ def rmenu_check_copy(self):
+ try:
+ indx = self.text.index('sel.first')
+ except TclError:
+ return 'disabled'
+ else:
+ return 'normal' if indx else 'disabled'
+
+ def rmenu_check_paste(self):
+ try:
+ self.text.tk.call('tk::GetSelection', self.text, 'CLIPBOARD')
+ except TclError:
+ return 'disabled'
+ else:
+ return 'normal'
+
+ def about_dialog(self, event=None):
+ "Handle Help 'About IDLE' event."
+ # Synchronize with macosx.overrideRootMenu.about_dialog.
+ help_about.AboutDialog(self.top,'About IDLE')
+
+ def config_dialog(self, event=None):
+ "Handle Options 'Configure IDLE' event."
+ # Synchronize with macosx.overrideRootMenu.config_dialog.
+ configdialog.ConfigDialog(self.top,'Settings')
+
+ def help_dialog(self, event=None):
+ "Handle Help 'IDLE Help' event."
+ # Synchronize with macosx.overrideRootMenu.help_dialog.
+ if self.root:
+ parent = self.root
+ else:
+ parent = self.top
+ help.show_idlehelp(parent)
+
+ def python_docs(self, event=None):
+ if sys.platform[:3] == 'win':
+ try:
+ os.startfile(self.help_url)
+ except OSError as why:
+ tkMessageBox.showerror(title='Document Start Failure',
+ message=str(why), parent=self.text)
+ else:
+ webbrowser.open(self.help_url)
+ return "break"
+
+ def cut(self,event):
+ self.text.event_generate("<<Cut>>")
+ return "break"
+
+ def copy(self,event):
+ if not self.text.tag_ranges("sel"):
+ # There is no selection, so do nothing and maybe interrupt.
+ return
+ self.text.event_generate("<<Copy>>")
+ return "break"
+
+ def paste(self,event):
+ self.text.event_generate("<<Paste>>")
+ self.text.see("insert")
+ return "break"
+
+ def select_all(self, event=None):
+ self.text.tag_add("sel", "1.0", "end-1c")
+ self.text.mark_set("insert", "1.0")
+ self.text.see("insert")
+ return "break"
+
+ def remove_selection(self, event=None):
+ self.text.tag_remove("sel", "1.0", "end")
+ self.text.see("insert")
+
+ def move_at_edge_if_selection(self, edge_index):
+ """Cursor move begins at start or end of selection
+
+ When a left/right cursor key is pressed create and return to Tkinter a
+ function which causes a cursor move from the associated edge of the
+ selection.
+
+ """
+ self_text_index = self.text.index
+ self_text_mark_set = self.text.mark_set
+ edges_table = ("sel.first+1c", "sel.last-1c")
+ def move_at_edge(event):
+ if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
+ try:
+ self_text_index("sel.first")
+ self_text_mark_set("insert", edges_table[edge_index])
+ except TclError:
+ pass
+ return move_at_edge
+
+ def del_word_left(self, event):
+ self.text.event_generate('<Meta-Delete>')
+ return "break"
+
+ def del_word_right(self, event):
+ self.text.event_generate('<Meta-d>')
+ return "break"
+
+ def find_event(self, event):
+ search.find(self.text)
+ return "break"
+
+ def find_again_event(self, event):
+ search.find_again(self.text)
+ return "break"
+
+ def find_selection_event(self, event):
+ search.find_selection(self.text)
+ return "break"
+
+ def find_in_files_event(self, event):
+ grep.grep(self.text, self.io, self.flist)
+ return "break"
+
+ def replace_event(self, event):
+ replace.replace(self.text)
+ return "break"
+
+ def goto_line_event(self, event):
+ text = self.text
+ lineno = tkSimpleDialog.askinteger("Goto",
+ "Go to line number:",parent=text)
+ if lineno is None:
+ return "break"
+ if lineno <= 0:
+ text.bell()
+ return "break"
+ text.mark_set("insert", "%d.0" % lineno)
+ text.see("insert")
+
+ def open_module(self, event=None):
+ # XXX Shouldn't this be in IOBinding?
+ try:
+ name = self.text.get("sel.first", "sel.last")
+ except TclError:
+ name = ""
+ else:
+ name = name.strip()
+ name = tkSimpleDialog.askstring("Module",
+ "Enter the name of a Python module\n"
+ "to search on sys.path and open:",
+ parent=self.text, initialvalue=name)
+ if name:
+ name = name.strip()
+ if not name:
+ return
+ # XXX Ought to insert current file's directory in front of path
+ try:
+ spec = importlib.util.find_spec(name)
+ except (ValueError, ImportError) as msg:
+ tkMessageBox.showerror("Import error", str(msg), parent=self.text)
+ return
+ if spec is None:
+ tkMessageBox.showerror("Import error", "module not found",
+ parent=self.text)
+ return
+ if not isinstance(spec.loader, importlib.abc.SourceLoader):
+ tkMessageBox.showerror("Import error", "not a source-based module",
+ parent=self.text)
+ return
+ try:
+ file_path = spec.loader.get_filename(name)
+ except AttributeError:
+ tkMessageBox.showerror("Import error",
+ "loader does not support get_filename",
+ parent=self.text)
+ return
+ if self.flist:
+ self.flist.open(file_path)
+ else:
+ self.io.loadfile(file_path)
+ return file_path
+
+ def open_class_browser(self, event=None):
+ filename = self.io.filename
+ if not (self.__class__.__name__ == 'PyShellEditorWindow'
+ and filename):
+ filename = self.open_module()
+ if filename is None:
+ return
+ head, tail = os.path.split(filename)
+ base, ext = os.path.splitext(tail)
+ from idlelib import browser
+ browser.ClassBrowser(self.flist, base, [head])
+
+ def open_path_browser(self, event=None):
+ from idlelib import pathbrowser
+ pathbrowser.PathBrowser(self.flist)
+
+ def open_turtle_demo(self, event = None):
+ import subprocess
+
+ cmd = [sys.executable,
+ '-c',
+ 'from turtledemo.__main__ import main; main()']
+ subprocess.Popen(cmd, shell=False)
+
+ def gotoline(self, lineno):
+ if lineno is not None and lineno > 0:
+ self.text.mark_set("insert", "%d.0" % lineno)
+ self.text.tag_remove("sel", "1.0", "end")
+ self.text.tag_add("sel", "insert", "insert +1l")
+ self.center()
+
+ def ispythonsource(self, filename):
+ if not filename or os.path.isdir(filename):
+ return True
+ base, ext = os.path.splitext(os.path.basename(filename))
+ if os.path.normcase(ext) in (".py", ".pyw"):
+ return True
+ line = self.text.get('1.0', '1.0 lineend')
+ return line.startswith('#!') and 'python' in line
+
+ def close_hook(self):
+ if self.flist:
+ self.flist.unregister_maybe_terminate(self)
+ self.flist = None
+
+ def set_close_hook(self, close_hook):
+ self.close_hook = close_hook
+
+ def filename_change_hook(self):
+ if self.flist:
+ self.flist.filename_changed_edit(self)
+ self.saved_change_hook()
+ self.top.update_windowlist_registry(self)
+ self.ResetColorizer()
+
+ def _addcolorizer(self):
+ if self.color:
+ return
+ if self.ispythonsource(self.io.filename):
+ self.color = self.ColorDelegator()
+ # can add more colorizers here...
+ if self.color:
+ self.per.removefilter(self.undo)
+ self.per.insertfilter(self.color)
+ self.per.insertfilter(self.undo)
+
+ def _rmcolorizer(self):
+ if not self.color:
+ return
+ self.color.removecolors()
+ self.per.removefilter(self.color)
+ self.color = None
+
+ def ResetColorizer(self):
+ "Update the color theme"
+ # Called from self.filename_change_hook and from configdialog.py
+ self._rmcolorizer()
+ self._addcolorizer()
+ EditorWindow.color_config(self.text)
+
+ IDENTCHARS = string.ascii_letters + string.digits + "_"
+
+ def colorize_syntax_error(self, text, pos):
+ text.tag_add("ERROR", pos)
+ char = text.get(pos)
+ if char and char in self.IDENTCHARS:
+ text.tag_add("ERROR", pos + " wordstart", pos)
+ if '\n' == text.get(pos): # error at line end
+ text.mark_set("insert", pos)
+ else:
+ text.mark_set("insert", pos + "+1c")
+ text.see(pos)
+
+ def ResetFont(self):
+ "Update the text widgets' font if it is changed"
+ # Called from configdialog.py
+
+ self.text['font'] = idleConf.GetFont(self.root, 'main','EditorWindow')
+
+ def RemoveKeybindings(self):
+ "Remove the keybindings before they are changed."
+ # Called from configdialog.py
+ self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
+ for event, keylist in keydefs.items():
+ self.text.event_delete(event, *keylist)
+ for extensionName in self.get_standard_extension_names():
+ xkeydefs = idleConf.GetExtensionBindings(extensionName)
+ if xkeydefs:
+ for event, keylist in xkeydefs.items():
+ self.text.event_delete(event, *keylist)
+
+ def ApplyKeybindings(self):
+ "Update the keybindings after they are changed"
+ # Called from configdialog.py
+ self.mainmenu.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
+ self.apply_bindings()
+ for extensionName in self.get_standard_extension_names():
+ xkeydefs = idleConf.GetExtensionBindings(extensionName)
+ if xkeydefs:
+ self.apply_bindings(xkeydefs)
+ #update menu accelerators
+ menuEventDict = {}
+ for menu in self.mainmenu.menudefs:
+ menuEventDict[menu[0]] = {}
+ for item in menu[1]:
+ if item:
+ menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
+ for menubarItem in self.menudict:
+ menu = self.menudict[menubarItem]
+ end = menu.index(END)
+ if end is None:
+ # Skip empty menus
+ continue
+ end += 1
+ for index in range(0, end):
+ if menu.type(index) == 'command':
+ accel = menu.entrycget(index, 'accelerator')
+ if accel:
+ itemName = menu.entrycget(index, 'label')
+ event = ''
+ if menubarItem in menuEventDict:
+ if itemName in menuEventDict[menubarItem]:
+ event = menuEventDict[menubarItem][itemName]
+ if event:
+ accel = get_accelerator(keydefs, event)
+ menu.entryconfig(index, accelerator=accel)
+
+ def set_notabs_indentwidth(self):
+ "Update the indentwidth if changed and not using tabs in this window"
+ # Called from configdialog.py
+ if not self.usetabs:
+ self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
+ type='int')
+
+ def reset_help_menu_entries(self):
+ "Update the additional help entries on the Help menu"
+ help_list = idleConf.GetAllExtraHelpSourcesList()
+ helpmenu = self.menudict['help']
+ # first delete the extra help entries, if any
+ helpmenu_length = helpmenu.index(END)
+ if helpmenu_length > self.base_helpmenu_length:
+ helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
+ # then rebuild them
+ if help_list:
+ helpmenu.add_separator()
+ for entry in help_list:
+ cmd = self.__extra_help_callback(entry[1])
+ helpmenu.add_command(label=entry[0], command=cmd)
+ # and update the menu dictionary
+ self.menudict['help'] = helpmenu
+
+ def __extra_help_callback(self, helpfile):
+ "Create a callback with the helpfile value frozen at definition time"
+ def display_extra_help(helpfile=helpfile):
+ if not helpfile.startswith(('www', 'http')):
+ helpfile = os.path.normpath(helpfile)
+ if sys.platform[:3] == 'win':
+ try:
+ os.startfile(helpfile)
+ except OSError as why:
+ tkMessageBox.showerror(title='Document Start Failure',
+ message=str(why), parent=self.text)
+ else:
+ webbrowser.open(helpfile)
+ return display_extra_help
+
+ def update_recent_files_list(self, new_file=None):
+ "Load and update the recent files list and menus"
+ rf_list = []
+ if os.path.exists(self.recent_files_path):
+ with open(self.recent_files_path, 'r',
+ encoding='utf_8', errors='replace') as rf_list_file:
+ rf_list = rf_list_file.readlines()
+ if new_file:
+ new_file = os.path.abspath(new_file) + '\n'
+ if new_file in rf_list:
+ rf_list.remove(new_file) # move to top
+ rf_list.insert(0, new_file)
+ # clean and save the recent files list
+ bad_paths = []
+ for path in rf_list:
+ if '\0' in path or not os.path.exists(path[0:-1]):
+ bad_paths.append(path)
+ rf_list = [path for path in rf_list if path not in bad_paths]
+ ulchars = "1234567890ABCDEFGHIJK"
+ rf_list = rf_list[0:len(ulchars)]
+ try:
+ with open(self.recent_files_path, 'w',
+ encoding='utf_8', errors='replace') as rf_file:
+ rf_file.writelines(rf_list)
+ except OSError as err:
+ if not getattr(self.root, "recentfilelist_error_displayed", False):
+ self.root.recentfilelist_error_displayed = True
+ tkMessageBox.showwarning(title='IDLE Warning',
+ message="Cannot update File menu Recent Files list. "
+ "Your operating system says:\n%s\n"
+ "Select OK and IDLE will continue without updating."
+ % self._filename_to_unicode(str(err)),
+ parent=self.text)
+ # for each edit window instance, construct the recent files menu
+ for instance in self.top.instance_dict:
+ menu = instance.recent_files_menu
+ menu.delete(0, END) # clear, and rebuild:
+ for i, file_name in enumerate(rf_list):
+ file_name = file_name.rstrip() # zap \n
+ # make unicode string to display non-ASCII chars correctly
+ ufile_name = self._filename_to_unicode(file_name)
+ callback = instance.__recent_file_callback(file_name)
+ menu.add_command(label=ulchars[i] + " " + ufile_name,
+ command=callback,
+ underline=0)
+
+ def __recent_file_callback(self, file_name):
+ def open_recent_file(fn_closure=file_name):
+ self.io.open(editFile=fn_closure)
+ return open_recent_file
+
+ def saved_change_hook(self):
+ short = self.short_title()
+ long = self.long_title()
+ if short and long:
+ title = short + " - " + long + _py_version
+ elif short:
+ title = short
+ elif long:
+ title = long
+ else:
+ title = "Untitled"
+ icon = short or long or title
+ if not self.get_saved():
+ title = "*%s*" % title
+ icon = "*%s" % icon
+ self.top.wm_title(title)
+ self.top.wm_iconname(icon)
+
+ def get_saved(self):
+ return self.undo.get_saved()
+
+ def set_saved(self, flag):
+ self.undo.set_saved(flag)
+
+ def reset_undo(self):
+ self.undo.reset_undo()
+
+ def short_title(self):
+ filename = self.io.filename
+ if filename:
+ filename = os.path.basename(filename)
+ else:
+ filename = "Untitled"
+ # return unicode string to display non-ASCII chars correctly
+ return self._filename_to_unicode(filename)
+
+ def long_title(self):
+ # return unicode string to display non-ASCII chars correctly
+ return self._filename_to_unicode(self.io.filename or "")
+
+ def center_insert_event(self, event):
+ self.center()
+
+ def center(self, mark="insert"):
+ text = self.text
+ top, bot = self.getwindowlines()
+ lineno = self.getlineno(mark)
+ height = bot - top
+ newtop = max(1, lineno - height//2)
+ text.yview(float(newtop))
+
+ def getwindowlines(self):
+ text = self.text
+ top = self.getlineno("@0,0")
+ bot = self.getlineno("@0,65535")
+ if top == bot and text.winfo_height() == 1:
+ # Geometry manager hasn't run yet
+ height = int(text['height'])
+ bot = top + height - 1
+ return top, bot
+
+ def getlineno(self, mark="insert"):
+ text = self.text
+ return int(float(text.index(mark)))
+
+ def get_geometry(self):
+ "Return (width, height, x, y)"
+ geom = self.top.wm_geometry()
+ m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
+ return list(map(int, m.groups()))
+
+ def close_event(self, event):
+ self.close()
+
+ def maybesave(self):
+ if self.io:
+ if not self.get_saved():
+ if self.top.state()!='normal':
+ self.top.deiconify()
+ self.top.lower()
+ self.top.lift()
+ return self.io.maybesave()
+
+ def close(self):
+ reply = self.maybesave()
+ if str(reply) != "cancel":
+ self._close()
+ return reply
+
+ def _close(self):
+ if self.io.filename:
+ self.update_recent_files_list(new_file=self.io.filename)
+ windows.unregister_callback(self.postwindowsmenu)
+ self.unload_extensions()
+ self.io.close()
+ self.io = None
+ self.undo = None
+ if self.color:
+ self.color.close(False)
+ self.color = None
+ self.text = None
+ self.tkinter_vars = None
+ self.per.close()
+ self.per = None
+ self.top.destroy()
+ if self.close_hook:
+ # unless override: unregister from flist, terminate if last window
+ self.close_hook()
+
+ def load_extensions(self):
+ self.extensions = {}
+ self.load_standard_extensions()
+
+ def unload_extensions(self):
+ for ins in list(self.extensions.values()):
+ if hasattr(ins, "close"):
+ ins.close()
+ self.extensions = {}
+
+ def load_standard_extensions(self):
+ for name in self.get_standard_extension_names():
+ try:
+ self.load_extension(name)
+ except:
+ print("Failed to load extension", repr(name))
+ traceback.print_exc()
+
+ def get_standard_extension_names(self):
+ return idleConf.GetExtensions(editor_only=True)
+
+ extfiles = { # map config-extension section names to new file names
+ 'AutoComplete': 'autocomplete',
+ 'AutoExpand': 'autoexpand',
+ 'CallTips': 'calltips',
+ 'CodeContext': 'codecontext',
+ 'FormatParagraph': 'paragraph',
+ 'ParenMatch': 'parenmatch',
+ 'RstripExtension': 'rstrip',
+ 'ScriptBinding': 'runscript',
+ 'ZoomHeight': 'zoomheight',
+ }
+
+ def load_extension(self, name):
+ fname = self.extfiles.get(name, name)
+ try:
+ try:
+ mod = importlib.import_module('.' + fname, package=__package__)
+ except (ImportError, TypeError):
+ mod = importlib.import_module(fname)
+ except ImportError:
+ print("\nFailed to import extension: ", name)
+ raise
+ cls = getattr(mod, name)
+ keydefs = idleConf.GetExtensionBindings(name)
+ if hasattr(cls, "menudefs"):
+ self.fill_menus(cls.menudefs, keydefs)
+ ins = cls(self)
+ self.extensions[name] = ins
+ if keydefs:
+ self.apply_bindings(keydefs)
+ for vevent in keydefs:
+ methodname = vevent.replace("-", "_")
+ while methodname[:1] == '<':
+ methodname = methodname[1:]
+ while methodname[-1:] == '>':
+ methodname = methodname[:-1]
+ methodname = methodname + "_event"
+ if hasattr(ins, methodname):
+ self.text.bind(vevent, getattr(ins, methodname))
+
+ def apply_bindings(self, keydefs=None):
+ if keydefs is None:
+ keydefs = self.mainmenu.default_keydefs
+ text = self.text
+ text.keydefs = keydefs
+ for event, keylist in keydefs.items():
+ if keylist:
+ text.event_add(event, *keylist)
+
+ def fill_menus(self, menudefs=None, keydefs=None):
+ """Add appropriate entries to the menus and submenus
+
+ Menus that are absent or None in self.menudict are ignored.
+ """
+ if menudefs is None:
+ menudefs = self.mainmenu.menudefs
+ if keydefs is None:
+ keydefs = self.mainmenu.default_keydefs
+ menudict = self.menudict
+ text = self.text
+ for mname, entrylist in menudefs:
+ menu = menudict.get(mname)
+ if not menu:
+ continue
+ for entry in entrylist:
+ if not entry:
+ menu.add_separator()
+ else:
+ label, eventname = entry
+ checkbutton = (label[:1] == '!')
+ if checkbutton:
+ label = label[1:]
+ underline, label = prepstr(label)
+ accelerator = get_accelerator(keydefs, eventname)
+ def command(text=text, eventname=eventname):
+ text.event_generate(eventname)
+ if checkbutton:
+ var = self.get_var_obj(eventname, BooleanVar)
+ menu.add_checkbutton(label=label, underline=underline,
+ command=command, accelerator=accelerator,
+ variable=var)
+ else:
+ menu.add_command(label=label, underline=underline,
+ command=command,
+ accelerator=accelerator)
+
+ def getvar(self, name):
+ var = self.get_var_obj(name)
+ if var:
+ value = var.get()
+ return value
+ else:
+ raise NameError(name)
+
+ def setvar(self, name, value, vartype=None):
+ var = self.get_var_obj(name, vartype)
+ if var:
+ var.set(value)
+ else:
+ raise NameError(name)
+
+ def get_var_obj(self, name, vartype=None):
+ var = self.tkinter_vars.get(name)
+ if not var and vartype:
+ # create a Tkinter variable object with self.text as master:
+ self.tkinter_vars[name] = var = vartype(self.text)
+ return var
+
+ # Tk implementations of "virtual text methods" -- each platform
+ # reusing IDLE's support code needs to define these for its GUI's
+ # flavor of widget.
+
+ # Is character at text_index in a Python string? Return 0 for
+ # "guaranteed no", true for anything else. This info is expensive
+ # to compute ab initio, but is probably already known by the
+ # platform's colorizer.
+
+ def is_char_in_string(self, text_index):
+ if self.color:
+ # Return true iff colorizer hasn't (re)gotten this far
+ # yet, or the character is tagged as being in a string
+ return self.text.tag_prevrange("TODO", text_index) or \
+ "STRING" in self.text.tag_names(text_index)
+ else:
+ # The colorizer is missing: assume the worst
+ return 1
+
+ # If a selection is defined in the text widget, return (start,
+ # end) as Tkinter text indices, otherwise return (None, None)
+ def get_selection_indices(self):
+ try:
+ first = self.text.index("sel.first")
+ last = self.text.index("sel.last")
+ return first, last
+ except TclError:
+ return None, None
+
+ # Return the text widget's current view of what a tab stop means
+ # (equivalent width in spaces).
+
+ def get_tk_tabwidth(self):
+ current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
+ return int(current)
+
+ # Set the text widget's current view of what a tab stop means.
+
+ def set_tk_tabwidth(self, newtabwidth):
+ text = self.text
+ if self.get_tk_tabwidth() != newtabwidth:
+ # Set text widget tab width
+ pixels = text.tk.call("font", "measure", text["font"],
+ "-displayof", text.master,
+ "n" * newtabwidth)
+ text.configure(tabs=pixels)
+
+### begin autoindent code ### (configuration was moved to beginning of class)
+
+ def set_indentation_params(self, is_py_src, guess=True):
+ if is_py_src and guess:
+ i = self.guess_indent()
+ if 2 <= i <= 8:
+ self.indentwidth = i
+ if self.indentwidth != self.tabwidth:
+ self.usetabs = False
+ self.set_tk_tabwidth(self.tabwidth)
+
+ def smart_backspace_event(self, event):
+ text = self.text
+ first, last = self.get_selection_indices()
+ if first and last:
+ text.delete(first, last)
+ text.mark_set("insert", first)
+ return "break"
+ # Delete whitespace left, until hitting a real char or closest
+ # preceding virtual tab stop.
+ chars = text.get("insert linestart", "insert")
+ if chars == '':
+ if text.compare("insert", ">", "1.0"):
+ # easy: delete preceding newline
+ text.delete("insert-1c")
+ else:
+ text.bell() # at start of buffer
+ return "break"
+ if chars[-1] not in " \t":
+ # easy: delete preceding real char
+ text.delete("insert-1c")
+ return "break"
+ # Ick. It may require *inserting* spaces if we back up over a
+ # tab character! This is written to be clear, not fast.
+ tabwidth = self.tabwidth
+ have = len(chars.expandtabs(tabwidth))
+ assert have > 0
+ want = ((have - 1) // self.indentwidth) * self.indentwidth
+ # Debug prompt is multilined....
+ if self.context_use_ps1:
+ last_line_of_prompt = sys.ps1.split('\n')[-1]
+ else:
+ last_line_of_prompt = ''
+ ncharsdeleted = 0
+ while 1:
+ if chars == last_line_of_prompt:
+ break
+ chars = chars[:-1]
+ ncharsdeleted = ncharsdeleted + 1
+ have = len(chars.expandtabs(tabwidth))
+ if have <= want or chars[-1] not in " \t":
+ break
+ text.undo_block_start()
+ text.delete("insert-%dc" % ncharsdeleted, "insert")
+ if have < want:
+ text.insert("insert", ' ' * (want - have))
+ text.undo_block_stop()
+ return "break"
+
+ def smart_indent_event(self, event):
+ # if intraline selection:
+ # delete it
+ # elif multiline selection:
+ # do indent-region
+ # else:
+ # indent one level
+ text = self.text
+ first, last = self.get_selection_indices()
+ text.undo_block_start()
+ try:
+ if first and last:
+ if index2line(first) != index2line(last):
+ return self.indent_region_event(event)
+ text.delete(first, last)
+ text.mark_set("insert", first)
+ prefix = text.get("insert linestart", "insert")
+ raw, effective = classifyws(prefix, self.tabwidth)
+ if raw == len(prefix):
+ # only whitespace to the left
+ self.reindent_to(effective + self.indentwidth)
+ else:
+ # tab to the next 'stop' within or to right of line's text:
+ if self.usetabs:
+ pad = '\t'
+ else:
+ effective = len(prefix.expandtabs(self.tabwidth))
+ n = self.indentwidth
+ pad = ' ' * (n - effective % n)
+ text.insert("insert", pad)
+ text.see("insert")
+ return "break"
+ finally:
+ text.undo_block_stop()
+
+ def newline_and_indent_event(self, event):
+ text = self.text
+ first, last = self.get_selection_indices()
+ text.undo_block_start()
+ try:
+ if first and last:
+ text.delete(first, last)
+ text.mark_set("insert", first)
+ line = text.get("insert linestart", "insert")
+ i, n = 0, len(line)
+ while i < n and line[i] in " \t":
+ i = i+1
+ if i == n:
+ # the cursor is in or at leading indentation in a continuation
+ # line; just inject an empty line at the start
+ text.insert("insert linestart", '\n')
+ return "break"
+ indent = line[:i]
+ # strip whitespace before insert point unless it's in the prompt
+ i = 0
+ last_line_of_prompt = sys.ps1.split('\n')[-1]
+ while line and line[-1] in " \t" and line != last_line_of_prompt:
+ line = line[:-1]
+ i = i+1
+ if i:
+ text.delete("insert - %d chars" % i, "insert")
+ # strip whitespace after insert point
+ while text.get("insert") in " \t":
+ text.delete("insert")
+ # start new line
+ text.insert("insert", '\n')
+
+ # adjust indentation for continuations and block
+ # open/close first need to find the last stmt
+ lno = index2line(text.index('insert'))
+ y = pyparse.Parser(self.indentwidth, self.tabwidth)
+ if not self.context_use_ps1:
+ for context in self.num_context_lines:
+ startat = max(lno - context, 1)
+ startatindex = repr(startat) + ".0"
+ rawtext = text.get(startatindex, "insert")
+ y.set_str(rawtext)
+ bod = y.find_good_parse_start(
+ self.context_use_ps1,
+ self._build_char_in_string_func(startatindex))
+ if bod is not None or startat == 1:
+ break
+ y.set_lo(bod or 0)
+ else:
+ r = text.tag_prevrange("console", "insert")
+ if r:
+ startatindex = r[1]
+ else:
+ startatindex = "1.0"
+ rawtext = text.get(startatindex, "insert")
+ y.set_str(rawtext)
+ y.set_lo(0)
+
+ c = y.get_continuation_type()
+ if c != pyparse.C_NONE:
+ # The current stmt hasn't ended yet.
+ if c == pyparse.C_STRING_FIRST_LINE:
+ # after the first line of a string; do not indent at all
+ pass
+ elif c == pyparse.C_STRING_NEXT_LINES:
+ # inside a string which started before this line;
+ # just mimic the current indent
+ text.insert("insert", indent)
+ elif c == pyparse.C_BRACKET:
+ # line up with the first (if any) element of the
+ # last open bracket structure; else indent one
+ # level beyond the indent of the line with the
+ # last open bracket
+ self.reindent_to(y.compute_bracket_indent())
+ elif c == pyparse.C_BACKSLASH:
+ # if more than one line in this stmt already, just
+ # mimic the current indent; else if initial line
+ # has a start on an assignment stmt, indent to
+ # beyond leftmost =; else to beyond first chunk of
+ # non-whitespace on initial line
+ if y.get_num_lines_in_stmt() > 1:
+ text.insert("insert", indent)
+ else:
+ self.reindent_to(y.compute_backslash_indent())
+ else:
+ assert 0, "bogus continuation type %r" % (c,)
+ return "break"
+
+ # This line starts a brand new stmt; indent relative to
+ # indentation of initial line of closest preceding
+ # interesting stmt.
+ indent = y.get_base_indent_string()
+ text.insert("insert", indent)
+ if y.is_block_opener():
+ self.smart_indent_event(event)
+ elif indent and y.is_block_closer():
+ self.smart_backspace_event(event)
+ return "break"
+ finally:
+ text.see("insert")
+ text.undo_block_stop()
+
+ # Our editwin provides an is_char_in_string function that works
+ # with a Tk text index, but PyParse only knows about offsets into
+ # a string. This builds a function for PyParse that accepts an
+ # offset.
+
+ def _build_char_in_string_func(self, startindex):
+ def inner(offset, _startindex=startindex,
+ _icis=self.is_char_in_string):
+ return _icis(_startindex + "+%dc" % offset)
+ return inner
+
+ def indent_region_event(self, event):
+ head, tail, chars, lines = self.get_region()
+ for pos in range(len(lines)):
+ line = lines[pos]
+ if line:
+ raw, effective = classifyws(line, self.tabwidth)
+ effective = effective + self.indentwidth
+ lines[pos] = self._make_blanks(effective) + line[raw:]
+ self.set_region(head, tail, chars, lines)
+ return "break"
+
+ def dedent_region_event(self, event):
+ head, tail, chars, lines = self.get_region()
+ for pos in range(len(lines)):
+ line = lines[pos]
+ if line:
+ raw, effective = classifyws(line, self.tabwidth)
+ effective = max(effective - self.indentwidth, 0)
+ lines[pos] = self._make_blanks(effective) + line[raw:]
+ self.set_region(head, tail, chars, lines)
+ return "break"
+
+ def comment_region_event(self, event):
+ head, tail, chars, lines = self.get_region()
+ for pos in range(len(lines) - 1):
+ line = lines[pos]
+ lines[pos] = '##' + line
+ self.set_region(head, tail, chars, lines)
+
+ def uncomment_region_event(self, event):
+ head, tail, chars, lines = self.get_region()
+ for pos in range(len(lines)):
+ line = lines[pos]
+ if not line:
+ continue
+ if line[:2] == '##':
+ line = line[2:]
+ elif line[:1] == '#':
+ line = line[1:]
+ lines[pos] = line
+ self.set_region(head, tail, chars, lines)
+
+ def tabify_region_event(self, event):
+ head, tail, chars, lines = self.get_region()
+ tabwidth = self._asktabwidth()
+ if tabwidth is None: return
+ for pos in range(len(lines)):
+ line = lines[pos]
+ if line:
+ raw, effective = classifyws(line, tabwidth)
+ ntabs, nspaces = divmod(effective, tabwidth)
+ lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
+ self.set_region(head, tail, chars, lines)
+
+ def untabify_region_event(self, event):
+ head, tail, chars, lines = self.get_region()
+ tabwidth = self._asktabwidth()
+ if tabwidth is None: return
+ for pos in range(len(lines)):
+ lines[pos] = lines[pos].expandtabs(tabwidth)
+ self.set_region(head, tail, chars, lines)
+
+ def toggle_tabs_event(self, event):
+ if self.askyesno(
+ "Toggle tabs",
+ "Turn tabs " + ("on", "off")[self.usetabs] +
+ "?\nIndent width " +
+ ("will be", "remains at")[self.usetabs] + " 8." +
+ "\n Note: a tab is always 8 columns",
+ parent=self.text):
+ self.usetabs = not self.usetabs
+ # Try to prevent inconsistent indentation.
+ # User must change indent width manually after using tabs.
+ self.indentwidth = 8
+ return "break"
+
+ # XXX this isn't bound to anything -- see tabwidth comments
+## def change_tabwidth_event(self, event):
+## new = self._asktabwidth()
+## if new != self.tabwidth:
+## self.tabwidth = new
+## self.set_indentation_params(0, guess=0)
+## return "break"
+
+ def change_indentwidth_event(self, event):
+ new = self.askinteger(
+ "Indent width",
+ "New indent width (2-16)\n(Always use 8 when using tabs)",
+ parent=self.text,
+ initialvalue=self.indentwidth,
+ minvalue=2,
+ maxvalue=16)
+ if new and new != self.indentwidth and not self.usetabs:
+ self.indentwidth = new
+ return "break"
+
+ def get_region(self):
+ text = self.text
+ first, last = self.get_selection_indices()
+ if first and last:
+ head = text.index(first + " linestart")
+ tail = text.index(last + "-1c lineend +1c")
+ else:
+ head = text.index("insert linestart")
+ tail = text.index("insert lineend +1c")
+ chars = text.get(head, tail)
+ lines = chars.split("\n")
+ return head, tail, chars, lines
+
+ def set_region(self, head, tail, chars, lines):
+ text = self.text
+ newchars = "\n".join(lines)
+ if newchars == chars:
+ text.bell()
+ return
+ text.tag_remove("sel", "1.0", "end")
+ text.mark_set("insert", head)
+ text.undo_block_start()
+ text.delete(head, tail)
+ text.insert(head, newchars)
+ text.undo_block_stop()
+ text.tag_add("sel", head, "insert")
+
+ # Make string that displays as n leading blanks.
+
+ def _make_blanks(self, n):
+ if self.usetabs:
+ ntabs, nspaces = divmod(n, self.tabwidth)
+ return '\t' * ntabs + ' ' * nspaces
+ else:
+ return ' ' * n
+
+ # Delete from beginning of line to insert point, then reinsert
+ # column logical (meaning use tabs if appropriate) spaces.
+
+ def reindent_to(self, column):
+ text = self.text
+ text.undo_block_start()
+ if text.compare("insert linestart", "!=", "insert"):
+ text.delete("insert linestart", "insert")
+ if column:
+ text.insert("insert", self._make_blanks(column))
+ text.undo_block_stop()
+
+ def _asktabwidth(self):
+ return self.askinteger(
+ "Tab width",
+ "Columns per tab? (2-16)",
+ parent=self.text,
+ initialvalue=self.indentwidth,
+ minvalue=2,
+ maxvalue=16)
+
+ # Guess indentwidth from text content.
+ # Return guessed indentwidth. This should not be believed unless
+ # it's in a reasonable range (e.g., it will be 0 if no indented
+ # blocks are found).
+
+ def guess_indent(self):
+ opener, indented = IndentSearcher(self.text, self.tabwidth).run()
+ if opener and indented:
+ raw, indentsmall = classifyws(opener, self.tabwidth)
+ raw, indentlarge = classifyws(indented, self.tabwidth)
+ else:
+ indentsmall = indentlarge = 0
+ return indentlarge - indentsmall
+
+# "line.col" -> line, as an int
+def index2line(index):
+ return int(float(index))
+
+# Look at the leading whitespace in s.
+# Return pair (# of leading ws characters,
+# effective # of leading blanks after expanding
+# tabs to width tabwidth)
+
+def classifyws(s, tabwidth):
+ raw = effective = 0
+ for ch in s:
+ if ch == ' ':
+ raw = raw + 1
+ effective = effective + 1
+ elif ch == '\t':
+ raw = raw + 1
+ effective = (effective // tabwidth + 1) * tabwidth
+ else:
+ break
+ return raw, effective
+
+import tokenize
+_tokenize = tokenize
+del tokenize
+
+class IndentSearcher(object):
+
+ # .run() chews over the Text widget, looking for a block opener
+ # and the stmt following it. Returns a pair,
+ # (line containing block opener, line containing stmt)
+ # Either or both may be None.
+
+ def __init__(self, text, tabwidth):
+ self.text = text
+ self.tabwidth = tabwidth
+ self.i = self.finished = 0
+ self.blkopenline = self.indentedline = None
+
+ def readline(self):
+ if self.finished:
+ return ""
+ i = self.i = self.i + 1
+ mark = repr(i) + ".0"
+ if self.text.compare(mark, ">=", "end"):
+ return ""
+ return self.text.get(mark, mark + " lineend+1c")
+
+ def tokeneater(self, type, token, start, end, line,
+ INDENT=_tokenize.INDENT,
+ NAME=_tokenize.NAME,
+ OPENERS=('class', 'def', 'for', 'if', 'try', 'while')):
+ if self.finished:
+ pass
+ elif type == NAME and token in OPENERS:
+ self.blkopenline = line
+ elif type == INDENT and self.blkopenline:
+ self.indentedline = line
+ self.finished = 1
+
+ def run(self):
+ save_tabsize = _tokenize.tabsize
+ _tokenize.tabsize = self.tabwidth
+ try:
+ try:
+ tokens = _tokenize.generate_tokens(self.readline)
+ for token in tokens:
+ self.tokeneater(*token)
+ except (_tokenize.TokenError, SyntaxError):
+ # since we cut off the tokenizer early, we can trigger
+ # spurious errors
+ pass
+ finally:
+ _tokenize.tabsize = save_tabsize
+ return self.blkopenline, self.indentedline
+
+### end autoindent code ###
+
+def prepstr(s):
+ # Helper to extract the underscore from a string, e.g.
+ # prepstr("Co_py") returns (2, "Copy").
+ i = s.find('_')
+ if i >= 0:
+ s = s[:i] + s[i+1:]
+ return i, s
+
+
+keynames = {
+ 'bracketleft': '[',
+ 'bracketright': ']',
+ 'slash': '/',
+}
+
+def get_accelerator(keydefs, eventname):
+ keylist = keydefs.get(eventname)
+ # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
+ # if not keylist:
+ if (not keylist) or (macosx.isCocoaTk() and eventname in {
+ "<<open-module>>",
+ "<<goto-line>>",
+ "<<change-indentwidth>>"}):
+ return ""
+ s = keylist[0]
+ s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
+ s = re.sub(r"\b\w+\b", lambda m: keynames.get(m.group(), m.group()), s)
+ s = re.sub("Key-", "", s)
+ s = re.sub("Cancel","Ctrl-Break",s) # dscherer@cmu.edu
+ s = re.sub("Control-", "Ctrl-", s)
+ s = re.sub("-", "+", s)
+ s = re.sub("><", " ", s)
+ s = re.sub("<", "", s)
+ s = re.sub(">", "", s)
+ return s
+
+
+def fixwordbreaks(root):
+ # Make sure that Tk's double-click and next/previous word
+ # operations use our definition of a word (i.e. an identifier)
+ tk = root.tk
+ tk.call('tcl_wordBreakAfter', 'a b', 0) # make sure word.tcl is loaded
+ tk.call('set', 'tcl_wordchars', '[a-zA-Z0-9_]')
+ tk.call('set', 'tcl_nonwordchars', '[^a-zA-Z0-9_]')
+
+
+def _editor_window(parent): # htest #
+ # error if close master window first - timer event, after script
+ root = parent
+ fixwordbreaks(root)
+ if sys.argv[1:]:
+ filename = sys.argv[1]
+ else:
+ filename = None
+ macosx.setupApp(root, None)
+ edit = EditorWindow(root=root, filename=filename)
+ edit.text.bind("<<close-all-windows>>", edit.close_event)
+ # Does not stop error, neither does following
+ # edit.text.bind("<<close-window>>", edit.close_event)
+
+if __name__ == '__main__':
+ from idlelib.idle_test.htest import run
+ run(_editor_window)
'>1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751
/*
 * tclBinary.c --
 *
 *	This file contains the implementation of the "binary" Tcl built-in
 *	command and the Tcl binary data object.
 *
 * Copyright (c) 1997 by Sun Microsystems, Inc.
 * Copyright (c) 1998-1999 by Scriptics Corporation.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tclInt.h"
#include "tommath.h"

#include <math.h>

/*
 * The following constants are used by GetFormatSpec to indicate various
 * special conditions in the parsing of a format specifier.
 */

#define BINARY_ALL -1		/* Use all elements in the argument. */
#define BINARY_NOCOUNT -2	/* No count was specified in format. */

/*
 * The following flags may be ORed together and returned by GetFormatSpec
 */

#define BINARY_SIGNED 0		/* Field to be read as signed data */
#define BINARY_UNSIGNED 1	/* Field to be read as unsigned data */

/*
 * The following defines the maximum number of different (integer) numbers
 * placed in the object cache by 'binary scan' before it bails out and
 * switches back to Plan A (creating a new object for each value.)
 * Theoretically, it would be possible to keep the cache about for the values
 * that are already in it, but that makes the code slower in practise when
 * overflow happens, and makes little odds the rest of the time (as measured
 * on my machine.) It is also slower (on the sample I tried at least) to grow
 * the cache to hold all items we might want to put in it; presumably the
 * extra cost of managing the memory for the enlarged table outweighs the
 * benefit from allocating fewer objects. This is probably because as the
 * number of objects increases, the likelihood of reuse of any particular one
 * drops, and there is very little gain from larger maximum cache sizes (the
 * value below is chosen to allow caching to work in full with conversion of
 * bytes.) - DKF
 */

#define BINARY_SCAN_MAX_CACHE	260

/*
 * Prototypes for local procedures defined in this file:
 */

static void		DupByteArrayInternalRep(Tcl_Obj *srcPtr,
			    Tcl_Obj *copyPtr);
static int		FormatNumber(Tcl_Interp *interp, int type,
			    Tcl_Obj *src, unsigned char **cursorPtr);
static void		FreeByteArrayInternalRep(Tcl_Obj *objPtr);
static int		GetFormatSpec(const char **formatPtr, char *cmdPtr,
			    int *countPtr, int *flagsPtr);
static Tcl_Obj *	ScanNumber(unsigned char *buffer, int type,
			    int flags, Tcl_HashTable **numberCachePtr);
static int		SetByteArrayFromAny(Tcl_Interp *interp,
			    Tcl_Obj *objPtr);
static void		UpdateStringOfByteArray(Tcl_Obj *listPtr);
static void		DeleteScanNumberCache(Tcl_HashTable *numberCachePtr);
static int		NeedReversing(int format);
static void		CopyNumber(const void *from, void *to,
			    unsigned length, int type);
/* Binary ensemble commands */
static int		BinaryFormatCmd(ClientData clientData,
			    Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
static int		BinaryScanCmd(ClientData clientData,
			    Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
/* Binary encoding sub-ensemble commands */
static int		BinaryEncodeHex(ClientData clientData,
			    Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
static int		BinaryDecodeHex(ClientData clientData,
			    Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
static int		BinaryEncode64(ClientData clientData,
			    Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
static int		BinaryDecodeUu(ClientData clientData,
			    Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);
static int		BinaryDecode64(ClientData clientData,
			    Tcl_Interp *interp,
			    int objc, Tcl_Obj *const objv[]);

/*
 * The following tables are used by the binary encoders
 */

static const char HexDigits[16] = {
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};

static const char UueDigits[65] = {
    '`', '!', '"', '#', '$', '%', '&', '\'',
    '(', ')', '*', '+', ',', '-', '.', '/',
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', ':', ';', '<', '=', '>', '?',
    '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
    'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
    'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
    'X', 'Y', 'Z', '[', '\\',']', '^', '_',
    '`'
};

static const char B64Digits[65] = {
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
    'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
    'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
    'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
    'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
    'w', 'x', 'y', 'z', '0', '1', '2', '3',
    '4', '5', '6', '7', '8', '9', '+', '/',
    '='
};

/*
 * The following object type represents an array of bytes. An array of bytes
 * is not equivalent to an internationalized string. Conceptually, a string is
 * an array of 16-bit quantities organized as a sequence of properly formed
 * UTF-8 characters, while a ByteArray is an array of 8-bit quantities.
 * Accessor functions are provided to convert a ByteArray to a String or a
 * String to a ByteArray. Two or more consecutive bytes in an array of bytes
 * may look like a single UTF-8 character if the array is casually treated as
 * a string. But obtaining the String from a ByteArray is guaranteed to
 * produced properly formed UTF-8 sequences so that there is a one-to-one map
 * between bytes and characters.
 *
 * Converting a ByteArray to a String proceeds by casting each byte in the
 * array to a 16-bit quantity, treating that number as a Unicode character,
 * and storing the UTF-8 version of that Unicode character in the String. For
 * ByteArrays consisting entirely of values 1..127, the corresponding String
 * representation is the same as the ByteArray representation.
 *
 * Converting a String to a ByteArray proceeds by getting the Unicode
 * representation of each character in the String, casting it to a byte by
 * truncating the upper 8 bits, and then storing the byte in the ByteArray.
 * Converting from ByteArray to String and back to ByteArray is not lossy, but
 * converting an arbitrary String to a ByteArray may be.
 */

const Tcl_ObjType tclByteArrayType = {
    "bytearray",
    FreeByteArrayInternalRep,
    DupByteArrayInternalRep,
    UpdateStringOfByteArray,
    SetByteArrayFromAny
};

/*
 * The following structure is the internal rep for a ByteArray object. Keeps
 * track of how much memory has been used and how much has been allocated for
 * the byte array to enable growing and shrinking of the ByteArray object with
 * fewer mallocs.
 */

typedef struct ByteArray {
    int used;			/* The number of bytes used in the byte
				 * array. */
    int allocated;		/* The amount of space actually allocated
				 * minus 1 byte. */
    unsigned char bytes[1];	/* The array of bytes. The actual size of this
				 * field depends on the 'allocated' field
				 * above. */
} ByteArray;

#define BYTEARRAY_SIZE(len) \
		((unsigned) (TclOffset(ByteArray, bytes) + (len)))
#define GET_BYTEARRAY(objPtr) \
		((ByteArray *) (objPtr)->internalRep.otherValuePtr)
#define SET_BYTEARRAY(objPtr, baPtr) \
		(objPtr)->internalRep.otherValuePtr = (void *) (baPtr)

/*
 *----------------------------------------------------------------------
 *
 * Tcl_NewByteArrayObj --
 *
 *	This procedure is creates a new ByteArray object and initializes it
 *	from the given array of bytes.
 *
 * Results:
 *	The newly create object is returned. This object will have no initial
 *	string representation. The returned object has a ref count of 0.
 *
 * Side effects:
 *	Memory allocated for new object and copy of byte array argument.
 *
 *----------------------------------------------------------------------
 */

#undef Tcl_NewByteArrayObj

Tcl_Obj *
Tcl_NewByteArrayObj(
    const unsigned char *bytes,	/* The array of bytes used to initialize the
				 * new object. */
    int length)			/* Length of the array of bytes, which must be
				 * >= 0. */
{
#ifdef TCL_MEM_DEBUG
    return Tcl_DbNewByteArrayObj(bytes, length, "unknown", 0);
#else /* if not TCL_MEM_DEBUG */
    Tcl_Obj *objPtr;

    TclNewObj(objPtr);
    Tcl_SetByteArrayObj(objPtr, bytes, length);
    return objPtr;
#endif /* TCL_MEM_DEBUG */
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_DbNewByteArrayObj --
 *
 *	This procedure is normally called when debugging: i.e., when
 *	TCL_MEM_DEBUG is defined. It is the same as the Tcl_NewByteArrayObj
 *	above except that it calls Tcl_DbCkalloc directly with the file name
 *	and line number from its caller. This simplifies debugging since then
 *	the [memory active] command will report the correct file name and line
 *	number when reporting objects that haven't been freed.
 *
 *	When TCL_MEM_DEBUG is not defined, this procedure just returns the
 *	result of calling Tcl_NewByteArrayObj.
 *
 * Results:
 *	The newly create object is returned. This object will have no initial
 *	string representation. The returned object has a ref count of 0.
 *
 * Side effects:
 *	Memory allocated for new object and copy of byte array argument.
 *
 *----------------------------------------------------------------------
 */

Tcl_Obj *
Tcl_DbNewByteArrayObj(
    const unsigned char *bytes,	/* The array of bytes used to initialize the
				 * new object. */
    int length,			/* Length of the array of bytes, which must be
				 * >= 0. */
    const char *file,		/* The name of the source file calling this
				 * procedure; used for debugging. */
    int line)			/* Line number in the source file; used for
				 * debugging. */
{
#ifdef TCL_MEM_DEBUG
    Tcl_Obj *objPtr;

    TclDbNewObj(objPtr, file, line);
    Tcl_SetByteArrayObj(objPtr, bytes, length);
    return objPtr;
#else /* if not TCL_MEM_DEBUG */
    return Tcl_NewByteArrayObj(bytes, length);
#endif /* TCL_MEM_DEBUG */
}

/*
 *---------------------------------------------------------------------------
 *
 * Tcl_SetByteArrayObj --
 *
 *	Modify an object to be a ByteArray object and to have the specified
 *	array of bytes as its value.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The object's old string rep and internal rep is freed. Memory
 *	allocated for copy of byte array argument.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_SetByteArrayObj(
    Tcl_Obj *objPtr,		/* Object to initialize as a ByteArray. */
    const unsigned char *bytes,	/* The array of bytes to use as the new
				   value. May be NULL even if length > 0. */
    int length)			/* Length of the array of bytes, which must
				   be >= 0. */
{
    ByteArray *byteArrayPtr;

    if (Tcl_IsShared(objPtr)) {
	Tcl_Panic("%s called with shared object", "Tcl_SetByteArrayObj");
    }
    TclFreeIntRep(objPtr);
    Tcl_InvalidateStringRep(objPtr);

    length = (length < 0) ? 0 : length;
    byteArrayPtr = ckalloc(BYTEARRAY_SIZE(length));
    byteArrayPtr->used = length;
    byteArrayPtr->allocated = length;
    if (length) {
	if (bytes) {
	    memcpy(byteArrayPtr->bytes, bytes, (size_t) length);
	} else {
	    memset(byteArrayPtr->bytes, 0, (size_t) length);
	}
    }

    objPtr->typePtr = &tclByteArrayType;
    SET_BYTEARRAY(objPtr, byteArrayPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_GetByteArrayFromObj --
 *
 *	Attempt to get the array of bytes from the Tcl object. If the object
 *	is not already a ByteArray object, an attempt will be made to convert
 *	it to one.
 *
 * Results:
 *	Pointer to array of bytes representing the ByteArray object.
 *
 * Side effects:
 *	Frees old internal rep. Allocates memory for new internal rep.
 *
 *----------------------------------------------------------------------
 */

unsigned char *
Tcl_GetByteArrayFromObj(
    Tcl_Obj *objPtr,		/* The ByteArray object. */
    int *lengthPtr)		/* If non-NULL, filled with length of the
				 * array of bytes in the ByteArray object. */
{
    ByteArray *baPtr;

    if (objPtr->typePtr != &tclByteArrayType) {
	SetByteArrayFromAny(NULL, objPtr);
    }
    baPtr = GET_BYTEARRAY(objPtr);

    if (lengthPtr != NULL) {
	*lengthPtr = baPtr->used;
    }
    return (unsigned char *) baPtr->bytes;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_SetByteArrayLength --
 *
 *	This procedure changes the length of the byte array for this object.
 *	Once the caller has set the length of the array, it is acceptable to
 *	directly modify the bytes in the array up until Tcl_GetStringFromObj()
 *	has been called on this object.
 *
 * Results:
 *	The new byte array of the specified length.
 *
 * Side effects:
 *	Allocates enough memory for an array of bytes of the requested size.
 *	When growing the array, the old array is copied to the new array; new
 *	bytes are undefined. When shrinking, the old array is truncated to the
 *	specified length.
 *
 *----------------------------------------------------------------------
 */

unsigned char *
Tcl_SetByteArrayLength(
    Tcl_Obj *objPtr,		/* The ByteArray object. */
    int length)			/* New length for internal byte array. */
{
    ByteArray *byteArrayPtr;

    if (Tcl_IsShared(objPtr)) {
	Tcl_Panic("%s called with shared object", "Tcl_SetByteArrayLength");
    }
    if (objPtr->typePtr != &tclByteArrayType) {
	SetByteArrayFromAny(NULL, objPtr);
    }

    byteArrayPtr = GET_BYTEARRAY(objPtr);
    if (length > byteArrayPtr->allocated) {
	byteArrayPtr = ckrealloc(byteArrayPtr, BYTEARRAY_SIZE(length));
	byteArrayPtr->allocated = length;
	SET_BYTEARRAY(objPtr, byteArrayPtr);
    }
    Tcl_InvalidateStringRep(objPtr);
    byteArrayPtr->used = length;
    return byteArrayPtr->bytes;
}

/*
 *----------------------------------------------------------------------
 *
 * SetByteArrayFromAny --
 *
 *	Generate the ByteArray internal rep from the string rep.
 *
 * Results:
 *	The return value is always TCL_OK.
 *
 * Side effects:
 *	A ByteArray object is stored as the internal rep of objPtr.
 *
 *----------------------------------------------------------------------
 */

static int
SetByteArrayFromAny(
    Tcl_Interp *interp,		/* Not used. */
    Tcl_Obj *objPtr)		/* The object to convert to type ByteArray. */
{
    int length;
    const char *src, *srcEnd;
    unsigned char *dst;
    ByteArray *byteArrayPtr;
    Tcl_UniChar ch;

    if (objPtr->typePtr != &tclByteArrayType) {
	src = TclGetStringFromObj(objPtr, &length);
	srcEnd = src + length;

	byteArrayPtr = ckalloc(BYTEARRAY_SIZE(length));
	for (dst = byteArrayPtr->bytes; src < srcEnd; ) {
	    src += Tcl_UtfToUniChar(src, &ch);
	    *dst++ = UCHAR(ch);
	}

	byteArrayPtr->used = dst - byteArrayPtr->bytes;
	byteArrayPtr->allocated = length;

	TclFreeIntRep(objPtr);
	objPtr->typePtr = &tclByteArrayType;
	SET_BYTEARRAY(objPtr, byteArrayPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FreeByteArrayInternalRep --
 *
 *	Deallocate the storage associated with a ByteArray data object's
 *	internal representation.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Frees memory.
 *
 *----------------------------------------------------------------------
 */

static void
FreeByteArrayInternalRep(
    Tcl_Obj *objPtr)		/* Object with internal rep to free. */
{
    ckfree(GET_BYTEARRAY(objPtr));
    objPtr->typePtr = NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * DupByteArrayInternalRep --
 *
 *	Initialize the internal representation of a ByteArray Tcl_Obj to a
 *	copy of the internal representation of an existing ByteArray object.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Allocates memory.
 *
 *----------------------------------------------------------------------
 */

static void
DupByteArrayInternalRep(
    Tcl_Obj *srcPtr,		/* Object with internal rep to copy. */
    Tcl_Obj *copyPtr)		/* Object with internal rep to set. */
{
    int length;
    ByteArray *srcArrayPtr, *copyArrayPtr;

    srcArrayPtr = GET_BYTEARRAY(srcPtr);
    length = srcArrayPtr->used;

    copyArrayPtr = ckalloc(BYTEARRAY_SIZE(length));
    copyArrayPtr->used = length;
    copyArrayPtr->allocated = length;
    memcpy(copyArrayPtr->bytes, srcArrayPtr->bytes, (size_t) length);
    SET_BYTEARRAY(copyPtr, copyArrayPtr);

    copyPtr->typePtr = &tclByteArrayType;
}

/*
 *----------------------------------------------------------------------
 *
 * UpdateStringOfByteArray --
 *
 *	Update the string representation for a ByteArray data object. Note:
 *	This procedure does not invalidate an existing old string rep so
 *	storage will be lost if this has not already been done.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The object's string is set to a valid string that results from the
 *	ByteArray-to-string conversion.
 *
 *	The object becomes a string object -- the internal rep is discarded
 *	and the typePtr becomes NULL.
 *
 *----------------------------------------------------------------------
 */

static void
UpdateStringOfByteArray(
    Tcl_Obj *objPtr)		/* ByteArray object whose string rep to
				 * update. */
{
    int i, length, size;
    unsigned char *src;
    char *dst;
    ByteArray *byteArrayPtr;

    byteArrayPtr = GET_BYTEARRAY(objPtr);
    src = byteArrayPtr->bytes;
    length = byteArrayPtr->used;

    /*
     * How much space will string rep need?
     */

    size = length;
    for (i = 0; i < length && size >= 0; i++) {
	if ((src[i] == 0) || (src[i] > 127)) {
	    size++;
	}
    }
    if (size < 0) {
	Tcl_Panic("max size for a Tcl value (%d bytes) exceeded", INT_MAX);
    }

    dst = ckalloc(size + 1);
    objPtr->bytes = dst;
    objPtr->length = size;

    if (size == length) {
	memcpy(dst, src, (size_t) size);
	dst[size] = '\0';
    } else {
	for (i = 0; i < length; i++) {
	    dst += Tcl_UniCharToUtf(src[i], dst);
	}
	*dst = '\0';
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TclAppendBytesToByteArray --
 *
 *	This function appends an array of bytes to a byte array object. Note
 *	that the object *must* be unshared, and the array of bytes *must not*
 *	refer to the object being appended to.  Also the caller must have
 *	already checked that the final length of the bytearray after the
 *	append operations is complete will not overflow the int range.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Allocates enough memory for an array of bytes of the requested total
 *	size, or possibly larger. [Bug 2992970]
 *
 *----------------------------------------------------------------------
 */

void
TclAppendBytesToByteArray(
    Tcl_Obj *objPtr,
    const unsigned char *bytes,
    int len)
{
    ByteArray *byteArrayPtr;

    if (Tcl_IsShared(objPtr)) {
	Tcl_Panic("%s called with shared object","TclAppendBytesToByteArray");
    }
    if (len < 0) {
	Tcl_Panic("%s must be called with definite number of bytes to append",
		"TclAppendBytesToByteArray");
    }
    if (objPtr->typePtr != &tclByteArrayType) {
	SetByteArrayFromAny(NULL, objPtr);
    }
    byteArrayPtr = GET_BYTEARRAY(objPtr);

    /*
     * If we need to, resize the allocated space in the byte array.
     */

    if (byteArrayPtr->used + len > byteArrayPtr->allocated) {
	unsigned int attempt, used = byteArrayPtr->used;
	ByteArray *tmpByteArrayPtr = NULL;

	attempt = byteArrayPtr->allocated;
	if (attempt < 1) {
	    /*
	     * No allocated bytes, so must be none used too. We use this
	     * method to calculate how many bytes to allocate because we can
	     * end up with a zero-length buffer otherwise, when doubling can
	     * cause trouble. [Bug 3067036]
	     */

	    attempt = len + 1;
	} else {
	    do {
		attempt *= 2;
	    } while (attempt < used+len);
	}

	if (BYTEARRAY_SIZE(attempt) > BYTEARRAY_SIZE(used)) {
	    tmpByteArrayPtr = attemptckrealloc(byteArrayPtr,
		    BYTEARRAY_SIZE(attempt));
	}

	if (tmpByteArrayPtr == NULL) {
	    attempt = used + len;
	    if (BYTEARRAY_SIZE(attempt) < BYTEARRAY_SIZE(used)) {
		Tcl_Panic("attempt to allocate a bigger buffer than we can handle");
	    }
	    tmpByteArrayPtr = ckrealloc(byteArrayPtr,
		    BYTEARRAY_SIZE(attempt));
	}

	byteArrayPtr = tmpByteArrayPtr;
	byteArrayPtr->allocated = attempt;
	byteArrayPtr->used = used;
	SET_BYTEARRAY(objPtr, byteArrayPtr);
    }

    /*
     * Do the append if there's any point.
     */

    if (len > 0) {
	memcpy(byteArrayPtr->bytes + byteArrayPtr->used, bytes, len);
	byteArrayPtr->used += len;
	Tcl_InvalidateStringRep(objPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TclInitBinaryCmd --
 *
 *	This function is called to create the "binary" Tcl command. See the
 *	user documentation for details on what it does.
 *
 * Results:
 *	A command token for the new command.
 *
 * Side effects:
 *	Creates a new binary command as a mapped ensemble.
 *
 *----------------------------------------------------------------------
 */

static const EnsembleImplMap binaryMap[] = {
{ "format", BinaryFormatCmd, NULL, NULL, NULL, 0 },
{ "scan",   BinaryScanCmd, NULL, NULL, NULL, 0 },
{ "encode", NULL, NULL, NULL, NULL, 0 },
{ "decode", NULL, NULL, NULL, NULL, 0 },
{ NULL, NULL, NULL, NULL, NULL, 0 }
};
static const EnsembleImplMap encodeMap[] = {
{ "hex",      BinaryEncodeHex, NULL, NULL, (ClientData)HexDigits, 0 },
{ "uuencode", BinaryEncode64,  NULL, NULL, (ClientData)UueDigits, 0 },
{ "base64",   BinaryEncode64,  NULL, NULL, (ClientData)B64Digits, 0 },
{ NULL, NULL, NULL, NULL, NULL, 0 }
};
static const EnsembleImplMap decodeMap[] = {
{ "hex",      BinaryDecodeHex, NULL, NULL, NULL, 0 },
{ "uuencode", BinaryDecodeUu,  NULL, NULL, NULL, 0 },
{ "base64",   BinaryDecode64,  NULL, NULL, NULL, 0 },
{ NULL, NULL, NULL, NULL, NULL, 0 }
};

Tcl_Command
TclInitBinaryCmd(
    Tcl_Interp *interp)
{
    Tcl_Command binaryEnsemble;

    binaryEnsemble = TclMakeEnsemble(interp, "binary", binaryMap);
    TclMakeEnsemble(interp, "binary encode", encodeMap);
    TclMakeEnsemble(interp, "binary decode", decodeMap);
    return binaryEnsemble;
}

/*
 *----------------------------------------------------------------------
 *
 * BinaryFormatCmd --
 *
 *	This procedure implements the "binary format" Tcl command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

static int
BinaryFormatCmd(
    ClientData dummy,		/* Not used. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    int arg;			/* Index of next argument to consume. */
    int value = 0;		/* Current integer value to be packed.
				 * Initialized to avoid compiler warning. */
    char cmd;			/* Current format character. */
    int count;			/* Count associated with current format
				 * character. */
    int flags;			/* Format field flags */
    const char *format;	/* Pointer to current position in format
				 * string. */
    Tcl_Obj *resultPtr = NULL;	/* Object holding result buffer. */
    unsigned char *buffer;	/* Start of result buffer. */
    unsigned char *cursor;	/* Current position within result buffer. */
    unsigned char *maxPos;	/* Greatest position within result buffer that
				 * cursor has visited.*/
    const char *errorString;
    const char *errorValue, *str;
    int offset, size, length;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "formatString ?arg ...?");
	return TCL_ERROR;
    }

    /*
     * To avoid copying the data, we format the string in two passes. The
     * first pass computes the size of the output buffer. The second pass
     * places the formatted data into the buffer.
     */

    format = TclGetString(objv[1]);
    arg = 2;
    offset = 0;
    length = 0;
    while (*format != '\0') {
	str = format;
	flags = 0;
	if (!GetFormatSpec(&format, &cmd, &count, &flags)) {
	    break;
	}
	switch (cmd) {
	case 'a':
	case 'A':
	case 'b':
	case 'B':
	case 'h':
	case 'H':
	    /*
	     * For string-type specifiers, the count corresponds to the number
	     * of bytes in a single argument.
	     */

	    if (arg >= objc) {
		goto badIndex;
	    }
	    if (count == BINARY_ALL) {
		Tcl_GetByteArrayFromObj(objv[arg], &count);
	    } else if (count == BINARY_NOCOUNT) {
		count = 1;
	    }
	    arg++;
	    if (cmd == 'a' || cmd == 'A') {
		offset += count;
	    } else if (cmd == 'b' || cmd == 'B') {
		offset += (count + 7) / 8;
	    } else {
		offset += (count + 1) / 2;
	    }
	    break;
	case 'c':
	    size = 1;
	    goto doNumbers;
	case 't':
	case 's':
	case 'S':
	    size = 2;
	    goto doNumbers;
	case 'n':
	case 'i':
	case 'I':
	    size = 4;
	    goto doNumbers;
	case 'm':
	case 'w':
	case 'W':
	    size = 8;
	    goto doNumbers;
	case 'r':
	case 'R':
	case 'f':
	    size = sizeof(float);
	    goto doNumbers;
	case 'q':
	case 'Q':
	case 'd':
	    size = sizeof(double);

	doNumbers:
	    if (arg >= objc) {
		goto badIndex;
	    }

	    /*
	     * For number-type specifiers, the count corresponds to the number
	     * of elements in the list stored in a single argument. If no
	     * count is specified, then the argument is taken as a single
	     * non-list value.
	     */

	    if (count == BINARY_NOCOUNT) {
		arg++;
		count = 1;
	    } else {
		int listc;
		Tcl_Obj **listv;

		/*
		 * The macro evals its args more than once: avoid arg++
		 */

		if (TclListObjGetElements(interp, objv[arg], &listc,
			&listv) != TCL_OK) {
		    return TCL_ERROR;
		}
		arg++;

		if (count == BINARY_ALL) {
		    count = listc;
		} else if (count > listc) {
		    Tcl_AppendResult(interp,
			    "number of elements in list does not match count",
			    NULL);
		    return TCL_ERROR;
		}
	    }
	    offset += count*size;
	    break;

	case 'x':
	    if (count == BINARY_ALL) {
		Tcl_AppendResult(interp,
			"cannot use \"*\" in format string with \"x\"",
			NULL);
		return TCL_ERROR;
	    } else if (count == BINARY_NOCOUNT) {
		count = 1;
	    }
	    offset += count;
	    break;
	case 'X':
	    if (count == BINARY_NOCOUNT) {
		count = 1;
	    }
	    if ((count > offset) || (count == BINARY_ALL)) {
		count = offset;
	    }
	    if (offset > length) {
		length = offset;
	    }
	    offset -= count;
	    break;
	case '@':
	    if (offset > length) {
		length = offset;
	    }
	    if (count == BINARY_ALL) {
		offset = length;
	    } else if (count == BINARY_NOCOUNT) {
		goto badCount;
	    } else {
		offset = count;
	    }
	    break;
	default:
	    errorString = str;
	    goto badField;
	}
    }
    if (offset > length) {
	length = offset;
    }
    if (length == 0) {
	return TCL_OK;
    }

    /*
     * Prepare the result object by preallocating the caclulated number of
     * bytes and filling with nulls.
     */

    resultPtr = Tcl_NewObj();
    buffer = Tcl_SetByteArrayLength(resultPtr, length);
    memset(buffer, 0, (size_t) length);

    /*
     * Pack the data into the result object. Note that we can skip the
     * error checking during this pass, since we have already parsed the
     * string once.
     */

    arg = 2;
    format = TclGetString(objv[1]);
    cursor = buffer;
    maxPos = cursor;
    while (*format != 0) {
	flags = 0;
	if (!GetFormatSpec(&format, &cmd, &count, &flags)) {
	    break;
	}
	if ((count == 0) && (cmd != '@')) {
	    if (cmd != 'x') {
		arg++;
	    }
	    continue;
	}
	switch (cmd) {
	case 'a':
	case 'A': {
	    char pad = (char) (cmd == 'a' ? '\0' : ' ');
	    unsigned char *bytes;

	    bytes = Tcl_GetByteArrayFromObj(objv[arg++], &length);

	    if (count == BINARY_ALL) {
		count = length;
	    } else if (count == BINARY_NOCOUNT) {
		count = 1;
	    }
	    if (length >= count) {
		memcpy(cursor, bytes, (size_t) count);
	    } else {
		memcpy(cursor, bytes, (size_t) length);
		memset(cursor + length, pad, (size_t) (count - length));
	    }
	    cursor += count;
	    break;
	}
	case 'b':
	case 'B': {
	    unsigned char *last;

	    str = TclGetStringFromObj(objv[arg], &length);
	    arg++;
	    if (count == BINARY_ALL) {
		count = length;
	    } else if (count == BINARY_NOCOUNT) {
		count = 1;
	    }
	    last = cursor + ((count + 7) / 8);
	    if (count > length) {
		count = length;
	    }
	    value = 0;
	    errorString = "binary";
	    if (cmd == 'B') {
		for (offset = 0; offset < count; offset++) {
		    value <<= 1;
		    if (str[offset] == '1') {
			value |= 1;
		    } else if (str[offset] != '0') {
			errorValue = str;
			Tcl_DecrRefCount(resultPtr);
			goto badValue;
		    }
		    if (((offset + 1) % 8) == 0) {
			*cursor++ = UCHAR(value);
			value = 0;
		    }
		}
	    } else {
		for (offset = 0; offset < count; offset++) {
		    value >>= 1;
		    if (str[offset] == '1') {
			value |= 128;
		    } else if (str[offset] != '0') {
			errorValue = str;
			Tcl_DecrRefCount(resultPtr);
			goto badValue;
		    }
		    if (!((offset + 1) % 8)) {
			*cursor++ = UCHAR(value);
			value = 0;
		    }
		}
	    }
	    if ((offset % 8) != 0) {
		if (cmd == 'B') {
		    value <<= 8 - (offset % 8);
		} else {
		    value >>= 8 - (offset % 8);
		}
		*cursor++ = UCHAR(value);
	    }
	    while (cursor < last) {
		*cursor++ = '\0';
	    }
	    break;
	}
	case 'h':
	case 'H': {
	    unsigned char *last;
	    int c;

	    str = TclGetStringFromObj(objv[arg], &length);
	    arg++;
	    if (count == BINARY_ALL) {
		count = length;
	    } else if (count == BINARY_NOCOUNT) {
		count = 1;
	    }
	    last = cursor + ((count + 1) / 2);
	    if (count > length) {
		count = length;
	    }
	    value = 0;
	    errorString = "hexadecimal";
	    if (cmd == 'H') {
		for (offset = 0; offset < count; offset++) {
		    value <<= 4;
		    if (!isxdigit(UCHAR(str[offset]))) {     /* INTL: digit */
			errorValue = str;
			Tcl_DecrRefCount(resultPtr);
			goto badValue;
		    }
		    c = str[offset] - '0';
		    if (c > 9) {
			c += ('0' - 'A') + 10;
		    }
		    if (c > 16) {
			c += ('A' - 'a');
		    }
		    value |= (c & 0xf);
		    if (offset % 2) {
			*cursor++ = (char) value;
			value = 0;
		    }
		}
	    } else {
		for (offset = 0; offset < count; offset++) {
		    value >>= 4;

		    if (!isxdigit(UCHAR(str[offset]))) {     /* INTL: digit */
			errorValue = str;
			Tcl_DecrRefCount(resultPtr);
			goto badValue;
		    }
		    c = str[offset] - '0';
		    if (c > 9) {
			c += ('0' - 'A') + 10;
		    }
		    if (c > 16) {
			c += ('A' - 'a');
		    }
		    value |= ((c << 4) & 0xf0);
		    if (offset % 2) {
			*cursor++ = UCHAR(value & 0xff);
			value = 0;
		    }
		}
	    }
	    if (offset % 2) {
		if (cmd == 'H') {
		    value <<= 4;
		} else {
		    value >>= 4;
		}
		*cursor++ = UCHAR(value);
	    }

	    while (cursor < last) {
		*cursor++ = '\0';
	    }
	    break;
	}
	case 'c':
	case 't':
	case 's':
	case 'S':
	case 'n':
	case 'i':
	case 'I':
	case 'm':
	case 'w':
	case 'W':
	case 'r':
	case 'R':
	case 'd':
	case 'q':
	case 'Q':
	case 'f': {
	    int listc, i;
	    Tcl_Obj **listv;

	    if (count == BINARY_NOCOUNT) {
		/*
		 * Note that we are casting away the const-ness of objv, but
		 * this is safe since we aren't going to modify the array.
		 */

		listv = (Tcl_Obj **) (objv + arg);
		listc = 1;
		count = 1;
	    } else {
		TclListObjGetElements(interp, objv[arg], &listc, &listv);
		if (count == BINARY_ALL) {
		    count = listc;
		}
	    }
	    arg++;
	    for (i = 0; i < count; i++) {
		if (FormatNumber(interp, cmd, listv[i], &cursor)!=TCL_OK) {
		    Tcl_DecrRefCount(resultPtr);
		    return TCL_ERROR;
		}
	    }
	    break;
	}
	case 'x':
	    if (count == BINARY_NOCOUNT) {
		count = 1;
	    }
	    memset(cursor, 0, (size_t) count);
	    cursor += count;
	    break;
	case 'X':
	    if (cursor > maxPos) {
		maxPos = cursor;
	    }
	    if (count == BINARY_NOCOUNT) {
		count = 1;
	    }
	    if ((count == BINARY_ALL) || (count > (cursor - buffer))) {
		cursor = buffer;
	    } else {
		cursor -= count;
	    }
	    break;
	case '@':
	    if (cursor > maxPos) {
		maxPos = cursor;
	    }
	    if (count == BINARY_ALL) {
		cursor = maxPos;
	    } else {
		cursor = buffer + count;
	    }
	    break;
	}
    }
    Tcl_SetObjResult(interp, resultPtr);
    return TCL_OK;

 badValue:
    Tcl_ResetResult(interp);
    Tcl_AppendResult(interp, "expected ", errorString,
	" string but got \"", errorValue, "\" instead", NULL);
    return TCL_ERROR;

 badCount:
    errorString = "missing count for \"@\" field specifier";
    goto error;

 badIndex:
    errorString = "not enough arguments for all format specifiers";
    goto error;

 badField:
    {
	Tcl_UniChar ch;
	char buf[TCL_UTF_MAX + 1];

	Tcl_UtfToUniChar(errorString, &ch);
	buf[Tcl_UniCharToUtf(ch, buf)] = '\0';
	Tcl_AppendResult(interp, "bad field specifier \"", buf, "\"", NULL);
	return TCL_ERROR;
    }

 error:
    Tcl_AppendResult(interp, errorString, NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * BinaryScanCmd --
 *
 *	This procedure implements the "binary scan" Tcl command.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

int
BinaryScanCmd(
    ClientData dummy,		/* Not used. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    int arg;			/* Index of next argument to consume. */
    int value = 0;		/* Current integer value to be packed.
				 * Initialized to avoid compiler warning. */
    char cmd;			/* Current format character. */
    int count;			/* Count associated with current format
				 * character. */
    int flags;			/* Format field flags */
    const char *format;	/* Pointer to current position in format
				 * string. */
    Tcl_Obj *resultPtr = NULL;	/* Object holding result buffer. */
    unsigned char *buffer;	/* Start of result buffer. */
    const char *errorString;
    const char *str;
    int offset, size, length;

    int i;
    Tcl_Obj *valuePtr, *elementPtr;
    Tcl_HashTable numberCacheHash;
    Tcl_HashTable *numberCachePtr;

    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 1, objv,
		"value formatString ?varName ...?");
	return TCL_ERROR;
    }
    numberCachePtr = &numberCacheHash;
    Tcl_InitHashTable(numberCachePtr, TCL_ONE_WORD_KEYS);
    buffer = Tcl_GetByteArrayFromObj(objv[1], &length);
    format = TclGetString(objv[2]);
    arg = 3;
    offset = 0;
    while (*format != '\0') {
	str = format;
	flags = 0;
	if (!GetFormatSpec(&format, &cmd, &count, &flags)) {
	    goto done;
	}
	switch (cmd) {
	case 'a':
	case 'A': {
	    unsigned char *src;

	    if (arg >= objc) {
		DeleteScanNumberCache(numberCachePtr);
		goto badIndex;
	    }
	    if (count == BINARY_ALL) {
		count = length - offset;
	    } else {
		if (count == BINARY_NOCOUNT) {
		    count = 1;
		}
		if (count > (length - offset)) {
		    goto done;
		}
	    }

	    src = buffer + offset;
	    size = count;

	    /*
	     * Trim trailing nulls and spaces, if necessary.
	     */

	    if (cmd == 'A') {
		while (size > 0) {
		    if (src[size-1] != '\0' && src[size-1] != ' ') {
			break;
		    }
		    size--;
		}
	    }

	    /*
	     * Have to do this #ifdef-fery because (as part of defining
	     * Tcl_NewByteArrayObj) we removed the #def that hides this stuff
	     * normally. If this code ever gets copied to another file, it
	     * should be changed back to the simpler version.
	     */

#ifdef TCL_MEM_DEBUG
	    valuePtr = Tcl_DbNewByteArrayObj(src, size, __FILE__, __LINE__);
#else
	    valuePtr = Tcl_NewByteArrayObj(src, size);
#endif /* TCL_MEM_DEBUG */

	    resultPtr = Tcl_ObjSetVar2(interp, objv[arg], NULL, valuePtr,
		    TCL_LEAVE_ERR_MSG);
	    arg++;
	    if (resultPtr == NULL) {
		DeleteScanNumberCache(numberCachePtr);
		return TCL_ERROR;
	    }
	    offset += count;
	    break;
	}
	case 'b':
	case 'B': {
	    unsigned char *src;
	    char *dest;

	    if (arg >= objc) {
		DeleteScanNumberCache(numberCachePtr);
		goto badIndex;
	    }
	    if (count == BINARY_ALL) {
		count = (length - offset) * 8;
	    } else {
		if (count == BINARY_NOCOUNT) {
		    count = 1;
		}
		if (count > (length - offset) * 8) {
		    goto done;
		}
	    }
	    src = buffer + offset;
	    valuePtr = Tcl_NewObj();
	    Tcl_SetObjLength(valuePtr, count);
	    dest = TclGetString(valuePtr);

	    if (cmd == 'b') {
		for (i = 0; i < count; i++) {
		    if (i % 8) {
			value >>= 1;
		    } else {
			value = *src++;
		    }
		    *dest++ = (char) ((value & 1) ? '1' : '0');
		}
	    } else {
		for (i = 0; i < count; i++) {
		    if (i % 8) {
			value <<= 1;
		    } else {
			value = *src++;
		    }
		    *dest++ = (char) ((value & 0x80) ? '1' : '0');
		}
	    }

	    resultPtr = Tcl_ObjSetVar2(interp, objv[arg], NULL, valuePtr,
		    TCL_LEAVE_ERR_MSG);
	    arg++;
	    if (resultPtr == NULL) {
		DeleteScanNumberCache(numberCachePtr);
		return TCL_ERROR;
	    }
	    offset += (count + 7) / 8;
	    break;
	}
	case 'h':
	case 'H': {
	    char *dest;
	    unsigned char *src;
	    static const char hexdigit[] = "0123456789abcdef";

	    if (arg >= objc) {
		DeleteScanNumberCache(numberCachePtr);
		goto badIndex;
	    }
	    if (count == BINARY_ALL) {
		count = (length - offset)*2;
	    } else {
		if (count == BINARY_NOCOUNT) {
		    count = 1;
		}
		if (count > (length - offset)*2) {
		    goto done;
		}
	    }
	    src = buffer + offset;
	    valuePtr = Tcl_NewObj();
	    Tcl_SetObjLength(valuePtr, count);
	    dest = TclGetString(valuePtr);

	    if (cmd == 'h') {
		for (i = 0; i < count; i++) {
		    if (i % 2) {
			value >>= 4;
		    } else {
			value = *src++;
		    }
		    *dest++ = hexdigit[value & 0xf];
		}
	    } else {
		for (i = 0; i < count; i++) {
		    if (i % 2) {
			value <<= 4;
		    } else {
			value = *src++;
		    }
		    *dest++ = hexdigit[(value >> 4) & 0xf];
		}
	    }

	    resultPtr = Tcl_ObjSetVar2(interp, objv[arg], NULL, valuePtr,
		    TCL_LEAVE_ERR_MSG);
	    arg++;
	    if (resultPtr == NULL) {
		DeleteScanNumberCache(numberCachePtr);
		return TCL_ERROR;
	    }
	    offset += (count + 1) / 2;
	    break;
	}
	case 'c':
	    size = 1;
	    goto scanNumber;
	case 't':
	case 's':
	case 'S':
	    size = 2;
	    goto scanNumber;
	case 'n':
	case 'i':
	case 'I':
	    size = 4;
	    goto scanNumber;
	case 'm':
	case 'w':
	case 'W':
	    size = 8;
	    goto scanNumber;
	case 'r':
	case 'R':
	case 'f':
	    size = sizeof(float);
	    goto scanNumber;
	case 'q':
	case 'Q':
	case 'd': {
	    unsigned char *src;

	    size = sizeof(double);
	    /* fall through */

	scanNumber:
	    if (arg >= objc) {
		DeleteScanNumberCache(numberCachePtr);
		goto badIndex;
	    }
	    if (count == BINARY_NOCOUNT) {
		if ((length - offset) < size) {
		    goto done;
		}
		valuePtr = ScanNumber(buffer+offset, cmd, flags,
			&numberCachePtr);
		offset += size;
	    } else {
		if (count == BINARY_ALL) {
		    count = (length - offset) / size;
		}
		if ((length - offset) < (count * size)) {
		    goto done;
		}
		valuePtr = Tcl_NewObj();
		src = buffer + offset;
		for (i = 0; i < count; i++) {
		    elementPtr = ScanNumber(src, cmd, flags, &numberCachePtr);
		    src += size;
		    Tcl_ListObjAppendElement(NULL, valuePtr, elementPtr);
		}
		offset += count * size;
	    }

	    resultPtr = Tcl_ObjSetVar2(interp, objv[arg], NULL, valuePtr,
		    TCL_LEAVE_ERR_MSG);
	    arg++;
	    if (resultPtr == NULL) {
		DeleteScanNumberCache(numberCachePtr);
		return TCL_ERROR;
	    }
	    break;
	}
	case 'x':
	    if (count == BINARY_NOCOUNT) {
		count = 1;
	    }
	    if ((count == BINARY_ALL) || (count > (length - offset))) {
		offset = length;
	    } else {
		offset += count;
	    }
	    break;
	case 'X':
	    if (count == BINARY_NOCOUNT) {
		count = 1;
	    }
	    if ((count == BINARY_ALL) || (count > offset)) {
		offset = 0;
	    } else {
		offset -= count;
	    }
	    break;
	case '@':
	    if (count == BINARY_NOCOUNT) {
		DeleteScanNumberCache(numberCachePtr);
		goto badCount;
	    }
	    if ((count == BINARY_ALL) || (count > length)) {
		offset = length;
	    } else {
		offset = count;
	    }
	    break;
	default:
	    DeleteScanNumberCache(numberCachePtr);
	    errorString = str;
	    goto badField;
	}
    }

    /*
     * Set the result to the last position of the cursor.
     */

 done:
    Tcl_SetObjResult(interp, Tcl_NewLongObj(arg - 3));
    DeleteScanNumberCache(numberCachePtr);

    return TCL_OK;

 badCount:
    errorString = "missing count for \"@\" field specifier";
    goto error;

 badIndex:
    errorString = "not enough arguments for all format specifiers";
    goto error;

 badField:
    {
	Tcl_UniChar ch;
	char buf[TCL_UTF_MAX + 1];

	Tcl_UtfToUniChar(errorString, &ch);
	buf[Tcl_UniCharToUtf(ch, buf)] = '\0';
	Tcl_AppendResult(interp, "bad field specifier \"", buf, "\"", NULL);
	return TCL_ERROR;
    }

 error:
    Tcl_AppendResult(interp, errorString, NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * GetFormatSpec --
 *
 *	This function parses the format strings used in the binary format and
 *	scan commands.
 *
 * Results:
 *	Moves the formatPtr to the start of the next command. Returns the
 *	current command character and count in cmdPtr and countPtr. The count
 *	is set to BINARY_ALL if the count character was '*' or BINARY_NOCOUNT
 *	if no count was specified. Returns 1 on success, or 0 if the string
 *	did not have a format specifier.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
GetFormatSpec(
    const char **formatPtr,	/* Pointer to format string. */
    char *cmdPtr,		/* Pointer to location of command char. */
    int *countPtr,		/* Pointer to repeat count value. */
    int *flagsPtr)		/* Pointer to field flags */
{
    /*
     * Skip any leading blanks.
     */

    while (**formatPtr == ' ') {
	(*formatPtr)++;
    }

    /*
     * The string was empty, except for whitespace, so fail.
     */

    if (!(**formatPtr)) {
	return 0;
    }

    /*
     * Extract the command character and any trailing digits or '*'.
     */

    *cmdPtr = **formatPtr;
    (*formatPtr)++;
    if (**formatPtr == 'u') {
	(*formatPtr)++;
	*flagsPtr |= BINARY_UNSIGNED;
    }
    if (**formatPtr == '*') {
	(*formatPtr)++;
	*countPtr = BINARY_ALL;
    } else if (isdigit(UCHAR(**formatPtr))) { /* INTL: digit */
	*countPtr = strtoul(*formatPtr, (char **) formatPtr, 10);
    } else {
	*countPtr = BINARY_NOCOUNT;
    }
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * NeedReversing --
 *
 *	This routine determines, if bytes of a number need to be re-ordered,
 *	and returns a numeric code indicating the re-ordering to be done.
 *	This depends on the endiannes of the machine and the desired format.
 *	It is in effect a table (whose contents depend on the endianness of
 *	the system) describing whether a value needs reversing or not. Anyone
 *	porting the code to a big-endian platform should take care to make
 *	sure that they define WORDS_BIGENDIAN though this is already done by
 *	configure for the Unix build; little-endian platforms (including
 *	Windows) don't need to do anything.
 *
 * Results:
 *	0	No re-ordering needed.
 *	1	Reverse the bytes:	01234567 <-> 76543210 (little to big)
 *	2	Apply this re-ordering: 01234567 <-> 45670123 (Nokia to little)
 *	3	Apply this re-ordering: 01234567 <-> 32107654 (Nokia to big)
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
NeedReversing(
    int format)
{
    switch (format) {
	/* native floats and doubles: never reverse */
    case 'd':
    case 'f':
	/* big endian ints: never reverse */
    case 'I':
    case 'S':
    case 'W':
#ifdef WORDS_BIGENDIAN
	/* native ints: reverse if we're little-endian */
    case 'n':
    case 't':
    case 'm':
	/* f: reverse if we're little-endian */
    case 'Q':
    case 'R':
#else /* !WORDS_BIGENDIAN */
	/* small endian floats: reverse if we're big-endian */
    case 'r':
#endif /* WORDS_BIGENDIAN */
	return 0;

#ifdef WORDS_BIGENDIAN
	/* small endian floats: reverse if we're big-endian */
    case 'q':
    case 'r':
#else /* !WORDS_BIGENDIAN */
	/* native ints: reverse if we're little-endian */
    case 'n':
    case 't':
    case 'm':
	/* f: reverse if we're little-endian */
    case 'R':
#endif /* WORDS_BIGENDIAN */
	/* small endian ints: always reverse */
    case 'i':
    case 's':
    case 'w':
	return 1;

#ifndef WORDS_BIGENDIAN
    /*
     * The Q and q formats need special handling to account for the unusual
     * byte ordering of 8-byte floats on Nokia 770 systems, which claim to be
     * little-endian, but also reverse word order.
     */

    case 'Q':
	if (TclNokia770Doubles()) {
	    return 3;
	}
	return 1;
    case 'q':
	if (TclNokia770Doubles()) {
	    return 2;
	}
	return 0;
#endif
    }

    Tcl_Panic("unexpected fallthrough");
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * CopyNumber --
 *
 *	This routine is called by FormatNumber and ScanNumber to copy a
 *	floating-point number. If required, bytes are reversed while copying.
 *	The behaviour is only fully defined when used with IEEE float and
 *	double values (guaranteed to be 4 and 8 bytes long, respectively.)
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Copies length bytes
 *
 *----------------------------------------------------------------------
 */

static void
CopyNumber(
    const void *from,		/* source */
    void *to,			/* destination */
    unsigned length,		/* Number of bytes to copy */
    int type)			/* What type of thing are we copying? */
{
    switch (NeedReversing(type)) {
    case 0:
	memcpy(to, from, length);
	break;
    case 1: {
	const unsigned char *fromPtr = from;
	unsigned char *toPtr = to;

	switch (length) {
	case 4:
	    toPtr[0] = fromPtr[3];
	    toPtr[1] = fromPtr[2];
	    toPtr[2] = fromPtr[1];
	    toPtr[3] = fromPtr[0];
	    break;
	case 8:
	    toPtr[0] = fromPtr[7];
	    toPtr[1] = fromPtr[6];
	    toPtr[2] = fromPtr[5];
	    toPtr[3] = fromPtr[4];
	    toPtr[4] = fromPtr[3];
	    toPtr[5] = fromPtr[2];
	    toPtr[6] = fromPtr[1];
	    toPtr[7] = fromPtr[0];
	    break;
	}
	break;
    }
    case 2: {
	const unsigned char *fromPtr = from;
	unsigned char *toPtr = to;

	toPtr[0] = fromPtr[4];
	toPtr[1] = fromPtr[5];
	toPtr[2] = fromPtr[6];
	toPtr[3] = fromPtr[7];
	toPtr[4] = fromPtr[0];
	toPtr[5] = fromPtr[1];
	toPtr[6] = fromPtr[2];
	toPtr[7] = fromPtr[3];
	break;
    }
    case 3: {
	const unsigned char *fromPtr = from;
	unsigned char *toPtr = to;

	toPtr[0] = fromPtr[3];
	toPtr[1] = fromPtr[2];
	toPtr[2] = fromPtr[1];
	toPtr[3] = fromPtr[0];
	toPtr[4] = fromPtr[7];
	toPtr[5] = fromPtr[6];
	toPtr[6] = fromPtr[5];
	toPtr[7] = fromPtr[4];
	break;
    }
    }
}

/*
 *----------------------------------------------------------------------
 *
 * FormatNumber --
 *
 *	This routine is called by Tcl_BinaryObjCmd to format a number into a
 *	location pointed at by cursor.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Moves the cursor to the next location to be written into.
 *
 *----------------------------------------------------------------------
 */

static int
FormatNumber(
    Tcl_Interp *interp,		/* Current interpreter, used to report
				 * errors. */
    int type,			/* Type of number to format. */
    Tcl_Obj *src,		/* Number to format. */
    unsigned char **cursorPtr)	/* Pointer to index into destination buffer. */
{
    long value;
    double dvalue;
    Tcl_WideInt wvalue;
    float fvalue;

    switch (type) {
    case 'd':
    case 'q':
    case 'Q':
	/*
	 * Double-precision floating point values. Tcl_GetDoubleFromObj
	 * returns TCL_ERROR for NaN, but we can check by comparing the
	 * object's type pointer.
	 */

	if (Tcl_GetDoubleFromObj(interp, src, &dvalue) != TCL_OK) {
	    if (src->typePtr != &tclDoubleType) {
		return TCL_ERROR;
	    }
	    dvalue = src->internalRep.doubleValue;
	}
	CopyNumber(&dvalue, *cursorPtr, sizeof(double), type);
	*cursorPtr += sizeof(double);
	return TCL_OK;

    case 'f':
    case 'r':
    case 'R':
	/*
	 * Single-precision floating point values. Tcl_GetDoubleFromObj
	 * returns TCL_ERROR for NaN, but we can check by comparing the
	 * object's type pointer.
	 */

	if (Tcl_GetDoubleFromObj(interp, src, &dvalue) != TCL_OK) {
	    if (src->typePtr != &tclDoubleType) {
		return TCL_ERROR;
	    }
	    dvalue = src->internalRep.doubleValue;
	}

	/*
	 * Because some compilers will generate floating point exceptions on
	 * an overflow cast (e.g. Borland), we restrict the values to the
	 * valid range for float.
	 */

	if (fabs(dvalue) > (double)FLT_MAX) {
	    fvalue = (dvalue >= 0.0) ? FLT_MAX : -FLT_MAX;
	} else {
	    fvalue = (float) dvalue;
	}
	CopyNumber(&fvalue, *cursorPtr, sizeof(float), type);
	*cursorPtr += sizeof(float);
	return TCL_OK;

	/*
	 * 64-bit integer values.
	 */
    case 'w':
    case 'W':
    case 'm':
	if (Tcl_GetWideIntFromObj(interp, src, &wvalue) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (NeedReversing(type)) {
	    *(*cursorPtr)++ = UCHAR(wvalue);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 8);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 16);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 24);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 32);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 40);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 48);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 56);
	} else {
	    *(*cursorPtr)++ = UCHAR(wvalue >> 56);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 48);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 40);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 32);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 24);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 16);
	    *(*cursorPtr)++ = UCHAR(wvalue >> 8);
	    *(*cursorPtr)++ = UCHAR(wvalue);
	}
	return TCL_OK;

	/*
	 * 32-bit integer values.
	 */
    case 'i':
    case 'I':
    case 'n':
	if (TclGetLongFromObj(interp, src, &value) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (NeedReversing(type)) {
	    *(*cursorPtr)++ = UCHAR(value);
	    *(*cursorPtr)++ = UCHAR(value >> 8);
	    *(*cursorPtr)++ = UCHAR(value >> 16);
	    *(*cursorPtr)++ = UCHAR(value >> 24);
	} else {
	    *(*cursorPtr)++ = UCHAR(value >> 24);
	    *(*cursorPtr)++ = UCHAR(value >> 16);
	    *(*cursorPtr)++ = UCHAR(value >> 8);
	    *(*cursorPtr)++ = UCHAR(value);
	}
	return TCL_OK;

	/*
	 * 16-bit integer values.
	 */
    case 's':
    case 'S':
    case 't':
	if (TclGetLongFromObj(interp, src, &value) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (NeedReversing(type)) {
	    *(*cursorPtr)++ = UCHAR(value);
	    *(*cursorPtr)++ = UCHAR(value >> 8);
	} else {
	    *(*cursorPtr)++ = UCHAR(value >> 8);
	    *(*cursorPtr)++ = UCHAR(value);
	}
	return TCL_OK;

	/*
	 * 8-bit integer values.
	 */
    case 'c':
	if (TclGetLongFromObj(interp, src, &value) != TCL_OK) {
	    return TCL_ERROR;
	}
	*(*cursorPtr)++ = UCHAR(value);
	return TCL_OK;

    default:
	Tcl_Panic("unexpected fallthrough");
	return TCL_ERROR;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ScanNumber --
 *
 *	This routine is called by Tcl_BinaryObjCmd to scan a number out of a
 *	buffer.
 *
 * Results:
 *	Returns a newly created object containing the scanned number. This
 *	object has a ref count of zero.
 *
 * Side effects:
 *	Might reuse an object in the number cache, place a new object in the
 *	cache, or delete the cache and set the reference to it (itself passed
 *	in by reference) to NULL.
 *
 *----------------------------------------------------------------------
 */

static Tcl_Obj *
ScanNumber(
    unsigned char *buffer,	/* Buffer to scan number from. */
    int type,			/* Format character from "binary scan" */
    int flags,			/* Format field flags */
    Tcl_HashTable **numberCachePtrPtr)
				/* Place to look for cache of scanned
				 * value objects, or NULL if too many
				 * different numbers have been scanned. */
{
    long value;
    float fvalue;
    double dvalue;
    Tcl_WideUInt uwvalue;

    /*
     * We cannot rely on the compiler to properly sign extend integer values
     * when we cast from smaller values to larger values because we don't know
     * the exact size of the integer types. So, we have to handle sign
     * extension explicitly by checking the high bit and padding with 1's as
     * needed. This practice is disabled if the BINARY_UNSIGNED flag is set.
     */

    switch (type) {
    case 'c':
	/*
	 * Characters need special handling. We want to produce a signed
	 * result, but on some platforms (such as AIX) chars are unsigned. To
	 * deal with this, check for a value that should be negative but
	 * isn't.
	 */

	value = buffer[0];
	if (!(flags & BINARY_UNSIGNED)) {
	    if (value & 0x80) {
		value |= -0x100;
	    }
	}
	goto returnNumericObject;

	/*
	 * 16-bit numeric values. We need the sign extension trick (see above)
	 * here as well.
	 */

    case 's':
    case 'S':
    case 't':
	if (NeedReversing(type)) {
	    value = (long) (buffer[0] + (buffer[1] << 8));
	} else {
	    value = (long) (buffer[1] + (buffer[0] << 8));
	}
	if (!(flags & BINARY_UNSIGNED)) {
	    if (value & 0x8000) {
		value |= -0x10000;
	    }
	}
	goto returnNumericObject;

	/*
	 * 32-bit numeric values.
	 */

    case 'i':
    case 'I':
    case 'n':
	if (NeedReversing(type)) {
	    value = (long) (buffer[0]
		    + (buffer[1] << 8)
		    + (buffer[2] << 16)
		    + (((long)buffer[3]) << 24));
	} else {
	    value = (long) (buffer[3]
		    + (buffer[2] << 8)
		    + (buffer[1] << 16)
		    + (((long) buffer[0]) << 24));
	}

	/*
	 * Check to see if the value was sign extended properly on systems
	 * where an int is more than 32-bits.
	 * We avoid caching unsigned integers as we cannot distinguish between
	 * 32bit signed and unsigned in the hash (short and char are ok).
	 */

	if (flags & BINARY_UNSIGNED) {
	    return Tcl_NewWideIntObj((Tcl_WideInt)(unsigned long)value);
	}
	if ((value & (((unsigned) 1)<<31)) && (value > 0)) {
	    value -= (((unsigned) 1)<<31);
	    value -= (((unsigned) 1)<<31);
	}

    returnNumericObject:
	if (*numberCachePtrPtr == NULL) {
	    return Tcl_NewLongObj(value);
	} else {
	    register Tcl_HashTable *tablePtr = *numberCachePtrPtr;
	    register Tcl_HashEntry *hPtr;
	    int isNew;

	    hPtr = Tcl_CreateHashEntry(tablePtr, INT2PTR(value), &isNew);
	    if (!isNew) {
		return Tcl_GetHashValue(hPtr);
	    }
	    if (tablePtr->numEntries <= BINARY_SCAN_MAX_CACHE) {
		register Tcl_Obj *objPtr = Tcl_NewLongObj(value);

		Tcl_IncrRefCount(objPtr);
		Tcl_SetHashValue(hPtr, objPtr);
		return objPtr;
	    }

	    /*
	     * We've overflowed the cache! Someone's parsing a LOT of varied
	     * binary data in a single call! Bail out by switching back to the
	     * old behaviour for the rest of the scan.
	     *
	     * Note that anyone just using the 'c' conversion (for bytes)
	     * cannot trigger this.
	     */

	    DeleteScanNumberCache(tablePtr);
	    *numberCachePtrPtr = NULL;
	    return Tcl_NewLongObj(value);
	}

	/*
	 * Do not cache wide (64-bit) values; they are already too large to
	 * use as keys.
	 */

    case 'w':
    case 'W':
    case 'm':
	if (NeedReversing(type)) {
	    uwvalue = ((Tcl_WideUInt) buffer[0])
		    | (((Tcl_WideUInt) buffer[1]) << 8)
		    | (((Tcl_WideUInt) buffer[2]) << 16)
		    | (((Tcl_WideUInt) buffer[3]) << 24)
		    | (((Tcl_WideUInt) buffer[4]) << 32)
		    | (((Tcl_WideUInt) buffer[5]) << 40)
		    | (((Tcl_WideUInt) buffer[6]) << 48)
		    | (((Tcl_WideUInt) buffer[7]) << 56);
	} else {
	    uwvalue = ((Tcl_WideUInt) buffer[7])
		    | (((Tcl_WideUInt) buffer[6]) << 8)
		    | (((Tcl_WideUInt) buffer[5]) << 16)
		    | (((Tcl_WideUInt) buffer[4]) << 24)
		    | (((Tcl_WideUInt) buffer[3]) << 32)
		    | (((Tcl_WideUInt) buffer[2]) << 40)
		    | (((Tcl_WideUInt) buffer[1]) << 48)
		    | (((Tcl_WideUInt) buffer[0]) << 56);
	}
	if (flags & BINARY_UNSIGNED) {
	    Tcl_Obj *bigObj = NULL;
	    mp_int big;

	    TclBNInitBignumFromWideUInt(&big, uwvalue);
	    bigObj = Tcl_NewBignumObj(&big);
	    return bigObj;
	}
	return Tcl_NewWideIntObj((Tcl_WideInt) uwvalue);

	/*
	 * Do not cache double values; they are already too large to use as
	 * keys and the values stored are utterly incompatible with the
	 * integer part of the cache.
	 */

	/*
	 * 32-bit IEEE single-precision floating point.
	 */

    case 'f':
    case 'R':
    case 'r':
	CopyNumber(buffer, &fvalue, sizeof(float), type);
	return Tcl_NewDoubleObj(fvalue);

	/*
	 * 64-bit IEEE double-precision floating point.
	 */

    case 'd':
    case 'Q':
    case 'q':
	CopyNumber(buffer, &dvalue, sizeof(double), type);
	return Tcl_NewDoubleObj(dvalue);
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteScanNumberCache --
 *
 *	Deletes the hash table acting as a scan number cache.
 *
 * Results:
 *	None
 *
 * Side effects:
 *	Decrements the reference counts of the objects in the cache.
 *
 *----------------------------------------------------------------------
 */

static void
DeleteScanNumberCache(
    Tcl_HashTable *numberCachePtr)
				/* Pointer to the hash table, or NULL (when
				 * the cache has already been deleted due to
				 * overflow.) */
{
    Tcl_HashEntry *hEntry;
    Tcl_HashSearch search;

    if (numberCachePtr == NULL) {
	return;
    }

    hEntry = Tcl_FirstHashEntry(numberCachePtr, &search);
    while (hEntry != NULL) {
	register Tcl_Obj *value = Tcl_GetHashValue(hEntry);

	if (value != NULL) {
	    Tcl_DecrRefCount(value);
	}
	hEntry = Tcl_NextHashEntry(&search);
    }
    Tcl_DeleteHashTable(numberCachePtr);
}

/*
 * ----------------------------------------------------------------------
 *
 * NOTES --
 *
 *	Some measurements show that it is faster to use a table to to perform
 *	uuencode and base64 value encoding than to calculate the output (at
 *	least on intel P4 arch).
 *
 *	Conversely using a lookup table for the decoding is slower than just
 *	calculating the values. We therefore use the fastest of each method.
 *
 *	Presumably this has to do with the size of the tables. The base64
 *	decode table is 255 bytes while the encode table is only 65 bytes. The
 *	choice likely depends on CPU memory cache sizes.
 */

/*
 *----------------------------------------------------------------------
 *
 * BinaryEncodeHex --
 *
 *	Implement the [binary encode hex] binary encoding. clientData must be
 *	a table to convert values to hexadecimal digits.
 *
 * Results:
 *	Interp result set to an encoded byte array object
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
BinaryEncodeHex(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *const objv[])
{
    Tcl_Obj *resultObj = NULL;
    unsigned char *data = NULL;
    unsigned char *cursor = NULL;
    const char *digits = clientData;
    int offset = 0, count = 0;

    if (objc != 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "data");
	return TCL_ERROR;
    }

    TclNewObj(resultObj);
    data = Tcl_GetByteArrayFromObj(objv[1], &count);
    cursor = Tcl_SetByteArrayLength(resultObj, count * 2);
    for (offset = 0; offset < count; ++offset) {
	*cursor++ = digits[((data[offset] >> 4) & 0x0f)];
	*cursor++ = digits[(data[offset] & 0x0f)];
    }
    Tcl_SetObjResult(interp, resultObj);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * BinaryDecodeHex --
 *
 *	Implement the [binary decode hex] binary encoding.
 *
 * Results:
 *	Interp result set to an decoded byte array object
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
BinaryDecodeHex(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *const objv[])
{
    Tcl_Obj *resultObj = NULL;
    unsigned char *data, *datastart, *dataend;
    unsigned char *begin, *cursor, c;
    int i, index, value, size, count = 0, cut = 0, strict = 0;
    enum {OPT_STRICT };
    static const char *const optStrings[] = { "-strict", NULL };

    if (objc < 2 || objc > 3) {
	Tcl_WrongNumArgs(interp, 1, objv, "data");
	return TCL_ERROR;
    }
    for (i = 1; i < objc-1; ++i) {
	if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option",
		TCL_EXACT, &index) != TCL_OK) {
	    return TCL_ERROR;
	}
	switch (index) {
	case OPT_STRICT:
	    strict = 1;
	    break;
	}
    }

    TclNewObj(resultObj);
    datastart = data = (unsigned char *)
	    TclGetStringFromObj(objv[objc-1], &count);
    dataend = data + count;
    size = (count + 1) / 2;
    begin = cursor = Tcl_SetByteArrayLength(resultObj, size);
    while (data < dataend) {
	value = 0;
	for (i=0 ; i<2 ; i++) {
	    if (data < dataend) {
		c = *data++;

		if (!isxdigit((int) c)) {
		    if (strict || !isspace(c)) {
			goto badChar;
		    }
		    i--;
		    continue;
		}
		value <<= 4;
		c -= '0';
		if (c > 9) {
		    c += ('0' - 'A') + 10;
		}
		if (c > 16) {
		    c += ('A' - 'a');
		}
		value |= (c & 0xf);
	    } else {
		value <<= 4;
		cut++;
	    }
	}
	*cursor++ = UCHAR(value);
	value = 0;
    }
    if (cut > size) {
	cut = size;
    }
    Tcl_SetByteArrayLength(resultObj, cursor - begin - cut);
    Tcl_SetObjResult(interp, resultObj);
    return TCL_OK;

  badChar:
    TclDecrRefCount(resultObj);
    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
	    "invalid hexadecimal digit \"%c\" at position %d",
	    c, (int) (data - datastart - 1)));
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * BinaryEncode64 --
 *
 *	This implements a generic 6 bit binary encoding. Input is broken into
 *	6 bit chunks and a lookup table passed in via clientData is used to
 *	turn these values into output characters. This is used to implement
 *	base64 and uuencode binary encodings.
 *
 * Results:
 *	Interp result set to an encoded byte array object
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

#define OUTPUT(c) \
    do {						\
	*cursor++ = (c);				\
	outindex++;					\
	if (maxlen > 0 && cursor != limit) {		\
	    if (outindex == maxlen) {			\
		memcpy(cursor, wrapchar, wrapcharlen);	\
		cursor += wrapcharlen;			\
		outindex = 0;				\
	    }						\
	}						\
	if (cursor > limit) {				\
	    Tcl_Panic("limit hit");			\
	}						\
    } while (0)

static int
BinaryEncode64(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *const objv[])
{
    Tcl_Obj *resultObj;
    unsigned char *data, *cursor, *limit;
    const char *digits = clientData;
    int maxlen = 0;
    const char *wrapchar = "\n";
    int wrapcharlen = 1;
    int offset, i, index, size, outindex = 0, count = 0;
    enum {OPT_MAXLEN, OPT_WRAPCHAR };
    static const char *const optStrings[] = { "-maxlen", "-wrapchar", NULL };

    if (objc < 2 || objc%2 != 0) {
	Tcl_WrongNumArgs(interp, 1, objv,
		"?-maxlen len? ?-wrapchar char? data");
	return TCL_ERROR;
    }
    for (i = 1; i < objc-1; i += 2) {
	if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option",
		TCL_EXACT, &index) != TCL_OK) {
	    return TCL_ERROR;
	}
	switch (index) {
	case OPT_MAXLEN:
	    if (Tcl_GetIntFromObj(interp, objv[i+1], &maxlen) != TCL_OK) {
		return TCL_ERROR;
	    }
	    break;
	case OPT_WRAPCHAR:
	    wrapchar = Tcl_GetStringFromObj(objv[i+1], &wrapcharlen);
	    if (wrapcharlen == 0) {
		maxlen = 0;
	    }
	    break;
	}
    }

    resultObj = Tcl_NewObj();
    data = Tcl_GetByteArrayFromObj(objv[objc-1], &count);
    if (count > 0) {
	size = (((count * 4) / 3) + 3) & ~3; /* ensure 4 byte chunks */
	if (maxlen > 0 && size > maxlen) {
	    int adjusted = size + (wrapcharlen * (size / maxlen));

	    if (size % maxlen == 0) {
		adjusted -= wrapcharlen;
	    }
	    size = adjusted;
	}
	cursor = Tcl_SetByteArrayLength(resultObj, size);
	limit = cursor + size;
	for (offset = 0; offset < count; offset+=3) {
	    unsigned char d[3] = {0, 0, 0};

	    for (i = 0; i < 3 && offset+i < count; ++i) {
		d[i] = data[offset + i];
	    }
	    OUTPUT(digits[d[0] >> 2]);
	    OUTPUT(digits[((d[0] & 0x03) << 4) | (d[1] >> 4)]);
	    if (offset+1 < count) {
		OUTPUT(digits[((d[1] & 0x0f) << 2) | (d[2] >> 6)]);
	    } else {
		OUTPUT(digits[64]);
	    }
	    if (offset+2 < count) {
		OUTPUT(digits[d[2] & 0x3f]);
	    } else {
		OUTPUT(digits[64]);
	    }
	}
    }
    Tcl_SetObjResult(interp, resultObj);
    return TCL_OK;
}
#undef OUTPUT

/*
 *----------------------------------------------------------------------
 *
 * BinaryDecodeUu --
 *
 *	Decode a uuencoded string.
 *
 * Results:
 *	Interp result set to an byte array object
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
BinaryDecodeUu(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *const objv[])
{
    Tcl_Obj *resultObj = NULL;
    unsigned char *data, *datastart, *dataend;
    unsigned char *begin, *cursor;
    int i, index, size, count = 0, cut = 0, strict = 0;
    char c;
    enum {OPT_STRICT };
    static const char *const optStrings[] = { "-strict", NULL };

    if (objc < 2 || objc > 3) {
	Tcl_WrongNumArgs(interp, 1, objv, "data");
	return TCL_ERROR;
    }
    for (i = 1; i < objc-1; ++i) {
	if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option",
		TCL_EXACT, &index) != TCL_OK) {
	    return TCL_ERROR;
	}
	switch (index) {
	case OPT_STRICT:
	    strict = 1;
	    break;
	}
    }

    TclNewObj(resultObj);
    datastart = data = (unsigned char *)
	    TclGetStringFromObj(objv[objc-1], &count);
    dataend = data + count;
    size = ((count + 3) & ~3) * 3 / 4;
    begin = cursor = Tcl_SetByteArrayLength(resultObj, size);
    while (data < dataend) {
	char d[4] = {0, 0, 0, 0};

	for (i=0 ; i<4 ; i++) {
	    if (data < dataend) {
		d[i] = c = *data++;
		if (c < 33 || c > 96) {
		    if (strict || !isspace(UCHAR(c))) {
			goto badUu;
		    }
		    i--;
		    continue;
		}
	    } else {
		cut++;
	    }
	}
	if (cut > 3) {
	    cut = 3;
	}
	*cursor++ = (((d[0] - 0x20) & 0x3f) << 2)
		| (((d[1] - 0x20) & 0x3f) >> 4);
	*cursor++ = (((d[1] - 0x20) & 0x3f) << 4)
		| (((d[2] - 0x20) & 0x3f) >> 2);
	*cursor++ = (((d[2] - 0x20) & 0x3f) << 6)
		| (((d[3] - 0x20) & 0x3f));
    }
    if (cut > size) {
	cut = size;
    }
    Tcl_SetByteArrayLength(resultObj, cursor - begin - cut);
    Tcl_SetObjResult(interp, resultObj);
    return TCL_OK;

  badUu:
    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
	    "invalid uuencode character \"%c\" at position %d",
	    c, (int) (data - datastart - 1)));
    TclDecrRefCount(resultObj);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * BinaryDecode64 --
 *
 *	Decode a base64 encoded string.
 *
 * Results:
 *	Interp result set to an byte array object
 *
 * Side effects:
 *	None
 *
 *----------------------------------------------------------------------
 */

static int
BinaryDecode64(
    ClientData clientData,
    Tcl_Interp *interp,
    int objc,
    Tcl_Obj *const objv[])
{
    Tcl_Obj *resultObj = NULL;
    unsigned char *data, *datastart, *dataend, c;
    unsigned char *begin = NULL;
    unsigned char *cursor = NULL;
    int strict = 0;
    int i, index, size, cut = 0, count = 0;
    enum {OPT_STRICT };
    static const char *const optStrings[] = { "-strict", NULL };

    if (objc < 2 || objc > 3) {
	Tcl_WrongNumArgs(interp, 1, objv, "data");
	return TCL_ERROR;
    }
    for (i = 1; i < objc-1; ++i) {
	if (Tcl_GetIndexFromObj(interp, objv[i], optStrings, "option",
		TCL_EXACT, &index) != TCL_OK) {
	    return TCL_ERROR;
	}
	switch (index) {
	case OPT_STRICT:
	    strict = 1;
	    break;
	}
    }

    TclNewObj(resultObj);
    datastart = data = (unsigned char *)
	    TclGetStringFromObj(objv[objc-1], &count);
    dataend = data + count;
    size = ((count + 3) & ~3) * 3 / 4;
    begin = cursor = Tcl_SetByteArrayLength(resultObj, size);
    while (data < dataend) {
	unsigned long value = 0;

	for (i=0 ; i<4 ; i++) {
	    if (data < dataend) {
		c = *data++;

		if (c >= 'A' && c <= 'Z') {
		    value = (value << 6) | ((c - 'A') & 0x3f);
		} else if (c >= 'a' && c <= 'z') {
		    value = (value << 6) | ((c - 'a' + 26) & 0x3f);
		} else if (c >= '0' && c <= '9') {
		    value = (value << 6) | ((c - '0' + 52) & 0x3f);
		} else if (c == '+') {
		    value = (value << 6) | 0x3e;
		} else if (c == '/') {
		    value = (value << 6) | 0x3f;
		} else if (c == '=') {
		    value <<= 6;
		    if (cut < 2) {
			cut++;
		    }
		} else {
		    if (strict || !isspace(c)) {
			goto bad64;
		    }
		    i--;
		    continue;
		}
	    } else {
		value <<= 6;
		cut++;
	    }
	}
	*cursor++ = UCHAR((value >> 16) & 0xff);
	*cursor++ = UCHAR((value >> 8) & 0xff);
	*cursor++ = UCHAR(value & 0xff);
    }
    if (cut > size) {
	cut = size;
    }
    Tcl_SetByteArrayLength(resultObj, cursor - begin - cut);
    Tcl_SetObjResult(interp, resultObj);
    return TCL_OK;

  bad64:
    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
	    "invalid base64 character \"%c\" at position %d",
	    (char) c, (int) (data - datastart - 1)));
    TclDecrRefCount(resultObj);
    return TCL_ERROR;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */