From b7c38777568e57ce7185d97d20dcadb9f146b180 Mon Sep 17 00:00:00 2001 From: aarneranta Date: Wed, 24 Mar 2021 09:16:47 +0100 Subject: [PATCH] python examples added --- python/Draw.gf | 47 ++ python/DrawEng.gf | 55 +++ python/DrawFin.gf | 56 +++ python/DrawSwe.gf | 60 +++ python/Makefile | 11 + python/Query.gf | 40 ++ python/QueryEng.gf | 94 ++++ python/QuerySwe.gf | 45 ++ python/README.md | 122 +++++ python/draw.py | 193 ++++++++ python/findrawscript.txt | 4 + python/graphics.py | 1015 ++++++++++++++++++++++++++++++++++++++ python/minidraw.py | 51 ++ python/miniquery.py | 72 +++ python/minitranslator.py | 25 + python/query.py | 151 ++++++ python/translator.py | 68 +++ 17 files changed, 2109 insertions(+) create mode 100644 python/Draw.gf create mode 100644 python/DrawEng.gf create mode 100644 python/DrawFin.gf create mode 100644 python/DrawSwe.gf create mode 100644 python/Makefile create mode 100644 python/Query.gf create mode 100644 python/QueryEng.gf create mode 100644 python/QuerySwe.gf create mode 100644 python/README.md create mode 100644 python/draw.py create mode 100644 python/findrawscript.txt create mode 100644 python/graphics.py create mode 100644 python/minidraw.py create mode 100644 python/miniquery.py create mode 100644 python/minitranslator.py create mode 100644 python/query.py create mode 100644 python/translator.py diff --git a/python/Draw.gf b/python/Draw.gf new file mode 100644 index 0000000..2f30048 --- /dev/null +++ b/python/Draw.gf @@ -0,0 +1,47 @@ +abstract Draw = { + +flags startcat = Command ; + +cat + Command ; + Object ; + ObjectRef ; + Shape ; + Colour ; + Size ; + Place ; + +fun + drawCommand : Object -> Command ; + removeCommand : ObjectRef -> Command ; + moveCommand : ObjectRef -> Place -> Command ; + + shapeObject : Size -> Colour -> Shape -> Object ; + + theObjectRef : Object -> ObjectRef ; + itObjectRef : ObjectRef ; + + circle_Shape : Shape ; + square_Shape : Shape ; + + big_Size : Size ; + small_Size : Size ; + + noSize : Size ; + + green_Colour : Colour ; + red_Colour : Colour ; + blue_Colour : Colour ; + yellow_Colour : Colour ; + + noColour : Colour ; + + upPlace : Place ; + downPlace : Place ; + leftPlace : Place ; + rightPlace : Place ; + midPlace : Place ; + + noPlace : Place ; + +} \ No newline at end of file diff --git a/python/DrawEng.gf b/python/DrawEng.gf new file mode 100644 index 0000000..46d9455 --- /dev/null +++ b/python/DrawEng.gf @@ -0,0 +1,55 @@ +concrete DrawEng of Draw = + + open SyntaxEng, ParadigmsEng, LexiconEng, IrregEng + in { + +lincat + Command = Utt ; + Object = CN ; + ObjectRef = NP ; + Shape = CN ; + Colour = AP ; + Size = AP ; + Place = Adv ; + +lin + drawCommand object = + mkUtt (mkImp (mkVP (mkV2 draw_V) (mkNP a_Det object))) -- draw a circle + | mkUtt (mkNP a_Det object) -- a circle + | mkUtt object -- circle + ; + removeCommand object = + mkUtt (mkImp (mkVP (mkV2 "remove") object)) ; + moveCommand object place = + mkUtt (mkImp (mkVP (mkVP (mkV2 "move") object) place)) ; + + shapeObject size colour shape = mkCN size (mkCN colour shape) ; + + theObjectRef object = mkNP the_Det object ; + itObjectRef = it_NP ; + + circle_Shape = mkCN (mkN "circle") ; + square_Shape = mkCN (mkN "square") ; + + big_Size = mkAP big_A ; + small_Size = mkAP small_A ; + noSize = mkAP (mkA "") ; --- + + green_Colour = mkAP green_A ; + red_Colour = mkAP red_A ; + blue_Colour = mkAP blue_A ; + yellow_Colour = mkAP yellow_A ; + + noColour = mkAP (mkA "") ; --- + + upPlace = pmkAdv "up" ; + downPlace = pmkAdv "down" ; + leftPlace = pmkAdv "left" ; + rightPlace = pmkAdv "right" ; + midPlace = pmkAdv "to the middle" ; + + noPlace = pmkAdv "" ; + +oper + pmkAdv : Str -> Adv = ParadigmsEng.mkAdv ; +} \ No newline at end of file diff --git a/python/DrawFin.gf b/python/DrawFin.gf new file mode 100644 index 0000000..71e40df --- /dev/null +++ b/python/DrawFin.gf @@ -0,0 +1,56 @@ +concrete DrawFin of Draw = + + open SyntaxFin, ParadigmsFin, LexiconFin + in { + +lincat + Command = Utt ; + Object = CN ; + ObjectRef = NP ; + Shape = CN ; + Colour = AP ; + Size = AP ; + Place = Adv ; + +lin + drawCommand object = + mkUtt (mkImp (mkVP (mkV2 "piirtää") (mkNP a_Det object))) -- draw a circle + | mkUtt (mkNP a_Det object) -- a circle + | mkUtt object -- circle + ; + removeCommand object = + mkUtt (mkImp (mkVP (mkV2 "poistaa") object)) ; + moveCommand object place = + mkUtt (mkImp (mkVP (mkVP (mkV2 (mkV "siirtää") partitive) object) place)) ; + + shapeObject size colour shape = mkCN size (mkCN colour shape) ; + + theObjectRef object = mkNP the_Det object ; + itObjectRef = it_NP ; + + circle_Shape = mkCN (mkN "ympyrä" "ympyröitä") ; + square_Shape = mkCN (mkN "neliö" "neliöitä") ; + + big_Size = mkAP big_A ; + small_Size = mkAP small_A ; + noSize = mkAP (invarA "") ; --- + + green_Colour = mkAP green_A ; + red_Colour = mkAP red_A ; + blue_Colour = mkAP blue_A ; + yellow_Colour = mkAP yellow_A ; + + noColour = mkAP (invarA "") ; --- + + upPlace = pmkAdv "ylöspäin" ; + downPlace = pmkAdv "alaspäin" ; + leftPlace = pmkAdv "vasemmalle" ; + rightPlace = pmkAdv "oikealle" ; + midPlace = pmkAdv "keskelle" ; + + noPlace = pmkAdv "" ; + +oper + pmkAdv : Str -> Adv = ParadigmsFin.mkAdv ; + +} \ No newline at end of file diff --git a/python/DrawSwe.gf b/python/DrawSwe.gf new file mode 100644 index 0000000..c172d53 --- /dev/null +++ b/python/DrawSwe.gf @@ -0,0 +1,60 @@ +concrete DrawSwe of Draw = + + open SyntaxSwe, ParadigmsSwe, LexiconSwe, IrregSwe, Prelude, + (G = GrammarSwe) + in { + +lincat + Command = Utt ; + Object = CN ; + ObjectRef = NP ; + Shape = CN ; + Colour = AP ** {isAdj : Bool} ; + Size = AP ** {isAdj : Bool} ; + Place = Adv ; + +lin + drawCommand object = + mkUtt (mkImp (mkVP (mkV2 "rita") (mkNP a_Det object))) -- draw a circle + | mkUtt (mkNP a_Det object) -- a circle + | mkUtt object -- circle + ; + removeCommand object = + mkUtt (mkImp (mkVP (mkV2 (mkV (mkV "ta") "bort")) object)) ; + moveCommand object place = + mkUtt (mkImp (mkVP (mkVP (mkV2 "flytta") object) place)) ; + + shapeObject size colour shape = + G.AdjCN size (G.AdjCN colour shape ** {isMod = colour.isAdj}) + ** {isMod = orB size.isAdj colour.isAdj} ; + + theObjectRef object = mkNP the_Det object ; + itObjectRef = mkNP (mkPN "den") ; --- it_NP = det ; + + circle_Shape = mkCN (mkN "cirkel" "cirkeln" "cirklar" "cirklarna") ; + square_Shape = mkCN (mkN "kvadrat" "kvadrater") ; + + big_Size = mkrAP big_A ; + small_Size = mkrAP small_A ; + noSize = emptyAP ; + + green_Colour = mkrAP green_A ; + red_Colour = mkrAP red_A ; + blue_Colour = mkrAP blue_A ; + yellow_Colour = mkrAP yellow_A ; + + noColour = emptyAP ; + + upPlace = pmkAdv "upp" ; + downPlace = pmkAdv "ner" ; + leftPlace = pmkAdv "åt vänster" ; + rightPlace = pmkAdv "åt höger" ; + midPlace = pmkAdv "till mitten" ; + + noPlace = pmkAdv "" ; +oper + emptyAP : AP ** {isAdj : Bool} = mkAP (invarA "") ** {isAdj = False} ; --- + mkrAP : A -> AP ** {isAdj : Bool} = \a -> mkAP a ** {isAdj = True} ; + pmkAdv : Str -> Adv = ParadigmsSwe.mkAdv ; +} + diff --git a/python/Makefile b/python/Makefile new file mode 100644 index 0000000..202ea33 --- /dev/null +++ b/python/Makefile @@ -0,0 +1,11 @@ +minilang: + gf -make ../lab2/grammar/english/MiniLangEng.gf ../lab2/grammar/swedish/MiniLangSwe.gf + +query: + gf -make QueryEng.gf QuerySwe.gf + +draw: + gf -make DrawEng.gf DrawSwe.gf DrawFin.gf + + + diff --git a/python/Query.gf b/python/Query.gf new file mode 100644 index 0000000..583070d --- /dev/null +++ b/python/Query.gf @@ -0,0 +1,40 @@ +abstract Query = { + +flags startcat = Query ; + +cat + Query ; + Kind ; + Property ; + Term ; + Element ; + +fun + QWhich : Kind -> Property -> Query ; -- which numbers are prime + QWhether : Term -> Property -> Query ; -- is any number prime + QWhat : Element -> Query ; -- what is the factorial of 10 + + TAll : Kind -> Term ; -- all numbers + TAny : Kind -> Term ; -- any number + TElement : Element -> Term ; -- 42 + + PAnd : Property -> Property -> Property ; -- even and prime + POr : Property -> Property -> Property ; -- even or odd + PNot : Property -> Property ; -- not prime + + KProperty : Property -> Kind -> Kind ; -- even number + +-- lexicon + + KNumber : Kind ; + + EInteger : Int -> Element ; + PEven, POdd, PPrime : Property ; + PDivisible : Term -> Property ; + PSmaller, PGreater, PEqual : Term -> Property ; + PBetween : Term -> Term -> Property ; + + EFactorial : Element -> Element ; + ESum, EProduct, EMinus, EDivided : Element -> Element -> Element ; + +} \ No newline at end of file diff --git a/python/QueryEng.gf b/python/QueryEng.gf new file mode 100644 index 0000000..d0b2c82 --- /dev/null +++ b/python/QueryEng.gf @@ -0,0 +1,94 @@ +concrete QueryEng of Query = + +open + SyntaxEng, ParadigmsEng, + SymbolicEng, + MarkupEng, + (M = MakeStructuralEng) +in { + +lincat + Query = Utt ; + Kind = CN ; + Property = AP ; + Term = NP ; + Element = NP ; + +lin + QWhich kind property = + mkUtt (mkQS (mkQCl (mkIP whichPl_IDet kind) property)) -- which numbers are prime + | mkUtt (mkImp (mkVP (mkV2 "list") (mkNP all_Predet (mkNP aPl_Det (mkCN property kind))))) -- list all prime numbers + | mkUtt (mkNP aPl_Det (mkCN property kind)) -- prime numbers + ; + + QWhether term property = + mkUtt (mkQS (mkQCl (mkCl term property))) -- is 51 prime + | mkUtt (mkQS (mkCl term property)) -- 51 is prime + ; + + QWhat element = + mkUtt (mkQS (mkQCl what_IP element)) -- what is 2 + 2 + | mkUtt (mkImp (mkVP (mkV2 "compute") element)) -- compute 2 + 2 + | mkUtt element -- 2 + 2 + ; + + TAll kind = + mkNP all_Predet (mkNP aPl_Det kind) -- all numbers + | mkNP every_Det kind -- every number + ; + + TAny kind = + mkNP someSg_Det kind -- some number + | mkNP somePl_Det kind -- some numbers + | mkNP (M.mkDet "any" singular) kind -- any number + | mkNP (M.mkDet "any" plural) kind -- any numbers + ; + + TElement element = element ; + + PAnd p q = mkAP and_Conj p q ; + POr p q = mkAP or_Conj p q ; + PNot p = mkAP (lin AdA {s = "not"}) p ; + + KProperty property kind = mkCN property kind ; + +-- lexicon + + KNumber = mkCN (mkN "number") ; + EInteger i = symb i ; + PEven = mkAP (mkA "even") ; + POdd = mkAP (mkA "odd") ; + PPrime = mkAP (mkA "prime") ; + PDivisible term = mkAP (mkA2 (mkA "divisible") by8means_Prep) term ; + PSmaller term = mkAP (mkA "small") term ; + PGreater term = mkAP (mkA "great") term ; + PEqual term = mkAP (mkA2 (mkA "equal") to_Prep) term ; + PBetween x y = mkAP (mkA2 (mkA "between") (mkPrep "")) (mkNP and_Conj x y) ; --- + + ESum x y = + mkNP the_Det (mkCN (mkN2 (mkN "sum")) (mkNP and_Conj x y)) -- the sum of x and y + | mkNP (mkConj "+" singular) x y -- x + y + | parenthNP (mkNP (mkConj "+" singular) x y) -- ( x + y ) + ; + EMinus x y = + mkNP the_Det (mkCN (mkN2 (mkN "difference")) (mkNP and_Conj x y)) -- the difference of x and y + | mkNP (mkConj "-" singular) x y -- x - y + | parenthNP (mkNP (mkConj "-" singular) x y) -- x - y + ; + EProduct x y = + mkNP the_Det (mkCN (mkN2 (mkN "product")) (mkNP and_Conj x y)) -- the product of x and y + | mkNP (mkConj "*" singular) x y -- x * y + ; + EDivided x y = + mkNP (mkConj "divided by" singular) x y -- the product of x and y + | mkNP (mkConj "/" singular) x y -- x * y + ; + EFactorial x = + mkNP the_Det (mkCN (mkN2 (mkN "factorial")) x) -- the factorial of x + | mkNP x (ParadigmsEng.mkAdv "!") -- x ! + ; + +oper + parenthNP : NP -> NP = \np -> MarkupNP (lin Mark {begin = "(" ; end = ")"}) np ; + +} \ No newline at end of file diff --git a/python/QuerySwe.gf b/python/QuerySwe.gf new file mode 100644 index 0000000..2ffe485 --- /dev/null +++ b/python/QuerySwe.gf @@ -0,0 +1,45 @@ +concrete QuerySwe of Query = + +open + SyntaxSwe, ParadigmsSwe, SymbolicSwe, (L = LexiconSwe) +in { + +lincat + Query = Utt ; + Kind = CN ; + Property = AP ; + Term = NP ; + Element = NP ; + +lin + QWhich kind property = mkUtt (mkQS (mkQCl (mkIP whichPl_IDet kind) property)) ; + QWhether term property = mkUtt (mkQS (mkQCl (mkCl term property))) ; + QWhat element = mkUtt (mkQS (mkQCl what_IP element)) ; + + TAll kind = mkNP all_Predet (mkNP aPl_Det kind) ; + TAny kind = mkNP someSg_Det kind ; + TElement element = element ; + + PAnd p q = mkAP and_Conj p q ; + POr p q = mkAP or_Conj p q ; + PNot p = mkAP (lin AdA {s = "inte"}) p ; --- + + KProperty property kind = mkCN property kind ; + +-- lexicon + + KNumber = mkCN (mkN "tal" "tal") ; + EInteger i = symb i ; + PEven = mkAP (mkA "jämn") ; + POdd = mkAP (invarA "udda") ; + PPrime = mkAP (mkA "prim") ; ---- + PDivisible term = mkAP (mkA2 (mkA "delbar") with_Prep) term ; + PSmaller term = mkAP L.small_A term ; + PGreater term = mkAP L.big_A term ; + PBetween x y = mkAP (mkA2 (invarA "mellan") (mkPrep "")) (mkNP and_Conj x y) ; --- + + ESum x y = mkNP the_Det (mkCN (mkN2 (mkN "summa")) (mkNP and_Conj x y)) ; + EProduct x y = mkNP the_Det (mkCN (mkN2 (mkN "produkt")) (mkNP and_Conj x y)) ; + EFactorial x = mkNP the_Det (mkCN (mkN2 (mkN "fakultet")) x) ; + +} \ No newline at end of file diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..8b87f9d --- /dev/null +++ b/python/README.md @@ -0,0 +1,122 @@ +# Examples of embedded GF grammars in Python + +The grammars use the Python binding available in + +https://github.com/GrammaticalFramework/gf-core + +see + +`src/runtime/python/` + +for installing the Python module `pgf`, and + +`src/runtime/c/` + +for the C runtime library that the bindings are based on. +You don't need Haskell to build these. + +A general tutorial on the Python bindings can be found in + +http://www.grammaticalframework.org/doc/runtime-api.html#python + +A guide to GF for Python programmers can be found in + +https://daherb.github.io/GF-for-Python-programmers/ + + +## Translator + +A minimal translator can be found in + +[`minitranslator.py`](./minitranslator.py) + +This program reads one line of input and translates it from English to Swedish by using `MiniGrammar.pgf`. Example: +``` +$ echo "the cat is black" | python3 minitranslator.py +katten är svart +``` +A more general version is in + +[`translator.py`](./translator.py) + +This program reads input line by line, tokenizes it (by a simple tokenizer), and uses an arbitrary pgf and language codes. Example: +``` +$ cat findrawscript.txt | python3 translator.py Draw Fin Eng +# translating with Draw.pgf from DrawFin to DrawEng +draw a small red circle +draw a big yellow square +move the small red circle +remove it +``` + + +## Drawing figures + +A minimal drawing program with natural language input can found in + +[`minidraw.py`](./minidraw.py) + +This program reads one line of input in English and converts it to an action of drawing a circle or a square. Example: +``` +$ python3 minidraw.py +draw a circle +``` +The result is [this window](./dump-minidraw.png). +The program uses the simple graphics library from + +https://mcsp.wartburg.edu/zelle/python/graphics.py + +We also presuppose the grammar to be compiled with +` +make draw +` +A more complete version (using the same grammar) is in + +[`draw.py`](./draw.py) + +This program opens a session where it reads input line by line, in a language specified by a language code. Example: +``` +$ python3 draw.py Eng +draw a small red circle +draw a blue square +move it +move the small red circle +``` +The result is [this window](./dump-draw.png). + +The grammar, [`Draw.gf`](./Draw.gf), illustrates a few things... + + +## Answering queries + +A minimal query answering program with natural language input can found in + +[`miniquery.py`](./miniquery.py) + +This program reads one line of input in English and executes a query, which is either asking if a number is prime, or calculating a sum. Examples: +``` +$ echo "is 143 prime" | python3 miniquery.py +False +$ echo "1 + 2 + 3" | python3 miniquery.py +6 +``` +We presuppose the grammar to be compiled with +` +make query +` +A more complete version (using the same grammar) is in + +[`query.py`](./query.py) + +This program uses a large grammar with more predicates, operations, and logical forms such as quantifiers. It still reads just one line of input, but allows the language to be specified by a language code. Example: +``` +$ echo "what is the factorial of 12" | python3 query.py Eng +479001600 +$ echo "prime numbers between 200 and 300" | python3 query.py Eng +[211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293] +WARNING: ambiguous +$ echo "are all prime numbers odd" | python3 query.py Eng +False +``` +The grammar, [`Query.gf`](./Query.gf), illustrates a few things... + diff --git a/python/draw.py b/python/draw.py new file mode 100644 index 0000000..8f906d4 --- /dev/null +++ b/python/draw.py @@ -0,0 +1,193 @@ +# drawing figures with natural language commands +# presupposes 'gf -make DrawEng.gf (DrawSwe.gf ...)' + +import pgf +from graphics import * +import sys +from random import randrange + +# we assume this abstract syntax +absmodule = "Draw" + +# change this to change input language +langname = absmodule + "Eng" + +# the size of canvas +hsize, vsize = 1000,1000 + +# the mid point of the canvas +midpoint = (hsize/2,vsize/2) + +# unit size granularity +sizeunit = hsize//10 + +# big and small size factors +bigsize = 2 +smallsize = 0.2 + +# limits of radius and diameter/2 +maxradius = sizeunit +minradius = sizeunit/2 + +# distance of movement +distunit = 2*sizeunit + +def execute(command,win): + "top level: execute a Command in a window (given in main)" + fun,args = command.unpack() + + if fun == "drawCommand": + obj = shape(args[0]) + obj.draw(win) + return str(args[0]),obj,fun + + if fun == "removeCommand": + key = reference(args[0]) + return key,None,fun + + if fun == "moveCommand": + key = reference(args[0]) + return key,direction(args[1]),fun + +def reference(objectref): + fun,args = objectref.unpack() + if fun == "theObjectRef": + return(str(args[0])) + else: + return(str(objectref)) + +def shape(obj): + "draw a Shape of random size and position, possibly with colour and rough size specified" + fun,args = obj.unpack() + + sz,xx = args[0].unpack() + if sz == "big_Size": + factor = bigsize + elif sz == "small_Size": + factor = smallsize + else: + factor = 1 + + midx,midy = midpoint + x1 = midx + randrange(-midx,midx,1) + y1 = midy + randrange(-midy,midy,1) + r = factor * randrange(minradius, maxradius, 1) + d = 2 * r + x2 = x1 + d + y2 = y1 + d + sh,xx = args[2].unpack() + if sh == "circle_Shape": + shap = Circle(Point(x1,y1),r) + elif sh == "square_Shape": + shap = Rectangle(Point(x1,y1),Point(x2,y2)) + else: + shap = None + + co,xx = args[1].unpack() + if co == "green_Colour": + shap.setFill('green') + elif co == "red_Colour": + shap.setFill('red') + elif co == "blue_Colour": + shap.setFill('blue') + elif co == "yellow_Colour": + shap.setFill('yellow') + else: + pass + + return shap + +def direction(place): + fun,args = place.unpack() + if fun == "upPlace": + return 0,-distunit + if fun == "downPlace": + return 0,distunit + if fun == "leftPlace": + return -distunit,0 + if fun == "rightPlace": + return distunit,0 + else: + return randrange(-2*distunit,2*distunit,1),randrange(-2*distunit,2*distunit,1) + +def addRef(ref,obj,refs): + "adds a reference and object to the top of the stack" + return [(ref,obj)] + refs + +def lookupRef(ref,refs): + "return the first value and index of ref in a list of pairs refs" + i = 0 + for ro in refs: + r,obj = ro + if matchRef(ref,r): + return r,obj,i + else: + i = i + 1 + return None,None,None + +def removeRef(ref,refs): + "remove the first pair matching ref in refs" + r,obj,i = lookupRef(ref,refs) + refs.pop(i) + return refs + +def matchRef(ref,r): + if ref == "itObjectRef": + return True + else: + refws = ref.split() + rws = r.split() + m = True + for i in range(1,4): + if (refws[i][0:2] != "no") and (refws[i] != rws[i]): + return False + return m + +def main(): + "initialize with a window, process Command input line by line; optional language argument" + win = GraphWin("GF Draw", hsize, vsize) + refs = [] + latest = "it" + gr = pgf.readPGF(absmodule + ".pgf") + lang = langname + if len(sys.argv) > 1: + lang = absmodule + sys.argv[1] + eng = gr.languages[lang] + while True: + try: + line = input("") + except EOFError: + break + if not(line): + pass + else: + try: + px = eng.parse(line.lower()) + p,tree = px.__next__() + key,obj,co = execute(tree,win) + if co == "drawCommand": + refs = addRef(key,obj,refs) + elif co == "removeCommand": + ref,sh,i = lookupRef(key,refs) + if sh is None: + print("no such object") + else: + sh.undraw() + refs = removeRef(key,refs) + elif co == "moveCommand": + x,y = obj + ref,sh,i = lookupRef(key,refs) + if sh is None: + print("no such object") + else: + sh.move(x,y) + refs = [(ref,sh)] + refs[:i] + refs[i+1:] + else: + print("nothing else can be done") + print(refs) ## debugging + except pgf.ParseError: + print("# NO PARSE", line) + +main() + + diff --git a/python/findrawscript.txt b/python/findrawscript.txt new file mode 100644 index 0000000..d6d3d55 --- /dev/null +++ b/python/findrawscript.txt @@ -0,0 +1,4 @@ +piirrä pieni punainen ympyrä +piirrä suuri keltainen neliö +siirrä pientä punaista ympyrää +poista se diff --git a/python/graphics.py b/python/graphics.py new file mode 100644 index 0000000..a626141 --- /dev/null +++ b/python/graphics.py @@ -0,0 +1,1015 @@ +# graphics.py +"""Simple object oriented graphics library + +The library is designed to make it very easy for novice programmers to +experiment with computer graphics in an object oriented fashion. It is +written by John Zelle for use with the book "Python Programming: An +Introduction to Computer Science" (Franklin, Beedle & Associates). + +LICENSE: This is open-source software released under the terms of the +GPL (http://www.gnu.org/licenses/gpl.html). + +PLATFORMS: The package is a wrapper around Tkinter and should run on +any platform where Tkinter is available. + +INSTALLATION: Put this file somewhere where Python can see it. + +OVERVIEW: There are two kinds of objects in the library. The GraphWin +class implements a window where drawing can be done and various +GraphicsObjects are provided that can be drawn into a GraphWin. As a +simple example, here is a complete program to draw a circle of radius +10 centered in a 100x100 window: + +-------------------------------------------------------------------- +from graphics import * + +def main(): + win = GraphWin("My Circle", 100, 100) + c = Circle(Point(50,50), 10) + c.draw(win) + win.getMouse() # Pause to view result + win.close() # Close window when done + +main() +-------------------------------------------------------------------- +GraphWin objects support coordinate transformation through the +setCoords method and mouse and keyboard interaction methods. + +The library provides the following graphical objects: + Point + Line + Circle + Oval + Rectangle + Polygon + Text + Entry (for text-based input) + Image + +Various attributes of graphical objects can be set such as +outline-color, fill-color and line-width. Graphical objects also +support moving and hiding for animation effects. + +The library also provides a very simple class for pixel-based image +manipulation, Pixmap. A pixmap can be loaded from a file and displayed +using an Image object. Both getPixel and setPixel methods are provided +for manipulating the image. + +DOCUMENTATION: For complete documentation, see Chapter 4 of "Python +Programming: An Introduction to Computer Science" by John Zelle, +published by Franklin, Beedle & Associates. Also see +http://mcsp.wartburg.edu/zelle/python for a quick reference""" + +__version__ = "5.0" + +# Version 5 8/26/2016 +# * update at bottom to fix MacOS issue causing askopenfile() to hang +# * update takes an optional parameter specifying update rate +# * Entry objects get focus when drawn +# * __repr_ for all objects +# * fixed offset problem in window, made canvas borderless + +# Version 4.3 4/25/2014 +# * Fixed Image getPixel to work with Python 3.4, TK 8.6 (tuple type handling) +# * Added interactive keyboard input (getKey and checkKey) to GraphWin +# * Modified setCoords to cause redraw of current objects, thus +# changing the view. This supports scrolling around via setCoords. +# +# Version 4.2 5/26/2011 +# * Modified Image to allow multiple undraws like other GraphicsObjects +# Version 4.1 12/29/2009 +# * Merged Pixmap and Image class. Old Pixmap removed, use Image. +# Version 4.0.1 10/08/2009 +# * Modified the autoflush on GraphWin to default to True +# * Autoflush check on close, setBackground +# * Fixed getMouse to flush pending clicks at entry +# Version 4.0 08/2009 +# * Reverted to non-threaded version. The advantages (robustness, +# efficiency, ability to use with other Tk code, etc.) outweigh +# the disadvantage that interactive use with IDLE is slightly more +# cumbersome. +# * Modified to run in either Python 2.x or 3.x (same file). +# * Added Image.getPixmap() +# * Added update() -- stand alone function to cause any pending +# graphics changes to display. +# +# Version 3.4 10/16/07 +# Fixed GraphicsError to avoid "exploded" error messages. +# Version 3.3 8/8/06 +# Added checkMouse method to GraphWin +# Version 3.2.3 +# Fixed error in Polygon init spotted by Andrew Harrington +# Fixed improper threading in Image constructor +# Version 3.2.2 5/30/05 +# Cleaned up handling of exceptions in Tk thread. The graphics package +# now raises an exception if attempt is made to communicate with +# a dead Tk thread. +# Version 3.2.1 5/22/05 +# Added shutdown function for tk thread to eliminate race-condition +# error "chatter" when main thread terminates +# Renamed various private globals with _ +# Version 3.2 5/4/05 +# Added Pixmap object for simple image manipulation. +# Version 3.1 4/13/05 +# Improved the Tk thread communication so that most Tk calls +# do not have to wait for synchonization with the Tk thread. +# (see _tkCall and _tkExec) +# Version 3.0 12/30/04 +# Implemented Tk event loop in separate thread. Should now work +# interactively with IDLE. Undocumented autoflush feature is +# no longer necessary. Its default is now False (off). It may +# be removed in a future version. +# Better handling of errors regarding operations on windows that +# have been closed. +# Addition of an isClosed method to GraphWindow class. + +# Version 2.2 8/26/04 +# Fixed cloning bug reported by Joseph Oldham. +# Now implements deep copy of config info. +# Version 2.1 1/15/04 +# Added autoflush option to GraphWin. When True (default) updates on +# the window are done after each action. This makes some graphics +# intensive programs sluggish. Turning off autoflush causes updates +# to happen during idle periods or when flush is called. +# Version 2.0 +# Updated Documentation +# Made Polygon accept a list of Points in constructor +# Made all drawing functions call TK update for easier animations +# and to make the overall package work better with +# Python 2.3 and IDLE 1.0 under Windows (still some issues). +# Removed vestigial turtle graphics. +# Added ability to configure font for Entry objects (analogous to Text) +# Added setTextColor for Text as an alias of setFill +# Changed to class-style exceptions +# Fixed cloning of Text objects + +# Version 1.6 +# Fixed Entry so StringVar uses _root as master, solves weird +# interaction with shell in Idle +# Fixed bug in setCoords. X and Y coordinates can increase in +# "non-intuitive" direction. +# Tweaked wm_protocol so window is not resizable and kill box closes. + +# Version 1.5 +# Fixed bug in Entry. Can now define entry before creating a +# GraphWin. All GraphWins are now toplevel windows and share +# a fixed root (called _root). + +# Version 1.4 +# Fixed Garbage collection of Tkinter images bug. +# Added ability to set text atttributes. +# Added Entry boxes. + +import time, os, sys + +try: # import as appropriate for 2.x vs. 3.x + import tkinter as tk +except: + import Tkinter as tk + + +########################################################################## +# Module Exceptions + +class GraphicsError(Exception): + """Generic error class for graphics module exceptions.""" + pass + +OBJ_ALREADY_DRAWN = "Object currently drawn" +UNSUPPORTED_METHOD = "Object doesn't support operation" +BAD_OPTION = "Illegal option value" + +########################################################################## +# global variables and funtions + +_root = tk.Tk() +_root.withdraw() + +_update_lasttime = time.time() + +def update(rate=None): + global _update_lasttime + if rate: + now = time.time() + pauseLength = 1/rate-(now-_update_lasttime) + if pauseLength > 0: + time.sleep(pauseLength) + _update_lasttime = now + pauseLength + else: + _update_lasttime = now + + _root.update() + +############################################################################ +# Graphics classes start here + +class GraphWin(tk.Canvas): + + """A GraphWin is a toplevel window for displaying graphics.""" + + def __init__(self, title="Graphics Window", + width=200, height=200, autoflush=True): + assert type(title) == type(""), "Title must be a string" + master = tk.Toplevel(_root) + master.protocol("WM_DELETE_WINDOW", self.close) + tk.Canvas.__init__(self, master, width=width, height=height, + highlightthickness=0, bd=0) + self.master.title(title) + self.pack() + master.resizable(0,0) + self.foreground = "black" + self.items = [] + self.mouseX = None + self.mouseY = None + self.bind("", self._onClick) + self.bind_all("", self._onKey) + self.height = int(height) + self.width = int(width) + self.autoflush = autoflush + self._mouseCallback = None + self.trans = None + self.closed = False + master.lift() + self.lastKey = "" + if autoflush: _root.update() + + def __repr__(self): + if self.isClosed(): + return "" + else: + return "GraphWin('{}', {}, {})".format(self.master.title(), + self.getWidth(), + self.getHeight()) + + def __str__(self): + return repr(self) + + def __checkOpen(self): + if self.closed: + raise GraphicsError("window is closed") + + def _onKey(self, evnt): + self.lastKey = evnt.keysym + + + def setBackground(self, color): + """Set background color of the window""" + self.__checkOpen() + self.config(bg=color) + self.__autoflush() + + def setCoords(self, x1, y1, x2, y2): + """Set coordinates of window to run from (x1,y1) in the + lower-left corner to (x2,y2) in the upper-right corner.""" + self.trans = Transform(self.width, self.height, x1, y1, x2, y2) + self.redraw() + + def close(self): + """Close the window""" + + if self.closed: return + self.closed = True + self.master.destroy() + self.__autoflush() + + + def isClosed(self): + return self.closed + + + def isOpen(self): + return not self.closed + + + def __autoflush(self): + if self.autoflush: + _root.update() + + + def plot(self, x, y, color="black"): + """Set pixel (x,y) to the given color""" + self.__checkOpen() + xs,ys = self.toScreen(x,y) + self.create_line(xs,ys,xs+1,ys, fill=color) + self.__autoflush() + + def plotPixel(self, x, y, color="black"): + """Set pixel raw (independent of window coordinates) pixel + (x,y) to color""" + self.__checkOpen() + self.create_line(x,y,x+1,y, fill=color) + self.__autoflush() + + def flush(self): + """Update drawing to the window""" + self.__checkOpen() + self.update_idletasks() + + def getMouse(self): + """Wait for mouse click and return Point object representing + the click""" + self.update() # flush any prior clicks + self.mouseX = None + self.mouseY = None + while self.mouseX == None or self.mouseY == None: + self.update() + if self.isClosed(): raise GraphicsError("getMouse in closed window") + time.sleep(.1) # give up thread + x,y = self.toWorld(self.mouseX, self.mouseY) + self.mouseX = None + self.mouseY = None + return Point(x,y) + + def checkMouse(self): + """Return last mouse click or None if mouse has + not been clicked since last call""" + if self.isClosed(): + raise GraphicsError("checkMouse in closed window") + self.update() + if self.mouseX != None and self.mouseY != None: + x,y = self.toWorld(self.mouseX, self.mouseY) + self.mouseX = None + self.mouseY = None + return Point(x,y) + else: + return None + + def getKey(self): + """Wait for user to press a key and return it as a string.""" + self.lastKey = "" + while self.lastKey == "": + self.update() + if self.isClosed(): raise GraphicsError("getKey in closed window") + time.sleep(.1) # give up thread + + key = self.lastKey + self.lastKey = "" + return key + + def checkKey(self): + """Return last key pressed or None if no key pressed since last call""" + if self.isClosed(): + raise GraphicsError("checkKey in closed window") + self.update() + key = self.lastKey + self.lastKey = "" + return key + + def getHeight(self): + """Return the height of the window""" + return self.height + + def getWidth(self): + """Return the width of the window""" + return self.width + + def toScreen(self, x, y): + trans = self.trans + if trans: + return self.trans.screen(x,y) + else: + return x,y + + def toWorld(self, x, y): + trans = self.trans + if trans: + return self.trans.world(x,y) + else: + return x,y + + def setMouseHandler(self, func): + self._mouseCallback = func + + def _onClick(self, e): + self.mouseX = e.x + self.mouseY = e.y + if self._mouseCallback: + self._mouseCallback(Point(e.x, e.y)) + + def addItem(self, item): + self.items.append(item) + + def delItem(self, item): + self.items.remove(item) + + def redraw(self): + for item in self.items[:]: + item.undraw() + item.draw(self) + self.update() + + +class Transform: + + """Internal class for 2-D coordinate transformations""" + + def __init__(self, w, h, xlow, ylow, xhigh, yhigh): + # w, h are width and height of window + # (xlow,ylow) coordinates of lower-left [raw (0,h-1)] + # (xhigh,yhigh) coordinates of upper-right [raw (w-1,0)] + xspan = (xhigh-xlow) + yspan = (yhigh-ylow) + self.xbase = xlow + self.ybase = yhigh + self.xscale = xspan/float(w-1) + self.yscale = yspan/float(h-1) + + def screen(self,x,y): + # Returns x,y in screen (actually window) coordinates + xs = (x-self.xbase) / self.xscale + ys = (self.ybase-y) / self.yscale + return int(xs+0.5),int(ys+0.5) + + def world(self,xs,ys): + # Returns xs,ys in world coordinates + x = xs*self.xscale + self.xbase + y = self.ybase - ys*self.yscale + return x,y + + +# Default values for various item configuration options. Only a subset of +# keys may be present in the configuration dictionary for a given item +DEFAULT_CONFIG = {"fill":"", + "outline":"black", + "width":"1", + "arrow":"none", + "text":"", + "justify":"center", + "font": ("helvetica", 12, "normal")} + +class GraphicsObject: + + """Generic base class for all of the drawable objects""" + # A subclass of GraphicsObject should override _draw and + # and _move methods. + + def __init__(self, options): + # options is a list of strings indicating which options are + # legal for this object. + + # When an object is drawn, canvas is set to the GraphWin(canvas) + # object where it is drawn and id is the TK identifier of the + # drawn shape. + self.canvas = None + self.id = None + + # config is the dictionary of configuration options for the widget. + config = {} + for option in options: + config[option] = DEFAULT_CONFIG[option] + self.config = config + + def setFill(self, color): + """Set interior color to color""" + self._reconfig("fill", color) + + def setOutline(self, color): + """Set outline color to color""" + self._reconfig("outline", color) + + def setWidth(self, width): + """Set line weight to width""" + self._reconfig("width", width) + + def draw(self, graphwin): + + """Draw the object in graphwin, which should be a GraphWin + object. A GraphicsObject may only be drawn into one + window. Raises an error if attempt made to draw an object that + is already visible.""" + + if self.canvas and not self.canvas.isClosed(): raise GraphicsError(OBJ_ALREADY_DRAWN) + if graphwin.isClosed(): raise GraphicsError("Can't draw to closed window") + self.canvas = graphwin + self.id = self._draw(graphwin, self.config) + graphwin.addItem(self) + if graphwin.autoflush: + _root.update() + return self + + + def undraw(self): + + """Undraw the object (i.e. hide it). Returns silently if the + object is not currently drawn.""" + + if not self.canvas: return + if not self.canvas.isClosed(): + self.canvas.delete(self.id) + self.canvas.delItem(self) + if self.canvas.autoflush: + _root.update() + self.canvas = None + self.id = None + + + def move(self, dx, dy): + + """move object dx units in x direction and dy units in y + direction""" + + self._move(dx,dy) + canvas = self.canvas + if canvas and not canvas.isClosed(): + trans = canvas.trans + if trans: + x = dx/ trans.xscale + y = -dy / trans.yscale + else: + x = dx + y = dy + self.canvas.move(self.id, x, y) + if canvas.autoflush: + _root.update() + + def _reconfig(self, option, setting): + # Internal method for changing configuration of the object + # Raises an error if the option does not exist in the config + # dictionary for this object + if option not in self.config: + raise GraphicsError(UNSUPPORTED_METHOD) + options = self.config + options[option] = setting + if self.canvas and not self.canvas.isClosed(): + self.canvas.itemconfig(self.id, options) + if self.canvas.autoflush: + _root.update() + + + def _draw(self, canvas, options): + """draws appropriate figure on canvas with options provided + Returns Tk id of item drawn""" + pass # must override in subclass + + + def _move(self, dx, dy): + """updates internal state of object to move it dx,dy units""" + pass # must override in subclass + + +class Point(GraphicsObject): + def __init__(self, x, y): + GraphicsObject.__init__(self, ["outline", "fill"]) + self.setFill = self.setOutline + self.x = float(x) + self.y = float(y) + + def __repr__(self): + return "Point({}, {})".format(self.x, self.y) + + def _draw(self, canvas, options): + x,y = canvas.toScreen(self.x,self.y) + return canvas.create_rectangle(x,y,x+1,y+1,options) + + def _move(self, dx, dy): + self.x = self.x + dx + self.y = self.y + dy + + def clone(self): + other = Point(self.x,self.y) + other.config = self.config.copy() + return other + + def getX(self): return self.x + def getY(self): return self.y + +class _BBox(GraphicsObject): + # Internal base class for objects represented by bounding box + # (opposite corners) Line segment is a degenerate case. + + def __init__(self, p1, p2, options=["outline","width","fill"]): + GraphicsObject.__init__(self, options) + self.p1 = p1.clone() + self.p2 = p2.clone() + + def _move(self, dx, dy): + self.p1.x = self.p1.x + dx + self.p1.y = self.p1.y + dy + self.p2.x = self.p2.x + dx + self.p2.y = self.p2.y + dy + + def getP1(self): return self.p1.clone() + + def getP2(self): return self.p2.clone() + + def getCenter(self): + p1 = self.p1 + p2 = self.p2 + return Point((p1.x+p2.x)/2.0, (p1.y+p2.y)/2.0) + + +class Rectangle(_BBox): + + def __init__(self, p1, p2): + _BBox.__init__(self, p1, p2) + + def __repr__(self): + return "Rectangle({}, {})".format(str(self.p1), str(self.p2)) + + def _draw(self, canvas, options): + p1 = self.p1 + p2 = self.p2 + x1,y1 = canvas.toScreen(p1.x,p1.y) + x2,y2 = canvas.toScreen(p2.x,p2.y) + return canvas.create_rectangle(x1,y1,x2,y2,options) + + def clone(self): + other = Rectangle(self.p1, self.p2) + other.config = self.config.copy() + return other + + +class Oval(_BBox): + + def __init__(self, p1, p2): + _BBox.__init__(self, p1, p2) + + def __repr__(self): + return "Oval({}, {})".format(str(self.p1), str(self.p2)) + + + def clone(self): + other = Oval(self.p1, self.p2) + other.config = self.config.copy() + return other + + def _draw(self, canvas, options): + p1 = self.p1 + p2 = self.p2 + x1,y1 = canvas.toScreen(p1.x,p1.y) + x2,y2 = canvas.toScreen(p2.x,p2.y) + return canvas.create_oval(x1,y1,x2,y2,options) + +class Circle(Oval): + + def __init__(self, center, radius): + p1 = Point(center.x-radius, center.y-radius) + p2 = Point(center.x+radius, center.y+radius) + Oval.__init__(self, p1, p2) + self.radius = radius + + def __repr__(self): + return "Circle({}, {})".format(str(self.getCenter()), str(self.radius)) + + def clone(self): + other = Circle(self.getCenter(), self.radius) + other.config = self.config.copy() + return other + + def getRadius(self): + return self.radius + + +class Line(_BBox): + + def __init__(self, p1, p2): + _BBox.__init__(self, p1, p2, ["arrow","fill","width"]) + self.setFill(DEFAULT_CONFIG['outline']) + self.setOutline = self.setFill + + def __repr__(self): + return "Line({}, {})".format(str(self.p1), str(self.p2)) + + def clone(self): + other = Line(self.p1, self.p2) + other.config = self.config.copy() + return other + + def _draw(self, canvas, options): + p1 = self.p1 + p2 = self.p2 + x1,y1 = canvas.toScreen(p1.x,p1.y) + x2,y2 = canvas.toScreen(p2.x,p2.y) + return canvas.create_line(x1,y1,x2,y2,options) + + def setArrow(self, option): + if not option in ["first","last","both","none"]: + raise GraphicsError(BAD_OPTION) + self._reconfig("arrow", option) + + +class Polygon(GraphicsObject): + + def __init__(self, *points): + # if points passed as a list, extract it + if len(points) == 1 and type(points[0]) == type([]): + points = points[0] + self.points = list(map(Point.clone, points)) + GraphicsObject.__init__(self, ["outline", "width", "fill"]) + + def __repr__(self): + return "Polygon"+str(tuple(p for p in self.points)) + + def clone(self): + other = Polygon(*self.points) + other.config = self.config.copy() + return other + + def getPoints(self): + return list(map(Point.clone, self.points)) + + def _move(self, dx, dy): + for p in self.points: + p.move(dx,dy) + + def _draw(self, canvas, options): + args = [canvas] + for p in self.points: + x,y = canvas.toScreen(p.x,p.y) + args.append(x) + args.append(y) + args.append(options) + return GraphWin.create_polygon(*args) + +class Text(GraphicsObject): + + def __init__(self, p, text): + GraphicsObject.__init__(self, ["justify","fill","text","font"]) + self.setText(text) + self.anchor = p.clone() + self.setFill(DEFAULT_CONFIG['outline']) + self.setOutline = self.setFill + + def __repr__(self): + return "Text({}, '{}')".format(self.anchor, self.getText()) + + def _draw(self, canvas, options): + p = self.anchor + x,y = canvas.toScreen(p.x,p.y) + return canvas.create_text(x,y,options) + + def _move(self, dx, dy): + self.anchor.move(dx,dy) + + def clone(self): + other = Text(self.anchor, self.config['text']) + other.config = self.config.copy() + return other + + def setText(self,text): + self._reconfig("text", text) + + def getText(self): + return self.config["text"] + + def getAnchor(self): + return self.anchor.clone() + + def setFace(self, face): + if face in ['helvetica','arial','courier','times roman']: + f,s,b = self.config['font'] + self._reconfig("font",(face,s,b)) + else: + raise GraphicsError(BAD_OPTION) + + def setSize(self, size): + if 5 <= size <= 36: + f,s,b = self.config['font'] + self._reconfig("font", (f,size,b)) + else: + raise GraphicsError(BAD_OPTION) + + def setStyle(self, style): + if style in ['bold','normal','italic', 'bold italic']: + f,s,b = self.config['font'] + self._reconfig("font", (f,s,style)) + else: + raise GraphicsError(BAD_OPTION) + + def setTextColor(self, color): + self.setFill(color) + + +class Entry(GraphicsObject): + + def __init__(self, p, width): + GraphicsObject.__init__(self, []) + self.anchor = p.clone() + #print self.anchor + self.width = width + self.text = tk.StringVar(_root) + self.text.set("") + self.fill = "gray" + self.color = "black" + self.font = DEFAULT_CONFIG['font'] + self.entry = None + + def __repr__(self): + return "Entry({}, {})".format(self.anchor, self.width) + + def _draw(self, canvas, options): + p = self.anchor + x,y = canvas.toScreen(p.x,p.y) + frm = tk.Frame(canvas.master) + self.entry = tk.Entry(frm, + width=self.width, + textvariable=self.text, + bg = self.fill, + fg = self.color, + font=self.font) + self.entry.pack() + #self.setFill(self.fill) + self.entry.focus_set() + return canvas.create_window(x,y,window=frm) + + def getText(self): + return self.text.get() + + def _move(self, dx, dy): + self.anchor.move(dx,dy) + + def getAnchor(self): + return self.anchor.clone() + + def clone(self): + other = Entry(self.anchor, self.width) + other.config = self.config.copy() + other.text = tk.StringVar() + other.text.set(self.text.get()) + other.fill = self.fill + return other + + def setText(self, t): + self.text.set(t) + + + def setFill(self, color): + self.fill = color + if self.entry: + self.entry.config(bg=color) + + + def _setFontComponent(self, which, value): + font = list(self.font) + font[which] = value + self.font = tuple(font) + if self.entry: + self.entry.config(font=self.font) + + + def setFace(self, face): + if face in ['helvetica','arial','courier','times roman']: + self._setFontComponent(0, face) + else: + raise GraphicsError(BAD_OPTION) + + def setSize(self, size): + if 5 <= size <= 36: + self._setFontComponent(1,size) + else: + raise GraphicsError(BAD_OPTION) + + def setStyle(self, style): + if style in ['bold','normal','italic', 'bold italic']: + self._setFontComponent(2,style) + else: + raise GraphicsError(BAD_OPTION) + + def setTextColor(self, color): + self.color=color + if self.entry: + self.entry.config(fg=color) + + +class Image(GraphicsObject): + + idCount = 0 + imageCache = {} # tk photoimages go here to avoid GC while drawn + + def __init__(self, p, *pixmap): + GraphicsObject.__init__(self, []) + self.anchor = p.clone() + self.imageId = Image.idCount + Image.idCount = Image.idCount + 1 + if len(pixmap) == 1: # file name provided + self.img = tk.PhotoImage(file=pixmap[0], master=_root) + else: # width and height provided + width, height = pixmap + self.img = tk.PhotoImage(master=_root, width=width, height=height) + + def __repr__(self): + return "Image({}, {}, {})".format(self.anchor, self.getWidth(), self.getHeight()) + + def _draw(self, canvas, options): + p = self.anchor + x,y = canvas.toScreen(p.x,p.y) + self.imageCache[self.imageId] = self.img # save a reference + return canvas.create_image(x,y,image=self.img) + + def _move(self, dx, dy): + self.anchor.move(dx,dy) + + def undraw(self): + try: + del self.imageCache[self.imageId] # allow gc of tk photoimage + except KeyError: + pass + GraphicsObject.undraw(self) + + def getAnchor(self): + return self.anchor.clone() + + def clone(self): + other = Image(Point(0,0), 0, 0) + other.img = self.img.copy() + other.anchor = self.anchor.clone() + other.config = self.config.copy() + return other + + def getWidth(self): + """Returns the width of the image in pixels""" + return self.img.width() + + def getHeight(self): + """Returns the height of the image in pixels""" + return self.img.height() + + def getPixel(self, x, y): + """Returns a list [r,g,b] with the RGB color values for pixel (x,y) + r,g,b are in range(256) + + """ + + value = self.img.get(x,y) + if type(value) == type(0): + return [value, value, value] + elif type(value) == type((0,0,0)): + return list(value) + else: + return list(map(int, value.split())) + + def setPixel(self, x, y, color): + """Sets pixel (x,y) to the given color + + """ + self.img.put("{" + color +"}", (x, y)) + + + def save(self, filename): + """Saves the pixmap image to filename. + The format for the save image is determined from the filname extension. + + """ + + path, name = os.path.split(filename) + ext = name.split(".")[-1] + self.img.write( filename, format=ext) + + +def color_rgb(r,g,b): + """r,g,b are intensities of red, green, and blue in range(256) + Returns color specifier string for the resulting color""" + return "#%02x%02x%02x" % (r,g,b) + +def test(): + win = GraphWin() + win.setCoords(0,0,10,10) + t = Text(Point(5,5), "Centered Text") + t.draw(win) + p = Polygon(Point(1,1), Point(5,3), Point(2,7)) + p.draw(win) + e = Entry(Point(5,6), 10) + e.draw(win) + win.getMouse() + p.setFill("red") + p.setOutline("blue") + p.setWidth(2) + s = "" + for pt in p.getPoints(): + s = s + "(%0.1f,%0.1f) " % (pt.getX(), pt.getY()) + t.setText(e.getText()) + e.setFill("green") + e.setText("Spam!") + e.move(2,0) + win.getMouse() + p.move(2,3) + s = "" + for pt in p.getPoints(): + s = s + "(%0.1f,%0.1f) " % (pt.getX(), pt.getY()) + t.setText(s) + win.getMouse() + p.undraw() + e.undraw() + t.setStyle("bold") + win.getMouse() + t.setStyle("normal") + win.getMouse() + t.setStyle("italic") + win.getMouse() + t.setStyle("bold italic") + win.getMouse() + t.setSize(14) + win.getMouse() + t.setFace("arial") + t.setSize(20) + win.getMouse() + win.close() + +#MacOS fix 2 +#tk.Toplevel(_root).destroy() + +# MacOS fix 1 +update() + +if __name__ == "__main__": + test() diff --git a/python/minidraw.py b/python/minidraw.py new file mode 100644 index 0000000..f18017b --- /dev/null +++ b/python/minidraw.py @@ -0,0 +1,51 @@ +# minimal drawing from natural language input: just draw one square or circle +# presupposes 'gf -make DrawEng.gf' + +import pgf +from graphics import * + +# we assume this abstract syntax +absmodule = "Draw" + +# change this to change input language +langname = absmodule + "Eng" + +def execute(command,win): + "interpret drawCommand; other commands ignored" + fun,args = command.unpack() + + if fun == "drawCommand": + obj = shape(args[0]) + obj.draw(win) + else: + print("command not available") + + +def shape(obj): + "draw Shape, ignoring given colour and size in this simple example" + fun,args = obj.unpack() + sh,xx = args[2].unpack() + + if sh == "circle_Shape": + shap = Circle(Point(300,300),200) + elif sh == "square_Shape": + shap = Rectangle(Point(200,200),Point(600,600)) + else: + shap = None + + return shap + +def main(): + "execute one line of input, quit by second Enter" + win = GraphWin("GF Draw", 1000, 1000) + gr = pgf.readPGF(absmodule + ".pgf") + eng = gr.languages[langname] + line = input() + px = eng.parse(line.lower()) + p,tree = px.__next__() + execute(tree,win) + input() + +main() + + diff --git a/python/miniquery.py b/python/miniquery.py new file mode 100644 index 0000000..33f3703 --- /dev/null +++ b/python/miniquery.py @@ -0,0 +1,72 @@ +# a minimal query system for arithmetic, using Montague-style semantics +# and an embedded GF grammar +# examples: "is 131 prime", "2 + 2" +# presupposes 'gf -make QueryEng (QuerySwe ...)' + +import pgf +import sys + +# we assume this abstract syntax +absmodule = "Query" + +# change this to change input language +langname = absmodule + "Eng" + +# the meaning of "number" in unrestricted quantification +allNumbers = range(1,10000) + +def answer(tree): + "top level Query interpreter" + fun,args = tree.unpack() + + if fun == "QWhether": + ans = predicate(args[1])(value(args[0])) + else: # QWhat + ans = value(args[0]) + + return str(ans) + +def predicate(property): + "takes Property or Kind to int -> bool" + prop,pargs = property.unpack() + if prop == "PPrime": + return prime + else: + print("property not available") + +def value(term): + "takes Term to int, does not cover quantifiers" + fun,args = term.unpack() + if fun == "TElement": + return value(args[0]) + elif fun == "EInteger": + return int(args[0].unpack()) + elif fun == "ESum": + return value(args[0]) + value(args[1]) + else: + print("term not availeble") + +def prime(n): + "simple way to decide if a positive number is prime" + if n == 1: + r = False + else: + r = True + for k in range(2,n//2+1): + if n % k == 0: return False + return r + +def main(): + "main function to be called from stdio" + + # read in the grammar, set up the input language + grammar = pgf.readPGF(absmodule + ".pgf") + lang = grammar.languages[langname] + + # read a line of input, parse in lang, return answer + line = input("") + parseresult = lang.parse(line) + prob,tree = parseresult.__next__() + print(answer(tree)) + +main() diff --git a/python/minitranslator.py b/python/minitranslator.py new file mode 100644 index 0000000..efb4d76 --- /dev/null +++ b/python/minitranslator.py @@ -0,0 +1,25 @@ +# minimal translator that reads one line from stdio and translates from Eng to Swe with Minilang +# presupposes 'make minilang' + +import pgf + +# change these three to translate with other grammars and languages +absmodule = "MiniLang" +fromname = absmodule + "Eng" +toname = absmodule + "Swe" + +def main(): + # read in the grammar, set up to and from languages + grammar = pgf.readPGF(absmodule + ".pgf") + fromlang = grammar.languages[fromname] + tolang = grammar.languages[toname] + + # read a line of input, parse in "from" language, linearize in "to" language + line = input("") + parseresult = fromlang.parse(line) + prob,tree = parseresult.__next__() + print(tolang.linearize(tree)) + +main() + + diff --git a/python/query.py b/python/query.py new file mode 100644 index 0000000..864cf13 --- /dev/null +++ b/python/query.py @@ -0,0 +1,151 @@ +# a simple query system for arithmetic, using Montague-style semantics +# and an embedded GF grammar +# examples: "is 131 prime", "which numbers are prime", "365 * 24 * 60" +# presupposes 'gf -make QueryEng (QuerySwe ...)' + +import pgf +import sys + +# we assume this abstract syntax +absmodule = "Query" + +# change this to change input language +langname = absmodule + "Eng" + +# the meaning of "number" in unrestricted quantification +allNumbers = range(1,10000) + +def answer(tree): + "top level Query interpreter" + fun,args = tree.unpack() + + if fun == "QWhich": + ans = enumerate(lambda x: predicate(args[0])(x) and predicate(args[1])(x), allNumbers) + elif fun == "QWhether": + ans = quantifier(args[0])(predicate(args[1])) + else: # QWhat + ans = value(args[0]) + + return str(ans) + +def predicate(property): + "takes Property or Kind to int -> bool" + prop,pargs = property.unpack() + if prop == "PAnd": + return lambda x: predicate(pargs[0])(x) and predicate(pargs[1])(x) + elif prop == "POr": + return lambda x: predicate(pargs[0])(x) or predicate(pargs[1])(x) + elif prop == "PNot": + return lambda x: not predicate(pargs[0])(x) + elif prop == "PEven": + return lambda x: x % 2 == 0 + elif prop == "POdd": + return lambda x: x % 2 != 0 + elif prop == "PEqual": + return lambda x: quantifier(pargs[0])(lambda y: x == y) + elif prop == "PSmaller": + return lambda x: quantifier(pargs[0])(lambda y: x < y) + elif prop == "PGreater": + return lambda x: quantifier(pargs[0])(lambda y: x > y) + elif prop == "PBetween": + return lambda y: quantifier(pargs[0])(lambda x: quantifier(pargs[1])(lambda z: x < y and y < z)) + elif prop == "PPrime": + return prime + +# works also for Kind: + elif prop == "KNumber": + return lambda x: True + elif prop == "KProperty": + return lambda x: predicate(pargs[0])(x) and predicate(pargs[1])(x) + + else: + print("not a valid property",property) + +def quantifier(term): + "takes Term to (int -> bool) -> bool" + fun,args = term.unpack() + if fun == "TElement": + return lambda p: p(value(args[0])) + elif fun == "TAll": + return lambda p: forAll(lambda x: not predicate(args[0])(x) or p(x), allNumbers) + elif fun == "TAny": + return lambda p: forSome(lambda x: predicate(args[0])(x) and p(x), allNumbers) + else: + print("not a valid term",term) + +def value(element): + "takes Element to int" + fun,args = element.unpack() + if fun == "EInteger": + return int(args[0].unpack()) + elif fun == "ESum": + return value(args[0]) + value(args[1]) + elif fun == "EProduct": + return value(args[0]) * value(args[1]) + elif fun == "EMinus": + return value(args[0]) - value(args[1]) + elif fun == "EDivided": + return value(args[0]) / value(args[1]) ## // ? + elif fun == "EFactorial": + return factorial(value(args[0])) + else: + print("not a valid element",element) + +def prime(n): + "simple way to decide if a positive number is prime" + if n == 1: + r = False + else: + r = True + for k in range(2,n//2+1): + if n % k == 0: return False + return r + +def forAll(p,ns): + "universal quantifier over a list" + for n in ns: + if not p(n): return False + return True + +def forSome(p,ns): + "existential quantifier over a list" + for n in ns: + if p(n): return True + return False + +def enumerate(p,ns): + "filter over a list" + r = [] + for n in ns: + if p(n): r = r + [n] + return r + +def enumer(p,ns): + return [n for n in ns if p(n)] + +def factorial(n): + "factorial of positive numbers" + r = 1 + for k in range(1,n+1): + r = k*r + return r + +def main(): + "main function to be called from stdio, language code as optional argument" + + # read in the grammar, set up the input language + grammar = pgf.readPGF(absmodule + ".pgf") + langcode = langname + if len(sys.argv) > 1: + langcode = absmodule + sys.argv[1] + lang = grammar.languages[langcode] + + # read a line of input, parse in lang, return answer + line = input("") + parseresult = lang.parse(line) + prob,tree = parseresult.__next__() +# print(tree) ## debugging + print(answer(tree)) + for r in parseresult: print("WARNING: ambiguous") + +main() diff --git a/python/translator.py b/python/translator.py new file mode 100644 index 0000000..b073544 --- /dev/null +++ b/python/translator.py @@ -0,0 +1,68 @@ +# translator for arbitrary pgf grammar and language pair, given as arguments + +import pgf +import sys + +def trim(s0): + "lower-case first word and tokenize" + s = s0.strip() + if not(s): + return s + s = (s[0].lower() + s[1:]) + r = "" + for c in s: + if c in [',','.',':','(',')']: + r = r + ' ' + c + ' ' + else: + r = r + c + return r + +def missing(eng,s): + "words in a sentence not found in morphological analysis" + ws = s.split() + ms = [] + for w in ws: + if not eng.lookupMorpho(w): # strange behaviour of eng + ms = ms + [w] + return ms + +def main(): + "translation loop, reads stdio line by line" + if len(sys.argv) < 4: + print(" usage: python3 translator.py -debug?") + print(" e.g. python3 translator.py CSETranslator Eng Swe") + print(" The program reads and writes stdio line by line, e.g") + print(" cat ../course_plans/TIN214Eng.txt | python3 translator.py CSEShallow Eng Swe") + return + pgfm = sys.argv[1] + pgff = pgfm + ".pgf" + ceng = pgfm + sys.argv[2] + cswe = pgfm + sys.argv[3] + debug = len(sys.argv) == 5 and sys.argv[4] == "-debug" + gr = pgf.readPGF(pgff) + eng = gr.languages[ceng] + swe = gr.languages[cswe] + print("#","translating with",pgff,"from",ceng,"to",cswe) + while True: + try: + line = input("") + except EOFError: + break + t = trim(line) + if not(t): + pass + else: + try: + px = eng.parse(t) + p,e = px.__next__() + if debug: + print("# TREE", e) + print(swe.linearize(e)) + except pgf.ParseError: + print("# NO PARSE", t) + if debug: + print("# MISSING", missing(eng,t)) + +main() + +