views:

147

answers:

2

Whitespace Assembler

#! /usr/bin/env python
"""Assembler.py

Compiles a program from "Assembly" folder into "Program" folder.
Can be executed directly by double-click or on the command line.
Give name of *.WSA file without extension (example: stack_calc)."""

################################################################################

__author__ = 'Stephen "Zero" Chappell <[email protected]>'
__date__ = '14 March 2010'
__version__ = '$Revision: 3 $'

################################################################################

import string
from Interpreter import INS, MNEMONIC

################################################################################

def parse(code):
    program = []
    process_virtual(program, code)
    process_control(program)
    return tuple(program)

def process_virtual(program, code):
    for line, text in enumerate(code.split('\n')):
        if not text or text[0] == '#':
            continue
        if text.startswith('part '):
            parse_part(program, line, text[5:])
        elif text.startswith('     '):
            parse_code(program, line, text[5:])
        else:
            syntax_error(line)

def syntax_error(line):
    raise SyntaxError('Line ' + str(line + 1))

################################################################################

def process_control(program):
    parts = get_parts(program)
    names = dict(pair for pair in zip(parts, generate_index()))
    correct_control(program, names)

def get_parts(program):
    parts = []
    for ins in program:
        if isinstance(ins, tuple):
            ins, arg = ins
            if ins == INS.PART:
                if arg in parts:
                    raise NameError('Part definition was found twice: ' + arg)
                parts.append(arg)
    return parts

def generate_index():
    index = 1
    while True:
        yield index
        index *= -1
        if index > 0:
            index += 1

def correct_control(program, names):
    for index, ins in enumerate(program):
        if isinstance(ins, tuple):
            ins, arg = ins
            if ins in HAS_LABEL:
                if arg not in names:
                    raise NameError('Part definition was never found: ' + arg)
                program[index] = (ins, names[arg])

################################################################################

def parse_part(program, line, text):
    if not valid_label(text):
        syntax_error(line)
    program.append((INS.PART, text))

def valid_label(text):
    if not between_quotes(text):
        return False
    label = text[1:-1]
    if not valid_name(label):
        return False
    return True

def between_quotes(text):
    if len(text) < 3:
        return False
    if text.count('"') != 2:
        return False
    if text[0] != '"' or text[-1] != '"':
        return False
    return True

def valid_name(label):
    valid_characters = string.ascii_letters + string.digits + '_'
    valid_set = frozenset(valid_characters)
    label_set = frozenset(label)
    if len(label_set - valid_set) != 0:
        return False
    return True

################################################################################

from Interpreter import HAS_LABEL, Program

NO_ARGS = Program.NO_ARGS
HAS_ARG = Program.HAS_ARG
TWO_WAY = tuple(set(NO_ARGS) & set(HAS_ARG))

################################################################################

def parse_code(program, line, text):
    for ins, word in enumerate(MNEMONIC):
        if text.startswith(word):
            check_code(program, line, text[len(word):], ins)
            break
    else:
        syntax_error(line)

def check_code(program, line, text, ins):
    if ins in TWO_WAY:
        if text:
            number = parse_number(line, text)
            program.append((ins, number))
        else:
            program.append(ins)
    elif ins in HAS_LABEL:
        text = parse_label(line, text)
        program.append((ins, text))
    elif ins in HAS_ARG:
        number = parse_number(line, text)
        program.append((ins, number))
    elif ins in NO_ARGS:
        if text:
            syntax_error(line)
        program.append(ins)
    else:
        syntax_error(line)


def parse_label(line, text):
    if not text or text[0] != ' ':
        syntax_error(line)
    text = text[1:]
    if not valid_label(text):
        syntax_error(line)
    return text

################################################################################

def parse_number(line, text):
    if not valid_number(text):
        syntax_error(line)
    return int(text)

def valid_number(text):
    if len(text) < 2:
        return False
    if text[0] != ' ':
        return False
    text = text[1:]
    if '+' in text and '-' in text:
        return False
    if '+' in text:
        if text.count('+') != 1:
            return False
        if text[0] != '+':
            return False
        text = text[1:]
        if not text:
            return False
    if '-' in text:
        if text.count('-') != 1:
            return False
        if text[0] != '-':
            return False
        text = text[1:]
        if not text:
            return False
    valid_set = frozenset(string.digits)
    value_set = frozenset(text)
    if len(value_set - valid_set) != 0:
        return False
    return True

################################################################################
################################################################################

from Interpreter import partition_number

