Some integration with Type Checking.

Now the UI highlights commands and numbers as you move the mouse, numbers
are blue, commands that type-check are green, commands that fail to
type-check are orange and will not be interpreted, and if there is no
stack effect information available for a command it is grey but you can
still attempt to execute it.

You can still evaluate whole expressions by selceting them and
right-inter-clicking before you release the left button, or by putting
the cursor on a line and typing ctrl-enter, which will run the whole
line.  These expressions are NOT (yet) type-checked.
This commit is contained in:
Simon Forman 2018-07-15 11:48:08 -07:00
parent 0292e8a297
commit e169c6aae2
5 changed files with 76 additions and 15 deletions

View File

@ -19,10 +19,10 @@ from joy.utils.stack import stack_to_string
tb = TEXT_BINDINGS.copy() tb = TEXT_BINDINGS.copy()
tb.update({ tb.update({
'<F4>': lambda tv: tv.cut,
'<F3>': lambda tv: tv.copy_selection_to_stack, '<F3>': lambda tv: tv.copy_selection_to_stack,
'<F4>': lambda tv: tv.cut,
# '<F-->': lambda tv: tv.pastecut, # '<F-->': lambda tv: tv.pastecut,
'<F6>': lambda tv: tv.copyto, # '<F6>': lambda tv: tv.copyto,
}) })
defaults = dict(text_bindings=tb, width=80, height=25) defaults = dict(text_bindings=tb, width=80, height=25)

View File

@ -65,6 +65,7 @@ class MouseBindingsMixin:
self.bind("<ButtonRelease-3>", self.B3r) self.bind("<ButtonRelease-3>", self.B3r)
self.bind("<Any-Leave>", self.leave) self.bind("<Any-Leave>", self.leave)
self.bind("<Motion>", self.scan_command)
def B1d(self, event): def B1d(self, event):
'''button one pressed''' '''button one pressed'''
@ -167,6 +168,9 @@ class MouseBindingsMixin:
return "break" return "break"
def scan_command(self, event):
self.update_command_word(event)
def B1r(self, event): def B1r(self, event):
'''button one released''' '''button one released'''
self.B1_DOWN = False self.B1_DOWN = False

View File

@ -164,10 +164,11 @@ class TextViewerWidget(tk.Text, MouseBindingsMixin, SavingMixin):
#These are the config tags for command text when it's highlighted. #These are the config tags for command text when it's highlighted.
command_tags = dict( command_tags = dict(
underline = 1, #underline = 1,
bgstipple = "gray50", #bgstipple = "gray50",
borderwidth = "1", borderwidth = 2,
foreground = "orange" relief=tk.RIDGE,
foreground = "green"
) )
def __init__(self, world, master=None, **kw): def __init__(self, world, master=None, **kw):
@ -196,6 +197,9 @@ class TextViewerWidget(tk.Text, MouseBindingsMixin, SavingMixin):
#Add tag config for command highlighting. #Add tag config for command highlighting.
self.tag_config('command', **self.command_tags) self.tag_config('command', **self.command_tags)
self.tag_config('bzzt', foreground = "orange")
self.tag_config('huh', foreground = "grey")
self.tag_config('number', foreground = "blue")
#Create us a command instance variable #Create us a command instance variable
self.command = '' self.command = ''
@ -246,18 +250,28 @@ class TextViewerWidget(tk.Text, MouseBindingsMixin, SavingMixin):
return return
cmd, b, e = cmd cmd, b, e = cmd
if self.world.has(cmd) or is_numerical(cmd): if is_numerical(cmd):
self.command = cmd extra_tags = 'number',
self.highlight_command( elif self.world.has(cmd):
'%d.%d' % (row, b), check = self.world.check(cmd)
'%d.%d' % (row, e), if check: extra_tags = ()
) elif check is None: extra_tags = 'huh',
else: extra_tags = 'bzzt',
else:
return
self.command = cmd
self.highlight_command(
'%d.%d' % (row, b),
'%d.%d' % (row, e),
*extra_tags)
def highlight_command(self, from_, to): def highlight_command(self, from_, to, *extra_tags):
'''Apply command style from from_ to to.''' '''Apply command style from from_ to to.'''
cmdstart = self.index(from_) cmdstart = self.index(from_)
cmdend = self.index(to) cmdend = self.index(to)
self.tag_add('command', cmdstart, cmdend) self.tag_add('command', cmdstart, cmdend)
for tag in extra_tags:
self.tag_add(tag, cmdstart, cmdend)
def do_command(self, event): def do_command(self, event):
'''Do the currently highlighted command.''' '''Do the currently highlighted command.'''
@ -288,6 +302,9 @@ class TextViewerWidget(tk.Text, MouseBindingsMixin, SavingMixin):
def unhighlight_command(self): def unhighlight_command(self):
'''Remove any command highlighting.''' '''Remove any command highlighting.'''
self.tag_remove('number', 1.0, tk.END)
self.tag_remove('huh', 1.0, tk.END)
self.tag_remove('bzzt', 1.0, tk.END)
self.tag_remove('command', 1.0, tk.END) self.tag_remove('command', 1.0, tk.END)
def set_insertion_point(self, event): def set_insertion_point(self, event):

