{"id":20397292,"url":"https://github.com/tlaplus/pluspy","last_synced_at":"2025-04-10T16:06:05.357Z","repository":{"id":51375465,"uuid":"255404677","full_name":"tlaplus/PlusPy","owner":"tlaplus","description":"Python interpreter for TLA+ specifications","archived":false,"fork":false,"pushed_at":"2024-06-02T19:33:31.000Z","size":66,"stargazers_count":113,"open_issues_count":1,"forks_count":13,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-03-24T13:51:20.415Z","etag":null,"topics":["interpreter","python","tla"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tlaplus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-13T18:03:03.000Z","updated_at":"2025-01-02T04:25:32.000Z","dependencies_parsed_at":"2022-09-07T15:40:46.752Z","dependency_job_id":null,"html_url":"https://github.com/tlaplus/PlusPy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tlaplus%2FPlusPy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tlaplus%2FPlusPy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tlaplus%2FPlusPy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tlaplus%2FPlusPy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tlaplus","download_url":"https://codeload.github.com/tlaplus/PlusPy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248250743,"owners_count":21072682,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["interpreter","python","tla"],"created_at":"2024-11-15T04:13:05.628Z","updated_at":"2025-04-10T16:06:05.338Z","avatar_url":"https://github.com/tlaplus.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pluspy\n\npluspy is a TLA+ interpreter, written in Python.\n\nRun \"./pluspy -h\" for basic help.  The output should be something like:\n\n    Usage: pluspy [options] [module]\n      options: \n        -c cnt: #times that Next should be evaluated\n        -h: help\n        -i: Init operator (default Init)\n        -n: Next operator (default Next)\n        -P path: module directory search path\n        -s: silent output\n        -S seed: random seed for reproducible tests\n        -v: verbose output\n\nYou can also invoke pluspy with \"-i Spec\" where Spec is a full TLA+\nspecification.  Try for example \"./pluspy -i Spec Prime\", which\nshould print the prime numbers.\n\npluspy is a Bash shell script.  If you can't run it, try \"python3 pluspy.py\"\ninstead.\n\n-------- EXAMPLE 1: HourClock --------\n\nRun \"./pluspy -c2 HourClock\"\n\nThe output should be something like:\n\n    Initial context: [ hr: 6 ]\n    Next state: 0 [ hr: 7 ]\n    Next state: 1 [ hr: 8 ]\n    MAIN DONE\n\nYou can find the HourClock module in modules/book/HourClock.tla.\nThe interpreter shows three \"contexts\", which are assignments of\nconstants and variables to values.  It first shows the \"Initial\ncontext,\" computed by the \"Init\" operator in HourClock.tla.  (The\ninitial value of hr is a random number between 1 and 12.)  Then it\nshows two more contexts computed by the \"Next\" operator in\nHourClock.tla.  The number of steps can be controlled with the \"-c\"\noption to pluspy.\n\nYou should be able to run Peterson.tla, Prime.tla, and Qsort.tla in\nmuch the same way, but try larger -c counts for those.\n\n-------- EXAMPLE 2: Threads --------\n\nThe Peterson module has multiple processes.  PlusPy can run each as\na separate thread.  Run: \"./pluspy -c100 -n proc%0 -n proc%1 Peterson\".\n\"proc\" is an operator in the Peterson module that takes an argument.\nThe \"-n x%y\" argument to pluspy starts a thread that repeatedly evaluates\nthe x operator with argument y instead of the default \"Next\".  You may\nalso want to try: \"./pluspy -c100 -n proc%0 Peterson\" and \n\"./pluspy -c100 -n proc%1 Peterson\" which each will run just one of the\nprocesses.\n\nUsing the new JWait/JSignalReturn interfaces in TLCExt, threads can use\ncondition variables.  See modules/other/BoundedQueueSplit.tla for an\nexample.  Run as follows:\n\n    ./pluspy -n producer%p1 -c100 -n producer%p2 -n producer%p3 \\\n                -n consumer%c1 -n consumer%c2 -n consumer%c3 TestBQ\n\n(This won't work if you try to run this without threads.)\n\n-------- EXAMPLE 3: Sending and Receiving Messages --------\n\nOpen three windows.  Run the following commands, one in each of the windows:\n\n    ./pluspy -c100 -n Proc%localhost:5001 TestBinBosco\n    ./pluspy -c100 -n Proc%localhost:5002 TestBinBosco\n    ./pluspy -c100 -n Proc%localhost:5003 TestBinBosco\n\nThe Proc action takes one argument, the process identifier,\n\nThe processes will actually send and receive messages, trying to\nsolve consensus.  Although BinBosco has no rule to actually decide,\nin all likelihood they will each end up with the same estimate after\nabout five rounds.  Only three of the four processes can do so.  You\ncan inspect the output and see the state of each of the processes\nand the set Messages.  The \"processes\" variable of localhost:5001\nshould contain something like the following in the end:\n\n    [\n        \"localhost:5001\" |-\u003e [ estimate |-\u003e \"red\",  round |-\u003e 5 ],\n        \"localhost:5002\" |-\u003e [ estimate |-\u003e \"blue\", round |-\u003e 0 ],\n        \"localhost:5003\" |-\u003e [ estimate |-\u003e \"red\",  round |-\u003e 0 ],\n        \"localhost:5004\" |-\u003e [ estimate |-\u003e \"blue\", round |-\u003e 0 ]\n    ]\n\nSimilarly, the \"processes\" variable of localhost:5002 might contain:\n\n    [\n        \"localhost:5001\" |-\u003e [ estimate |-\u003e \"red\",  round |-\u003e 0 ],\n        \"localhost:5002\" |-\u003e [ estimate |-\u003e \"red\",  round |-\u003e 5 ],\n        \"localhost:5003\" |-\u003e [ estimate |-\u003e \"red\",  round |-\u003e 0 ],\n        \"localhost:5004\" |-\u003e [ estimate |-\u003e \"blue\", round |-\u003e 0 ]\n    ]\n\nNote that all processes end up with the same estimate, probably\naround round 5.  Running BinBosco this way is a little odd, as each\npluspy process only updates part of what is really supposed to be\na global state.  You can imagine there being a refinement mapping\nto the virtual global state, where the local state of TLA+ process\ni in pluspy process i maps to the state of TLA+ process i in the\nglobal state.\n\nMessaging is specified in the modules/lib/Messaging.tla module.\nBesides the message interface variable mi, there are the following\nthree operators exported:\n\n    Init                        \\* initializes mi\n    Send(msgs)                  \\* sends the given \u003c\u003c destination, payload \u003e\u003e messages\n    Receive(p, Deliver(_, _))   \\* wait for a message for p and call Deliver(p, payload)\n\nThe spec proper is specified after the comment \"\\*++:SPEC\".  If you look further\nyou'll find a second comment \"\\*++:PlusPy\".  This is where the implementation of\nthe messaging module starts.  It uses the IOUtils module described below.\n\n-------- The IOUtils module --------\n\nPlusPy exports a module in modules/lib/IOUtils.tla that allows processes to do various\nkinds of I/O.  There are currently three kinds of interfaces defined: \"fd\", \"tcp\",\nand \"local\".  For \"fd\", there are three muxes: \"stdin\", \"stdout\", and \"stderr\".\nFor example IOPut(\"fd\", \"stdout\", \"hello\") prints hello.  Note that all I/O is\n\"deferred\": it is only performed if the action in which it occurs evaluates to TRUE.\n\nThe modules/lib/Input.tla module shows how one can read from standard input.\nThe Input.tla module is \"pure TLA+\" -- it has no implied variable.  It also\nprovides an example of the \"\\*++:SPEC\" and \"\\*++:PlusPy\" sections.\n\nTo send and receive TCP messages, use the \"tcp\" interface and use as \"mux\" a\nTCP/IP address of the form \"x.x.x.x:port\".\n\nThe \"local\" interface is a little like the IP loopback interface.  It allows\nsending messages to self.\n\n-------- TLA+ value representations in Python --------\n\nThe basic values are booleans, numbers, and strings:\n\n    FALSE:      False\n    TRUE:       True\n    number:     number\n    string:     string\n\nTLA+ sets are represented as Python frozensets,  A frozenset is like\na Python set but is hashable because it cannot be modified.\n\nEssentially, a tuple is represented as a tuple, and a record as a\npluspy.FrozenDict.\n\nHowever, to ensure that equal values have the same representation,\nrecords that are tuples (because their keys are in the range 1..len)\nare represented as tuples, and tuples that are strings (because all\ntheir elements are characters) are represented as strings.  In\nparticular, empty records and tuples are represented as the empty\nstring.\n\nThere is also a \"Nonce\" type for \"CHOOSE x: x \\notin S\" expressions.\n\nNote that in Python False == 0 and True == 1.  This can lead to\nweird behaviors.\n\nFor example, { False, 0 } == { False } == { 0 } == { 0, False }.\n\n-------- PlusPy Module --------\nPlusPy can also be used as a Python module, so you integrate Python and\nTLA+ programs quite easily.  The interface is as follows:\n\n    pp = pluspy.PlusPy(module, constants={})\n        'module' is a string containing either a file name or the\n        name of a TLA+ module.  Constants is a dictionary that maps\n        constant names to values (see TLA+ value represenations.)\n\n    pp.init(Init)\n        'Init' is a string containing the name of the 'init' operator\n        in the TLA+ formula: \"Spec == Init /\\ [][Next]\".  You can also\n        pass the \"Spec\" operator, but it will not give back control\n        until \"Next\" evaluates to FALSE.  Also, does not implement\n        environment variables at the moment.\n\n    pp.next(Next, arg=None)\n        'Next' is a string containing the name of the 'Next' operator\n        in the TLA+ formula: \"Spec == Init /\\ [][Next]\".  If Next takes\n        an argument, \"arg\" contains its value.  It returns True if some\n        action was enabled and False if no action was enabled.  Typically\n        pp.next() is called within a loop.  Interface state variables\n        can be changed in the loop if desired.\n\n    pp.unchanged()\n        Checks to see if the state has changed after pp.next()\n\n    value = pp.get(variable)\n        \"variable\" is a string containing the name of a variable.  Its\n        value is returned.  For the representation of TLA+ values, see\n        below.\n\n    value = pp.getall():\n        returns a single record value containing all variables\n\n    pp.set(variable, value)\n        \"variable\" is a string containing the name of a variable.  Its\n        value is set to \"value\".  For the representation of TLA+ values,\n        see below.\n\n    v = pluspy.format(v):\n        Convert a value to a string formatted in TLA+ format\n\n    v = pluspy.convert(v):\n        Convert a value to something a little more normal and better for\n        handling in Python.  In particular, it turns records into dictionaries,\n        and frozensets into lists\n\n-------- PlusPy Wrappers --------\n\nPlusPy allows operators in specified modules to be replaced by Python\ncode.  You can specify this in the pluspy.wrappers variable.  For example:\n\n    class LenWrapper(pluspy.Wrapper):\n        def eval(self, id, args):\n            assert id == \"Len\"\n            assert len(args) == 1\n            return len(args[0])\n\n    pluspy.wrappers[\"Sequences\"] = {\n        \"Len\": LenWrapper()\n    }\n\nThe IOUtils module is implemented using a wrapper.\n\n-------- Discussion of interfacing between Python and TLA+ --------\n\nPlusPy allows TLA+ and Python to interface in a variety of ways, as demonstrated\nabove.  It is important to try to avoid ad hoc solutions.  The Messaging and Input\nmodules provide a clean separation between the TLA+ world and the Python world.\nThey can be used with the TLC model checker or TLAPS to validate your code.  The\nIOUtils module is outside the TLA+ world, so if you extend it, try to do it through a\nmodule that has clean semantics.\n\nSimilarly, while it is possible to invoke operators other than \"Spec\" directly from\nPython, and while it is possible to write state variables directly from Python, this\ncircumvents the TLA+ framework.  Use caution.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftlaplus%2Fpluspy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftlaplus%2Fpluspy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftlaplus%2Fpluspy/lists"}