VMC_2_TRI = {
    (INS.PUSH, True):  (0, 0),
    (INS.COPY, False): (0, 2, 0),
    (INS.COPY, True):  (0, 1, 0),
    (INS.SWAP, False): (0, 2, 1),
    (INS.AWAY, False): (0, 2, 2),
    (INS.AWAY, True):  (0, 1, 2),
    (INS.ADD, False):  (1, 0, 0, 0),
    (INS.SUB, False):  (1, 0, 0, 1),
    (INS.MUL, False):  (1, 0, 0, 2),
    (INS.DIV, False):  (1, 0, 1, 0),
    (INS.MOD, False):  (1, 0, 1, 1),
    (INS.SET, False):  (1, 1, 0),
    (INS.GET, False):  (1, 1, 1),
    (INS.PART, True):  (2, 0, 0),
    (INS.CALL, True):  (2, 0, 1),
    (INS.GOTO, True):  (2, 0, 2),
    (INS.ZERO, True):  (2, 1, 0),
    (INS.LESS, True):  (2, 1, 1),
    (INS.BACK, False): (2, 1, 2),
    (INS.EXIT, False): (2, 2, 2),
    (INS.OCHR, False): (1, 2, 0, 0),
    (INS.OINT, False): (1, 2, 0, 1),
    (INS.ICHR, False): (1, 2, 1, 0),
    (INS.IINT, False): (1, 2, 1, 1)
    }

################################################################################

def to_trinary(program):
    trinary_code = []
    for ins in program:
        if isinstance(ins, tuple):
            ins, arg = ins
            trinary_code.extend(VMC_2_TRI[(ins, True)])
            trinary_code.extend(from_number(arg))
        else:
            trinary_code.extend(VMC_2_TRI[(ins, False)])
    return tuple(trinary_code)

def from_number(arg):
    code = [int(arg < 0)]
    if arg:
        for bit in reversed(list(partition_number(abs(arg), 2))):
            code.append(bit)
        return code + [2]
    return code + [0, 2]

to_ws = lambda trinary: ''.join(' \t\n'[index] for index in trinary)

def compile_wsa(source):
    program = parse(source)
    trinary = to_trinary(program)
    ws_code = to_ws(trinary)
    return ws_code

################################################################################
################################################################################

import os
import sys
import time
import traceback

def main():
    name, source, command_line, error = get_source()
    if not error:
        start = time.clock()
        try:
            ws_code = compile_wsa(source)
        except:
            print('ERROR: File could not be compiled.\n')
            traceback.print_exc()
            error = True
        else:
            path = os.path.join('Programs', name + '.ws')
            try:
                open(path, 'w').write(ws_code)
            except IOError as err:
                print(err)
                error = True
            else:
                div, mod = divmod((time.clock() - start) * 1000, 1)
                args = int(div), '{:.3}'.format(mod)[1:]
                print('DONE: Comipled in {}{} ms'.format(*args))
    handle_close(error, command_line)

def get_source():
    if len(sys.argv) > 1:
        command_line = True
        name = sys.argv[1]
    else:
        command_line = False
        try:
            name = input('Source File: ')
        except:
            return None, None, False, True
    print()
    path = os.path.join('Assembly', name + '.wsa')
    try:
        return name, open(path).read(), command_line, False
    except IOError as err:
        print(err)
        return None, None, command_line, True

def handle_close(error, command_line):
    if error:
        usage = 'Usage: {} <assembly>'.format(os.path.basename(sys.argv[0]))
        print('\n{}\n{}'.format('-' * len(usage), usage))
    if not command_line:
        time.sleep(10)

################################################################################

if __name__ == '__main__':
    main()

Whitespace Helpers

#! /usr/bin/env python
"""Helpers.py

Includes a function to encode Python strings into my WSA format.
Has a "PRINT_LINE" function that can be copied to a WSA program.
Contains a "PRINT" function and documentation as an explanation."""

################################################################################

__author__ = 'Stephen "Zero" Chappell <[email protected]>'
__date__ = '14 March 2010'
__version__ = '$Revision: 1 $'

################################################################################

def encode_string(string, addr):
    print('     push', addr)
    print('     push', len(string))
    print('     set')
    addr += 1
    for offset, character in enumerate(string):
        print('     push', addr + offset)
        print('     push', ord(character))
        print('     set')

################################################################################

# Prints a string with newline.
# push addr
# call "PRINT_LINE"

"""
part "PRINT_LINE"
     call "PRINT"
     push 10
     ochr
     back
"""

