{ "info": { "author": "John-Anthony Owens", "author_email": "johnao@zillowgroup.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Compilers", "Topic :: Software Development :: Interpreters" ], "description": "=======\nAbysmal\n=======\n\n.. include-documentation-begin-marker\n\n.. image:: https://travis-ci.org/zillow/abysmal.svg?branch=master\n :target: https://travis-ci.org/zillow/abysmal\n\n.. image:: https://codecov.io/gh/zillow/abysmal/branch/master/graph/badge.svg\n :target: https://codecov.io/gh/zillow/abysmal\n\nAbysmal stands for \"appallingly basic yet somehow mostly adequate language\".\n\nAbysmal is a programming language designed to allow non-programmers\nto implement simple business logic for computing prices, rankings, or\nother kinds of numeric values without incurring the security and\nstability risks that would normally result when non-professional coders\ncontribute to production code. In other words, it's a sandbox in which\nbusinesspeople can tinker with their business logic to their hearts'\ncontent without involving your developers or breaking anything.\n\n\nFeatures\n--------\n\n* Supports Python 3.3 and above\n\n\nDependencies\n------------\n\n* `python3-dev` native library including Python C header files\n* `libmpdec-dev` native library for decimal arithmetic\n\n\n.. include-documentation-end-marker\n\n\nLanguage Reference\n------------------\n\nAbysmal programs are designed to be written by businesspeople, so the\nlanguage foregoes almost all the features programmers want in a programming\nlanguage in favor of mimicking something business people understand:\nflowcharts.\n\nJust about the only way your businesspeople can \"crash\" an Abysmal program\nis by dividing by zero, because:\n\n* it's not Turing-complete\n* it can't allocate memory\n* it can't access the host process or environment\n* it operates on one and only one type: arbitrary-precision decimal numbers\n* its only control flow construct is GOTO\n* it doesn't even allow loops!\n\nExample program\n~~~~~~~~~~~~~~~\n\n::\n\n # input variables:\n #\n # flavor: VANILLA, CHOCOLATE, or STRAWBERRY\n # scoops: 1, 2, etc.\n # cone: SUGAR or WAFFLE\n # sprinkles: 0 or 1\n # weekday: MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, or SUNDAY\n\n # output variables:\n #\n # price: total price, including tax\n\n let TAX_RATE = 5.3%\n let WEEKDAY_DISCOUNT = 25%\n let GIVEAWAY_RATE = 1%\n\n @start:\n random! <= GIVEAWAY_RATE => @giveaway_winner\n price = scoops * (flavor == STRAWBERRY ? 1.25 : 1.00)\n price = price + (cone == WAFFLE ? 1.00 : 0.00)\n price = price + (sprinkles * 0.25)\n weekday not in {SATURDAY, SUNDAY} => @apply_weekday_discount\n => @compute_total\n\n @apply_weekday_discount:\n price = price * (1 - WEEKDAY_DISCOUNT)\n => @compute_total\n\n @giveaway_winner:\n price = 0.00\n\n @compute_total:\n price = price * (1 + TAX_RATE)\n\n\nControl flow\n~~~~~~~~~~~~\n\nAn Abysmal program models a flowchart containing one or more steps, or *states*.\nProgram execution begins at the beginning of the first state and continues\nuntil it reaches a dead end. Along the way, variables can be assigned new\nvalues, and execution can jump to other states. **That's it.**\n\nEvery state has a name that starts with `@`. A state is declared like this:\n\n::\n\n @start:\n\nA state declaration is followed by a sequence of *actions*. Each action appears\non its own line, and is one of the following:\n\n(1) an *assignment* of a value to a variable, like this:\n\n::\n\n price = scoops * flavor == STRAWBERRY ? 1.25 : 1.00\n\n(2) a *conditional jump* to another state, like this:\n\n::\n\n weekday not in {SATURDAY, SUNDAY} => @apply_weekday_discount\n\n(3) an *unconditional jump* to another state, like this:\n\n::\n\n => @compute_total\n\nWhen execution reaches a state, that state's actions are executed in order.\nIf execution reaches the end of a state without jumping to a new state, the\nprogram exits.\n\nPrograms are not allowed to contain loops or any other exeuction cycles.\nAny program containing a cycle will fail to compile.\n\nActions are typically indented to make the state labels easier to see, but\nthis is just a stylistic convention and is not enforced by the language.\n\nComments\n~~~~~~~~\n\nAnything following a `#` on a line is treated as a comment and is ignored.\n\nLine continuations\n~~~~~~~~~~~~~~~~~~\n\nA `\\\\` at the end of a line indicates that the next line is a continuation of\nthe line. This makes it easy to format long lines readably by splitting them\ninto multiple, shorter lines. Note that comments can appear after a `\\\\`.\n\nNumbers\n~~~~~~~\n\nAbysmal supports integer and fixed-point decimal literals like `123`,\n`3.14159`, etc. In addition, numbers can have the following suffixes:\n\n========== ======================================================\nsuffix meaning\n========== ======================================================\n`%` percent (`12.5%` is equivalent to `0.125`)\n`k` or `K` thousand (`50k` is equivalent to `50000`)\n`m` or `M` million (`1.2m` is equivalent to `1200000`)\n`b` or `B` billion (`0.5b` is equivalent to `500000000`)\n========== ======================================================\n\nScientific notation is not supported.\n\nBooleans\n~~~~~~~~\n\nAbysmal uses `1` and `0` to represent the result of any operation that\nyields a logical true/false value. When evaluating conditions in a\nconditional jump or a `?` expression, zero is considered false and\nany non-zero value is considered true.\n\nExpressions\n~~~~~~~~~~~\n\nPrograms can evaluate expressions containing the following operators:\n\n======================= =========== ============================================== ========================\noperator precedence meaning example\n======================= =========== ============================================== ========================\n`( exp )` 0 (highest) grouping `(x + 1) * y`\n`!` 1 logical NOT `!x`\n`+` 1 unary plus (has no effect) `+x`\n`-` 1 unary minus `-x`\n`^` 2 exponentiation (right associative) `x ^ 3`\n`*` 3 multiplication `x * 100`\n`/` 3 division `x / 2`\n`+` 4 addition `x + 5`\n`-` 4 subtraction `x - 3`\n`in { exp, ... }` 5 is a member of the set `x in {0, y, -z}`\n`not in { exp, ... }` 5 is not a member of the set `x not in {0, y, -z}`\n`in [ low , high ]` 5 falls within the interval (see Intervals) `x in [-3, 7]`\n`not in [ low , high ]` 5 does not fall within the interval `x not in [-3, 7]`\n`<` 6 is less than `x < y`\n`<=` 6 is less than or equal to `x <= y`\n`>` 6 is greater than `x > y`\n`>=` 6 is greater than or equal to `x >= y`\n`==` 7 is equal to `x == y`\n`!=` 7 is not equal to `x != y`\n`&&` 8 logical AND (short-circuiting) `x && (y / x > 0.8)`\n`||` 9 logical OR (short-circuiting) `x > 3 || y > 7`\n`exp ? exp : exp` 10 (lowest) if-then-else `x < 0 ? -x : x`\n======================= =========== ============================================== ========================\n\nIntervals\n~~~~~~~~~\n\nIntervals support inclusive endpoints (specified with square brackets)\nand exclusive endpoints (specified with parentheses), and the two can be\nfreely mixed. For example, the follwing are all valid checks:\n\n* `x in (0, 1)`\n* `x in (0, 1]`\n* `x in [0, 1)`\n* `x in [0, 1]`\n\nNote that \"backwards\" intervals (where the first endpoint is greater\nthan the second) are considered pathological and treated as empty.\nTherefore `2 in (1, 3)` evaluates to `1` (aka true), but `2 in (3, 1)`\nevaluates to `0` (aka false).\n\nFunctions\n~~~~~~~~~\n\nExpressions can take advantage of the following built-in functions:\n\n====================== ======================================================================\nfunction returns\n====================== ======================================================================\n`ABS(exp)` the absolute value of the specified value\n`CEILING(exp)` the nearest integer value greater than or equal to the specified value\n`FLOOR(exp)` the nearest integer value less than or equal to the specified value\n`MAX(exp1, exp2, ...)` the maximum of the specified values\n`MIN(exp1, exp2, ...)` the minimum of the specified values\n`ROUND(exp)` the specified value, rounded to the nearest integer\n====================== ======================================================================\n\nVariables\n~~~~~~~~~\n\nAbysmal programs can read from and write to variables that you define\nwhen you compile the program. Some of these variables will be inputs,\nwhose values you will set before you run the program. Others will be outputs,\nwhose values the program will compute so that those values can be examined\nafter the program has terminated.\n\nAbysmal does not distinguish between input and output variables.\n\n*All* variables and constant values are decimal numbers. Abysmal does not\nhave any concept of strings, booleans, null, or any other types.\n\nIf not explicitly set, variables default to 0.\n\n`random!` is a special, read-only variable that yields a new, random value\nevery time it is referenced.\n\nYou can also provide named constants to your programs when you compile them.\nConstants cannot be modified.\n\nA program can also declare custom variables that it can use to store\nintermediate results while the model is being run, or simply to define\nfriendlier names for values that are used within the model. Custom variables\nmust be declared before the first state is declared.\n\nEach custom variable is declared on its own line, like this:\n\n::\n\n let PI = 3.14159\n let area = PI * r * r\n\n\nUsage\n-----\n\nAn Abysmal program must be compiled before it can be run. The compiler needs\nto know the names of the variables that the program should have access to\nand names and values of any constants you want to define:\n\n.. code-block:: python\n\n ICE_CREAM_VARIABLES = {\n # inputs\n 'flavor',\n 'scoops',\n 'cone',\n 'sprinkles',\n 'weekday',\n\n # outputs\n 'price',\n }\n\n ICE_CREAM_CONSTANTS = {\n # flavors\n 'VANILLA': 1,\n 'CHOCOLATE': 2,\n 'STRAWBERRY': 3,\n\n # cones\n 'SUGAR': 1,\n 'WAFFLE': 2,\n\n # weekdays\n 'MONDAY': 1,\n 'TUESDAY': 2,\n 'WEDNESDAY': 3,\n 'THURSDAY': 4,\n 'FRIDAY': 5,\n 'SATURDAY': 6,\n 'SUNDAY': 7,\n }\n\n compiled_program, source_map = abysmal.compile(source_code, ICE_CREAM_VARIABLES, ICE_CREAM_CONSTANTS)\n\nIgnore the second value returned by `abysmal.compile()` for now (refer to the\nMeasuring Coverage section to see what it's useful for).\n\nNext, we need to make a virtual machine for the compiled program to run on:\n\n.. code-block:: python\n\n machine = compiled_program.machine()\n\nNext, we can set any variables as we see fit:\n\n.. code-block:: python\n\n # Variables can be set in bulk during reset()...\n machine.reset(\n flavor=ICE_CREAM_CONSTANTS['CHOCOLATE'],\n scoops=2,\n cone=ICE_CREAM_CONSTANTS['WAFFLE']\n )\n\n # ... or one at a time (though this is less efficient)\n machine['sprinkles'] = True # automatically converted to '1'\n\nFinally, we can run the machine and examine final variable values:\n\n.. code-block:: python\n\n price = Decimal('0.00')\n try:\n machine.run()\n price = round(Decimal(machine['price']), 2)\n except abysmal.ExecutionError as ex:\n print('The ice cream pricing algorithm is broken: ' + str(ex))\n else:\n print('Two scoops of chocolate ice cream in a waffle cone with sprinkles costs: ${0}'.format(price))\n\nNote that the virtual machine exposes variable values as strings, which\nmay be formatted in scientific or fixed-point notation.\n\nVariables can be set from int, float, bool, Decimal, and string values\nbut are converted to strings when assigned. When examining variables\nafter running a machine, you need to convert to the values back to\nDecimal, float, or whatever numeric type you are interested in.\n\n\nRandom Numbers\n--------------\n\nBy default, `random!` generates numbers between 0 and 1 with 9 decimal\nplaces of precision, and uses the default Python PRNG (`random.randrange`).\n\nIf you require a more secure PRNG, or different precision, or if you want\nto force certain values to be produced for testing purposes, you can supply\nyour own random number iterator before running a machine:\n\n.. code-block:: python\n\n # force random! to yield 0, 1, 0, 1, ...\n machine.random_number_iterator = itertools.cycle([0, 1])\n\nThe values you return are not required to fall within any particular\nrange, but [0, 1] is recommended, for consistency with the default behavior.\n\n\nLimits\n~~~~~~\n\nDecimal values are constrained in accordance with the IEEE 754 `decimal128`\nformat. This provides 34 digits of precision and an exponent range of\n-6143 to +6144.\n\nInfinity, negative infinity, and NaN (not-a-number) are not allowed.\nCalculations that would give rise to one of these will instead trigger\nan error.\n\nIn addition, a calculation can result in overflow or underflow if its\nresult is too large or too small to fit into the `decimal128` range.\n\n\nErrors\n------\n\n`abysmal.CompilationError`\n raised by `abysmal.compile()` if the source code cannot be compiled\n`abysmal.ExecutionError`\n raised by `machine.run()` and `machine.run_with_coverage()`\n if a program encounters an error while running; this includes conditions\n such as: division by zero, invalid exponentiation, stack overflow,\n floating-point overflow, floating-point underflow, out-of-space, and\n failure to generate a random number\n`abysmal.InstructionLimitExceededError`\n raised by `machine.run()` and `machine.run_with_coverage()`\n if a program exceeds its allowed instruction count and is aborted;\n this error is a subclass of `abysmal.ExecutionError`\n\n\nPerformance Tips\n----------------\n\nAbysmal programs run very quickly once compiled, and the virtual machine is\noptimized to make repeated runs with different inputs as cheap as possible.\n\nAs always, decide on your performance goals and measure before optimizing.\n\nTo get the best performance, follow these tips:\n\nAvoid recompilation\n~~~~~~~~~~~~~~~~~~~\n\nCompiling a program is orders of magnitude slower than actually running it.\n\nSave the compiled program and reuse it rather than recompiling every time.\nCompiled programs are pickleable, so they are easy to cache.\n\nUse baseline images\n~~~~~~~~~~~~~~~~~~~\n\nWhen you create a machine, you can pass keyword arguments to set the machine's\nvariables to initial values. The state of the variables at this moment is\ncalled a *baseline image*. When you reset a machine, it restores all variables\nto the baseline image very efficiently. Therefore, if you are going to run a\nparticular program repeatedly with some inputs having the same values for all\nthe runs, you should specify those input values in the baseline.\n\nFor example:\n\n.. code-block:: python\n\n def compute_shipping_costs(product, weight, zip_codes, compiled_program):\n shipping_costs = {}\n machine = compiled_program.machine(product=product, weight=weight)\n for zip_code in zip_codes:\n machine.reset(zip=zip_code).run()\n shipping_costs[zip_code] = round(Decimal(machine['shippingCost']), 2)\n return shipping_costs\n\nSet multiple variables at once\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nOverride baseline variable values by passing keywords to `machine.reset()`\nrather than assigning variables one-by-one. The overhead of making multiple\nPython function calls is non-trivial if your scenario needs performance!\n\nOnly read and write variables you need\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nInitializing variables before a program runs and reading variables afterwards\ncan easily add up to more time it takes to actually run a typical program.\nIf performance is critical for your scenario, you can save time by only\nexamining variables whose values you really need.\n\nLimit instruction execution\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nSince Abysmal does not support loops, it is very difficult to create a program\nthat runs for very long. However, you can impose an additional limit on the\nnumber of instructions that a program can execute by setting the `instruction_limit`\nattribute of a machine:\n\n.. code-block:: python\n\n machine.instruction_limit = 5000\n\nIf a program exceeds its instruction limit, it will raise an `abysmal.InstructionLimitExceededError`.\n\nThe default instruction limit is 10000.\n\nThe `run()` method returns the number of instructions that were run before\nthe program exited.\n\n\nMeasuring Coverage\n------------------\n\nIn addition to `run()`, virtual machines expose a `run_with_coverage()` method\nwhich can be used in conjunction with the source map returned by\n`abysmal.compile()` to generate coverage reports for Abysmal programs.\n\n.. code-block:: python\n\n coverage_tuples = [\n machine.reset(**test_case_inputs).run_with_coverage()\n for test_case_inputs in test_cases\n ]\n coverage_report = abysmal.get_uncovered_lines(source_map, coverage_tuples)\n print('Partially covered lines: ' + ', '.join(map(str, coverage_report.partially_covered_line_numbers)))\n print('Totally uncovered lines: ' + ', '.join(map(str, coverage_report.uncovered_line_numbers))\n\nHow coverage works:\n\n`run_with_coverage()` returns a *coverage tuple* whose length is equal\nto the number of instructions in the compiled program. The value at index *i*\nin the coverage tuple will be True or False depending on whether instruction\n*i* was executed during the program's run.\n\nThe *source map* is another tuple, with the same length as the coverage tuple.\nThe value at index *i* in the source map indicates which line or lines in the\nsource code generated instruction *i* of the compiled program. There are three\npossibilities:\n\n* None - the instruction was not directly generated by any source line\n* int - the instruction was generated by a single source line\n* (int, int, ...) - the instruction was generated by multiple source lines\n (due to line continuations being used)\n\n\nInstallation\n------------\n\nNote that native library dependencies must be installed BEFORE\nyou install the `abysmal` library.\n\n.. code-block:: console\n\n pip install abysmal\n\n\nDevelopment\n-----------\n\n.. code-block:: console\n\n # Install system-level dependencies on Debian/Ubuntu\n make setup\n\n # Run unit tests\n make test\n\n # Check code cleanliness\n make pylint\n\n # Check code coverage\n make cover\n\n # Create sdist package\n make package", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/zillow/abysmal", "keywords": "absymal,programming,language", "license": "MIT license", "maintainer": "", "maintainer_email": "", "name": "abysmal", "package_url": "https://pypi.org/project/abysmal/", "platform": "", "project_url": "https://pypi.org/project/abysmal/", "project_urls": { "Homepage": "https://github.com/zillow/abysmal" }, "release_url": "https://pypi.org/project/abysmal/1.2.0/", "requires_dist": null, "requires_python": ">= 3.3", "summary": "Abysmal (Appallingly Basic Yet Somehow Mostly Adequate Language)", "version": "1.2.0" }, "last_serial": 3515606, "releases": { "1.0.0": [ { "comment_text": "", "digests": { "md5": "1f6ea3a36ee71b0962d9d91458dbd690", "sha256": "3eb2ed5d16c082c5c28a8d5b09d6d6c86f1f96defa0d8c79348d3c2377b67fe9" }, "downloads": -1, "filename": "abysmal-1.0.0.tar.gz", "has_sig": false, "md5_digest": "1f6ea3a36ee71b0962d9d91458dbd690", "packagetype": "sdist", "python_version": "source", "requires_python": ">= 3.5", "size": 37955, "upload_time": "2017-12-27T23:17:23", "url": "https://files.pythonhosted.org/packages/d1/d1/0050f5a098c3d69201008522dd97a115216636584d6a8bc16e5a2e5a0eae/abysmal-1.0.0.tar.gz" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "1dcbd4109023f344366389b39c45d263", "sha256": "652744c73f60d7315074a8ec3a37228a85588653af1ad27edafd7f9efc40d7b1" }, "downloads": -1, "filename": "abysmal-1.1.0.tar.gz", "has_sig": false, "md5_digest": "1dcbd4109023f344366389b39c45d263", "packagetype": "sdist", "python_version": "source", "requires_python": ">= 3.5", "size": 39389, "upload_time": "2017-12-28T23:04:23", "url": "https://files.pythonhosted.org/packages/fa/c6/7f1156cbd0bb50b36c7e08f614d8500fd163dd1ef77acf508299f250f1d1/abysmal-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "74a110a61dba3e602976c41d03691170", "sha256": "4df0c9542a26eb317348178167504a7ae8ea5df53ee136279bbd4806d63ffc54" }, "downloads": -1, "filename": "abysmal-1.1.1.tar.gz", "has_sig": false, "md5_digest": "74a110a61dba3e602976c41d03691170", "packagetype": "sdist", "python_version": "source", "requires_python": ">= 3.3", "size": 39803, "upload_time": "2018-01-02T22:26:01", "url": "https://files.pythonhosted.org/packages/20/24/bb8ec02a42eb64134c0ccab75b607378e962445654fa2eb70246288c210e/abysmal-1.1.1.tar.gz" } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "1f92de134166d3e00fdc97bcd4d51fa4", "sha256": "85eebbbc4ca3024bf7496e9941b5cfbe26c728c1b1ef3bcde837e83e0b964a31" }, "downloads": -1, "filename": "abysmal-1.2.0.tar.gz", "has_sig": false, "md5_digest": "1f92de134166d3e00fdc97bcd4d51fa4", "packagetype": "sdist", "python_version": "source", "requires_python": ">= 3.3", "size": 43741, "upload_time": "2018-01-23T22:56:31", "url": "https://files.pythonhosted.org/packages/6d/51/612d8c883658b758e54db4ef041a0c2d1eb1b6e1770b737d3c8d99122bc0/abysmal-1.2.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "1f92de134166d3e00fdc97bcd4d51fa4", "sha256": "85eebbbc4ca3024bf7496e9941b5cfbe26c728c1b1ef3bcde837e83e0b964a31" }, "downloads": -1, "filename": "abysmal-1.2.0.tar.gz", "has_sig": false, "md5_digest": "1f92de134166d3e00fdc97bcd4d51fa4", "packagetype": "sdist", "python_version": "source", "requires_python": ">= 3.3", "size": 43741, "upload_time": "2018-01-23T22:56:31", "url": "https://files.pythonhosted.org/packages/6d/51/612d8c883658b758e54db4ef041a0c2d1eb1b6e1770b737d3c8d99122bc0/abysmal-1.2.0.tar.gz" } ] }