View File

@ -23,6 +23,7 @@ from inspect import getdoc
from joy.joy import run from joy.joy import run
from joy.parser import Symbol from joy.parser import Symbol
from joy.utils.stack import stack_to_string from joy.utils.stack import stack_to_string
from joy.utils.polytypes import type_check
def is_numerical(s): def is_numerical(s):
@ -40,6 +41,9 @@ class World(object):
self.dictionary = dictionary or {} self.dictionary = dictionary or {}
self.text_widget = text_widget self.text_widget = text_widget
def check(self, name):
return type_check(name, self.stack)
def do_lookup(self, name): def do_lookup(self, name):
if name in self.dictionary: if name in self.dictionary:
self.stack = (Symbol(name), ()), self.stack self.stack = (Symbol(name), ()), self.stack
@ -75,6 +79,10 @@ class World(object):
return self.stack[0] return self.stack[0]
def interpret(self, command): def interpret(self, command):
if len(command.split()) == 1 and not is_numerical(command):
assert self.has(command), repr(command)
if self.check(command) == False: # not in {True, None}:
return
try: try:
self.stack, _, self.dictionary = run( self.stack, _, self.dictionary = run(
command, command,
@ -109,8 +117,14 @@ class StackDisplayWorld(World):
self.relative_STACK_FN = rel_filename self.relative_STACK_FN = rel_filename
def interpret(self, command): def interpret(self, command):
print '\njoy?', command if (
super(StackDisplayWorld, self).interpret(command) is_numerical(command)
or len(command.split()) > 1
or self.has(command)
and self.check(command) in {True, None}
):
print '\njoy?', command
super(StackDisplayWorld, self).interpret(command)
def print_stack(self): def print_stack(self):
print '\n%s <-' % stack_to_string(self.stack) print '\n%s <-' % stack_to_string(self.stack)

View File

@ -9,6 +9,7 @@ and we can introduce a kind of Kleene Star or sequence type that can stand for
an unbounded sequence of other types. an unbounded sequence of other types.
''' '''
import sys
from inspect import stack as inspect_stack from inspect import stack as inspect_stack
from itertools import chain, product from itertools import chain, product
from logging import getLogger from logging import getLogger
@ -343,6 +344,31 @@ def infer(*expression):
return sorted(set(_infer(list_to_stack(expression)))) return sorted(set(_infer(list_to_stack(expression))))
def type_check(name, stack):
'''
Trinary predicate. True if named function type-checks, False if it
fails, None if it's indeterminate (because I haven't entered it into
the FUNCTIONS dict yet.)
'''
try:
func = FUNCTIONS[name]
except KeyError:
return # None, indicating unknown
for fi, fo in infer(func):
try:
U = unify(fi, stack)
except JoyTypeError, e:
#print e
continue
except ValueError, e:
#print >> sys.stderr, name, e, stack
continue
#print U
return True
return False
a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = A a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = A
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 = B b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 = B
n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = N n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = N