From: Keith A. <ca...@pi...> - 2012-05-08 17:02:46
|
I've been helping to develop and conduct a class that introduces kids to electronics, computer engineering, and software development concepts using Arduino & ATMega microcontrollers. We chose to use AMForth for a part of the programming section of the class and were concerned the kids would have trouble understanding the results of uploading using the currently bundled amforth-shell.py and amforth-uplaod.py scripts when something went wrong. To address those concerns I whipped together the script below which, after a bit of scope creep, implements the following enhancements to the existing scripts: 1) Based on the pyserial module, it aggressively reads character echos which appears to prevent occasional microcontroller resets during uploads that I think may be related to serial tx overruns. It also checks for lines that are too long (preventing rx overruns) and attempts to compress out excess whitespace and comment lines and avoid sending them to the microcontroller to shorten the upload time. 2) When uploading it prints the contents of each line sent to the microcontroller as they are sent, with some status information prefixed so it is easy to understand the progress of the upload. Lines successfully sent are prefixed with an 'S', whitespace lines that weren't sent at all are prefixed with a 'W', errors, with 'E', etc. 3) By default it stops an upload on the first error observed, detected by matching on the pattern of amforth error messages or receiving a timeout. This can be overridden for a single line, an entire file, or an entire group of files (using nested includes). 4) In interactive mode it implements host-side command history, word completion (including host-side directives), and filename completion for the directives for including (uploading) and editing files. The file editing directive has a default mode where it opens an editor at the location of the last error. Typical usage is very simple: ./frt-interact.py -p /dev/ttyUSB0 Replace the path to the port device with whatever port is connected to your microcontroller. For Windows users, I believe you can specify a COM device and it should work but I haven't tested that. This will get you an interaction prompt just as if you were interacting with amforth over a regular terminal but with all the above enhancements. There is a lot more information about the operation of the script, available host-side directives, etc. in the comment block at the beginning of the script below. We've been using this in the class for a couple of weeks now and it seems to be working well. I'd be very pleased if this could be included in the base distribution of amforth. Regards, Keith --- Cut here ---- #!/usr/bin/python # # pySerial based upload & interpreter interaction module for amforth. # # Copyright 2011 Keith Amidon (ca...@pi...) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # ===================================================================== # DOCUMENTATION # ===================================================================== # This module module may be used as a script or imported for use as a # part of a larger python program. # # Script Usage # ------------ # When used as a script this module provides two main functions, the # ability to reliably upload files to the amforth interpreter and an # interpreter interaction mode with line editing, word completion and # previous input history. For information on how to access these # features when invoking the module as a script, execute it with the # --help option and read the following sections on the interaction # protocol and local directives. # # # Interaction Protocol # -------------------- # The amforth interaction protocol used by this module is to send a # line to amforth character by character, reading the echos as quickly # as possible. Once the entire line has been sent it then reads the # response up until the " ok" prompt or a prompt that looks like an # error response. The character by character handling of echos # appears to eliminate unexpected device resets when compared to the # line by line method used by previous tools, possibly by eliminating # the possibility of serial tx overrun on the device. # # To further optimize interaction with the device lines are evaluated # before sending and redundant whitespace is compressed out. Lines # which are all whitespace or whitespace and comments are not sent and # the next line is handled. # # # Local Directives # ---------------- # A number of special directives are supported which instruct the # script to do something and are handled locally without being sent to # amforth. Directives may be specified within comments or outside # comments as controlled by the "#directive" directive. They must be # the only contents of a line or they will be ignored. The directives # include: # # #include <file> # Upload the file named by <file> before proceeding further. # # #cd <dir> # Change the current local directory to the location specified. # During uploads, this directive affects the current file and # files it includes. Once the current file is complete the old # value will be restored. # # #directive <config> # Change how directives are discovered. The valid values for # <config> are: # none : Stop looking for any directives # commented : Only look for directives within comments # Commented directives must be the first word of the # comment. The remaining text in the comment is the # argument provided to the directive. There must # not be any other non-whitespace text other than # the comment start and (if required) end characters # and the directive and any directive argument on a # commented directive line. If any other text is # present on the line an error will be generated. # uncommented : Only look for directives outside comments. # Uncommented directives must be the first word of a # line and extend to the end of the line. If a # directive name exists in a subsequent word of a # line it will be sent to the interpreter as a word # like any other. # all : Allow both commented and uncommented directives. # This is the default. # During uploads, this directive affects the current file and # files it includes. Once the current file is complete the old # value will be restored. # # #timeout <float> # Change the timeout value to <float> seconds. Fractional # values are supported. During uploads, this directive affects # the current file and files it includes. Once the current file # is complete the old value will be restored. # # #timeout-next <float> # Change the timeout value for the next line sent to the # interpreter to <float> seconds. Fractional values are # supported. The timeout returns to its previous value after # the next line is sent to the interpreter. If this directive # is encountered as the very last line of an upload file it will # have no effect. # # #error-on-output [<yes-or-no>] # Controls whether an error is generated if unexpected output # occurs during an upload. The default is yes. This directive # can not be used in interactive mode as it would not have any # effect. During uploads it affects the rest of the current file # and any files it includes. The argument is optional. If not # given it is assumed to be "yes". # # #ignore-error [<yes-or-no>] # Ignore any error that occurs later in the current upload file # or a file it includes. The argument is optional. If given # the behavior is set as specified. If not given it is assumed # to be "yes". # # #ignore-error-next [<yes-or-no>] # Ignore any error that occurs on the next line. The argument # is optional. If given the behavior is set as specified. If # not given it is assumed to be "yes". # # #expect-output-next [<regexp>] # Expect specific output on the next line. The argument is # optional. If it is not specified a default regular expression # of ".*" (match everything) is assumed. This overrides the # #error-on-output directive. An error is raised if the output # doesn't match the regular expression. It will be ignored if # #ignore-error is yes. Use of this directive without an # argument is the way to prevent an error on output when # #error-on-output is yes # # #start-string-word <word> # Add a word that starts a string. The string will end when a # double quote character is read. # # #quote-char-word <word> # Add a word that quotes the immediately next word # # #interact # Start an interactive session before proceeding with file upload. # This only makes sense during a file upload. # # #edit [<filename>] # Edit a file. The filename is optional. If it is provided the # named file will be edited. If it is not provided and the last # upload ended in an error the file that had the error will be # edited at the location of the error. If there was no previous # upload or the last upload completed successfully but an #edit # directive was previously issued with a filename, edit the file # previously named. Finally, if none of these apply an error is # printed. The editor used can be specified with the --editor # option when starting the program or through the EDITOR # environment variable. # # #update-words # This directive is only available in an interactive session. # It cause the interaction code to reload the list of words used # for completion from the amforth interpreter. Typically it is # not required as words are updated automatically when the # session starts and any time a file is uploaded as a results of # a #include directive. The case where it is required is when # completion is needed for words defined interactively during # the session. # # #exit # Exit an interactive session or the current upload immediately. # If encountered during an upload, no further lines from the # file will be processed. # # # Programmatic Usage # ------------------ # For programmatic usage, a single class named AMForth is provided. # It can be instantiated with no arguments but typically a serial port # device and port speed will be provided as the defaults are unlikely # to be correct. # # Once an instance is obtained, and connected the high-level entry # points are the "upload_file" and "interact" methods, the former # uploading a file to the AMForth interperter and the latter providing # an interative interpreter shell with command history and word # completion. These methods provide progress information in various # cases by calling the function stored in the "progress_callback" # property with three arguments, the type of progress being reported, # a line number if available (otherwise it is None) and a message with # further information. The default progress callback prints this # information to the screen in a terse format. Other programs may # wish to replace this with their own progress presentation function. # # Low-level interaction with the AMForth interpreter would typically # use the "send_line" and "read_response" methods. Before these can # be used the serial connection must be established. The # serial_connected property indicates whether a connection currently # exists. A good way to obtain a connection and rule out errors in # serial communication is to call "find_prompt" which ensures the # existence of a serial connection and sends a newline to the AMForth # interperter and watches for the echo. This is usually the best way # of establishing a connection but the "serial_connect" method will # open a connection without sending anything if that is required. # # Elimination of whitespace and discovery of directives (see below) is # provided through the "preprocess_line" method and directives that # have common implementations can be handled with the # "handle_common_directives" method. # TODO: - Update comments on most functions explaining what they do. import argparse import atexit import copy import glob import os import re import readline import serial import StringIO import subprocess import sys import traceback class AMForthException(Exception): pass class Behaviors(object): """Simple class for storing configurable processing behaviors""" def __init__(self): self.working_directory = os.getcwd() self.filename = None self.timeout = 15.0 self.quote_char_words = ["[char]", "char"] self.start_string_words = ['s"', '."', 'abort"'] self.error_on_output = True self.ignore_errors = False self.directive_uncommented = True self.directive_commented = True self.expected_output_regexp = None @property def directive_config(self): "Get the current directive configuration" if self.directive_uncommented: if self.directive_commented: return "all" else: return "uncommented" else: if self.directive_commented: return "commented" else: return "none" @directive_config.setter def directive_config(self, value): "Set the directive configuration" if value == "none": self.directive_uncommented = False self.directive_commented = False elif value == "all": self.directive_uncommented = True self.directive_commented = True elif value == "uncommented": self.directive_uncommented = True self.directive_commented = False elif value == "commented": self.directive_uncommented = False self.directive_commented = True else: raise AMForthException("Unknown directive config: %s" % value) class BehaviorManager(object): """Class for determining currently configured behavior This class manages the lifetime of behaviors established through configuration options and directives to minimize the impact of that support on the AMForth class. """ def __init__(self): self.default_behavior = Behaviors() self.clear() def clear(self): "Clear out accumulated behavior" self._next_line_behavior = None self._current_line_behavior = None self._file_behaviors = [] @property def current_behavior(self): """The behavior currently in effect""" if self._current_line_behavior: return self._current_line_behavior elif self._file_behaviors: return self._file_behaviors[0] else: return self.default_behavior def advance_line(self): """Call when changing to the next line""" self._current_line_behavior = self._next_line_behavior self._next_line_behavior = None def push_file(self, filename): """Call when starting processing a new nested file""" behavior = copy.deepcopy(self.current_behavior) behavior.filename = filename self._file_behaviors.insert(0, behavior) def pop_file(self): """Call when returning from a nested file""" del(self._file_behaviors[0]) @property def next_line_behavior(self): """The behavior to use for the next line""" return self._next_line_behavior @next_line_behavior.setter def next_line_behavior(self, behavior): self._next_line_behavior = behavior @property def current_file_behavior(self): """The behavior for the current file. Will raise an exception if there is no file currently.""" return self._file_behaviors[0] @current_file_behavior.setter def current_file_behavior(self, behavior): self._file_behaviors[0] = behavior class AMForth(object): "Class for interacting with the AMForth interpreter" amforth_error_cre = re.compile(" \?\? -\d+ \d+ \r\n> $") upload_directives = [ "#cd", "#include", "#directive", "#ignore-error", "#ignore-error-next", "#error-on-output", "#expect-output-next", "#string-start-word", "#quote-char-word", "#timeout", "#timeout-next", "#interact", "#exit" ] interact_directives = [ "#cd", "#edit", "#include", "#directive", "#ignore-error", "#error-on-output", "#string-start-word", "#quote-char-word", "#timeout", "#timeout-next", "#update-words", "#exit" ] def __init__(self, serial_port="/dev/amforth", speed=9600): self.debug = False self.max_line_length = 80 self.progress_callback = self.print_progress self.editor = None self._serial_port = serial_port self._serial_speed = speed self._serialconn = None self._readline_initialized = False self._amforth_dp = None self._amforth_words = [] self._last_error = () self._last_edited_file = None self._config = BehaviorManager() @property def serial_port(self): "Serial port device attached to AMForth" return self._serial_port @serial_port.setter def serial_port(self, value): """Set the serial port device attached to AMForth If the value provided is different than the current value any existing serial connection will be closed and a new connection opened.""" if self._serial_port != value: self._serial_port = value self.serial_reconnect() @property def serial_speed(self): "Speed of the serial connection to AMForth" return self._serial_speed @serial_speed.setter def serial_speed(self, value): if self._serial_speed != value: self._serial_speed = value self.serial_reconnect() @property def serial_connected(self): "Boolean status for whether currently connected to AMForth" return self._serialconn is not None def main(self): "Main function called when module is used as a script" upload_files, interact = self.parse_arg() try: for fn in upload_files: if fn == "-": self.interact() else: self.upload_file(fn) if interact: self.interact() except AMForthException: return 1 except KeyboardInterrupt: print "\nAborted with keyboard interrupt" except Exception, e: print "\n---- Unexpected exception ----" traceback.print_exc() return 1 finally: self.serial_disconnect() return 0 def parse_arg(self): "Argument parsing used when module is used as a script" parser = argparse.ArgumentParser(description="Interact with AMForth") parser.add_argument("--timeout", "-t", action="store", type=float, default=15.0, help="Timeout for response in seconds (float value)") parser.add_argument("--port", "-p", action="store", default=self.serial_port, help="Name of serial port on which AMForth is connected") parser.add_argument("--speed", "-s", action="store", type=int, default=self.serial_speed, help="Speed of serial port on which AMForth is connected") parser.add_argument("--line-length", "-l", action="store", type=int, default=self.max_line_length, help="Maximum length of amforth input line") parser.add_argument("--interact", "-i", action="store_true", help="Enter interactive prompt after upload") parser.add_argument("--directive", "-d", action="store", default="all", help="Local directive configuration (where found)") parser.add_argument("--editor", action="store", default = os.environ.get("EDITOR", None), help="Editor to use for #edit directive") parser.add_argument("--no-error-on-output", action="store_true", help="Indicate an error if upload causes output") parser.add_argument("--ignore-error", action="store_true", help="Ignore errors during upload (not recommended)") parser.add_argument("--debug-serial", action="store_true", help="Output extra info about serial transfers in stderr") parser.add_argument("files", nargs="*") arg = parser.parse_args() self.debug = arg.debug_serial self.max_line_length = arg.line_length self._serial_port = arg.port self._serial_speed = arg.speed self.editor = arg.editor behavior = self._config.current_behavior behavior.error_on_output = not arg.no_error_on_output behavior.directive_config = arg.directive behavior.timeout = arg.timeout behavior.ignore_errors = arg.ignore_error return arg.files, (arg.interact or len(arg.files) == 0) def serial_connect(self, port=None, speed=None): """Connect to AMForth on a serial port The port and speed argument are optional. If not specified the current values set in the object are used. These will be the defaults if the have not been changed. If either is specified corresponding property of the instance will be updated to the new value. This is safe to call even if a connection already exists as existing an existing connection will be closed before the new connection is made.""" if port != None: self.serial_port = port if speed != None: self.serial_speed = speed if self._serialconn: self.serial_disconnect() try: timeout = self._config.current_behavior.timeout self._serialconn = serial.Serial(self.serial_port, self.serial_speed, serial.EIGHTBITS, serial.PARITY_NONE, serial.STOPBITS_ONE, timeout, False, False, None, False) except serial.SerialException, e: raise AMForthException("Serial port connect failure: %s" % str(e)) def serial_disconnect(self): """Disconnect the serial connection to AMForth This is safe to call even if there is currently no connection.""" if self._serialconn: self._serialconn.close() self._serialconn = None def serial_reconnect(self): """Reconnect the serial connection to AMForth This is the same as calling serial_connect while there is an existing connection. It is provided to make the clear when the intent is to re-establish an existing connection (usually to apply new settings) versus creating a new connectoion.""" self.serial_connect() def find_prompt(self): "Attempt to find a prompt by sending a newline and verifying echo" if not self.serial_connected: self.serial_connect() # Use a short timeout to quickly detect if can't communicate self._serialconn.timeout = 2.0 try: try: self.send_line("\n") # Get empty line echo to make sure ready self.read_response() # Throw away the response. except serial.SerialException, e: self.progress_callback("Error", None, str(e)) raise AMForthException("Failed to get prompt: %s" % str(e)) finally: # Restore the current timeout self._serialconn.timeout = self._config.current_behavior.timeout def upload_file(self, filename): wd = self._config.current_behavior.working_directory fpath = os.path.normpath(os.path.join(wd, filename)) self._config.push_file(fpath) try: try: self.find_prompt() except AMForthException, e: self.progress_callback("Error", None, str(e)) raise self.progress_callback("File", None, fpath) try: with open(fpath, "r") as f: self._send_file_contents(f) except (OSError, IOError), e: self.progress_callback("Error", None, str(e)) raise AMForthException("Unknown file: " + fpath) self._last_error = () finally: self._config.pop_file() self._serialconn.timeout = self._config.current_behavior.timeout try: os.chdir(self._config.current_behavior.working_directory) except OSError, e: errmsg = ("Failed to change to directory '%s': %s" % (self._config.current_behavior.working_directory, str(e))) self.progress_callback("Error", None, errmsg) raise AMForthException(errmsg) def _send_file_contents(self, f): in_comment = False lineno = 0 for full_line in f: self._config.advance_line() self._serialconn.timeout = self._config.current_behavior.timeout try: os.chdir(self._config.current_behavior.working_directory) except OSError, e: errmsg = ("Failed to change to directory '%s': %s" % (self._config.current_behavior.working_directory, str(e))) self.progress_callback("Error", None, errmsg) raise AMForthException(errmsg) lineno += 1 if full_line and full_line[-1] == "\n": full_line = full_line[:-1] if full_line and full_line[-1] == "\r": full_line = full_line[:-1] line = full_line.strip() if len(line) == 0: if in_comment: self.progress_callback("Comment", lineno, full_line) else: self.progress_callback("Whitespace", lineno, full_line) continue try: (line, in_comment, directive, directive_arg) = self.preprocess_line(full_line, in_comment, self.upload_directives) except AMForthException, e: self._record_error(lineno) self.progress_callback("Error", lineno, full_line) self.progress_callback("Error", None, str(e)) raise if directive: self.progress_callback("Directive", lineno, full_line) if directive == "#exit": break elif directive == "#interact": self.interact() continue self.handle_common_directives(directive, directive_arg) continue if len(line) == 0: self.progress_callback("Comment", lineno, full_line) continue try: self.send_line(line) except AMForthException, e: self._record_error(lineno) self.progress_callback("Error", lineno, full_line) self.progress_callback("Error", None, str(e)) raise response = self.read_response() self.progress_callback("Sent", lineno, full_line) if response[-3:] == " ok": if len(response) > 3: for l in StringIO.StringIO(response[:-3]): self.progress_callback("Output", lineno, l.rstrip()) r = self._config.current_behavior.expected_output_regexp if r: m = re.match(r, response[:-3], re.MULTILINE) response_ok = m is not None else: response_ok = False if not response_ok: if self._config.current_behavior.error_on_output: errmsg = "Unexpected output after line." errmsg += " To allow, specify --no-error-on-output." self.progress_callback("Error", lineno, errmsg) if not self._config.current_behavior.ignore_errors: self._record_error(lineno) raise AMForthException(errmsg) else: self.progress_callback("Error", None, response) if not self._config.current_behavior.ignore_errors: self._record_error(lineno) raise AMForthException("Error in line sent") def preprocess_line(self, line, in_delim_comment=False, directives=[]): # Compresses whitespace, including comments so send minimum # data to atmega result = [] comment_words = [] char_quote = False in_string = False in_line_comment = False directive = None directive_arg = [] words = self._split_space_or_tab(line) for w in words: if in_string: try: i = w.index('"') except ValueError: result[-1] += " " + w continue in_string = False result[-1] += " " + w[:i+1] result[-1] = result[-1][1:] # remove extra initial space w = w[i+1:] if not w: continue if char_quote: result.append(w) char_quote = False continue if w == "(": if not in_delim_comment: in_delim_comment = True else: raise AMForthException("Illegal nested comment") continue if w == ")": if in_delim_comment: in_delim_comment = False else: raise AMForthException("Comment end without begin") continue if not in_delim_comment and not in_line_comment: if w == "\\": in_line_comment = True continue elif w in self._config.current_behavior.start_string_words: in_string = True result.append(w) result.append('') continue if w in self._config.current_behavior.quote_char_words: char_quote = True # no continue deliberately if directive: directive_arg.append(w) else: if (self._config.current_behavior.directive_uncommented and not result and w in directives): directive = w else: result.append(w) else: if directive: directive_arg.append(w) else: if (self._config.current_behavior.directive_commented and not result and not comment_words and w in directives): directive = w else: comment_words.append(w) if directive and len(result): raise AMForthError("Directive must not have other content: %s", " ".join(result)) return (" ".join(result), in_delim_comment, directive, " ".join(directive_arg)) def _record_error(self, lineno): fn = self._config.current_behavior.filename if fn: self._last_error = (fn, lineno) def _split_space_or_tab(self, line): result = [""] for c in line: if c == " " or c == "\t": result.append("") else: result[-1] += c return result def handle_common_directives(self, directive, directive_arg): if directive == "#include": fn = directive_arg.strip() self.upload_file(fn) resume_fn = self._config.current_behavior.filename if resume_fn: self.progress_callback("File", None, resume_fn + " (resumed)") elif directive == "#cd": dirname = directive_arg.strip() if os.path.isabs(dirname): dirpath = os.path.normpath(dirname) else: oldpath = self._config.current_behavior.working_directory dirpath = os.path.normpath(os.path.join(oldpath, dirname)) self._config.current_behavior.working_directory = dirpath elif directive == "#timeout": try: timeout = float(directive_arg) except ValueError, e: self.progress_callback("Error", None, "Invalid timeout") return self._config.current_file_behavior.timeout = timeout elif directive == "#timeout-next": try: timeout = float(directive_arg) except ValueError, e: self.progress_callback("Error", None, "Invalid timeout") return behavior = copy.deepcopy(self._config.current_behavior) behavior.timeout = timeout self._config.next_line_behavior = behavior elif directive == "#ignore-error": v = self._yes_or_no_arg(directive_arg) self._config.current_file_behavior.ignore_errors = v elif directive == "#ignore-error-next": v = self._yes_or_no_arg(directive_arg) behavior = copy.deepcopy(self._config.current_behavior) behavior.ignore_errors = v self._config.next_line_behavior = behavior elif directive == "#error-on-output": v = self._yes_or_no_arg(directive_arg) behavior = self._config.current_file_behavior behavior.error_on_output = v elif directive == "#expect-output-next": regexp = directive_arg.strip() if not regexp: regexp = ".*" behavior = copy.deepcopy(self._config.current_behavior) behavior.expected_output_regexp = regexp self._config.next_line_behavior = behavior elif directive == "#start-string-word": behavior = self._config.current_file_behavior behavior.start_string_words.append(directive_arg.strip().split(" ")) elif directive == "#quote-char-word": behavior = self._config.current_file_behavior behavior.quote_char_words.append(directive_arg.strip().split(" ")) elif directive == "#directive": behavior = self._config.current_file_behavior behavior.directive_config = directive_arg.strip() else: errmsg = "Unknown directive: %s %s" % (directive, directive_arg) raise AMForthException(errmsg) def _yes_or_no_arg(self, directive_arg): if not directive_arg: return True else: if directive_arg.lower() == "yes": return True elif directive_arg.lower() == "no": return False else: errmsg = "Invalid directive argument. Must be yes or no." raise AMForthExcetion(errmsg) def send_line(self, line): if len(line) > self.max_line_length - 1: # For newline raise AMForthException("Input line > %d char" % self.max_line_length) if self.debug: sys.stderr.write("|a( )" + repr(line)[1:-1] + "\n") sys.stderr.write("|s( )") for c in line + "\n": if self.debug: sys.stderr.write(repr(c)[1:-1]+"->") sys.stderr.flush() self._serialconn.write(c) self._serialconn.flush() r = self._serialconn.read(1) # Read echo of character we just sent while r and (r != c or (c == '\t' and r != ' ')): if self.debug: sys.stderr.write(repr(r)[1:-1]) sys.stderr.flush() r = self._serialconn.read(1) if not r: raise AMForthException("Input character not echoed.") if self.debug: sys.stderr.write(repr(r)[1:-1] + "|") sys.stderr.flush() if self.debug: sys.stderr.write("\n") def read_response(self): if self.debug: sys.stderr.write("|r( )") response = "" r = self._serialconn.read(1) while r != "": if self.debug: sys.stderr.write(repr(r)[1:-1]) sys.stderr.flush() response = response + r if response[-3:] == " ok": # Interactive prompt read and discarded while handling # echo of next line sent. break elif self.amforth_error_cre.search(response) is not None: response = response[:-3] # Don't return prompt in response break r = self._serialconn.read(1) if not response: response = "Timed out waiting for ok response" if self.debug: sys.stderr.write("\n") return response def print_progress(self, type, lineno, info): if not lineno: print "|%s=%s" % (type[:1], info) else: print "|%s|%5d|%s" % (type[:1], lineno, info) def interact(self): self.progress_callback("Interact", None, "Entering amforth interactive interpreter") # Use null filename "file" to capture interactive config self._config.push_file(None) try: self.find_prompt() except AMForthException, e: self.progress_callback("Error", None, str(e)) self._config.pop_file() raise self._init_readline() in_comment = False while True: try: full_line = raw_input("> ") except EOFError, e: print "" break self._config.advance_line() self._serialconn.timeout = self._config.current_behavior.timeout try: os.chdir(self._config.current_behavior.working_directory) except OSError, e: errmsg = ("Failed to change to directory '%s': %s" % (self._config.current_behavior.working_directory, str(e))) self.progress_callback("Error", None, errmsg) raise AMForthException(errmsg) (line, in_comment, directive, directive_arg) = self.preprocess_line(full_line, in_comment, self.interact_directives) try: if directive: self.progress_callback("Directive", None, full_line) if directive == "#exit": break elif directive == "#update-words": self._update_words() continue elif directive == "#edit": if directive_arg: self.edit_file(directive_arg.strip()) elif self._last_error: self.edit_file(*self._last_error) elif self._last_edited_file: self.edit_file(self._last_edited_file) else: print "No file to edit" continue self.handle_common_directives(directive, directive_arg) if directive == "#include": self._update_words() continue if in_comment or not line: continue else: self.send_line(line) print self.read_response() except AMForthException, e: print "Error: " + str(e) self._config.pop_file() self._serialconn.timeout = self._config.current_behavior.timeout try: os.chdir(self._config.current_behavior.working_directory) except OSError, e: errmsg = ("Failed to change to directory '%s': %s" % (self._config.current_behavior.working_directory, str(e))) self.progress_callback("Error", None, errmsg) raise AMForthException(errmsg) self.progress_callback("Interact", None, "Leaving interactive interpreter") def _init_readline(self): if not self._readline_initialized: readline.set_completer_delims(" ") readline.set_completer(self._rlcompleter) readline.parse_and_bind("tab: complete") histfn = os.path.join(os.path.expanduser("~"), ".frt-interact.history") try: readline.read_history_file(histfn) except IOError, e: pass self._update_words() atexit.register(readline.write_history_file, histfn) def _update_words(self): # TODO: - handle multiple wordlists in _update_words self.send_line("dp .") dp = self.read_response() if dp[-3:] != " ok": return # Something went wrong, just silently ignore dp = int(dp[:-3]) if self._amforth_dp != dp: self._amforth_dp = dp self.send_line("words") words = self.read_response() if words[-3:] != " ok": return # Something went wrong, just silently ignore self._amforth_words = words.split(" ") + self.interact_directives def _rlcompleter(self, text, state): if state == 0: line_words = readline.get_line_buffer().split(" ") if line_words and line_words[-1] == text: line_words = line_words[:-1] while line_words and line_words[-1] == "": line_words = line_words[:-1] if line_words: if line_words[-1] in ["#include", "#edit"]: self._rl_matches = glob.glob(text + "*") elif line_words[-1] == "#cd": fnames = glob.glob(text + '*') self._rl_matches = [f + "/" for f in fnames if os.path.isdir(f)] elif line_words[-1] == "#directive": self._rl_matches = [w for w in ("all ", "uncommented ", "commented ", "none ") if w.startswith(text)] elif line_words[-1] in ["#error-on-output", "#ignore-error", "#ignore-error-next"]: self._rl_matches = [w for w in ["yes", "no"] if w.startswith(text)] elif line_words[-1] in ["#exit", "#update-words", "#timeout", "#timeout-next"]: self._rl_matches = [] else: self._rl_matches = [w + " " for w in self._amforth_words if not text or w.startswith(text)] else: self._rl_matches = [w + " " for w in self._amforth_words if not text or w.startswith(text)] if self._rl_matches: return self._rl_matches[0] else: return None else: if state < len(self._rl_matches): return self._rl_matches[state] else: return None def edit_file(self, filename, lineno=0): if self.editor: # Have to construct command line differently for different # editors to be able to move to specific line... exename = os.path.basename(self.editor) if exename in ["emacs", "emacsclient", "nano"]: cmd = [self.editor, "+" + str(lineno), filename] elif exename in ["vi", "vim"]: cmd = [self.editor, filename, "+" + str(lineno)] elif exename == "gedit": cmd = [self.editor, "-b", filename, "+" + str(lineno)] else: cmd = [self.editor, filename] try: subprocess.call(cmd) self._last_edited_file = filename except OSError, e: raise AMForthException("Could not start editor: "+self.editor) else: raise AMForthException("No editor specified. Use --editor or EDITOR environment variable") if __name__ == "__main__": sys.exit(AMForth().main()) |
From: Keith A. <ca...@pi...> - 2012-06-28 21:26:57
|
{-- Tue, 08 May 2012 09:35:52 -0700: Keith <ca...@pi...> wrote: --} Keith> I've been helping to develop and conduct a class that Keith> introduces kids to electronics, computer engineering, and Keith> software development concepts using Arduino & ATMega Keith> microcontrollers. We chose to use AMForth for a part of the Keith> programming section of the class and were concerned the kids Keith> would have trouble understanding the results of uploading using Keith> the currently bundled amforth-shell.py and amforth-uplaod.py Keith> scripts when something went wrong. Keith> To address those concerns I whipped together the script below Keith> which, after a bit of scope creep, implements the following Keith> enhancements to the existing scripts.... Returning after a month+ of being consumed by other things, I was wondering if anyone got a chance to take a look at the interaction script I sent to the list. The class I wrote it for is over and the script definitely helped reduce the kid's confusion when things went wrong and improved their workflow. For that reason I think it would also be helpful to people who are new to forth, for example trying out amforth after coming from the Arduino environment etc. What do others on the list think? --- Keith |
From: Matthias T. <mt...@we...> - 2012-06-29 17:38:10
|
Hi Keith, > Returning after a month+ of being consumed by other things, I was > wondering if anyone got a chance to take a look at the interaction > script I sent to the list. I did and I forgot to give you feedback :( short: its a great tool, by far the best I know. I very much prefer it over my own upload script now :) > For that reason I think it would also be helpful to people who are > new to forth, for example trying out amforth after coming from the > Arduino environment etc. What do others on the list think? I have sometimes problems to get it starting, the option ..no-error-on-output solves the problem. I did not dig deeper into the causes however, probably the problem sits at the keyboard and not in the CPU(s) ;) The things I find most amazingly: command completion with TAB, full command history and seemless access to sourcefile for upload. If you agree, I want to add your tool to the source tree. (again: sorry for the late answer) Matthias |
From: Keith A. <ca...@pi...> - 2012-06-29 19:45:11
|
{-- Fri, 29 Jun 2012 19:38:03 +0200: Matthias <mt...@we...> wrote: --} Matthias> short: its a great tool, by far the best I know. I very much Matthias> prefer it over my own upload script now :) Thanks! I'm really glad to hear you like it. Matthias> I have sometimes problems to get it starting, the option Matthias> ..no-error-on-output solves the problem. I did not dig Matthias> deeper into the causes however, probably the problem sits at Matthias> the keyboard and not in the CPU(s) ;) Could you reply with an example showing the exact command line options you are giving and the output you get when it does not work. Then repeat the same command but pass the --debug-serial option and send that output? From those I should be able to figure out what is going on. I haven't had a chance to upgrade my microcontroller to anything more recent than 4.6 yet so one possibility is that there is something different about the behavior in 4.7 or 4.8 that causes the problem. Matthias> The things I find most amazingly: command completion with Matthias> TAB, full command history and seemless access to sourcefile Matthias> for upload. Thanks. What amazed me was how easy it was to implement some of those features leveraging the Python standard library modules. It was really neat when I saw it all coming together. :-) Matthias> If you agree, I want to add your tool to the source tree. That's what I was hoping for when I sent it to the list. I'd be honored to have it part of the distribution. And thanks for maintaining such a cool Forth implementation. Regards, Keith |
From: Matthias T. <mt...@we...> - 2012-06-30 13:54:19
|
hi Keith, > Could you reply with an example showing the exact command line options > you are giving and the output you get when it does not work. mt@ayla:amforth/tools$ ./amforth-term.py -p /dev/ttyUSB2 ./test.frt --debug |a( )\n |s( )\n->\r\n|\n-> ok\r\n| |r( )> \r\n ok |F=mcudef |a( )dp . |s( )d->\r\n> d|p->p| -> |.->.|\n->\r\n| |r( )3569 ok |a( )s" cpu" environment search-wordlist drop execute itype |s( )s->\r\n> s|"->"| -> |c->c|p->p|u->u|"->"| -> |e->e|n->n|v->v|i->i|r->r|o->o|n->n|m->m|e->e|n->n|t->t| -> |s->s|e->e|a->a|r->r|c->c|h->h|-->-|w->w|o->o|r->r|d->d|l->l|i->i|s->s|t->t| -> |d->d|r->r|o->o|p->p| -> |e->e|x->x|e->e|c->c|u->u|t->t|e->e| -> |i->i|t->t|y->y|p->p|e->e|\n->\r\n| |r( )ATmega1280 ok failed using device.py for atmega1280 .. continuing |F=/..../amforth/tools/test.frt |C| 1|\ this is a test |W| 2| |a( )ver 1000 ms cr |s( )v->\r\n> v|e->e|r->r| -> |1->1|0->0|0->0|0->0| -> |m->m|s->s| -> |c->c|r->r|\n->\r\n| |r( )amforth 4.9 ATmega1280\r\n ok |S| 3|ver 1000 ms cr |O| 3|amforth 4.9 ATmega1280 |E| 3|Unexpected output after line. To allow, specify --no-error-on-output. mt@ayla:amforth/tools$ cat test.frt \ this is a test ver 1000 ms cr 1000 ms ver cr ver 1000 ms words 1000 ms 1 2 + . 1000 ms mt@ayla:amforth/tools$ With the --no-error-on-output option everything works well. > Thanks. What amazed me was how easy it was to implement some of those > features leveraging the Python standard library modules. It was really > neat when I saw it all coming together. :-) Yeah, I tried it myself and it's really easy. It now talks with the controller and tries to detect its name to load the device.py file from the core/devices/<controller> directory. This gives me the huge number of register names and other specific constants without filling up the dictionary on the controller. My next topic on the wishlist is the inclusion of the environment variable AMFORTH_LIB for a list of directories in which the tool looks for a given file. Matthias |
From: Keith A. <ca...@pi...> - 2012-07-03 01:19:01
|
{-- Sat, 30 Jun 2012 15:54:09 +0200: Matthias <mt...@we...> wrote: --} >> Could you reply with an example showing the exact command line >> options you are giving and the output you get when it does not >> work. Matthias> <keith:... output omitted ...> Matthias> With the --no-error-on-output option everything works well. Ah, I think this is working as I intended but the behavior may be somewhat surprising to others, I'm not sure. I made the assumption that well behaved upload files never generate output because in my use cases for the class I was helping develop they consisted of one of two types of files: 1) definitions of words 2) tests written using the tester.frt package from the library In both these cases, output generally indicated a problem and I wanted to flag problems as quickly as possible so the kids didn't get confused about the source of the error. The current version of the script supports a couple of different ways to handle this situation. The --no-error-on-output command line option is one. That is my least preferred option, which is part of why I gave it such a long option name. My preferred methods would be to modify the file with comments that direct the script to treat specific lines generating output more appropriately. I'm going to show some solutions based on your test file but I don't have access to my microcontroller to test these right now so they may not be completely correct. The brute force solution is to ignore all output for the entire file and all the files it includes through #include directives. This would be done by making the following change to the file: \ this is a test \ #error-on-output no ver 1000 ms cr 1000 ms ver cr ver 1000 ms words 1000 ms 1 2 + . 1000 ms This would be the solution to use if you have an existing file or set of files that you know generate a lot of output. There are two more refined approaches, using the "#expect-output-next" directive. The first is to use this directive without any arguments before a line that will generate output. This will allow any output from that line. In your test file: \ this is a test \ #expect-output-next ver 1000 ms cr 1000 ms \ #expect-output-next ver cr ver 1000 ms \ #expect-output-next words 1000 ms \ #expect-output-next 1 2 + . 1000 ms You can also match the output more exactly by specifying a regular expression to the "#expect-output-next" directive which will cause an error if the output does not match that regular expression. The regular expression syntax is that expected by the python "re" module. This can be useful for writing host-based test cases for words that generate serial output. In your test file: \ this is a test \ #expect-output-next amforth 4.9 ATmega1280 ver 1000 ms cr 1000 ms \ #expect-output-next amforth 4.9 ATmega1280 ver cr ver 1000 ms \ #expect-output-next words 1000 ms \ #expect-output-next 3 1 2 + . 1000 ms Notice for example that this would verify that "1 2 + ." resulted in 3 being printed. It should pass. If you change the "#expect-output-next 3" comment to "#expect-output-next 4" it should cause an error. I didn't try to write the regular expression to correctly match the output of "words" since that may vary and because it would be huge. ;-) Note that the implementation that captures regular expression that is used to check the output in #expect-output-next may have a few quirks due to the way that whitespace is stripped/compressed, etc. It's been working for me but I haven't tried to explore the ugly corner cases. Matthias> Yeah, I tried it myself and it's really easy. It now talks Matthias> with the controller and tries to detect its name to load the Matthias> device.py file from the core/devices/<controller> directory. Matthias> This gives me the huge number of register names and other Matthias> specific constants without filling up the dictionary on the Matthias> controller. Neat! I didn't have a chance to look at the implementation closely, but from a brief glance it looks great and like it would be very useful. Matthias> My next topic on the wishlist is the inclusion of the Matthias> environment variable AMFORTH_LIB for a list of directories Matthias> in which the tool looks for a given file. That would be really handy as well. I unfortunately don't have time to work on it right now but if you don't get to it before I get time I'll take a crack at it when I do. --- Keith |
From: pito <pi...@vo...> - 2012-07-03 09:24:04
|
Hi Keith, I'm trying to run your script under winXP. I had to install "pyreadline-1.7.1.win32.exe" in order to get imported the readline. Also I changed the /dev/.. to COM6 (my BT module) and speed to 115k. Let me ask you following, pls - how to specify an Editor into the script - ie. my C:\WinAVR\pn\pn.exe (Programmers Notepad)? |I=Entering amforth interactive interpreter |I=using device.py for atmega1284p (ATmega1284P)> 100 100 + . 200 ok (ATmega1284P)> #edit myfrt |D=#edit myfrt Error: No editor specified. Use --editor or EDITOR environment variable (ATmega1284P)> thnks, pito ----- PŮVODNÍ ZPRÁVA ----- Od: "Keith Amidon" <ca...@pi...> Komu: "Matthias Trute" <mt...@we...> Předmět: Re: [Amforth] New forth interaction script Datum: 3.7.2012 - 3:18:51 > {-- Sat, 30 Jun 2012 15:54:09 +0200: Matthias > <mt...@we...> wrote: --} > >> Could you reply with an example showing the > >> exact command line > >> >> options you are giving and the output you get > >> when it does not > >> >> work. > > Matthias> <keith:... output omitted ...> > Matthias> With the --no-error-on-output option > everything works well. > > Ah, I think this is working as I intended but the > behavior may be > somewhat surprising to others, I'm not sure. I > made the assumption that > well behaved upload files never generate output > because in my use cases > for the class I was helping develop they consisted > of one of two types > of files: > > 1) definitions of words > 2) tests written using the tester.frt package from > the library > > In both these cases, output generally indicated a > problem and I wanted > to flag problems as quickly as possible so the > kids didn't get confused > about the source of the error. The current > version of the script > supports a couple of different ways to handle this > situation. The > --no-error-on-output command line option is one. > That is my least > preferred option, which is part of why I gave it > such a long option > name. > > My preferred methods would be to modify the file > with comments that > direct the script to treat specific lines > generating output more > appropriately. I'm going to show some solutions > based on your test file > but I don't have access to my microcontroller to > test these right now so > they may not be completely correct. > > The brute force solution is to ignore all output > for the entire file and > all the files it includes through #include > directives. This would be > done by making the following change to the file: > > \ this is a test > \ #error-on-output no > ver 1000 ms cr > 1000 ms > ver cr ver > 1000 ms > words > 1000 ms > 1 2 + . > 1000 ms > > This would be the solution to use if you have an > existing file or set of > files that you know generate a lot of output. > > There are two more refined approaches, using the > "#expect-output-next" > directive. The first is to use this directive > without any arguments > before a line that will generate output. This > will allow any output > from that line. In your test file: > > \ this is a test > \ #expect-output-next > ver 1000 ms cr > 1000 ms > \ #expect-output-next > ver cr ver > 1000 ms > \ #expect-output-next > words > 1000 ms > \ #expect-output-next > 1 2 + . > 1000 ms > > You can also match the output more exactly by > specifying a regular > expression to the "#expect-output-next" directive > which will cause an > error if the output does not match that regular > expression. The regular > expression syntax is that expected by the python > "re" module. This can > be useful for writing host-based test cases for > words that generate > serial output. In your test file: > > \ this is a test > \ #expect-output-next amforth 4.9 ATmega1280 > ver 1000 ms cr > 1000 ms > \ #expect-output-next amforth 4.9 ATmega1280 > ver cr ver > 1000 ms > \ #expect-output-next > words > 1000 ms > \ #expect-output-next 3 > 1 2 + . > 1000 ms > > Notice for example that this would verify that "1 > 2 + ." resulted in 3 > being printed. It should pass. If you change the > "#expect-output-next > 3" comment to "#expect-output-next 4" it should > cause an error. > > I didn't try to write the regular expression to > correctly match the > output of "words" since that may vary and because > it would be huge. ;-) > > Note that the implementation that captures regular > expression that is > used to check the output in #expect-output-next > may have a few quirks > due to the way that whitespace is > stripped/compressed, etc. It's been > working for me but I haven't tried to explore the > ugly corner cases. > > Matthias> Yeah, I tried it myself and it's really > easy. It now talks > Matthias> with the controller and tries to detect > its name to load the > Matthias> device.py file from the > core/devices/<controller> directory. > Matthias> This gives me the huge number of > register names and other > Matthias> specific constants without filling up > the dictionary on the > Matthias> controller. > > Neat! I didn't have a chance to look at the > implementation closely, but > from a brief glance it looks great and like it > would be very useful. > > Matthias> My next topic on the wishlist is the > inclusion of the > Matthias> environment variable AMFORTH_LIB for a > list of directories > Matthias> in which the tool looks for a given > file. > > That would be really handy as well. I > unfortunately don't have time to > work on it right now but if you don't get to it > before I get time I'll > take a crack at it when I do. > > --- Keith > > ------------------------------------------------------------------------------ > > Live Security Virtual Conference > Exclusive live event will cover all the ways > today's security and > threat landscape has changed and how IT managers > can respond. Discussions > will include endpoint security, mobile security > and the latest in malware > threats. > http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/ > _______________________________________________ > Amforth-devel mailing list for > http://amforth.sf.net/ > Amf...@li... > https://lists.sourceforge.net/lists/listinfo/amforth-devel > |
From: Keith A. <ca...@pi...> - 2012-07-03 15:11:34
|
{-- Tue, 03 Jul 2012 11:23:56 +0200 (CEST): pito <pi...@vo...> wrote: --} pito> Hi Keith, I'm trying to run your script under winXP. I had to pito> install "pyreadline-1.7.1.win32.exe" in order to get imported pito> the readline. Interesting. I wasn't aware that the Windows version of python didn't come with the required readline support. Are you using the python from python.org or some other one? In any case, I did all my development under Linux so I'm very happy to know it wasn't too difficult to get it running under Windows. pito> Also I changed the /dev/.. to COM6 (my BT module) pito> and speed to 115k. Yup, that makes sense. pito> Let me ask you following, pls - how to pito> specify an Editor into the script - ie. my C:\WinAVR\pn\pn.exe pito> (Programmers Notepad)? pito> |I=Entering amforth interactive interpreter pito> |I=using device.py for atmega1284p (ATmega1284P) pito> > 100 100 + . 200 ok pito> (ATmega1284P)> #edit myfrt pito> |D=#edit myfrt Error: No editor specified. Use --editor or EDITOR environment variable pito> (ATmega1284P)> As the error says, you can either: 1) Pass an additional option to the script, for example "--editor=C:\WinAVR\pn\pn.exe" 2) Set an environment variable named EDITOR to the path to your editor. In Windows I believe that environment variables are set through a system control panel somewhere but I haven't used recent versions of Windows very much so I'm not sure if Microsoft has made changes to that. Two other things to be aware of: 1) I'm not sure whether the path handling for calling the editor executable properly handles drive letters on Windows or not. Please let us know if you try it out. I don't think it should be too difficult to fix if it doesn't work properly. 2) The feature of opening the editor to the line where an upload error occurred only works for editors for which the script knows the appropriate command line syntax. Otherwise it just attempts to open the file without moving to the line of the error. The editors it knows about currently are: vi, vim, emacs, emacsclient, nano, and gedit. You might want to add your editor. The code is all in the "edit_file()" method. --- Keith |
From: pito <pi...@vo...> - 2012-07-03 15:51:55
|
Keith > version of python didn't > come with the required readline support. I am using python 2.7, installed long time back. No idea where it comes from :) > 1) Pass an additional option to the script, for > example > > "--editor=C:\WinAVR\pn\pn.exe" Yes, it works, I did a .bat file where I start your script with above option. #edit pytest.frt opens the PN with the file (file located in the same dir as your script). Closing the PN returns to the prompt. (ATmega1284P)> #edit pytest.frt |D=#edit pytest.frt (ATmega1284P)> #include pytest.frt |D=#include pytest.frt |I=mcudef |I=using device.py for atmega1284p |F=C:\MyCode\AVR\WINAVR\projects\AMFORTH\new_amforth\trunk\tools\pytest.frt ||S| 1|-pytest |E=-pytest ?? -13 7 Error: Error in line sent (ATmega1284P)> #include pytest.frt |D=#include pytest.frt |I=mcudef |I=using device.py for atmega1284p |F=C:\MyCode\AVR\WINAVR\projects\AMFORTH\new_amforth\trunk\tools\pytest.frt ||W| 1| |S| 2|marker -pytest |S| 3|: foo 1.2223e-4 555.322e2 f* fs. ; (ATmega1284P)> foo 6.78770E0 ok (ATmega1284P)> #include pytest.frt |D=#include pytest.frt |I=mcudef |I=using device.py for atmega1284p |F=C:\MyCode\AVR\WINAVR\projects\AMFORTH\new_amforth\trunk\tools\pytest.frt ||S| 1|-pytest |S| 2|marker -pytest |S| 3|: foo 1.2223e-4 555.322e2 f* fs. ; (ATmega1284P)> Now I have to tackle the automatic opening the PN when error.. Thanks, P. |
From: pito <pi...@vo...> - 2012-07-03 16:31:12
|
Keith, > 2) The feature of opening the editor to the line > where an upload error > occurred only works for editors for which the > script knows the > appropriate command line syntax. the PN editor opens and jumps to the line with following command (ie to the line 3) C:\WinAVR\pn\pn.exe --line 3 filename So I added to the edit_file ... elif exename == "pn.exe": cmd = [self.editor, " --line", " " + str(lineno), filename ] ... however it does not even attempts to open the editor when error - ie (ATmega1284P)> #include pytest.frt |D=#include pytest.frt |I=mcudef |I=using device.py for atmega1284p |F=C:\MyCode\AVR\WINAVR\projects\AMFORTH\new_amforth\trunk\tools\pytest.frt ||S| 1|-pytest |S| 2|marker -pytest |S| 3|ggg |E=ggg ?? -13 3 Error: Error in line sent (ATmega1284P)> Any hint? Thanks, p. |
From: pito <pi...@vo...> - 2012-07-03 17:26:00
|
.. edit at error line - it does not start automaticaly. I have to open the editor with #edit as the user guide says :) p. |
From: Matthias T. <mt...@we...> - 2012-07-03 18:03:08
|
Hi Keith, > Ah, I think this is working as I intended but the behavior may be > somewhat surprising to others, I'm not sure. I made the assumption > that well behaved upload files never generate output because in my > use cases for the class I was helping develop they consisted of one > of two types of files: > > 1) definitions of words 2) tests written using the tester.frt package > from the library > > In both these cases, output generally indicated a problem Not necessarily. I've seen quite a lot forth code that prints some messages with .( text to indicate some progress ) that is definitly not an error message. Same with the output of the tester. > My preferred methods would be to modify the file with comments that > direct the script to treat specific lines generating output more > appropriately. I'm going to show some solutions based on your test > file but I don't have access to my microcontroller to test these > right now so they may not be completely correct. Editing the source files builds a rather high barrier for your excellent tool. For me your ideas sound like introducing concepts like Design By Contract (http://en.wikipedia.org/wiki/Design_by_contract). Something the Forthers do not (yet?) think about. > Matthias> My next topic on the wishlist is the inclusion of the > Matthias> environment variable AMFORTH_LIB for a list of directories > Matthias> in which the tool looks for a given file. > > That would be really handy as well. I unfortunately don't have time > to work on it right now but if you don't get to it before I get time > I'll take a crack at it when I do. I collect the changes in the repository at http://amforth.svn.sourceforge.net/viewvc/amforth/trunk/tools/amforth-term.py?view=log Matthias |
From: Keith A. <ca...@pi...> - 2012-07-05 16:29:21
|
{-- Tue, 03 Jul 2012 20:02:55 +0200: Matthias <mt...@we...> wrote: --} Matthias> Editing the source files builds a rather high barrier for Matthias> your excellent tool. For me your ideas sound like Matthias> introducing concepts like Design By Contract Matthias> (http://en.wikipedia.org/wiki/Design_by_contract). Something Matthias> the Forthers do not (yet?) think about. Actually, my motivation was a lot simpler. I wasn't sure if the regular expression I use to match an error being indicated by the amforth interpreter really covered all cases. That expression (without the delimiting double quotes included below) is: " \?\? -\d+ \d+ \r\n> $" Is it correct? I spent some time looking at the main interpreter trying to verify it but I don't read assembly and forth fluently enough yet to be sure without investing more time than I had available. If the above regular expression will catch all errors explicitly indicated by the interpreter, then I think it would be reasonable to change the default behavior to *not* generate an error on output as it *will still generate an error* if the error regular expression matches. This won't cause an upload of a tester.frt based test file to fail on the first test that has an error which I really want for the class in which we're using this, but I can easily get that behavior by adding the comment at the top of the file for those cases. Matthias> I collect the changes in the repository at Matthias> http://amforth.svn.sourceforge.net/viewvc/amforth/trunk/tools/amforth-term.py?view=log Wonderful. I don't really see any need for this to live outside the amforth repository, so if I get a chance to make further improvements I'll prepare patches against the code there. --- Keith |
From: Matthias T. <mt...@we...> - 2012-07-05 17:56:43
|
> > Actually, my motivation was a lot simpler. I wasn't sure if the regular > expression I use to match an error being indicated by the amforth > interpreter really covered all cases. That expression (without the > delimiting double quotes included below) is: > > " \?\? -\d+ \d+ \r\n> $" > > Is it correct? Yes it is. > I spent some time looking at the main interpreter trying > to verify it but I don't read assembly and forth fluently enough yet to > be sure without investing more time than I had available. It a bit difficult indeed. The messages get printed by the words in the prompts.asm file, coordinated in quit.asm. The first " ?? " comes from PROMPTERROR which prints the two numbers too. After that quit calls the PROMPTRDY which generates the line feed and the "> ". The two numbers are printed using . (DOT) which makes it possible to get both positive and negative numbers. The not-so-obvious code in quit makes sure, that the first number is always negative and the second number is always positive. (THROW Code resp the value of >IN). That means that a THROW with a positive code number does not print anything, THROWs with a negative number trigger the error prompt as described above. The second consequence is that the size of the input buffer into which >IN points should never exceed 32KB. > If the above regular expression will catch all errors explicitly > indicated by the interpreter, then I think it would be reasonable to > change the default behavior to *not* generate an error on output as it > *will still generate an error* if the error regular expression matches. As long as nobody changes QUIT and the prompt words, the regex will work. > Matthias> http://amforth.svn.sourceforge.net/viewvc/amforth/trunk/tools/amforth-term.py?view=log > > Wonderful. I don't really see any need for this to live outside the > amforth repository, so if I get a chance to make further improvements > I'll prepare patches against the code there. Great. Matthias |
From: Erich W. <ew....@na...> - 2012-07-08 17:52:33
|
Hello Keith! On 07/05/2012 06:29 PM, Keith Amidon wrote: > > Matthias> I collect the changes in the repository at > Matthias> http://amforth.svn.sourceforge.net/viewvc/amforth/trunk/tools/amforth-term.py?view=log > I finally got around to try out the new upload tool. The result is nothing short of spectacular! Ctrl-D to exit the thing! Cool! Nostalgia, sort of, I know :-) Command history! Bash for controllers, sort of :-))) This tool is going to be very useful for the class I give once in a while. Thank you for writing and publishing! -- I found a minor thing that puzzles me: |I=mcudef |I=using device.py for atmega32 |F=/home/ew/Forth/atmega/46_trunk-2/lib/bitnames.frt |C| 1|\ V 1.3 02.11.2007 |W| 2| |C| 3|\ Code: Matthias Trute |C| 4|\ Text: M.Kalus |W| 5| |C| 6|\ A named port pin puts a bitmask on stack, wherin the set bit indicates which |C| 7|\ bit of the port register corresponds to the pin. |C| 8|\ And then puts the address of its port on stack too. |W| 9| |C| 10|\ Use it this way: |C| 11|\ PORTD 7 portpin: PD.7 ( define portD pin #7) |E| 12|\ PD.7 high ( turn portD pin #7 on, i.e. set it high-level) |E=Illegal nested comment make: *** [marker] Error 1 Illegal nested comment? Not quite. "\ " starts a comment, which extends to the end of line, no questions asked. This is probably simple to fix, but I have not yet looked into the code. Cheers, Erich |
From: Matthias T. <mt...@we...> - 2012-07-08 18:04:33
|
Hi all, >> >> Matthias> I collect the changes in the repository at >> Matthias> http://amforth.svn.sourceforge.net/viewvc/amforth/trunk/tools/amforth-term.py?view=log Just for the record: I renamed the tool to amforth-shell, You'll find it here: http://amforth.svn.sourceforge.net/viewvc/amforth/trunk/tools/amforth-shell.py?view=log Matthias PS: Funny, that no-one uses a PC Forth (gforth) for such tools ;) |
From: Erich W. <ew....@na...> - 2012-07-08 18:01:12
|
More info below ... > I found a minor thing that puzzles me: > > |I=mcudef > |I=using device.py for atmega32 > |F=/home/ew/Forth/atmega/46_trunk-2/lib/bitnames.frt > |C| 1|\ V 1.3 02.11.2007 > |W| 2| > |C| 3|\ Code: Matthias Trute > |C| 4|\ Text: M.Kalus > |W| 5| > |C| 6|\ A named port pin puts a bitmask on stack, wherin the set > bit indicates which > |C| 7|\ bit of the port register corresponds to the pin. > |C| 8|\ And then puts the address of its port on stack too. > |W| 9| > |C| 10|\ Use it this way: > |C| 11|\ PORTD 7 portpin: PD.7 ( define portD pin #7) > |E| 12|\ PD.7 high ( turn portD pin #7 on, i.e. set it high-level) > |E=Illegal nested comment > make: *** [marker] Error 1 This can be worked around by adding a space before the closing ')'. ... |C| 10|\ Use it this way: |C| 11|\ PORTD 7 portpin: PD.7 ( define portD pin #7 ) |C| 12|\ PD.7 high ( turn portD pin #7 on, i.e. set it high-level ) ... > Illegal nested comment? Not quite. "\ " starts a comment, which > extends to the end of line, no questions asked. This is probably > simple to fix, but I have not yet looked into the code. Erich |
From: Keith A. <ca...@pi...> - 2012-07-09 01:06:05
|
{-- Sun, 08 Jul 2012 20:01:02 +0200: Erich <ew....@na...> wrote: --} Erich> More info below ... >> I found a minor thing that puzzles me: >> |C| 11|\ PORTD 7 portpin: PD.7 ( define portD pin #7) >> |E| 12|\ PD.7 high ( turn portD pin #7 on, i.e. set it high-level) >> |E=Illegal nested comment >> make: *** [marker] Error 1 Erich> This can be worked around by adding a space before the Erich> closing ')'. Erich> ... Erich> |C| 10|\ Use it this way: Erich> |C| 11|\ PORTD 7 portpin: PD.7 ( define portD pin #7 ) Erich> |C| 12|\ PD.7 high ( turn portD pin #7 on, i.e. set it high-level ) Ah, yes, I forgot I didn't take care of that. You may notice that the line starts with |C|. To reduce the amount of data sent to the microcontroller the script detects lines that are all comments or whitespace and doesn't send them. When I originally wrote this support I was under the mistaken impression that comments delimited by parenthesis had the following characteristics: 1: the start parenthesis had to be surrounded by whitespace (true I believe) 2: the end parenthesis had to be surrounded by whitespace (false I believe) 3: these comments could span multiple lines, similar to the way that C "/* */" comments can (also false I believe) I forgot to go clean this up when I discovered the problem. I'll add that to my list of things to try to get to when I get a chance to work on the script. Alas, it doesn't look like that is going to happen this weekend as I had hoped after all. --- Keith |
From: Erich W. <ew....@na...> - 2012-07-09 19:26:25
|
Hello Keith, On 07/09/2012 03:05 AM, Keith Amidon wrote: > {-- Sun, 08 Jul 2012 20:01:02 +0200: Erich<ew....@na...> wrote: --} > > Erich> More info below ... > >> I found a minor thing that puzzles me: > >> |C| 11|\ PORTD 7 portpin: PD.7 ( define portD pin #7) > >> |E| 12|\ PD.7 high ( turn portD pin #7 on, i.e. set it high-level) > >> |E=Illegal nested comment > >> make: *** [marker] Error 1 > > Erich> This can be worked around by adding a space before the > Erich> closing ')'. > Erich> ... > Erich> |C| 10|\ Use it this way: > Erich> |C| 11|\ PORTD 7 portpin: PD.7 ( define portD pin #7 ) > Erich> |C| 12|\ PD.7 high ( turn portD pin #7 on, i.e. set it high-level ) > > Ah, yes, I forgot I didn't take care of that. You may notice that the > line starts with |C|. To reduce the amount of data sent to the > microcontroller the script detects lines that are all comments or > whitespace and doesn't send them. > > When I originally wrote this support I was under the mistaken impression > that comments delimited by parenthesis had the following > characteristics: > > 1: the start parenthesis had to be surrounded by whitespace (true I > believe) True. > 2: the end parenthesis had to be surrounded by whitespace (false I > believe) Looking at the code: XT_CSCAN looks for character 'c' (from stack) in the input stream. " ( " is in lparenthesis.asm and looks for the next closing paren ')'. so: False. > 3: these comments could span multiple lines, similar to the way that > C "/* */" comments can (also false I believe) False. cscan will look in the current input buffer. gforth barks as well when trying to break comment over more lines. > > I forgot to go clean this up when I discovered the problem. I'll add > that to my list of things to try to get to when I get a chance to work > on the script. Alas, it doesn't look like that is going to happen this > weekend as I had hoped after all. No sweat! Folks start using your tool in unexpected ways, that's all :-) Cheers, Erich |