################################################################################

# def print(array):
#     if len(array) <= 0:
#         return
#     offset = 1
#     while len(array) - offset >= 0:
#          ptr = array.ptr + offset
#          putch(array[ptr])
#          offset += 1

"""
part "PRINT"
# Line 1-2
     copy
     get
     less "__PRINT_RET_1"
     copy
     get
     zero "__PRINT_RET_1"
# Line 3
     push 1
# Line 4
part "__PRINT_LOOP"
     copy
     copy 2
     get
     swap
     sub
     less "__PRINT_RET_2"
# Line 5
     copy 1
     copy 1
     add
# Line 6
     get
     ochr
# Line 7
     push 1
     add
     goto "__PRINT_LOOP"
part "__PRINT_RET_2"
     away
part "__PRINT_RET_1"
     away
     back
"""

Whitespace Interpreter

#! /usr/bin/env python
"""Interpreter.py

Runs programs in "Programs" and creates *.WSO files when needed.
Can be executed directly by double-click or on the command line.
If run on command line, add "ASM" flag to dump program assembly."""

################################################################################

__author__ = 'Stephen "Zero" Chappell <[email protected]>'
__date__ = '14 March 2010'
__version__ = '$Revision: 4 $'

################################################################################

def test_file(path):
    disassemble(parse(trinary(load(path))), True)

################################################################################

load = lambda ws: ''.join(c for r in open(ws) for c in r if c in ' \t\n')

trinary = lambda ws: tuple(' \t\n'.index(c) for c in ws)

################################################################################

def enum(names):
    names = names.replace(',', ' ').split()
    space = dict((reversed(pair) for pair in enumerate(names)), __slots__=())
    return type('enum', (object,), space)()

INS = enum('''\
PUSH, COPY, SWAP, AWAY, \
ADD, SUB, MUL, DIV, MOD, \
SET, GET, \
PART, CALL, GOTO, ZERO, LESS, BACK, EXIT, \
OCHR, OINT, ICHR, IINT''')

################################################################################

def parse(code):
    ins = iter(code).__next__
    program = []
    while True:
        try:
            imp = ins()
        except StopIteration:
            return tuple(program)
        if imp == 0:
            # [Space]
            parse_stack(ins, program)
        elif imp == 1:
            # [Tab]
            imp = ins()
            if imp == 0:
                # [Tab][Space]
                parse_math(ins, program)
            elif imp == 1:
                # [Tab][Tab]
                parse_heap(ins, program)
            else:
                # [Tab][Line]
                parse_io(ins, program)
        else:
            # [Line]
            parse_flow(ins, program)

def parse_number(ins):
    sign = ins()
    if sign == 2:
        raise StopIteration()
    buffer = ''
    code = ins()
    if code == 2:
        raise StopIteration()
    while code != 2:
        buffer += str(code)
        code = ins()
    if sign == 1:
        return int(buffer, 2) * -1
    return int(buffer, 2)

################################################################################

def parse_stack(ins, program):
    code = ins()
    if code == 0:
        # [Space]
        number = parse_number(ins)
        program.append((INS.PUSH, number))
    elif code == 1:
        # [Tab]
        code = ins()
        number = parse_number(ins)
        if code == 0:
            # [Tab][Space]
            program.append((INS.COPY, number))
        elif code == 1:
            # [Tab][Tab]
            raise StopIteration()
        else:
            # [Tab][Line]
            program.append((INS.AWAY, number))
    else:
        # [Line]
        code = ins()
        if code == 0:
            # [Line][Space]
            program.append(INS.COPY)
        elif code == 1:
            # [Line][Tab]
            program.append(INS.SWAP)
        else:
            # [Line][Line]
            program.append(INS.AWAY)

def parse_math(ins, program):
    code = ins()
    if code == 0:
        # [Space]
        code = ins()
        if code == 0:
            # [Space][Space]
            program.append(INS.ADD)
        elif code == 1:
            # [Space][Tab]
            program.append(INS.SUB)
        else:
            # [Space][Line]
            program.append(INS.MUL)
    elif code == 1:
        # [Tab]
        code = ins()
        if code == 0:
            # [Tab][Space]
            program.append(INS.DIV)
        elif code == 1:
            # [Tab][Tab]
            program.append(INS.MOD)
        else:
            # [Tab][Line]
            raise StopIteration()
    else:
        # [Line]
        raise StopIteration()

def parse_heap(ins, program):
    code = ins()
    if code == 0:
        # [Space]
        program.append(INS.SET)
    elif code == 1:
        # [Tab]
        program.append(INS.GET)
    else:
        # [Line]
        raise StopIteration()

def parse_io(ins, program):
    code = ins()
    if code == 0:
        # [Space]
        code = ins()
        if code == 0:
            # [Space][Space]
            program.append(INS.OCHR)
        elif code == 1:
            # [Space][Tab]
            program.append(INS.OINT)
        else:
            # [Space][Line]
            raise StopIteration()
    elif code == 1:
        # [Tab]
        code = ins()
        if code == 0:
            # [Tab][Space]
            program.append(INS.ICHR)
        elif code == 1:
            # [Tab][Tab]
            program.append(INS.IINT)
        else:
            # [Tab][Line]
            raise StopIteration()
    else:
        # [Line]
        raise StopIteration()

def parse_flow(ins, program):
    code = ins()
    if code == 0:
        # [Space]
        code = ins()
        label = parse_number(ins)
        if code == 0:
            # [Space][Space]
            program.append((INS.PART, label))
        elif code == 1:
            # [Space][Tab]
            program.append((INS.CALL, label))
        else:
            # [Space][Line]
            program.append((INS.GOTO, label))
    elif code == 1:
        # [Tab]
        code = ins()
        if code == 0:
            # [Tab][Space]
            label = parse_number(ins)
            program.append((INS.ZERO, label))
        elif code == 1:
            # [Tab][Tab]
            label = parse_number(ins)
            program.append((INS.LESS, label))
        else:
            # [Tab][Line]
            program.append(INS.BACK)
    else:
        # [Line]
        code = ins()
        if code == 2:
            # [Line][Line]
            program.append(INS.EXIT)
        else:
            # [Line][Space] or [Line][Tab]
            raise StopIteration()

################################################################################

MNEMONIC = '\
push copy swap away add sub mul div mod set get part \
call goto zero less back exit ochr oint ichr iint'.split()

HAS_ARG = [getattr(INS, name) for name in
           'PUSH COPY AWAY PART CALL GOTO ZERO LESS'.split()]

HAS_LABEL = [getattr(INS, name) for name in
             'PART CALL GOTO ZERO LESS'.split()]

def disassemble(program, names=False):
    if names:
        names = create_names(program)
    for ins in program:
        if isinstance(ins, tuple):
            ins, arg = ins
            assert ins in HAS_ARG
            has_arg = True
        else:
            assert INS.PUSH <= ins <= INS.IINT
            has_arg = False
        if ins == INS.PART:
            if names:
                print(MNEMONIC[ins], '"' + names[arg] + '"')
            else:
                print(MNEMONIC[ins], arg)
        elif has_arg and ins in HAS_ARG:
            if ins in HAS_LABEL and names:
                assert arg in names
                print('     ' + MNEMONIC[ins], '"' + names[arg] + '"')
            else:
                print('     ' + MNEMONIC[ins], arg)
        else:
            print('     ' + MNEMONIC[ins])

################################################################################

def create_names(program):
    names = {}
    number = 1
    for ins in program:
        if isinstance(ins, tuple) and ins[0] == INS.PART:
            label = ins[1]
            assert label not in names
            names[label] = number_to_name(number)
            number += 1
    return names

def number_to_name(number):
    name = ''
    for offset in reversed(list(partition_number(number, 27))):
        if offset:
            name += chr(ord('A') + offset - 1)
        else:
            name += '_'
    return name

def partition_number(number, base):
    div, mod = divmod(number, base)
    yield mod
    while div:
        div, mod = divmod(div, base)
        yield mod

################################################################################

CODE = ('   \t\n',
        ' \n ',
        ' \t  \t\n',
        ' \n\t',
        ' \n\n',
        ' \t\n \t\n',
        '\t   ',
        '\t  \t',
        '\t  \n',
        '\t \t ',
        '\t \t\t',
        '\t\t ',
        '\t\t\t',
        '\n   \t\n',
        '\n \t \t\n',
        '\n \n \t\n',
        '\n\t  \t\n',
        '\n\t\t \t\n',
        '\n\t\n',
        '\n\n\n',
        '\t\n  ',
        '\t\n \t',
        '\t\n\t ',
        '\t\n\t\t')

EXAMPLE = ''.join(CODE)

################################################################################

NOTES = '''\
STACK
=====
  push number
  copy
  copy number
  swap
  away
  away number

MATH
====
  add
  sub
  mul
  div
  mod

HEAP
====
  set
  get

FLOW
====
  part label
  call label
  goto label
  zero label
  less label
  back
  exit

I/O
===
  ochr
  oint
  ichr
  iint'''

################################################################################
################################################################################

class Stack:

    def __init__(self):
        self.__data = []

    # Stack Operators

    def push(self, number):
        self.__data.append(number)

    def copy(self, number=None):
        if number is None:
            self.__data.append(self.__data[-1])
        else:
            size = len(self.__data)
            index = size - number - 1
            assert 0 <= index < size
            self.__data.append(self.__data[index])

    def swap(self):
        self.__data[-2], self.__data[-1] = self.__data[-1], self.__data[-2]

    def away(self, number=None):
        if number is None:
            self.__data.pop()
        else:
            size = len(self.__data)
            index = size - number - 1
            assert 0 <= index < size
            del self.__data[index:-1]

    # Math Operators

    def add(self):
        suffix = self.__data.pop()
        prefix = self.__data.pop()
        self.__data.append(prefix + suffix)

    def sub(self):
        suffix = self.__data.pop()
        prefix = self.__data.pop()
        self.__data.append(prefix - suffix)

    def mul(self):
        suffix = self.__data.pop()
        prefix = self.__data.pop()
        self.__data.append(prefix * suffix)

    def div(self):
        suffix = self.__data.pop()
        prefix = self.__data.pop()
        self.__data.append(prefix // suffix)

    def mod(self):
        suffix = self.__data.pop()
        prefix = self.__data.pop()
        self.__data.append(prefix % suffix)

    # Program Operator

    def pop(self):
        return self.__data.pop()

################################################################################

class Heap:

    def __init__(self):
        self.__data = {}

    def set_(self, addr, item):
        if item:
            self.__data[addr] = item
        elif addr in self.__data:
            del self.__data[addr]

    def get_(self, addr):
        return self.__data.get(addr, 0)

################################################################################

import os
import zlib
import msvcrt
import pickle
import string

class CleanExit(Exception): pass

NOP = lambda arg: None

DEBUG_WHITESPACE = False

################################################################################

class Program:

    NO_ARGS = INS.COPY, INS.SWAP, INS.AWAY, INS.ADD, \
              INS.SUB, INS.MUL, INS.DIV, INS.MOD, \
              INS.SET, INS.GET, INS.BACK, INS.EXIT, \
              INS.OCHR, INS.OINT, INS.ICHR, INS.IINT
    HAS_ARG = INS.PUSH, INS.COPY, INS.AWAY, INS.PART, \
              INS.CALL, INS.GOTO, INS.ZERO, INS.LESS

    def __init__(self, code):
        self.__data = code
        self.__validate()
        self.__build_jump()
        self.__check_jump()
        self.__setup_exec()

    def __setup_exec(self):
        self.__iptr = 0
        self.__stck = stack = Stack()
        self.__heap = Heap()
        self.__cast = []
        self.__meth = (stack.push, stack.copy, stack.swap, stack.away,
                       stack.add, stack.sub, stack.mul, stack.div, stack.mod,
                       self.__set, self.__get,
                       NOP, self.__call, self.__goto, self.__zero,
                            self.__less, self.__back, self.__exit,
                       self.__ochr, self.__oint, self.__ichr, self.__iint)

    def step(self):
        ins = self.__data[self.__iptr]
        self.__iptr += 1
        if isinstance(ins, tuple):
            self.__meth[ins[0]](ins[1])
        else:
            self.__meth[ins]()

    def run(self):
        while True:
            ins = self.__data[self.__iptr]
            self.__iptr += 1
            if isinstance(ins, tuple):
                self.__meth[ins[0]](ins[1])
            else:
                self.__meth[ins]()

    def __oint(self):
        for digit in str(self.__stck.pop()):
            msvcrt.putwch(digit)

    def __ichr(self):
        addr = self.__stck.pop()
        # Input Routine
        while msvcrt.kbhit():
            msvcrt.getwch()
        while True:
            char = msvcrt.getwch()
            if char in '\x00\xE0':
                msvcrt.getwch()
            elif char in string.printable:
                char = char.replace('\r', '\n')
                msvcrt.putwch(char)
                break
        item = ord(char)
        # Storing Number
        self.__heap.set_(addr, item)

    def __iint(self):
        addr = self.__stck.pop()
        # Input Routine
        while msvcrt.kbhit():
            msvcrt.getwch()
        buff = ''
        char = msvcrt.getwch()
        while char != '\r' or not buff:
            if char in '\x00\xE0':
                msvcrt.getwch()
            elif char in '+-' and not buff:
                msvcrt.putwch(char)
                buff += char
            elif '0' <= char <= '9':
                msvcrt.putwch(char)
                buff += char
            elif char == '\b':
                if buff:
                    buff = buff[:-1]
                    msvcrt.putwch(char)
                    msvcrt.putwch(' ')
                    msvcrt.putwch(char)
            char = msvcrt.getwch()
        msvcrt.putwch(char)
        msvcrt.putwch('\n')
        item = int(buff)
        # Storing Number
        self.__heap.set_(addr, item)

    def __goto(self, label):
        self.__iptr = self.__jump[label]

    def __zero(self, label):
        if self.__stck.pop() == 0:
            self.__iptr = self.__jump[label]

    def __less(self, label):
        if self.__stck.pop() < 0:
            self.__iptr = self.__jump[label]

    def __exit(self):
        self.__setup_exec()
        raise CleanExit()

    def __set(self):
        item = self.__stck.pop()
        addr = self.__stck.po
+4  A: 

There are plenty of venues for substantial open-source projects like these pieces of code taken togther: look at code.google.com, bitbucket.org, launchpad.net, sourceforge, github... seriously, "an embarassment of riches"! And you can make a free blog, etc, to discuss these things in different formats if you wish.

An open source project that's a library of separate modules is just as fine as one that's one single big, integrated package, by the way.

Alex Martelli
That was an excellent answer to the question!
Noctis Skytower
@Noctis, thanks!
Alex Martelli
After looking through the sites you listed, I noticed they all have the same problem: they all use some type of version control system. I am at a college with a very restrictive internet filter, so any VCS is not an option. Do you know of any other alternative web sites?
Noctis Skytower
@Noctis, Subversion at least (supported by code.google.com and sourceforge at least), and probably other VCS, use, as their transport, HTTP; so, if your "very restrictive filter" blocks even *THAT*, it probably blocks any other possible transport you could conceivably use.
Alex Martelli
Tortoise SVN definitely works. Thank you for the advice! The first test worked beautifully: http://code.google.com/p/verse-quiz/
Noctis Skytower
+2  A: 

Depends on your reason for posting. If for assistance, you've come to the right place.

For example, this function:

def parse_number(line, text):
    if not valid_number(text):
        syntax_error(line)
    return int(text)

would be much better expressed as

def parse_number(line_number, text):
    try:
        return int(text)
    except ValueError:
        syntax_error(line_number)

Fortunately this avoids having to trawl through the valid_number function, which should be removed.

Update Analysis of the "valid_number" function shows that the only restriction not enforced by int() is the requirement that a so-called "valid number" must start with a space!

def valid_number(text):
    if len(text) < 2:
        return False
    if text[0] != ' ':
        ### The concept of a "valid number" that MUST start with a space
        ### is preposterous.
        return False
    text = text[1:]
    ### The remainder of the function can be replaced by:
    ###  if text[0] in '-+':
    ###      text = text[1:]
    ###  return text.isdigit()

    if '+' in text and '-' in text:
        ### enforced by int()
        return False
    if '+' in text:
        if text.count('+') != 1:
            ### enforced by int()
            return False
        if text[0] != '+':
            ### enforced by int()
            return False
        text = text[1:]
        if not text:
            ### enforced by int()
            return False
    if '-' in text:
        ### same comments as for '+'
        if text.count('-') != 1:
            return False
        if text[0] != '-':
            return False
        text = text[1:]
        if not text:
            return False
    valid_set = frozenset(string.digits)
    value_set = frozenset(text)
    if len(value_set - valid_set) != 0:
        return False
    return True
    ### Why frozenset instead of set?
    ### Try return value_set.issubset(valid_set)
    ### or return not bool(value_set - valid_set)
    ### Instead of sets, try return text.isdigit()
John Machin
Yeah, "invalid numbers" that don't start with a space! See my update.
John Machin
Unfortunately, calling int(text) would allow invalid numbers. The code for valid_number(text) is far more restrictive. A valid number must start with one and only one space, there may be no trailing spaces, and a period is not allowed in the number. These are requirements of the assembly language I designed for Whitespace and requirements for the proper operation of the parser. The idea behind the parser is that you must use the same style of coding at the interpreter's disassembler. However, the style of the code and any of its errors has nothing to do with the question that was asked.
Noctis Skytower