├── .python-version ├── .coveragerc ├── shakespearelang ├── __init__.py ├── tests │ ├── __init__.py │ ├── sample_plays │ │ ├── echo.spl │ │ ├── hi.spl │ │ ├── catch.spl │ │ ├── reverse.spl │ │ ├── primes.spl │ │ ├── hello_world.spl │ │ ├── sierpinski.spl │ │ └── parse_everything.spl │ ├── utils.py │ ├── test_runtime_error_format.py │ ├── test_dramatis_personae.py │ ├── test_numeric_output.py │ ├── test_sample_plays.py │ ├── test_input_styles.py │ ├── test_assignment.py │ ├── test_parse_error_format.py │ ├── test_numeric_input.py │ ├── test_character_input.py │ ├── test_stacks.py │ ├── test_character_output.py │ ├── test_gotos.py │ ├── test_enter_and_exit.py │ ├── test_console.py │ └── test_output_styles.py ├── _character.py ├── _output.py ├── _preprocess.py ├── settings.py ├── _input.py ├── _utils.py ├── errors.py ├── cli.py ├── _state.py ├── _repl.py ├── _expression.py ├── shakespeare.py ├── _operation.py └── shakespeare.ebnf ├── scripts ├── visual_profile.sh ├── compile_grammar.sh └── profile_sierpinski.py ├── tox.ini ├── mkdocs_overrides └── main.html ├── docs ├── CLI.md ├── API.md └── index.md ├── .codeclimate.yml ├── mkdocs.yml ├── pyproject.toml ├── LICENSE ├── CONTRIBUTING.md ├── .gitignore ├── .github └── workflows │ └── main.yml └── README.rst /.python-version: -------------------------------------------------------------------------------- 1 | 3.9.1 2 | 3.8.6 3 | 3.7.9 4 | 3.6.12 5 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = shakespearelang 3 | omit = **/tests/** 4 | -------------------------------------------------------------------------------- /shakespearelang/__init__.py: -------------------------------------------------------------------------------- 1 | from .shakespeare import * 2 | from .errors import * 3 | from .settings import * 4 | -------------------------------------------------------------------------------- /shakespearelang/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | pytest.register_assert_rewrite("shakespearelang.tests.utils") 4 | -------------------------------------------------------------------------------- /scripts/visual_profile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | poetry run python3 scripts/profile_sierpinski.py 6 | poetry run snakeviz profilestats 7 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | isolated_build = true 3 | envlist = py38, py39, py310 4 | 5 | [testenv] 6 | commands = 7 | pip install pytest==6.2.5 pexpect==4.8.0 8 | pytest 9 | -------------------------------------------------------------------------------- /scripts/compile_grammar.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | poetry run tatsu shakespearelang/shakespeare.ebnf -m shakespeare -o shakespearelang/_parser.py 4 | poetry run black shakespearelang/_parser.py 5 | -------------------------------------------------------------------------------- /mkdocs_overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block outdated %} 4 | You're not viewing the latest version. 5 | 6 | Click here to go to latest. 7 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /scripts/profile_sierpinski.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from pathlib import Path 3 | import cProfile 4 | 5 | path = ( 6 | Path(__file__).parent.parent / f"shakespearelang/tests/sample_plays/sierpinski.spl" 7 | ) 8 | with path.open() as f: 9 | cProfile.run("Shakespeare(f.read()).run()", "profilestats") 10 | -------------------------------------------------------------------------------- /docs/CLI.md: -------------------------------------------------------------------------------- 1 | # Command line usage reference 2 | 3 | This is a full reference for `shakespeare` commands and usage. The same information is 4 | available by using the option `--help` on the CLI commands themselves. 5 | 6 | ::: mkdocs-click 7 | :module: shakespearelang.cli 8 | :command: main 9 | :prog_name: shakespeare 10 | :depth: 1 11 | -------------------------------------------------------------------------------- /shakespearelang/tests/sample_plays/echo.spl: -------------------------------------------------------------------------------- 1 | A rose by any other name. 2 | Romeo, a speaker. 3 | Juliet, his muse. 4 | Act I: Something. 5 | Scene I: A garden. 6 | [Enter Romeo and Juliet] 7 | Romeo: Open your mind! Speak your mind! 8 | Romeo: Listen to your heart! Open your heart! 9 | Romeo: Open your mind! Speak your mind! 10 | [Exeunt] 11 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | duplication: 4 | enabled: true 5 | config: 6 | languages: 7 | - python 8 | fixme: 9 | enabled: true 10 | radon: 11 | enabled: true 12 | pep8: 13 | enabled: true 14 | ratings: 15 | paths: 16 | - "**.py" 17 | exclude_paths: 18 | - 'shakespearelang/tests/**/*.py' 19 | - 'shakespearelang/_parser.py' 20 | -------------------------------------------------------------------------------- /docs/API.md: -------------------------------------------------------------------------------- 1 | # Python API reference 2 | 3 | If you want to interact with the interpreter programmatically from 4 | Python, you can do so using the public interfaces of the classes below. If you 5 | just want to write and execute SPL plays, the command line interface is likely 6 | easier. 7 | 8 | ::: shakespearelang.Shakespeare 9 | 10 | ::: shakespearelang.Settings 11 | 12 | ::: shakespearelang.ShakespeareError 13 | 14 | ::: shakespearelang.ShakespeareParseError 15 | 16 | ::: shakespearelang.ShakespeareRuntimeError 17 | -------------------------------------------------------------------------------- /shakespearelang/tests/sample_plays/hi.spl: -------------------------------------------------------------------------------- 1 | A New Beginning. 2 | 3 | Hamlet, a literary/storage device. 4 | Juliet, an orator. 5 | 6 | Act I: The Only Act. 7 | 8 | Scene I: The Prince's Speech. 9 | 10 | [Enter Hamlet and Juliet] 11 | 12 | Juliet: Thou art the sum of an amazing healthy honest noble peaceful 13 | fine Lord and a lovely sweet golden summer's day. Speak your 14 | mind! 15 | 16 | [A pause] 17 | 18 | Juliet: Thou art the sum of thyself and a King. Speak your mind! 19 | 20 | Thou art the sum of an amazing healthy honest hamster and a golden 21 | chihuahua. Speak your mind! 22 | 23 | [Exeunt] 24 | -------------------------------------------------------------------------------- /shakespearelang/_character.py: -------------------------------------------------------------------------------- 1 | from .errors import ShakespeareRuntimeError 2 | from ._utils import normalize_name 3 | 4 | 5 | class Character: 6 | """A character in an SPL play.""" 7 | 8 | def __init__(self): 9 | self.value = 0 10 | self.stack = [] 11 | 12 | def __str__(self): 13 | return f'{self.value} ({" ".join([str(v) for v in self.stack][::-1])})' 14 | 15 | def push(self, newValue): 16 | """Push a value onto the character's stack.""" 17 | self.stack.append(newValue) 18 | 19 | def pop(self): 20 | """Pop a value off the character's stack, and set the character to 21 | that value.""" 22 | if len(self.stack) == 0: 23 | raise ShakespeareRuntimeError("Tried to pop from an empty stack.") 24 | self.value = self.stack.pop() 25 | -------------------------------------------------------------------------------- /shakespearelang/_output.py: -------------------------------------------------------------------------------- 1 | from .errors import ShakespeareRuntimeError 2 | 3 | 4 | class BasicOutputManager: 5 | def output_number(self, number): 6 | print(number, end="") 7 | 8 | def output_character(self, character_code): 9 | print(_code_to_character(character_code), end="") 10 | 11 | 12 | class VerboseOutputManager: 13 | def output_number(self, number): 14 | print(f"Outputting number: {str(number)}") 15 | 16 | def output_character(self, character_code): 17 | char = _code_to_character(character_code) 18 | print(f"Outputting character: {repr(char)}") 19 | 20 | 21 | def _code_to_character(character_code): 22 | try: 23 | return chr(character_code) 24 | except ValueError: 25 | raise ShakespeareRuntimeError("Invalid character code: " + str(character_code)) 26 | -------------------------------------------------------------------------------- /shakespearelang/tests/sample_plays/catch.spl: -------------------------------------------------------------------------------- 1 | A Program to Woo the People. 2 | 3 | Tybalt, an orator. 4 | Macbeth, a literary/storage device. 5 | 6 | Act I: The Only Act. 7 | 8 | Scene I: The Prince's Speech. 9 | 10 | [Enter Macbeth and Tybalt] 11 | 12 | Tybalt: Thou art the sum of an amazing healthy honest noble peaceful fine Lord and the sum of a golden king and a lord. Speak your mind! 13 | 14 | Macbeth: Remember me! 15 | 16 | Tybalt: Thou art the sum of thyself and a smelly beggar. Speak your mind! 17 | 18 | Macbeth: Thou art the sum of a warm healthy amazing noble peaceful fine lord and the sum of a golden peaceful smooth amazing king and a golden healthy lord. Speak your mind! 19 | 20 | Macbeth: Recall your imminent demise! Speak your mind! 21 | 22 | Tybalt: You are as good as the sum of a golden warm healthy amazing noble fine lord and a golden good healthy king! Speak your mind! 23 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: shakespearelang 3 | site_url: https://shakespearelang.com 4 | site_author: Zeb Burke-Conte 5 | 6 | # Repository 7 | repo_url: https://github.com/zmbc/shakespearelang 8 | repo_name: zmbc/shakespearelang 9 | edit_uri: edit/main/docs/ 10 | nav: 11 | - Getting started: index.md 12 | - Command line usage reference: CLI.md 13 | - Python API reference: API.md 14 | theme: 15 | name: material 16 | custom_dir: mkdocs_overrides 17 | palette: 18 | primary: teal 19 | plugins: 20 | - mkdocstrings: 21 | default_handler: python 22 | handlers: 23 | python: 24 | rendering: 25 | show_root_heading: true 26 | members_order: source 27 | watch: 28 | - shakespearelang 29 | extra: 30 | version: 31 | provider: mike 32 | markdown_extensions: 33 | - mkdocs-click 34 | - toc: 35 | permalink: "#" 36 | -------------------------------------------------------------------------------- /shakespearelang/tests/utils.py: -------------------------------------------------------------------------------- 1 | # pexpect helpers 2 | def expect_interaction(cli, to_send, to_receive, prompt=True): 3 | cli.sendline(to_send) 4 | full_output = "" 5 | if to_receive: 6 | full_output = to_receive + "\n" 7 | if prompt: 8 | full_output = full_output + ">> " 9 | expect_output_exactly(cli, full_output) 10 | 11 | 12 | def expect_output_exactly(cli, output, eof=False): 13 | output = output.replace("\n", "\r\n") 14 | output_index = 0 15 | while output_index < len(output): 16 | output_received = cli.read_nonblocking(len(output) - output_index).decode( 17 | "utf-8" 18 | ) 19 | expected_output_batch = output[ 20 | output_index : (output_index + len(output_received)) 21 | ] 22 | assert output_received == expected_output_batch 23 | output_index = output_index + len(output_received) 24 | 25 | if eof: 26 | assert cli.read().decode("utf-8") == "" 27 | 28 | def create_play_file(path, contents): 29 | with open(path, "w") as f: 30 | f.write(contents) 31 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "shakespearelang" 3 | version = "1.0.0" 4 | description = "An interpreter for the Shakespeare Programming Language." 5 | authors = ["Zeb Burke-Conte "] 6 | readme = "README.rst" 7 | repository = "http://github.com/zmbc/shakespearelang" 8 | homepage = "http://shakespearelang.com" 9 | license = "MIT" 10 | classifiers = [ 11 | "Development Status :: 5 - Production/Stable", 12 | "Topic :: Software Development :: Interpreters", 13 | ] 14 | 15 | [tool.poetry.dependencies] 16 | python = "^3.8" 17 | click = "^7.1.2" 18 | tatsu = "~5.6.1" 19 | 20 | [tool.poetry.dev-dependencies] 21 | mkdocs = "^1.2.3" 22 | mkdocs-material = "^8.0.0" 23 | mkdocstrings = "^0.16.2" 24 | pytest = "^6.2.1" 25 | tox = "^3.20.1" 26 | coverage = "^5.3.1" 27 | black = "^21.9b0" 28 | snakeviz = "^2.1.1" 29 | pexpect = "^4.8.0" 30 | mkdocs-click = "^0.5.0" 31 | mike = "^1.1.2" 32 | 33 | [tool.poetry.scripts] 34 | shakespeare = "shakespearelang.cli:main" 35 | 36 | [build-system] 37 | requires = ["poetry-core>=1.0.0"] 38 | build-backend = "poetry.core.masonry.api" 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Zeb Burke-Conte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Environment setup 2 | 3 | This project uses [Poetry](https://python-poetry.org/) for managing dependencies 4 | and virtual environments. Once you've installed Poetry, simply run `poetry install` 5 | in the root directory of this git repository. 6 | 7 | ## Tests 8 | 9 | Run the tests with `poetry run pytest`. The CI runs the tests on all Python 10 | versions that shakespearelang supports (currently 3.8+). To do the 11 | same on your local machine, run `poetry run tox` -- you must have all applicable 12 | Python versions installed at the system level. 13 | 14 | ## Code formatting 15 | 16 | This project uses [black](https://black.readthedocs.io/en/stable/index.html) to 17 | enforce consistent code formatting. Make sure to run this after making code changes. 18 | 19 | ## Grammar changes 20 | 21 | If you make changes to [shakespearelang/shakespeare.ebnf](https://github.com/zmbc/shakespearelang/blob/main/shakespearelang/shakespeare.ebnf), you must run 22 | `./scripts/compile_grammar.sh` before the changes will be reflected in the AST. 23 | That's because shakespearelang uses [TatSu](https://tatsu.readthedocs.io/en/stable/) 24 | to compile the EBNF to Python. 25 | -------------------------------------------------------------------------------- /shakespearelang/tests/sample_plays/reverse.spl: -------------------------------------------------------------------------------- 1 | Outputting Input Reversedly. 2 | 3 | Othello, a stacky man. 4 | Lady Macbeth, who pushes him around till he pops. 5 | 6 | 7 | Act I: The one and only. 8 | 9 | Scene I: In the beginning, there was nothing. 10 | 11 | [Enter Othello and Lady Macbeth] 12 | 13 | Othello: 14 | You are nothing! 15 | 16 | Scene II: Pushing to the very end. 17 | 18 | Lady Macbeth: 19 | Open your mind! Remember yourself. 20 | 21 | Othello: 22 | You are as hard as the sum of yourself and a stone wall. Am I as 23 | horrid as a flirt-gill? 24 | 25 | [A pause] 26 | 27 | Lady Macbeth: 28 | If not, let us return to scene II. Recall your imminent death! 29 | 30 | Othello: 31 | You are as small as the difference between yourself and a hair! 32 | 33 | Scene III: Once you pop, you can't stop! 34 | 35 | Lady Macbeth: 36 | Recall your unhappy childhood. Speak your mind! 37 | 38 | Othello: 39 | You are as vile as the sum of yourself and a toad! Are you better 40 | than nothing? 41 | 42 | Lady Macbeth: 43 | If so, let us return to scene III. 44 | 45 | Scene IV: The end. 46 | 47 | [Exeunt] 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # celery beat schedule file 73 | celerybeat-schedule 74 | 75 | # dotenv 76 | .env 77 | 78 | # virtualenv 79 | venv/ 80 | ENV/ 81 | 82 | # Spyder project settings 83 | .spyderproject 84 | 85 | # Rope project settings 86 | .ropeproject 87 | 88 | .pytest_cache 89 | 90 | site 91 | profilestats 92 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | # Triggers the workflow on push or pull request events but only for the main branch 6 | push: 7 | branches: [ main ] 8 | pull_request: 9 | branches: [ main ] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | jobs: 15 | style: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: RojerGS/python-black-check@master 19 | test: 20 | runs-on: ubuntu-latest 21 | strategy: 22 | max-parallel: 4 23 | matrix: 24 | python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] 25 | 26 | steps: 27 | - uses: actions/checkout@v1 28 | - name: Set up Python ${{ matrix.python-version }} 29 | uses: actions/setup-python@v2 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | - name: Install 33 | run: | 34 | python -m pip install --upgrade pip 35 | pip install . 36 | pip install pytest==6.2.5 pexpect==4.8.0 coverage==5.3.1 37 | - name: Run tests 38 | run: pytest 39 | - name: Run tests with coverage 40 | if: ${{ matrix.python-version == '3.10' }} 41 | run: coverage run -m pytest shakespearelang/tests/ 42 | - uses: paambaati/codeclimate-action@v2.7.5 43 | if: ${{ matrix.python-version == '3.10' }} 44 | env: 45 | CC_TEST_REPORTER_ID: 5822386b50d7a5144fa9ea28cf5f0328d2f9df1fa7bde4a3ed6a82a6ac7f4ca8 46 | with: 47 | coverageCommand: coverage xml 48 | debug: true 49 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_runtime_error_format.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from shakespearelang.errors import ShakespeareRuntimeError 3 | import pytest 4 | import textwrap 5 | 6 | ERROR_PLAY = """ 7 | A comedy of errors. 8 | 9 | Romeo, a player. 10 | Juliet, a player. 11 | 12 | Act I: All the World. 13 | 14 | Scene I: A Stage. 15 | 16 | [Enter Romeo and Juliet] 17 | 18 | Romeo: You are a pig. Recall your mind! 19 | 20 | Recall your mind! 21 | 22 | [Exeunt] 23 | 24 | """ 25 | 26 | 27 | def test_full_error_format(): 28 | s = Shakespeare(ERROR_PLAY) 29 | with pytest.raises(ShakespeareRuntimeError) as exc: 30 | s.run() 31 | assert str(exc.value) == textwrap.dedent( 32 | """\ 33 | SPL runtime error: Tried to pop from an empty stack. 34 | at line 13 35 | ----- context ----- 36 | Scene I: A Stage. 37 | 38 | [Enter Romeo and Juliet] 39 | 40 | Romeo: You are a pig. >>Recall your mind!<< 41 | 42 | Recall your mind! 43 | 44 | [Exeunt] 45 | 46 | ----- state ----- 47 | global boolean = False 48 | on stage: 49 | Romeo = 0 () 50 | Juliet = -1 () 51 | off stage:""" 52 | ) 53 | 54 | 55 | def test_error_format_without_anything(): 56 | # This really shouldn't happen. But if it does, we at least don't want anything 57 | # *else* to blow up. 58 | 59 | with pytest.raises(ShakespeareRuntimeError) as exc: 60 | raise ShakespeareRuntimeError("How did this happen?") 61 | 62 | assert str(exc.value) == "SPL runtime error: How did this happen?" 63 | -------------------------------------------------------------------------------- /shakespearelang/_preprocess.py: -------------------------------------------------------------------------------- 1 | from ._operation import operations_from_event 2 | from .errors import ShakespeareRuntimeError 3 | from tatsu.ast import AST 4 | 5 | 6 | class Play: 7 | def __init__(self, ast: AST): 8 | self.operations = [] 9 | self.act_indices = [] 10 | self.scene_indices = {} 11 | self._preprocess(ast) 12 | 13 | def _preprocess(self, ast: AST): 14 | for act in ast.acts: 15 | act_number = act.number.value 16 | if act_number in self.scene_indices: 17 | raise ShakespeareRuntimeError( 18 | f"Act numeral {act_number} is not unique", 19 | parseinfo=act.number.parseinfo, 20 | ) 21 | self.act_indices.append((act_number, len(self.operations))) 22 | self.scene_indices[act_number] = {} 23 | for scene in act.scenes: 24 | scene_number = scene.number.value 25 | if scene_number in self.scene_indices[act_number]: 26 | raise ShakespeareRuntimeError( 27 | f"Scene numeral {scene_number} is not unique in {act_number}", 28 | parseinfo=scene.number.parseinfo, 29 | ) 30 | self.scene_indices[act_number][scene_number] = len(self.operations) 31 | for event in scene.events: 32 | self.operations += operations_from_event(event) 33 | 34 | def get_act(self, position: int): 35 | i = 0 36 | while i + 1 < len(self.act_indices) and self.act_indices[i + 1][1] <= position: 37 | i = i + 1 38 | return self.act_indices[i][0] 39 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | shakespearelang 2 | =============== 3 | 4 | .. image:: https://codeclimate.com/github/zmbc/shakespearelang/badges/gpa.svg 5 | :target: https://codeclimate.com/github/zmbc/shakespearelang 6 | :alt: Code Climate 7 | 8 | .. image:: https://badge.fury.io/py/shakespearelang.svg 9 | :target: https://badge.fury.io/py/shakespearelang 10 | :alt: PyPI version 11 | 12 | 13 | A friendly interpreter for the Shakespeare Programming Language, implemented in 14 | Python. 15 | 16 | What is the Shakespeare Programming Language? 17 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | The Shakespeare Programming Language (SPL) is a programming language 20 | with source code that looks like Shakespeare's plays. The language is 21 | Turing complete, so theoretically just as powerful as any other 22 | language. It's a lot of fun to write but not very practical. More info can be 23 | found `on Wikipedia`_. 24 | 25 | Note: Shakespeare's actual plays are not valid SPL. SPL does not aim to 26 | provide backwards compatibility with legacy code written ~400 years ago. 27 | 28 | Installation 29 | ^^^^^^^^^^^^ 30 | 31 | .. code-block:: 32 | 33 | pip install shakespearelang 34 | # Or however else you install things. You do you. 35 | 36 | Documentation 37 | ^^^^^^^^^^^^^ 38 | 39 | For more on how to use shakespearelang, see `the docs`_. 40 | 41 | Contributing 42 | ^^^^^^^^^^^^ 43 | 44 | Your contributions would be much appreciated! See `CONTRIBUTING.md`_. 45 | 46 | .. _on Wikipedia: https://en.wikipedia.org/wiki/Shakespeare_Programming_Language 47 | 48 | .. _the docs: https://shakespearelang.com/ 49 | 50 | .. _CONTRIBUTING.md: https://github.com/zmbc/shakespearelang/blob/main/CONTRIBUTING.md 51 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_dramatis_personae.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from shakespearelang.errors import ShakespeareRuntimeError 3 | import pytest 4 | 5 | MANY_CHARACTERS_PLAY = """ 6 | A lot of people. 7 | 8 | Achilles, a test. 9 | Christopher Sly, a test. 10 | Demetrius, a test. 11 | John of Lancaster, a test. 12 | Juliet, a test. 13 | Mistress Overdone, a test. 14 | Romeo, a test. 15 | Stephano, a test. 16 | The Abbot of Westminster, a test. 17 | The Ghost, a test. 18 | Titania, a test. 19 | Vincentio, a test. 20 | """ 21 | 22 | 23 | def test_correct_characters(): 24 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test. The Ghost, a test.") 25 | assert sorted(s.state.characters.keys()) == [ 26 | "Juliet", 27 | "Romeo", 28 | "The Ghost", 29 | ] 30 | 31 | 32 | def test_no_characters(): 33 | s = Shakespeare("Foo. Act I: The beginning.") 34 | assert s.state.characters == {} 35 | 36 | 37 | def test_many_characters(): 38 | s = Shakespeare(MANY_CHARACTERS_PLAY) 39 | assert sorted(s.state.characters.keys()) == [ 40 | "Achilles", 41 | "Christopher Sly", 42 | "Demetrius", 43 | "John of Lancaster", 44 | "Juliet", 45 | "Mistress Overdone", 46 | "Romeo", 47 | "Stephano", 48 | "The Abbot of Westminster", 49 | "The Ghost", 50 | "Titania", 51 | "Vincentio", 52 | ] 53 | 54 | 55 | def test_duplicate_characters(): 56 | with pytest.raises(ShakespeareRuntimeError) as exc: 57 | Shakespeare("Foo. Juliet, a test. Juliet, also a test.") 58 | assert "already initialized" in str(exc.value).lower() 59 | assert ">>Juliet, also a test.<<" in str(exc.value) 60 | assert exc.value.interpreter == None 61 | -------------------------------------------------------------------------------- /shakespearelang/settings.py: -------------------------------------------------------------------------------- 1 | from ._input import BasicInputManager, InteractiveInputManager 2 | from ._output import BasicOutputManager, VerboseOutputManager 3 | 4 | 5 | class Settings: 6 | """ 7 | The settings of a Shakespeare interpreter. Controls how and when the interpreter 8 | does input and output. 9 | """ 10 | 11 | _INPUT_MANAGERS = { 12 | "basic": BasicInputManager, 13 | "interactive": InteractiveInputManager, 14 | } 15 | 16 | _OUTPUT_MANAGERS = { 17 | "basic": BasicOutputManager, 18 | "verbose": VerboseOutputManager, 19 | "debug": VerboseOutputManager, 20 | } 21 | 22 | def __init__(self, input_style, output_style): 23 | self.input_style = input_style 24 | self.output_style = output_style 25 | 26 | @property 27 | def input_style(self): 28 | """ 29 | Input style of the interpreter. 'basic' is the best for piped input. 30 | 'interactive' is nicer when getting input from a human. 31 | """ 32 | return self._input_style 33 | 34 | @input_style.setter 35 | def input_style(self, value): 36 | if value not in self._INPUT_MANAGERS: 37 | raise ValueError("Unknown input style") 38 | 39 | self.input_manager = self._INPUT_MANAGERS[value]() 40 | self._input_style = value 41 | 42 | @property 43 | def output_style(self): 44 | """ 45 | Output style of the interpreter. 'basic' outputs exactly what the SPL play generated. 46 | 'verbose' prefixes output and shows visible representations of 47 | whitespace characters. 'debug' is like 'verbose' but with debug output 48 | from the interpreter. 49 | """ 50 | return self._output_style 51 | 52 | @output_style.setter 53 | def output_style(self, value): 54 | if value not in self._OUTPUT_MANAGERS: 55 | raise ValueError("Unknown output style") 56 | 57 | self.output_manager = self._OUTPUT_MANAGERS[value]() 58 | self._output_style = value 59 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_numeric_output.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from io import StringIO 3 | import pytest 4 | 5 | 6 | def test_outputs_numbers(capsys): 7 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 8 | s.run_event("[Enter Romeo and Juliet]") 9 | 10 | s.state.character_by_name("Romeo").value = 4100 11 | s.run_sentence("Open your heart!", "Juliet") 12 | captured = capsys.readouterr() 13 | assert captured.out == "4100" 14 | assert captured.err == "" 15 | 16 | s.state.character_by_name("Romeo").value = -5 17 | s.run_sentence("Open your heart!", "Juliet") 18 | captured = capsys.readouterr() 19 | assert captured.out == "-5" 20 | assert captured.err == "" 21 | 22 | s.state.character_by_name("Romeo").value = 9 23 | s.run_sentence("Open your heart!", "Juliet") 24 | captured = capsys.readouterr() 25 | assert captured.out == "9" 26 | assert captured.err == "" 27 | 28 | 29 | def test_conditional(capsys): 30 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 31 | s.run_event("[Enter Romeo and Juliet]") 32 | 33 | s.state.character_by_name("Romeo").value = 4100 34 | s.state.global_boolean = False 35 | s.run_sentence("If so, open your heart!", "Juliet") 36 | captured = capsys.readouterr() 37 | assert captured.out == "" 38 | assert captured.err == "" 39 | 40 | s.state.global_boolean = True 41 | s.run_sentence("If not, open your heart!", "Juliet") 42 | captured = capsys.readouterr() 43 | assert captured.out == "" 44 | assert captured.err == "" 45 | 46 | s.state.global_boolean = True 47 | s.run_sentence("If so, open your heart!", "Juliet") 48 | captured = capsys.readouterr() 49 | assert captured.out == "4100" 50 | assert captured.err == "" 51 | 52 | s.state.character_by_name("Romeo").value = -5 53 | s.state.global_boolean = False 54 | s.run_sentence("If not, open your heart!", "Juliet") 55 | captured = capsys.readouterr() 56 | assert captured.out == "-5" 57 | assert captured.err == "" 58 | -------------------------------------------------------------------------------- /shakespearelang/_input.py: -------------------------------------------------------------------------------- 1 | from .errors import ShakespeareRuntimeError 2 | import sys 3 | 4 | 5 | class BasicInputManager: 6 | def __init__(self): 7 | self._input_buffer = "" 8 | 9 | def consume_numeric_input(self): 10 | try: 11 | self._ensure_input_buffer() 12 | except EOFError: 13 | raise ShakespeareRuntimeError("End of file encountered.") from None 14 | 15 | number = self._consume_digits() 16 | self._consume_newline_if_present() 17 | 18 | return number 19 | 20 | def _consume_newline_if_present(self): 21 | if self._input_buffer and self._input_buffer[0] == "\n": 22 | self._input_buffer = self._input_buffer[1:] 23 | 24 | def _consume_digits(self): 25 | number_input = "" 26 | while self._input_buffer and self._input_buffer[0].isdigit(): 27 | number_input += self._input_buffer[0] 28 | self._input_buffer = self._input_buffer[1:] 29 | 30 | if len(number_input) == 0: 31 | raise ShakespeareRuntimeError("No numeric input was given.") 32 | 33 | return int(number_input) 34 | 35 | def consume_character_input(self): 36 | try: 37 | self._ensure_input_buffer() 38 | except EOFError: 39 | return -1 40 | 41 | value = ord(self._input_buffer[0]) 42 | self._input_buffer = self._input_buffer[1:] 43 | return value 44 | 45 | def _ensure_input_buffer(self): 46 | if not self._input_buffer: 47 | # We want all output that has already happened to appear before we 48 | # ask the user for input 49 | sys.stdout.flush() 50 | self._input_buffer = sys.stdin.readline() 51 | if not self._input_buffer: 52 | raise EOFError() 53 | 54 | 55 | class InteractiveInputManager: 56 | def consume_numeric_input(self): 57 | try: 58 | value = int(input("Taking input number: ")) 59 | except ValueError: 60 | raise ShakespeareRuntimeError("No numeric input was given.") 61 | 62 | return value 63 | 64 | def consume_character_input(self): 65 | value = input("Taking input character: ") 66 | if value == "EOF": 67 | return -1 68 | elif value == "": 69 | return ord("\n") 70 | else: 71 | return ord(value[0]) 72 | -------------------------------------------------------------------------------- /shakespearelang/tests/sample_plays/primes.spl: -------------------------------------------------------------------------------- 1 | Prime Number Computation in Copenhagen. 2 | 3 | Romeo, a young man of Verona. 4 | Juliet, a young woman. 5 | Hamlet, a temporary variable from Denmark. 6 | The Ghost, a limiting factor (and by a remarkable coincidence also 7 | Hamlet's father). 8 | 9 | 10 | Act I: Interview with the other side. 11 | 12 | Scene I: At the last hour before dawn. 13 | 14 | [Enter the Ghost and Juliet] 15 | 16 | The Ghost: 17 | You pretty little warm thing! Thou art as prompt as the difference 18 | between the square of thyself and your golden hair. Speak your mind. 19 | 20 | Juliet: 21 | Listen to your heart! 22 | 23 | [Exit the Ghost] 24 | 25 | [Enter Romeo] 26 | 27 | Juliet: 28 | Thou art as sweet as a sunny summer's day! 29 | 30 | 31 | Act II: Determining divisibility. 32 | 33 | Scene I: A private conversation. 34 | 35 | Juliet: 36 | Art thou more cunning than the Ghost? 37 | 38 | Romeo: 39 | If so, let us proceed to scene V. 40 | 41 | [Exit Romeo] 42 | 43 | [Enter Hamlet] 44 | 45 | Juliet: 46 | You are as villainous as the square root of Romeo! 47 | 48 | Hamlet: 49 | You are as lovely as a red rose. 50 | 51 | Scene II: Questions and the consequences thereof. 52 | 53 | Juliet: 54 | Am I better than you? 55 | 56 | Hamlet: 57 | If so, let us proceed to scene III. 58 | 59 | Juliet: 60 | Is the remainder of the quotient between Romeo and me as good as 61 | nothing? 62 | 63 | Hamlet: 64 | If so, let us proceed to scene IV. 65 | Thou art as bold as the sum of thyself and a roman. 66 | 67 | Juliet: 68 | Let us return to scene II. 69 | 70 | Scene III: Romeo must die! 71 | 72 | [Exit Hamlet] 73 | 74 | [Enter Romeo] 75 | 76 | Juliet: 77 | Open your heart. 78 | 79 | [Exit Juliet] 80 | 81 | [Enter Hamlet] 82 | 83 | Romeo: 84 | Thou art as rotten as the difference between nothing and the sum of a 85 | snotty stinking half-witted hog and a small toad! 86 | Speak your mind! 87 | 88 | [Exit Romeo] 89 | 90 | [Enter Juliet] 91 | 92 | Scene IV: One small dog at a time. 93 | 94 | [Exit Hamlet] 95 | 96 | [Enter Romeo] 97 | 98 | Juliet: 99 | Thou art as handsome as the sum of thyself and my chihuahua! 100 | Let us return to scene I. 101 | 102 | Scene V: Fin. 103 | 104 | [Exeunt] 105 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_sample_plays.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from io import StringIO 3 | from pathlib import Path 4 | 5 | 6 | def test_runs_hi(capsys): 7 | run_sample_play("hi.spl") 8 | 9 | assert_output(capsys, "HI\n") 10 | 11 | 12 | def test_runs_hello_world(capsys): 13 | run_sample_play("hello_world.spl") 14 | 15 | assert_output(capsys, "Hello World!\n") 16 | 17 | 18 | def test_runs_catch(capsys): 19 | run_sample_play("catch.spl") 20 | 21 | assert_output(capsys, "CATCH") 22 | 23 | 24 | def test_runs_echo(monkeypatch, capsys): 25 | set_input(monkeypatch, "m123cfoobar123") 26 | run_sample_play("echo.spl") 27 | 28 | assert_output(capsys, "m123c") 29 | 30 | 31 | def test_runs_primes(monkeypatch, capsys): 32 | set_input(monkeypatch, "20") 33 | run_sample_play("primes.spl") 34 | 35 | assert_output(capsys, ">2\n3\n5\n7\n11\n13\n17\n19\n") 36 | 37 | 38 | def test_runs_reverse(monkeypatch, capsys): 39 | set_input(monkeypatch, "first\nsecond\nthird") 40 | run_sample_play("reverse.spl") 41 | 42 | assert_output(capsys, "driht\ndnoces\ntsrif") 43 | 44 | 45 | def test_runs_sierpinski(monkeypatch, capsys): 46 | set_input(monkeypatch, "4") 47 | run_sample_play("sierpinski.spl") 48 | 49 | assert_output( 50 | capsys, 51 | """\ 52 | > * \n\ 53 | * * \n\ 54 | * * \n\ 55 | * * * * \n\ 56 | * * \n\ 57 | * * * * \n\ 58 | * * * * \n\ 59 | * * * * * * * * \n\ 60 | * * \n\ 61 | * * * * \n\ 62 | * * * * \n\ 63 | * * * * * * * * \n\ 64 | * * * * \n\ 65 | * * * * * * * * \n\ 66 | * * * * * * * * \n\ 67 | * * * * * * * * * * * * * * * *\n\ 68 | """, 69 | ) 70 | 71 | 72 | def test_runs_parse_everything(monkeypatch, capsys): 73 | set_input(monkeypatch, "45c") 74 | run_sample_play("parse_everything.spl") 75 | assert_output(capsys, "72H") 76 | 77 | 78 | def assert_output(capsys, output, stderr=""): 79 | captured = capsys.readouterr() 80 | assert captured.out == output 81 | assert captured.err == stderr 82 | 83 | 84 | def set_input(monkeypatch, input): 85 | monkeypatch.setattr("sys.stdin", StringIO(input)) 86 | 87 | 88 | def run_sample_play(filename): 89 | path = Path(__file__).parent / f"sample_plays/{filename}" 90 | with path.open() as f: 91 | Shakespeare(f.read()).run() 92 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_input_styles.py: -------------------------------------------------------------------------------- 1 | import pexpect 2 | from .utils import expect_interaction, expect_output_exactly, create_play_file 3 | from textwrap import dedent 4 | 5 | NUMERIC_INPUT = """ 6 | A Gathering. 7 | 8 | Hamlet, a literary/storage device. 9 | Juliet, an orator. 10 | 11 | Act I: The Only Act. 12 | 13 | Scene I: The Listening. 14 | 15 | [Enter Hamlet and Juliet] 16 | 17 | Juliet: Listen to your heart! Open your heart! 18 | 19 | Juliet: Listen to your heart! Open your heart! 20 | 21 | [Exeunt] 22 | """ 23 | 24 | CHARACTER_INPUT = """ 25 | A Gathering. 26 | 27 | Hamlet, a literary/storage device. 28 | Juliet, an orator. 29 | 30 | Act I: The Only Act. 31 | 32 | Scene I: The Listening. 33 | 34 | [Enter Hamlet and Juliet] 35 | 36 | Juliet: Open your mind! Open your heart! 37 | 38 | Juliet: Open your mind! Open your heart! 39 | 40 | [Exeunt] 41 | """ 42 | 43 | 44 | def test_piped_input_numeric(tmp_path): 45 | file_path = tmp_path / "play.spl" 46 | create_play_file(file_path, NUMERIC_INPUT) 47 | cli = pexpect.spawn(f'/bin/bash -c "printf \'1234\\n3112\' | shakespeare run {file_path} --input-style=basic"') 48 | cli.setecho(False) 49 | cli.waitnoecho() 50 | 51 | expect_output_exactly(cli, "12343112", eof=True) 52 | 53 | def test_interactive_input_numeric(tmp_path): 54 | file_path = tmp_path / "play.spl" 55 | create_play_file(file_path, NUMERIC_INPUT) 56 | cli = pexpect.spawn(f"shakespeare run {file_path} --input-style=interactive") 57 | cli.setecho(False) 58 | cli.waitnoecho() 59 | 60 | expect_output_exactly(cli, "Taking input number: ") 61 | cli.sendline("1234") 62 | expect_output_exactly(cli, "1234Taking input number: ") 63 | cli.sendline("3112") 64 | expect_output_exactly(cli, "3112", eof=True) 65 | 66 | def test_piped_input_character(tmp_path): 67 | file_path = tmp_path / "play.spl" 68 | create_play_file(file_path, CHARACTER_INPUT) 69 | cli = pexpect.spawn(f'/bin/bash -c "echo c | shakespeare run {file_path} --input-style=basic"') 70 | cli.setecho(False) 71 | cli.waitnoecho() 72 | 73 | expect_output_exactly(cli, "9910", eof=True) 74 | 75 | def test_interactive_input_character(tmp_path): 76 | file_path = tmp_path / "play.spl" 77 | create_play_file(file_path, CHARACTER_INPUT) 78 | cli = pexpect.spawn(f"shakespeare run {file_path} --input-style=interactive") 79 | cli.setecho(False) 80 | cli.waitnoecho() 81 | 82 | expect_output_exactly(cli, "Taking input character: ") 83 | cli.sendline("") 84 | expect_output_exactly(cli, "10Taking input character: ") 85 | cli.sendline("c") 86 | expect_output_exactly(cli, "99", eof=True) 87 | -------------------------------------------------------------------------------- /shakespearelang/tests/sample_plays/hello_world.spl: -------------------------------------------------------------------------------- 1 | The Infamous Hello World Program. 2 | 3 | Romeo, a young man with a remarkable patience. 4 | Juliet, a likewise young woman of remarkable grace. 5 | Ophelia, a remarkable woman much in dispute with Hamlet. 6 | Hamlet, the flatterer of Andersen Insulting A/S. 7 | 8 | 9 | Act I: Hamlet's insults and flattery. 10 | 11 | Scene I: The insulting of Romeo. 12 | 13 | [Enter Hamlet and Romeo] 14 | 15 | Hamlet: 16 | You lying stupid fatherless big smelly half-witted coward! 17 | You are as stupid as the difference between a handsome rich brave 18 | hero and thyself! Speak your mind! 19 | 20 | You are as brave as the sum of your fat little stuffed misused dusty 21 | old rotten codpiece and a beautiful fair warm peaceful sunny summer's 22 | day. You are as healthy as the difference between the sum of the 23 | sweetest reddest rose and my father and yourself! Speak your mind! 24 | 25 | You are as cowardly as the sum of yourself and the difference 26 | between a big mighty proud kingdom and a horse. Speak your mind. 27 | 28 | Speak your mind! 29 | 30 | [Exit Romeo] 31 | 32 | Scene II: The praising of Juliet. 33 | 34 | [Enter Juliet] 35 | 36 | Hamlet: 37 | Thou art as sweet as the sum of the sum of Romeo and his horse and his 38 | black cat! Speak thy mind! 39 | 40 | [Exit Juliet] 41 | 42 | Scene III: The praising of Ophelia. 43 | 44 | [Enter Ophelia] 45 | 46 | Hamlet: 47 | Thou art as lovely as the product of a large rural town and my amazing 48 | bottomless embroidered purse. Speak thy mind! 49 | 50 | Thou art as loving as the product of the bluest clearest sweetest sky 51 | and the sum of a squirrel and a white horse. Thou art as beautiful as 52 | the difference between Juliet and thyself. Speak thy mind! 53 | 54 | [Exeunt Ophelia and Hamlet] 55 | 56 | 57 | Act II: Behind Hamlet's back. 58 | 59 | Scene I: Romeo and Juliet's conversation. 60 | 61 | [Enter Romeo and Juliet] 62 | 63 | Romeo: 64 | Speak your mind. You are as worried as the sum of yourself and the 65 | difference between my small smooth hamster and my nose. Speak your 66 | mind! 67 | 68 | Juliet: 69 | Speak YOUR mind! You are as bad as Hamlet! You are as small as the 70 | difference between the square of the difference between my little pony 71 | and your big hairy hound and the cube of your sorry little 72 | codpiece. Speak your mind! 73 | 74 | [Exit Romeo] 75 | 76 | Scene II: Juliet and Ophelia's conversation. 77 | 78 | [Enter Ophelia] 79 | 80 | Juliet: 81 | Thou art as good as the quotient between Romeo and the sum of a small 82 | furry animal and a leech. Speak your mind! 83 | 84 | Ophelia: 85 | Thou art as disgusting as the quotient between Romeo and twice the 86 | difference between a mistletoe and an oozing infected blister! Speak 87 | your mind! 88 | 89 | [Exeunt] 90 | -------------------------------------------------------------------------------- /shakespearelang/_utils.py: -------------------------------------------------------------------------------- 1 | def normalize_name(name): 2 | if not isinstance(name, str): 3 | name = " ".join(name) 4 | return name.title().replace(" Of ", " of ") 5 | 6 | 7 | def pos_context(pos, tokenizer, context_amount=3): 8 | line = tokenizer.posline(pos) 9 | col = tokenizer.poscol(pos) 10 | before_context_lines = _before_context_lines(tokenizer, line, context_amount) 11 | pos_highlighted_lines = _pos_highlighted_lines(tokenizer, line, col) 12 | after_context_lines = _after_context_lines(tokenizer, line, context_amount) 13 | return "".join(before_context_lines + pos_highlighted_lines + after_context_lines) 14 | 15 | 16 | def parseinfo_context(parseinfo, context_amount=3): 17 | before_context_lines = _before_context_lines( 18 | parseinfo.tokenizer, parseinfo.line, context_amount 19 | ) 20 | parsed_item_lines = _parsed_item_lines(parseinfo) 21 | after_context_lines = _after_context_lines( 22 | parseinfo.tokenizer, parseinfo.endline, context_amount 23 | ) 24 | return "".join(before_context_lines + parsed_item_lines + after_context_lines) 25 | 26 | 27 | def _add_str_at_before_whitespace(string, character, index): 28 | while string[index - 1].isspace(): 29 | index = index - 1 30 | return _add_str_at(string, character, index) 31 | 32 | 33 | def _add_str_at(string, character, index): 34 | return string[:index] + character + string[index:] 35 | 36 | 37 | def _parsed_item_lines(parseinfo): 38 | tokenizer = parseinfo.tokenizer 39 | number_of_lines = (parseinfo.endline - parseinfo.line) + 1 40 | lines = tokenizer.get_lines(parseinfo.line, parseinfo.endline) 41 | 42 | end_column = tokenizer.poscol(parseinfo.endpos) 43 | if len(lines) < number_of_lines: 44 | end_column = len(lines[-1]) 45 | 46 | # Must insert later characters first; if you start with earlier characters, they change 47 | # the indices for later inserts. 48 | lines[-1] = _add_str_at_before_whitespace(lines[-1], "<<", end_column) 49 | lines[0] = _add_str_at(lines[0], ">>", tokenizer.poscol(parseinfo.pos)) 50 | 51 | return lines 52 | 53 | 54 | def _pos_highlighted_lines(tokenizer, line, col): 55 | line_list = tokenizer.get_lines(line, line + 1) 56 | if len(line_list) == 0: 57 | line_str = "" 58 | else: 59 | line_str = line_list[0] 60 | 61 | return [ 62 | (" " * col) + "∨\n", 63 | _ensure_ends_with_newline(line_str), 64 | (" " * col) + "∧\n", 65 | ] 66 | 67 | 68 | def _before_context_lines(tokenizer, line, context_amount=3): 69 | context_start_line = max(line - 1 - context_amount, 0) 70 | lines = tokenizer.get_lines(context_start_line, line - 1) 71 | return [_ensure_ends_with_newline(l) for l in lines] 72 | 73 | 74 | def _after_context_lines(tokenizer, endline, context_amount=3): 75 | lines = tokenizer.get_lines(endline + 1, endline + 1 + context_amount) 76 | return [_ensure_ends_with_newline(l) for l in lines] 77 | 78 | 79 | def _ensure_ends_with_newline(line_str): 80 | if not line_str.endswith("\n"): 81 | return line_str + "\n" 82 | else: 83 | return line_str 84 | -------------------------------------------------------------------------------- /shakespearelang/errors.py: -------------------------------------------------------------------------------- 1 | from ._utils import parseinfo_context, pos_context 2 | 3 | 4 | class ShakespeareError(Exception): 5 | """ 6 | The base class for errors caused by problems in Shakespeare Programming 7 | Language code. 8 | """ 9 | 10 | pass 11 | 12 | 13 | class ShakespeareParseError(ShakespeareError): 14 | """ 15 | An error caused by malformed Shakespeare Programming Language code. Inherits 16 | from [ShakespeareError][shakespearelang.ShakespeareError]. 17 | """ 18 | 19 | # Wraps the TatSu FailedParse exception, to inherit from ShakespeareError, make 20 | # the display more consistent with runtime errors, and hide the Python stack 21 | # traces. 22 | def __init__(self, failed_parse_exception): 23 | self.message = failed_parse_exception.message 24 | self.tokenizer = failed_parse_exception.tokenizer 25 | self.pos = failed_parse_exception.pos 26 | self.stack = failed_parse_exception.stack 27 | 28 | def __str__(self): 29 | entity_name = "" 30 | if len(self.stack) > 0: 31 | entity_name = self.stack[-1] 32 | return "\n".join( 33 | [f"SPL parse error: failed to parse {entity_name}"] 34 | + self._context_str_lines() 35 | + self._details_str_lines() 36 | ) 37 | 38 | def _context_str_lines(self): 39 | # TatSu reports lines zero-indexed 40 | line = self.tokenizer.posline(self.pos) + 1 41 | if self.pos == 0: 42 | # TatSu still says line 1 in this case 43 | line = 1 44 | return [ 45 | f" at line {line}", 46 | "----- context -----", 47 | pos_context(self.pos, self.tokenizer), 48 | ] 49 | 50 | def _details_str_lines(self): 51 | return [ 52 | "----- details -----", 53 | f"parsing stack: {', '.join(self.stack[::-1])}", 54 | "full error message:", 55 | f" {self.message}", 56 | ] 57 | 58 | 59 | class ShakespeareRuntimeError(ShakespeareError): 60 | """ 61 | An error caused by Shakespeare Programming Language code that is well-formed 62 | but does some illegal operation at runtime. Inherits from 63 | [ShakespeareError][shakespearelang.ShakespeareError]. 64 | """ 65 | 66 | def __init__(self, message, parseinfo=None, interpreter=None): 67 | self.message = message 68 | self.parseinfo = parseinfo 69 | self.interpreter = interpreter 70 | super().__init__() 71 | 72 | def __str__(self): 73 | return "\n".join( 74 | [f"SPL runtime error: {self.message}"] 75 | + self._context_str_lines() 76 | + self._state_str_lines() 77 | ) 78 | 79 | def _context_str_lines(self): 80 | if self.parseinfo is None: 81 | return [] 82 | return [ 83 | f" at line {self.parseinfo.line + 1}", 84 | "----- context -----", 85 | parseinfo_context(self.parseinfo), 86 | ] 87 | 88 | def _state_str_lines(self): 89 | if self.interpreter is None: 90 | return [] 91 | return ["----- state -----", str(self.interpreter.state)] 92 | -------------------------------------------------------------------------------- /shakespearelang/cli.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import click 4 | import sys 5 | from .shakespeare import Shakespeare 6 | from .errors import ShakespeareError 7 | from ._repl import start_console, debug_play 8 | from functools import wraps, partial 9 | 10 | 11 | def pretty_print_shakespeare_errors(func): 12 | @wraps(func) 13 | def wrapper(*args, **kwargs): 14 | try: 15 | func(*args, **kwargs) 16 | except ShakespeareError as e: 17 | print(str(e), file=sys.stderr) 18 | 19 | return wrapper 20 | 21 | 22 | @click.group(invoke_without_command=True) 23 | @click.pass_context 24 | @click.option( 25 | "--characters", 26 | default="Romeo,Juliet", 27 | help="Characters to make available to the console, separated by commas. Default is Romeo and Juliet.", 28 | ) 29 | @pretty_print_shakespeare_errors 30 | def main(ctx, characters): 31 | """ 32 | Run or debug Shakespeare Programming Language plays, or start a console. 33 | 34 | shakespeare alone without a subcommand starts a console. 35 | """ 36 | if ctx.invoked_subcommand is None: 37 | ctx.forward(console) 38 | 39 | 40 | @main.command() 41 | @click.option( 42 | "--characters", 43 | default="Romeo,Juliet", 44 | help="Characters to make available to the console, separated by commas. Default is Romeo and Juliet.", 45 | ) 46 | @pretty_print_shakespeare_errors 47 | def console(characters): 48 | """Run a Shakespeare Programming Language console.""" 49 | start_console(characters.split(",")) 50 | 51 | 52 | @main.command() 53 | @click.argument("file") 54 | @click.option( 55 | "--input-style", 56 | default="basic", 57 | help="Input style to use. 'basic' is the default and best for piped input. 'interactive' is nicer when getting input from a human.", 58 | ) 59 | @click.option( 60 | "--output-style", 61 | default="basic", 62 | help="Output style to use. 'basic' is the default and outputs exactly what the SPL play generated. 'verbose' prefixes output and shows visible representations of whitespace characters. 'debug' is like 'verbose' but with debug output from the interpreter.", 63 | ) 64 | @pretty_print_shakespeare_errors 65 | def run(file, input_style, output_style): 66 | """Execute the Shakespeare Programming Language play located at filepath FILE.""" 67 | with open(file, "r") as f: 68 | play = f.read() 69 | Shakespeare(play, input_style=input_style, output_style=output_style).run() 70 | 71 | 72 | @main.command() 73 | @click.argument("file") 74 | @click.option( 75 | "--input-style", 76 | default="interactive", 77 | help="Input style to use. 'interactive' is the default and nicer when getting input from a human. 'basic' is best for piped input.", 78 | ) 79 | @click.option( 80 | "--output-style", 81 | default="verbose", 82 | help="Output style to use. 'verbose' is the default, prefixes output, and shows visible representations of whitespace characters. 'basic' outputs exactly what the SPL play generated. 'debug' is like 'verbose' but with debug output from the interpreter.", 83 | ) 84 | @pretty_print_shakespeare_errors 85 | def debug(file, input_style, output_style): 86 | """Execute the Shakespeare Programming Language play located at filepath FILE, pausing at breakpoints.""" 87 | with open(file, "r") as f: 88 | play = f.read() 89 | debug_play(play, input_style=input_style, output_style=output_style) 90 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_assignment.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from shakespearelang.errors import ShakespeareRuntimeError 3 | from io import StringIO 4 | import pytest 5 | 6 | 7 | def test_assign_character(capsys): 8 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 9 | s.run_event("[Enter Romeo and Juliet]") 10 | 11 | assert s.state.character_by_name("Romeo").value == 0 12 | s.run_sentence("You are as good as a furry animal!", "Juliet") 13 | assert s.state.character_by_name("Romeo").value == 2 14 | 15 | s.state.character_by_name("Romeo").value = 0 16 | s.run_sentence("You are a pig!", "Juliet") 17 | assert s.state.character_by_name("Romeo").value == -1 18 | captured = capsys.readouterr() 19 | assert captured.out == "" 20 | assert captured.err == "" 21 | 22 | 23 | def test_errors_without_character_opposite(capsys): 24 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test. Macbeth, a test.") 25 | s.run_event("[Enter Juliet]") 26 | 27 | assert s.state.character_by_name("Romeo").value == 0 28 | assert s.state.character_by_name("Macbeth").value == 0 29 | with pytest.raises(ShakespeareRuntimeError) as exc: 30 | s.run_sentence("You are as good as a furry animal!", "Juliet") 31 | assert "talking to nobody" in str(exc.value).lower() 32 | assert ">>You are as good as a furry animal!<<" in str(exc.value) 33 | assert exc.value.interpreter == s 34 | assert s.state.character_by_name("Romeo").value == 0 35 | assert s.state.character_by_name("Macbeth").value == 0 36 | 37 | s.run_event("[Enter Macbeth and Romeo]") 38 | assert s.state.character_by_name("Romeo").value == 0 39 | assert s.state.character_by_name("Macbeth").value == 0 40 | with pytest.raises(ShakespeareRuntimeError) as exc: 41 | s.run_sentence("You are as good as a furry animal!", "Juliet") 42 | assert "ambiguous" in str(exc.value).lower() 43 | assert ">>You are as good as a furry animal!<<" in str(exc.value) 44 | assert exc.value.interpreter == s 45 | assert s.state.character_by_name("Romeo").value == 0 46 | assert s.state.character_by_name("Macbeth").value == 0 47 | 48 | captured = capsys.readouterr() 49 | assert captured.out == "" 50 | assert captured.err == "" 51 | 52 | 53 | def test_conditional(capsys): 54 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 55 | s.run_event("[Enter Romeo and Juliet]") 56 | 57 | assert s.state.character_by_name("Romeo").value == 0 58 | s.state.global_boolean = False 59 | s.run_sentence("If so, you are as good as a furry animal!", "Juliet") 60 | assert s.state.character_by_name("Romeo").value == 0 61 | 62 | assert s.state.character_by_name("Romeo").value == 0 63 | s.state.global_boolean = True 64 | s.run_sentence("If not, you are as good as a furry animal!", "Juliet") 65 | assert s.state.character_by_name("Romeo").value == 0 66 | 67 | assert s.state.character_by_name("Romeo").value == 0 68 | s.state.global_boolean = True 69 | s.run_sentence("If so, you are as good as a furry animal!", "Juliet") 70 | assert s.state.character_by_name("Romeo").value == 2 71 | 72 | assert s.state.character_by_name("Romeo").value == 2 73 | s.state.global_boolean = False 74 | s.run_sentence("If not, you are as good as a furry furry animal!", "Juliet") 75 | assert s.state.character_by_name("Romeo").value == 4 76 | 77 | captured = capsys.readouterr() 78 | assert captured.out == "" 79 | assert captured.err == "" 80 | -------------------------------------------------------------------------------- /shakespearelang/_state.py: -------------------------------------------------------------------------------- 1 | from .errors import ShakespeareRuntimeError 2 | from ._character import Character 3 | from ._utils import normalize_name 4 | 5 | 6 | class State: 7 | """State of a Shakespeare play execution context: variable values and who is on stage.""" 8 | 9 | def __init__(self, personae): 10 | self.global_boolean = False 11 | self.characters = {} 12 | for persona in personae: 13 | name = normalize_name(persona.character) 14 | if name in self.characters: 15 | raise ShakespeareRuntimeError( 16 | f"{name} already initialized", parseinfo=persona.parseinfo 17 | ) 18 | self.characters[name] = Character() 19 | self._characters_on_stage = {} 20 | self._characters_opposite = {} 21 | 22 | def __str__(self): 23 | return "\n".join( 24 | [f"global boolean = {self.global_boolean}", "on stage:"] 25 | + [f" {n} = {c}" for n, c in self._characters_on_stage.items()] 26 | + ["off stage:"] 27 | + [ 28 | f" {n} = {c}" 29 | for n, c in self.characters.items() 30 | if n not in self._characters_on_stage 31 | ] 32 | ) 33 | 34 | def enter_characters(self, characters): 35 | for character_name in characters: 36 | self.assert_character_off_stage(character_name) 37 | for character_name in characters: 38 | self._enter_character(character_name) 39 | 40 | def exeunt_characters(self, characters): 41 | for character_name in characters: 42 | self.assert_character_on_stage(character_name) 43 | for character_name in characters: 44 | self._exit_character(character_name) 45 | 46 | def exeunt_all(self): 47 | for character_name in list(self._characters_on_stage.keys()): 48 | self._exit_character(character_name) 49 | 50 | def exit_character(self, character_name): 51 | self.assert_character_on_stage(character_name) 52 | self._exit_character(character_name) 53 | 54 | def _enter_character(self, character_name): 55 | character = self.characters[character_name] 56 | self._characters_on_stage[character_name] = character 57 | self._update_opposites() 58 | 59 | def _exit_character(self, character_name): 60 | character = self.characters[character_name] 61 | del self._characters_on_stage[character_name] 62 | self._update_opposites() 63 | 64 | def _update_opposites(self): 65 | if len(self._characters_on_stage) != 2: 66 | self._characters_opposite = {} 67 | else: 68 | names = list(self._characters_on_stage.keys()) 69 | self._characters_opposite[names[0]] = names[1] 70 | self._characters_opposite[names[1]] = names[0] 71 | 72 | def character_opposite(self, character_name): 73 | if character_name in self._characters_opposite: 74 | return self._characters_opposite[character_name] 75 | 76 | if character_name not in self._characters_on_stage: 77 | raise ShakespeareRuntimeError(f"{character_name} is not on stage!") 78 | elif len(self._characters_on_stage) > 2: 79 | raise ShakespeareRuntimeError("Ambiguous second-person pronoun") 80 | else: 81 | raise ShakespeareRuntimeError(f"{character_name} is talking to nobody!") 82 | 83 | def character_by_name(self, name): 84 | if name in self.characters: 85 | return self.characters[name] 86 | else: 87 | raise ShakespeareRuntimeError(f"{name} was not initialized!") 88 | 89 | def assert_character_on_stage(self, character_name): 90 | if character_name not in self._characters_on_stage: 91 | if character_name not in self.characters: 92 | raise ShakespeareRuntimeError(f"{character_name} was not initialized!") 93 | else: 94 | raise ShakespeareRuntimeError(f"{character_name} is not on stage!") 95 | 96 | def assert_character_off_stage(self, character_name): 97 | if character_name in self._characters_on_stage: 98 | raise ShakespeareRuntimeError(f"{character_name} is already on stage!") 99 | if character_name not in self.characters: 100 | raise ShakespeareRuntimeError(f"{character_name} was not initialized!") 101 | -------------------------------------------------------------------------------- /shakespearelang/_repl.py: -------------------------------------------------------------------------------- 1 | from .shakespeare import Shakespeare 2 | from .errors import ShakespeareError 3 | from ._utils import normalize_name 4 | from tatsu.exceptions import FailedParse 5 | 6 | try: 7 | import readline 8 | except ModuleNotFoundError: 9 | print("readline module is not supported in Windows, continuing without it.") 10 | 11 | import sys 12 | 13 | 14 | DEFAULT_PLAY_TEMPLATE = """ 15 | A REPL-tastic Adventure. 16 | 17 | 18 | 19 | Act I: All the World. 20 | Scene I: A Stage. 21 | 22 | [Enter ] 23 | """ 24 | 25 | 26 | def start_console(characters=["Romeo", "Juliet"]): 27 | dramatis_personae = "\n".join([name + ", a player." for name in characters]) 28 | entrance_list = _entrance_list_from_characters(characters) 29 | play = DEFAULT_PLAY_TEMPLATE.replace( 30 | "", dramatis_personae 31 | ).replace("", entrance_list) 32 | 33 | print(play) 34 | interpreter = Shakespeare(play) 35 | # Run the entrance 36 | interpreter.step_forward() 37 | run_repl(interpreter) 38 | 39 | 40 | # E.g. ["Mercutio", "Romeo", "Tybalt"] => "Mercutio, Romeo and Tybalt" 41 | def _entrance_list_from_characters(characters): 42 | all_commas = ", ".join(characters) 43 | split_on_last = all_commas.rsplit(", ", 1) 44 | return " and ".join(split_on_last) 45 | 46 | 47 | def debug_play(text, input_style="interactive", output_style="verbose"): 48 | interpreter = Shakespeare(text, input_style=input_style, output_style=output_style) 49 | 50 | def on_breakpoint(): 51 | print("-----\n" + interpreter.next_operation_text() + "\n-----\n") 52 | run_repl(interpreter) 53 | 54 | interpreter.run(on_breakpoint) 55 | 56 | 57 | def run_repl(interpreter): 58 | previous_input_style = interpreter.settings.input_style 59 | previous_output_style = interpreter.settings.output_style 60 | interpreter.settings.input_style = "interactive" 61 | interpreter.settings.output_style = "verbose" 62 | current_character = None 63 | 64 | while True: 65 | try: 66 | pos_before = interpreter.current_position 67 | repl_input = input(">> ") 68 | if repl_input in ["exit", "quit"]: 69 | sys.exit() 70 | elif repl_input == "continue": 71 | break 72 | elif repl_input == "next": 73 | if interpreter.play_over(): 74 | break 75 | interpreter.step_forward() 76 | elif repl_input == "state": 77 | print(str(interpreter.state)) 78 | else: 79 | # TODO: make this not an awkward return value 80 | current_character = _run_repl_input( 81 | interpreter, repl_input, current_character 82 | ) 83 | 84 | if interpreter.current_position != pos_before: 85 | if interpreter.play_over(): 86 | break 87 | print("\n-----\n" + interpreter.next_operation_text() + "\n-----\n") 88 | except ShakespeareError as e: 89 | print(str(e), file=sys.stderr) 90 | 91 | interpreter.input_style = previous_input_style 92 | interpreter.output_style = previous_output_style 93 | 94 | 95 | def _run_repl_input(interpreter, repl_input, current_character): 96 | ast = interpreter.parse(repl_input + "\n", "repl_input") 97 | 98 | event = ast.event 99 | sentences = ast.sentences 100 | 101 | # Events that are lines should be considered sets of sentences. 102 | if event and event.parseinfo.rule == "line": 103 | current_character = normalize_name(event.character) 104 | sentences = event.contents 105 | event = None 106 | 107 | if event: 108 | interpreter.run_event(event) 109 | elif sentences: 110 | if not current_character: 111 | print("Who's saying this?") 112 | return 113 | 114 | for sentence in sentences: 115 | interpreter.run_sentence(sentence, current_character) 116 | elif ast.value: 117 | if ast.expression_character: 118 | current_character = normalize_name(ast.expression_character) 119 | 120 | print(interpreter.evaluate_expression(ast.value, current_character)) 121 | elif ast.display_character: 122 | print( 123 | interpreter.state.character_by_name(normalize_name(ast.display_character)) 124 | ) 125 | 126 | return current_character 127 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_parse_error_format.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from shakespearelang.errors import ShakespeareParseError 3 | import pytest 4 | import textwrap 5 | 6 | 7 | def test_full_error_format_empty(): 8 | with pytest.raises(ShakespeareParseError) as exc: 9 | Shakespeare("") 10 | assert str(exc.value) == textwrap.dedent( 11 | """\ 12 | SPL parse error: failed to parse play 13 | at line 1 14 | ----- context ----- 15 | ∨ 16 | 17 | ∧ 18 | 19 | ----- details ----- 20 | parsing stack: play 21 | full error message: 22 | expecting one of: '!' '.'""" 23 | ) 24 | 25 | 26 | def test_full_error_format_title(): 27 | with pytest.raises(ShakespeareParseError) as exc: 28 | Shakespeare("Foobar") 29 | assert str(exc.value) == textwrap.dedent( 30 | """\ 31 | SPL parse error: failed to parse play 32 | at line 2 33 | ----- context ----- 34 | Foobar 35 | ∨ 36 | 37 | ∧ 38 | 39 | ----- details ----- 40 | parsing stack: play 41 | full error message: 42 | expecting one of: '!' '.'""" 43 | ) 44 | 45 | 46 | def test_full_error_format_realistic(): 47 | with pytest.raises(ShakespeareParseError) as exc: 48 | Shakespeare( 49 | textwrap.dedent( 50 | """\ 51 | Foobar. Juliet, a test. Romeo, a test. 52 | 53 | Act I: The first act. 54 | 55 | Scene IVX: This is not a real numeral. 56 | 57 | [Enter Juliet and Romeo] 58 | 59 | Juliet: Thou art a pig.""" 60 | ), 61 | ) 62 | assert str(exc.value) == textwrap.dedent( 63 | """\ 64 | SPL parse error: failed to parse scene 65 | at line 5 66 | ----- context ----- 67 | Foobar. Juliet, a test. Romeo, a test. 68 | 69 | Act I: The first act. 70 | 71 | ∨ 72 | Scene IVX: This is not a real numeral. 73 | ∧ 74 | 75 | [Enter Juliet and Romeo] 76 | 77 | Juliet: Thou art a pig. 78 | 79 | ----- details ----- 80 | parsing stack: scene, act, play 81 | full error message: 82 | expecting ':'""" 83 | ) 84 | 85 | 86 | def test_full_error_format_long(): 87 | with pytest.raises(ShakespeareParseError) as exc: 88 | Shakespeare( 89 | textwrap.dedent( 90 | """\ 91 | Foobar. Baz, a test. Romeo, a test. 92 | 93 | Act I: The first act. 94 | 95 | Scene IV: This is a real numeral. 96 | 97 | [Enter Juliet and Romeo] 98 | 99 | Juliet: Thou art a pig.""" 100 | ), 101 | ) 102 | assert str(exc.value) == textwrap.dedent( 103 | """\ 104 | SPL parse error: failed to parse character 105 | at line 1 106 | ----- context ----- 107 | ∨ 108 | Foobar. Baz, a test. Romeo, a test. 109 | ∧ 110 | 111 | Act I: The first act. 112 | 113 | Scene IV: This is a real numeral. 114 | 115 | ----- details ----- 116 | parsing stack: character, dramatis_persona, dramatis_personae, play 117 | full error message: 118 | expecting one of: 'Achilles' 'Adonis' 'Adriana' 'Aegeon''Aemilia' 'Agamemnon' 'Agrippa' 'Ajax''Alonso' 'Andromache' 'Angelo''Antiochus' 'Antonio' 'Arthur''Autolycus' 'Balthazar' 'Banquo''Beatrice' 'Benedick' 'Benvolio''Bianca' 'Brabantio' 'Brutus' 'Capulet''Cassandra' 'Cassius' 'Christopher''Cicero' 'Claudio' 'Claudius''Cleopatra' 'Cordelia' 'Cornelius''Cressida' 'Cymberline' 'Demetrius''Desdemona' 'Dionyza' 'Doctor''Dogberry' 'Don' 'Donalbain' 'Dorcas''Duncan' 'Egeus' 'Emilia' 'Escalus''Falstaff' 'Fenton' 'Ferdinand' 'Ford''Fortinbras' 'Francisca' 'Friar''Gertrude' 'Goneril' 'Hamlet' 'Hecate''Hector' 'Helen' 'Helena' 'Hermia''Hermonie' 'Hippolyta' 'Horatio''Imogen' 'Isabella' 'John' 'Julia''Juliet' 'Julius' 'King' 'Lady' 'Lennox''Leonato' 'Luciana' 'Lucio' 'Lychorida''Lysander' 'Macbeth' 'Macduff' 'Malcolm''Mariana' 'Mark' 'Mercutio' 'Miranda''Mistress' 'Montague' 'Mopsa' 'Oberon''Octavia' 'Octavius' 'Olivia' 'Ophelia''Orlando' 'Orsino' 'Othello' 'Page''Pantino' 'Paris' 'Pericles' 'Pinch''Polonius' 'Pompeius' 'Portia' 'Priam''Prince' 'Prospero' 'Proteus' 'Publius''Puck' 'Queen' 'Regan' 'Robin' 'Romeo''Rosalind' 'Sebastian' 'Shallow''Shylock' 'Slender' 'Solinus' 'Stephano''Thaisa' 'The' 'Theseus' 'Thurio''Timon' 'Titania' 'Titus' 'Troilus''Tybalt' 'Ulysses' 'Valentine' 'Venus''Vincentio' 'Viola'""" 119 | ) 120 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_numeric_input.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from shakespearelang.errors import ShakespeareRuntimeError 3 | from io import StringIO 4 | import pytest 5 | 6 | 7 | def test_correctly_parses_number(monkeypatch, capsys): 8 | monkeypatch.setattr("sys.stdin", StringIO("4257")) 9 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 10 | s.run_event("[Enter Romeo and Juliet]") 11 | s.run_sentence("Listen to your heart!", "Juliet") 12 | assert s.state.character_by_name("Romeo").value == 4257 13 | captured = capsys.readouterr() 14 | assert captured.out == "" 15 | assert captured.err == "" 16 | 17 | 18 | def test_ignores_non_digits(monkeypatch, capsys): 19 | monkeypatch.setattr("sys.stdin", StringIO("4257a123")) 20 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 21 | s.run_event("[Enter Romeo and Juliet]") 22 | s.run_sentence("Listen to your heart!", "Juliet") 23 | assert s.state.character_by_name("Romeo").value == 4257 24 | captured = capsys.readouterr() 25 | assert captured.out == "" 26 | assert captured.err == "" 27 | 28 | 29 | def test_consumes_trailing_newline(monkeypatch, capsys): 30 | monkeypatch.setattr("sys.stdin", StringIO("4257\na")) 31 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 32 | s.run_event("[Enter Romeo and Juliet]") 33 | s.run_sentence("Listen to your heart!", "Juliet") 34 | assert s.state.character_by_name("Romeo").value == 4257 35 | assert input() == "a" 36 | captured = capsys.readouterr() 37 | assert captured.out == "" 38 | assert captured.err == "" 39 | 40 | # Make sure there isn't a '\n' still living in the buffer 41 | s.run_sentence("Open your mind!", "Juliet") 42 | assert s.state.character_by_name("Romeo").value == -1 43 | 44 | 45 | def test_errors_without_digits(monkeypatch, capsys): 46 | monkeypatch.setattr("sys.stdin", StringIO("a123")) 47 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 48 | s.run_event("[Enter Romeo and Juliet]") 49 | 50 | with pytest.raises(ShakespeareRuntimeError) as exc: 51 | s.run_sentence("Listen to your heart!", "Juliet") 52 | assert "no numeric input" in str(exc.value).lower() 53 | assert ">>Listen to your heart!<<" in str(exc.value) 54 | assert exc.value.interpreter == s 55 | assert s.state.character_by_name("Romeo").value == 0 56 | 57 | monkeypatch.setattr("sys.stdin", StringIO("a123")) 58 | with pytest.raises(ShakespeareRuntimeError) as exc: 59 | s.run_sentence("Listen to your heart!", "Juliet") 60 | assert "no numeric input" in str(exc.value).lower() 61 | assert ">>Listen to your heart!<<" in str(exc.value) 62 | assert exc.value.interpreter == s 63 | assert s.state.character_by_name("Romeo").value == 0 64 | captured = capsys.readouterr() 65 | assert captured.out == "" 66 | assert captured.err == "" 67 | 68 | 69 | def test_errors_on_eof(monkeypatch, capsys): 70 | monkeypatch.setattr("sys.stdin", StringIO("")) 71 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 72 | s.run_event("[Enter Romeo and Juliet]") 73 | with pytest.raises(ShakespeareRuntimeError) as exc: 74 | s.run_sentence("Listen to your heart!", "Juliet") 75 | assert "end of file" in str(exc.value).lower() 76 | assert ">>Listen to your heart!<<" in str(exc.value) 77 | assert exc.value.interpreter == s 78 | assert s.state.character_by_name("Romeo").value == 0 79 | captured = capsys.readouterr() 80 | assert captured.out == "" 81 | assert captured.err == "" 82 | 83 | 84 | def test_conditional(monkeypatch, capsys): 85 | monkeypatch.setattr("sys.stdin", StringIO("4257\n3211")) 86 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 87 | s.run_event("[Enter Romeo and Juliet]") 88 | 89 | s.state.global_boolean = False 90 | s.run_sentence("If so, listen to your heart!", "Juliet") 91 | assert s.state.character_by_name("Romeo").value == 0 92 | 93 | s.state.global_boolean = True 94 | s.run_sentence("If not, listen to your heart!", "Juliet") 95 | assert s.state.character_by_name("Romeo").value == 0 96 | 97 | s.state.global_boolean = True 98 | s.run_sentence("If so, listen to your heart!", "Juliet") 99 | assert s.state.character_by_name("Romeo").value == 4257 100 | 101 | s.state.global_boolean = False 102 | s.run_sentence("If not, listen to your heart!", "Juliet") 103 | assert s.state.character_by_name("Romeo").value == 3211 104 | 105 | captured = capsys.readouterr() 106 | assert captured.out == "" 107 | assert captured.err == "" 108 | 109 | def test_interactive_style(monkeypatch, capsys): 110 | monkeypatch.setattr("sys.stdin", StringIO("4257\n3211")) 111 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.", input_style="interactive") 112 | s.run_event("[Enter Romeo and Juliet]") 113 | 114 | s.run_sentence("Listen to your heart!", "Juliet") 115 | assert s.state.character_by_name("Romeo").value == 4257 116 | captured = capsys.readouterr() 117 | assert captured.out == "Taking input number: " 118 | assert captured.err == "" 119 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_character_input.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from io import StringIO 3 | 4 | 5 | def test_reads_characters_accurately(monkeypatch, capsys): 6 | monkeypatch.setattr("sys.stdin", StringIO("ab\nAB\t&@ ")) 7 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 8 | s.run_event("[Enter Romeo and Juliet]") 9 | 10 | s.run_sentence("Open your mind!", "Juliet") 11 | assert s.state.character_by_name("Romeo").value == 97 12 | 13 | s.run_sentence("Open your mind!", "Juliet") 14 | assert s.state.character_by_name("Romeo").value == 98 15 | 16 | s.run_sentence("Open your mind!", "Juliet") 17 | assert s.state.character_by_name("Romeo").value == 10 18 | 19 | s.run_sentence("Open your mind!", "Juliet") 20 | assert s.state.character_by_name("Romeo").value == 65 21 | 22 | s.run_sentence("Open your mind!", "Juliet") 23 | assert s.state.character_by_name("Romeo").value == 66 24 | 25 | s.run_sentence("Open your mind!", "Juliet") 26 | assert s.state.character_by_name("Romeo").value == 9 27 | 28 | s.run_sentence("Open your mind!", "Juliet") 29 | assert s.state.character_by_name("Romeo").value == 38 30 | 31 | s.run_sentence("Open your mind!", "Juliet") 32 | assert s.state.character_by_name("Romeo").value == 64 33 | 34 | s.run_sentence("Open your mind!", "Juliet") 35 | assert s.state.character_by_name("Romeo").value == 32 36 | 37 | captured = capsys.readouterr() 38 | assert captured.out == "" 39 | assert captured.err == "" 40 | 41 | 42 | def test_unicode(monkeypatch, capsys): 43 | monkeypatch.setattr("sys.stdin", StringIO("ʘɥӜआઔඦᢶᨆᵇḤ")) 44 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 45 | s.run_event("[Enter Romeo and Juliet]") 46 | 47 | s.run_sentence("Open your mind!", "Juliet") 48 | assert s.state.character_by_name("Romeo").value == 664 49 | 50 | s.run_sentence("Open your mind!", "Juliet") 51 | assert s.state.character_by_name("Romeo").value == 613 52 | 53 | s.run_sentence("Open your mind!", "Juliet") 54 | assert s.state.character_by_name("Romeo").value == 1244 55 | 56 | s.run_sentence("Open your mind!", "Juliet") 57 | assert s.state.character_by_name("Romeo").value == 2310 58 | 59 | s.run_sentence("Open your mind!", "Juliet") 60 | assert s.state.character_by_name("Romeo").value == 2708 61 | 62 | s.run_sentence("Open your mind!", "Juliet") 63 | assert s.state.character_by_name("Romeo").value == 3494 64 | 65 | s.run_sentence("Open your mind!", "Juliet") 66 | assert s.state.character_by_name("Romeo").value == 6326 67 | 68 | s.run_sentence("Open your mind!", "Juliet") 69 | assert s.state.character_by_name("Romeo").value == 6662 70 | 71 | s.run_sentence("Open your mind!", "Juliet") 72 | assert s.state.character_by_name("Romeo").value == 7495 73 | 74 | s.run_sentence("Open your mind!", "Juliet") 75 | assert s.state.character_by_name("Romeo").value == 7716 76 | 77 | captured = capsys.readouterr() 78 | assert captured.out == "" 79 | assert captured.err == "" 80 | 81 | 82 | def test_eof_character_code(monkeypatch, capsys): 83 | monkeypatch.setattr("sys.stdin", StringIO("&")) 84 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 85 | s.run_event("[Enter Romeo and Juliet]") 86 | 87 | s.run_sentence("Open your mind!", "Juliet") 88 | assert s.state.character_by_name("Romeo").value == 38 89 | 90 | s.run_sentence("Open your mind!", "Juliet") 91 | assert s.state.character_by_name("Romeo").value == -1 92 | 93 | captured = capsys.readouterr() 94 | assert captured.out == "" 95 | assert captured.err == "" 96 | 97 | 98 | def test_past_eof(monkeypatch, capsys): 99 | monkeypatch.setattr("sys.stdin", StringIO("")) 100 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 101 | s.run_event("[Enter Romeo and Juliet]") 102 | 103 | s.run_sentence("Open your mind!", "Juliet") 104 | assert s.state.character_by_name("Romeo").value == -1 105 | 106 | monkeypatch.setattr("sys.stdin", StringIO("a")) 107 | s.run_sentence("Open your mind!", "Juliet") 108 | assert s.state.character_by_name("Romeo").value == 97 109 | captured = capsys.readouterr() 110 | 111 | assert captured.out == "" 112 | assert captured.err == "" 113 | 114 | 115 | def test_conditional(monkeypatch, capsys): 116 | monkeypatch.setattr("sys.stdin", StringIO("ab")) 117 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 118 | s.run_event("[Enter Romeo and Juliet]") 119 | 120 | s.state.global_boolean = False 121 | s.run_sentence("If so, open your mind!", "Juliet") 122 | assert s.state.character_by_name("Romeo").value == 0 123 | 124 | s.state.global_boolean = True 125 | s.run_sentence("If not, open your mind!", "Juliet") 126 | assert s.state.character_by_name("Romeo").value == 0 127 | 128 | s.state.global_boolean = True 129 | s.run_sentence("If so, open your mind!", "Juliet") 130 | assert s.state.character_by_name("Romeo").value == 97 131 | 132 | s.state.global_boolean = False 133 | s.run_sentence("If not, open your mind!", "Juliet") 134 | assert s.state.character_by_name("Romeo").value == 98 135 | 136 | captured = capsys.readouterr() 137 | assert captured.out == "" 138 | assert captured.err == "" 139 | -------------------------------------------------------------------------------- /shakespearelang/_expression.py: -------------------------------------------------------------------------------- 1 | from ._utils import normalize_name 2 | from .errors import ShakespeareRuntimeError, ShakespeareParseError 3 | from tatsu.ast import AST 4 | import math 5 | 6 | 7 | class Expression: 8 | def __init__(self, ast_node: AST, character: str): 9 | self.ast_node = ast_node 10 | self.character = normalize_name(character) 11 | self.cacheable = False 12 | self.cached_value = None 13 | self._setup() 14 | 15 | def _setup(self): 16 | pass 17 | 18 | def evaluate(self, state): 19 | state.assert_character_on_stage(self.character) 20 | 21 | try: 22 | return self._evaluate_logic_cached(state) 23 | except ShakespeareRuntimeError as exc: 24 | if not exc.parseinfo: 25 | exc.parseinfo = self.ast_node.parseinfo 26 | raise exc 27 | 28 | def _evaluate_logic_cached(self, state): 29 | if self.cacheable and self.cached_value is not None: 30 | return self.cached_value 31 | 32 | result = self._evaluate_logic(state) 33 | 34 | if self.cacheable: 35 | self.cached_value = result 36 | 37 | return result 38 | 39 | 40 | class FirstPersonValue(Expression): 41 | def _evaluate_logic(self, state): 42 | return state.character_by_name(self.character).value 43 | 44 | 45 | class SecondPersonValue(Expression): 46 | def _evaluate_logic(self, state): 47 | character_opposite = state.character_opposite(self.character) 48 | return state.character_by_name(character_opposite).value 49 | 50 | 51 | class CharacterName(Expression): 52 | def _setup(self): 53 | self.name = normalize_name(self.ast_node.name) 54 | 55 | def _evaluate_logic(self, state): 56 | return state.character_by_name(self.name).value 57 | 58 | 59 | class NegativeNounPhrase(Expression): 60 | def _setup(self): 61 | self.cacheable = True 62 | self.cached_value = -pow(2, len(self.ast_node.adjectives)) 63 | 64 | 65 | class PositiveNounPhrase(Expression): 66 | def _setup(self): 67 | self.cacheable = True 68 | self.cached_value = pow(2, len(self.ast_node.adjectives)) 69 | 70 | 71 | class Nothing(Expression): 72 | def _setup(self): 73 | self.cacheable = True 74 | self.cached_value = 0 75 | 76 | 77 | class UnaryOperation(Expression): 78 | def _evaluate_factorial(operand): 79 | if operand < 0: 80 | raise ShakespeareRuntimeError( 81 | "Cannot take the factorial of a negative number: " + str(operand) 82 | ) 83 | return math.factorial(operand) 84 | 85 | def _evaluate_square_root(operand): 86 | if operand < 0: 87 | raise ShakespeareRuntimeError( 88 | "Cannot take the square root of a negative number: " + str(operand) 89 | ) 90 | # Truncates (does not round) result -- this is equivalent to C 91 | # implementation's cast. 92 | return int(math.sqrt(operand)) 93 | 94 | _UNARY_OPERATION_HANDLERS = { 95 | ("the", "cube", "of"): lambda x: pow(x, 3), 96 | ("the", "factorial", "of"): _evaluate_factorial, 97 | ("the", "square", "of"): lambda x: pow(x, 2), 98 | ("the", "square", "root", "of"): _evaluate_square_root, 99 | "twice": lambda x: x * 2, 100 | } 101 | 102 | def _setup(self): 103 | self.operand = expression_from_ast(self.ast_node.value, self.character) 104 | self.cacheable = self.operand.cacheable 105 | self.operation = self._UNARY_OPERATION_HANDLERS[self.ast_node.operation] 106 | 107 | def _evaluate_logic(self, state): 108 | return self.operation(self.operand.evaluate(state)) 109 | 110 | 111 | class BinaryOperation(Expression): 112 | def _evaluate_quotient(first_operand, second_operand): 113 | if second_operand == 0: 114 | raise ShakespeareRuntimeError("Cannot divide by zero") 115 | # Python's built-in integer division operator does not behave the 116 | # same as C for negative numbers, using floor instead of truncated 117 | # division 118 | return int(first_operand / second_operand) 119 | 120 | def _evaluate_remainder(first_operand, second_operand): 121 | if second_operand == 0: 122 | raise ShakespeareRuntimeError("Cannot divide by zero") 123 | # See note above. math.fmod replicates C behavior. 124 | return int(math.fmod(first_operand, second_operand)) 125 | 126 | _BINARY_OPERATION_HANDLERS = { 127 | ("the", "difference", "between"): lambda a, b: a - b, 128 | ("the", "product", "of"): lambda a, b: a * b, 129 | ("the", "quotient", "between"): _evaluate_quotient, 130 | ("the", "remainder", "of", "the", "quotient", "between"): _evaluate_remainder, 131 | ("the", "sum", "of"): lambda a, b: a + b, 132 | } 133 | 134 | def _setup(self): 135 | self.first_operand = expression_from_ast( 136 | self.ast_node.first_value, self.character 137 | ) 138 | self.second_operand = expression_from_ast( 139 | self.ast_node.second_value, self.character 140 | ) 141 | self.cacheable = self.first_operand.cacheable and self.second_operand.cacheable 142 | self.operation = self._BINARY_OPERATION_HANDLERS[self.ast_node.operation] 143 | 144 | def _evaluate_logic(self, state): 145 | return self.operation( 146 | self.first_operand.evaluate(state), self.second_operand.evaluate(state) 147 | ) 148 | 149 | 150 | _EXPRESSION_CONSTRUCTORS = { 151 | "first_person_value": FirstPersonValue, 152 | "second_person_value": SecondPersonValue, 153 | "character_name": CharacterName, 154 | "negative_noun_phrase": NegativeNounPhrase, 155 | "positive_noun_phrase": PositiveNounPhrase, 156 | "nothing": Nothing, 157 | "unary_expression": UnaryOperation, 158 | "binary_expression": BinaryOperation, 159 | } 160 | 161 | 162 | def expression_from_ast(ast_node: AST, character: str): 163 | return _EXPRESSION_CONSTRUCTORS[ast_node.parseinfo.rule](ast_node, character) 164 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_stacks.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from shakespearelang.errors import ShakespeareRuntimeError 3 | import pytest 4 | 5 | 6 | class FakeExpression: 7 | def __init__(self, value): 8 | self.value = value 9 | 10 | def evaluate(self, state): 11 | return self.value 12 | 13 | 14 | def test_push(): 15 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 16 | s.run_event("[Enter Romeo and Juliet]") 17 | 18 | c = s.state.character_by_name("Juliet") 19 | assert c.stack == [] 20 | assert c.value == 0 21 | 22 | s.run_sentence("Remember a furry animal.", "Romeo") 23 | assert c.stack == [2] 24 | assert c.value == 0 25 | 26 | s.run_sentence("Remember a furry furry animal.", "Romeo") 27 | assert c.stack == [2, 4] 28 | assert c.value == 0 29 | 30 | s.run_sentence("Remember a furry furry furry animal.", "Romeo") 31 | assert c.stack == [2, 4, 8] 32 | assert c.value == 0 33 | 34 | 35 | def test_pop(): 36 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 37 | s.run_event("[Enter Romeo and Juliet]") 38 | 39 | c = s.state.character_by_name("Juliet") 40 | assert c.stack == [] 41 | assert c.value == 0 42 | 43 | c.stack = [234, 123, 678] 44 | 45 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 46 | assert c.stack == [234, 123] 47 | assert c.value == 678 48 | 49 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 50 | assert c.stack == [234] 51 | assert c.value == 123 52 | 53 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 54 | assert c.stack == [] 55 | assert c.value == 234 56 | 57 | 58 | def test_sequence(): 59 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 60 | s.run_event("[Enter Romeo and Juliet]") 61 | 62 | c = s.state.character_by_name("Juliet") 63 | assert c.stack == [] 64 | assert c.value == 0 65 | 66 | c.stack = [234, 123, 678] 67 | 68 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 69 | assert c.stack == [234, 123] 70 | assert c.value == 678 71 | 72 | s.run_sentence("Remember a furry animal.", "Romeo") 73 | assert c.stack == [234, 123, 2] 74 | assert c.value == 678 75 | 76 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 77 | assert c.stack == [234, 123] 78 | assert c.value == 2 79 | 80 | s.run_sentence("Remember a furry furry animal.", "Romeo") 81 | assert c.stack == [234, 123, 4] 82 | assert c.value == 2 83 | 84 | s.run_sentence("Remember a furry furry furry animal.", "Romeo") 85 | assert c.stack == [234, 123, 4, 8] 86 | assert c.value == 2 87 | 88 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 89 | assert c.stack == [234, 123, 4] 90 | assert c.value == 8 91 | 92 | s.run_sentence("Remember a furry furry furry furry animal.", "Romeo") 93 | assert c.stack == [234, 123, 4, 16] 94 | assert c.value == 8 95 | 96 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 97 | assert c.stack == [234, 123, 4] 98 | assert c.value == 16 99 | 100 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 101 | assert c.stack == [234, 123] 102 | assert c.value == 4 103 | 104 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 105 | assert c.stack == [234] 106 | assert c.value == 123 107 | 108 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 109 | assert c.stack == [] 110 | assert c.value == 234 111 | 112 | 113 | def test_errors_on_pop_from_empty(): 114 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 115 | s.run_event("[Enter Romeo and Juliet]") 116 | 117 | c = s.state.character_by_name("Juliet") 118 | assert c.stack == [] 119 | assert c.value == 0 120 | 121 | with pytest.raises(ShakespeareRuntimeError) as exc: 122 | s.run_sentence("Recall thy terrible memory of thy imminent death.", "Romeo") 123 | assert "empty stack" in str(exc.value).lower() 124 | assert ">>Recall thy terrible memory of thy imminent death.<<" in str(exc.value) 125 | assert exc.value.interpreter == s 126 | 127 | assert c.stack == [] 128 | assert c.value == 0 129 | 130 | 131 | def test_conditional_push(): 132 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 133 | s.run_event("[Enter Romeo and Juliet]") 134 | 135 | c = s.state.character_by_name("Juliet") 136 | assert c.stack == [] 137 | assert c.value == 0 138 | 139 | s.state.global_boolean = False 140 | s.run_sentence("If so, remember a furry animal.", "Romeo") 141 | assert c.stack == [] 142 | assert c.value == 0 143 | 144 | s.state.global_boolean = True 145 | s.run_sentence("If not, remember a furry animal.", "Romeo") 146 | assert c.stack == [] 147 | assert c.value == 0 148 | 149 | s.state.global_boolean = True 150 | s.run_sentence("If so, remember a furry animal.", "Romeo") 151 | assert c.stack == [2] 152 | assert c.value == 0 153 | 154 | s.state.global_boolean = False 155 | s.run_sentence("If not, remember a furry furry animal.", "Romeo") 156 | assert c.stack == [2, 4] 157 | assert c.value == 0 158 | 159 | 160 | def test_conditional_pop(): 161 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 162 | s.run_event("[Enter Romeo and Juliet]") 163 | 164 | c = s.state.character_by_name("Juliet") 165 | assert c.stack == [] 166 | assert c.value == 0 167 | 168 | c.stack = [234, 123, 678] 169 | 170 | s.state.global_boolean = False 171 | s.run_sentence("If so, recall thy terrible memory of thy imminent death.", "Romeo") 172 | assert c.stack == [234, 123, 678] 173 | assert c.value == 0 174 | 175 | s.state.global_boolean = True 176 | s.run_sentence("If not, recall thy terrible memory of thy imminent death.", "Romeo") 177 | assert c.stack == [234, 123, 678] 178 | assert c.value == 0 179 | 180 | s.state.global_boolean = True 181 | s.run_sentence("If so, recall thy terrible memory of thy imminent death.", "Romeo") 182 | assert c.stack == [234, 123] 183 | assert c.value == 678 184 | 185 | s.state.global_boolean = False 186 | s.run_sentence("If not, recall thy terrible memory of thy imminent death.", "Romeo") 187 | assert c.stack == [234] 188 | assert c.value == 123 189 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Good morrow! 2 | 3 | Welcome to the home of shakespearelang, a friendly interpreter for the Shakespeare 4 | Programming Language (SPL) implemented in Python and available as a package on PyPI. 5 | Aside from simply running programs, it also offers a console and debugger. 6 | 7 | [The Shakespeare Programming Language](http://shakespearelang.sourceforge.net/) 8 | (SPL) is an esoteric language with source code that looks like William 9 | Shakespeare's plays. I take no credit for the language itself, which was designed 10 | by [Karl Wiberg](https://treskal.com/kha) and Jon Åslund in 2001. 11 | 12 | ## Installation 13 | 14 | `python -m pip install shakespearelang` 15 | 16 | ## Getting started 17 | 18 | Create a new file called `first_play.spl` with this text: 19 | 20 | ```spl 21 | A New Beginning. 22 | 23 | Hamlet, a literary/storage device. 24 | Juliet, an orator. 25 | 26 | Act I: The Only Act. 27 | 28 | Scene I: The Prince's Speech. 29 | 30 | [Enter Hamlet and Juliet] 31 | 32 | Juliet: Thou art the sum of an amazing healthy honest noble peaceful 33 | fine Lord and a lovely sweet golden summer's day. Speak your 34 | mind! 35 | 36 | [A pause] 37 | 38 | Juliet: Thou art the sum of thyself and a King. Speak your mind! 39 | 40 | Thou art the sum of an amazing healthy honest hamster and a golden 41 | chihuahua. Speak your mind! 42 | 43 | [Exeunt] 44 | ``` 45 | 46 | In the terminal, use `shakespeare run` to run the play, like so: 47 | 48 | ``` 49 | $ shakespeare run first_play.spl 50 | HI 51 | ``` 52 | 53 | If you see the output "HI", you just successfully ran your first SPL play! 54 | 55 | ## Debugging 56 | 57 | For a guide to writing SPL plays, see [the original SPL documentation](http://shakespearelang.sourceforge.net/report/shakespeare/). 58 | 59 | Written your play, but it's not working? shakespearelang tries to provide the 60 | most useful error messages possible, but with a language designed to be almost 61 | impossible to write, bugs are inevitable and an error message, if one is present, 62 | isn't always enough. 63 | 64 | ### Activating the debugger 65 | 66 | Remember the stage direction `[A pause]` in our `first_play.spl` file? That 67 | special stage direction represents a breakpoint to the shakespearelang debugger. 68 | To use the debugger, run the file again with the `debug` command, like so: 69 | 70 | ``` 71 | $ shakespeare debug first_play.spl 72 | Enter Hamlet, Juliet 73 | Hamlet set to 72 74 | Outputting Hamlet 75 | Outputting character: 'H' 76 | ----- 77 | mind! 78 | 79 | [A pause] 80 | 81 | Juliet: >>Thou art the sum of thyself and a King. Speak your mind!<< 82 | 83 | Thou art the sum of an amazing healthy honest hamster and a golden 84 | chihuahua. Speak your mind! 85 | 86 | [Exeunt] 87 | 88 | ----- 89 | 90 | >> 91 | ``` 92 | 93 | ### Understanding the debugger and console 94 | 95 | When a play is run with the debugger, it gives more step-by-step information 96 | about the execution of a play. Here, we can see that Hamlet and Juliet entered 97 | the stage, Hamlet was set to the value 72, and then output the character 'H'. 98 | 99 | Now, we've hit the breakpoint. The part of the text highlighted by `>>` and `<<` 100 | shows us what's about to run next. 101 | 102 | The last line (starting with `>>`) is a prompt, waiting for your input. This is 103 | called the "console" or the ["REPL"](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop). 104 | 105 | #### Lines 106 | 107 | You can type any lines, entrances, and exits and they will be run as if they were in 108 | the current scene. You cannot use the console to start new scenes or acts, nor 109 | can you jump to another scene. 110 | 111 | For example, you can increase Hamlet's value: 112 | 113 | ``` 114 | >> Juliet: Thou art the sum of thyself and a King. 115 | Hamlet set to 73 116 | >> 117 | ``` 118 | 119 | Having the speaking character's name at the beginning of the line is optional in 120 | the console. Unattributed lines will be spoken by the last speaker. 121 | 122 | ``` 123 | >> Thou art a pig! 124 | Hamlet set to -1 125 | ``` 126 | 127 | #### Expressions 128 | 129 | In addition to normal lines, the console also accepts standalone expressions, 130 | the result of which it displays. These cannot end with periods. The answers 131 | to questions are also displayed, but note that like all questions in SPL, they 132 | modify the global state! 133 | 134 | ``` 135 | >> Juliet: The sum of thyself and a King 136 | 0 137 | >> Are you nicer than a golden chihuahua? 138 | Setting global boolean to False 139 | >> 140 | ``` 141 | 142 | #### Inspecting the current state 143 | 144 | To see who's on stage and the values of characters and of the global boolean, 145 | type `state` into the console. 146 | 147 | ``` 148 | >> state 149 | global boolean = False 150 | on stage: 151 | Hamlet = -1 () 152 | Juliet = 0 () 153 | off stage: 154 | ``` 155 | 156 | In this display, the number next to a character is the current value, while the 157 | parentheses hold the values on their stack (the rightmost values are at the top). 158 | To see a single character only, you can enter their name. 159 | 160 | ``` 161 | >> Hamlet 162 | -1 () 163 | >> Juliet: Remember thyself! 164 | Hamlet pushed -1 165 | >> Hamlet 166 | -1 (-1) 167 | ``` 168 | 169 | #### Commands 170 | 171 | There are three special commands you can use in the console: 172 | 173 | - `next` executes the next sentence or event in the play, returning you to the interactive console afterwards. 174 | - `continue` continues running the play--it will not stop again unless it hits another breakpoint. 175 | - `quit` or `exit` stop execution of the play completely. 176 | 177 | ## Using the console outside the debugger 178 | 179 | If you don't have a play yet and just want to mess around, you can open the 180 | console in the context of an empty play with the `shakespeare console` command, 181 | or simply `shakespeare`. 182 | 183 | ## Other implementations 184 | 185 | shakespearelang is not the only implementation of SPL, though it aims to be the 186 | friendliest. 187 | 188 | Other options: 189 | 190 | - [The reference implementation](http://shakespearelang.sf.net/download/spl-1.2.1.tar.gz), 191 | an SPL-to-C source-to-source compiler 192 | - Note: you may need to downgrade `flex` to version 2.5.4 to compile the 193 | reference implementation due to [this issue](https://github.com/westes/flex/issues/193). 194 | On Ubuntu, this can be easily achieved with the [flex-old package](https://launchpad.net/ubuntu/+source/flex-old). 195 | - [Lingua::Shakespeare](http://search.cpan.org/dist/Lingua-Shakespeare/lib/Lingua/Shakespeare.pod), a source filter in Perl 196 | - [Spl](https://github.com/drsam94/Spl), an SPL-to-C compiler in Python 197 | - [horatio](https://github.com/mileszim/horatio), an interpreter in Javascript 198 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_character_output.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from shakespearelang.errors import ShakespeareRuntimeError 3 | from io import StringIO 4 | import pytest 5 | 6 | 7 | def test_outputs_correct_character(capsys): 8 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test. Act I: One. Scene I: One.") 9 | s.run_event("[Enter Romeo and Juliet]") 10 | 11 | s.state.character_by_name("Romeo").value = 97 12 | s.run_sentence("Speak your mind!", "Juliet") 13 | captured = capsys.readouterr() 14 | assert captured.out == "a" 15 | assert captured.err == "" 16 | 17 | s.state.character_by_name("Romeo").value = 98 18 | s.run_sentence("Speak your mind!", "Juliet") 19 | captured = capsys.readouterr() 20 | assert captured.out == "b" 21 | assert captured.err == "" 22 | 23 | s.state.character_by_name("Romeo").value = 10 24 | s.run_sentence("Speak your mind!", "Juliet") 25 | captured = capsys.readouterr() 26 | assert captured.out == "\n" 27 | assert captured.err == "" 28 | 29 | s.state.character_by_name("Romeo").value = 65 30 | s.run_sentence("Speak your mind!", "Juliet") 31 | captured = capsys.readouterr() 32 | assert captured.out == "A" 33 | assert captured.err == "" 34 | 35 | s.state.character_by_name("Romeo").value = 66 36 | s.run_sentence("Speak your mind!", "Juliet") 37 | captured = capsys.readouterr() 38 | assert captured.out == "B" 39 | assert captured.err == "" 40 | 41 | s.state.character_by_name("Romeo").value = 9 42 | s.run_sentence("Speak your mind!", "Juliet") 43 | captured = capsys.readouterr() 44 | assert captured.out == "\t" 45 | assert captured.err == "" 46 | 47 | s.state.character_by_name("Romeo").value = 38 48 | s.run_sentence("Speak your mind!", "Juliet") 49 | captured = capsys.readouterr() 50 | assert captured.out == "&" 51 | assert captured.err == "" 52 | 53 | s.state.character_by_name("Romeo").value = 64 54 | s.run_sentence("Speak your mind!", "Juliet") 55 | captured = capsys.readouterr() 56 | assert captured.out == "@" 57 | assert captured.err == "" 58 | 59 | s.state.character_by_name("Romeo").value = 32 60 | s.run_sentence("Speak your mind!", "Juliet") 61 | captured = capsys.readouterr() 62 | assert captured.out == " " 63 | assert captured.err == "" 64 | 65 | 66 | def test_unicode(capsys): 67 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test. Act I: One. Scene I: One.") 68 | s.run_event("[Enter Romeo and Juliet]") 69 | 70 | s.state.character_by_name("Romeo").value = 664 71 | s.run_sentence("Speak your mind!", "Juliet") 72 | captured = capsys.readouterr() 73 | assert captured.out == "ʘ" 74 | assert captured.err == "" 75 | 76 | s.state.character_by_name("Romeo").value = 613 77 | s.run_sentence("Speak your mind!", "Juliet") 78 | captured = capsys.readouterr() 79 | assert captured.out == "ɥ" 80 | assert captured.err == "" 81 | 82 | s.state.character_by_name("Romeo").value = 1244 83 | s.run_sentence("Speak your mind!", "Juliet") 84 | captured = capsys.readouterr() 85 | assert captured.out == "Ӝ" 86 | assert captured.err == "" 87 | 88 | s.state.character_by_name("Romeo").value = 2310 89 | s.run_sentence("Speak your mind!", "Juliet") 90 | captured = capsys.readouterr() 91 | assert captured.out == "आ" 92 | assert captured.err == "" 93 | 94 | s.state.character_by_name("Romeo").value = 2708 95 | s.run_sentence("Speak your mind!", "Juliet") 96 | captured = capsys.readouterr() 97 | assert captured.out == "ઔ" 98 | assert captured.err == "" 99 | 100 | s.state.character_by_name("Romeo").value = 3494 101 | s.run_sentence("Speak your mind!", "Juliet") 102 | captured = capsys.readouterr() 103 | assert captured.out == "ඦ" 104 | assert captured.err == "" 105 | 106 | s.state.character_by_name("Romeo").value = 6326 107 | s.run_sentence("Speak your mind!", "Juliet") 108 | captured = capsys.readouterr() 109 | assert captured.out == "ᢶ" 110 | assert captured.err == "" 111 | 112 | s.state.character_by_name("Romeo").value = 6662 113 | s.run_sentence("Speak your mind!", "Juliet") 114 | captured = capsys.readouterr() 115 | assert captured.out == "ᨆ" 116 | assert captured.err == "" 117 | 118 | s.state.character_by_name("Romeo").value = 7495 119 | s.run_sentence("Speak your mind!", "Juliet") 120 | captured = capsys.readouterr() 121 | assert captured.out == "ᵇ" 122 | assert captured.err == "" 123 | 124 | s.state.character_by_name("Romeo").value = 7716 125 | s.run_sentence("Speak your mind!", "Juliet") 126 | captured = capsys.readouterr() 127 | assert captured.out == "Ḥ" 128 | assert captured.err == "" 129 | 130 | 131 | def test_errors_on_invalid_code(capsys): 132 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test.") 133 | s.run_event("[Enter Romeo and Juliet]") 134 | 135 | s.state.character_by_name("Romeo").value = 100000000 136 | with pytest.raises(ShakespeareRuntimeError) as exc: 137 | s.run_sentence("Speak your mind!", "Juliet") 138 | assert "invalid character code" in str(exc.value).lower() 139 | assert ">>Speak your mind!<<" in str(exc.value) 140 | assert exc.value.interpreter == s 141 | 142 | s.state.character_by_name("Romeo").value = -1 143 | with pytest.raises(ShakespeareRuntimeError) as exc: 144 | s.run_sentence("Speak your mind!", "Juliet") 145 | assert "invalid character code" in str(exc.value).lower() 146 | assert ">>Speak your mind!<<" in str(exc.value) 147 | assert exc.value.interpreter == s 148 | 149 | captured = capsys.readouterr() 150 | assert captured.out == "" 151 | assert captured.err == "" 152 | 153 | 154 | def test_conditional(capsys): 155 | s = Shakespeare("Foo. Juliet, a test. Romeo, a test. Act I: One. Scene I: One.") 156 | s.run_event("[Enter Romeo and Juliet]") 157 | 158 | s.state.character_by_name("Romeo").value = 97 159 | s.state.global_boolean = False 160 | s.run_sentence("If so, speak your mind!", "Juliet") 161 | captured = capsys.readouterr() 162 | assert captured.out == "" 163 | assert captured.err == "" 164 | 165 | s.state.global_boolean = True 166 | s.run_sentence("If not, speak your mind!", "Juliet") 167 | captured = capsys.readouterr() 168 | assert captured.out == "" 169 | assert captured.err == "" 170 | 171 | s.state.global_boolean = False 172 | s.run_sentence("If not, speak your mind!", "Juliet") 173 | captured = capsys.readouterr() 174 | assert captured.out == "a" 175 | assert captured.err == "" 176 | 177 | s.state.character_by_name("Romeo").value = 98 178 | s.state.global_boolean = True 179 | s.run_sentence("If so, speak your mind!", "Juliet") 180 | captured = capsys.readouterr() 181 | assert captured.out == "b" 182 | assert captured.err == "" 183 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_gotos.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from shakespearelang.errors import ShakespeareRuntimeError 3 | from io import StringIO 4 | import pytest 5 | 6 | SAMPLE_PLAY = """ 7 | Test. 8 | 9 | Romeo, a test. 10 | Juliet, a test. 11 | Macbeth, a test. 12 | 13 | Act I: Nothing to see here. 14 | Scene I: These are not the actors you're looking for. 15 | 16 | [Enter Romeo and Juliet] 17 | 18 | Juliet: Are you as good as nothing? 19 | 20 | Scene II: Still nothing. 21 | 22 | [A pause] 23 | 24 | Scene III: Nothing strikes back. 25 | 26 | [A pause] 27 | 28 | Act II: So separate. 29 | Scene I: This is hard to get to. 30 | 31 | [A pause] 32 | 33 | Scene II: Likewise. 34 | 35 | [A pause] 36 | 37 | Scene III: Yep. 38 | 39 | [A pause] 40 | 41 | Scene IV: Still going. 42 | 43 | [A pause] 44 | """ 45 | 46 | 47 | def test_goto_current(capsys): 48 | s = Shakespeare(SAMPLE_PLAY) 49 | 50 | assert s.current_position == 0 51 | s.step_forward() 52 | assert s.current_position == 1 53 | s.run_sentence("Let us proceed to scene I.", "Juliet") 54 | assert s.current_position == 0 55 | 56 | captured = capsys.readouterr() 57 | assert captured.out == "" 58 | assert captured.err == "" 59 | 60 | 61 | def test_goto_next(capsys): 62 | s = Shakespeare(SAMPLE_PLAY) 63 | 64 | assert s.current_position == 0 65 | s.step_forward() 66 | assert s.current_position == 1 67 | s.run_sentence("Let us proceed to scene II.", "Juliet") 68 | assert s.current_position == 2 69 | 70 | captured = capsys.readouterr() 71 | assert captured.out == "" 72 | assert captured.err == "" 73 | 74 | 75 | def test_goto_prev(capsys): 76 | s = Shakespeare(SAMPLE_PLAY) 77 | 78 | assert s.current_position == 0 79 | s.step_forward() 80 | assert s.current_position == 1 81 | s.step_forward() 82 | assert s.current_position == 2 83 | s.step_forward() 84 | assert s.current_position == 3 85 | s.run_sentence("Let us return to scene I.", "Juliet") 86 | assert s.current_position == 0 87 | 88 | captured = capsys.readouterr() 89 | assert captured.out == "" 90 | assert captured.err == "" 91 | 92 | 93 | def test_goto_without_opposite_character(capsys): 94 | s = Shakespeare(SAMPLE_PLAY) 95 | 96 | assert s.current_position == 0 97 | s.step_forward() 98 | assert s.current_position == 1 99 | s.run_event("[Exit Romeo]") 100 | s.run_sentence("Let us proceed to scene II.", "Juliet") 101 | assert s.current_position == 2 102 | s.run_event("[Enter Romeo and Macbeth]") 103 | s.run_sentence("Let us proceed to scene I.", "Juliet") 104 | assert s.current_position == 0 105 | 106 | captured = capsys.readouterr() 107 | assert captured.out == "" 108 | assert captured.err == "" 109 | 110 | 111 | def test_goto_conditionals(capsys): 112 | s = Shakespeare(SAMPLE_PLAY) 113 | 114 | assert s.current_position == 0 115 | s.step_forward() 116 | assert s.current_position == 1 117 | s.state.global_boolean = True 118 | s.run_sentence("If so, let us proceed to scene II.", "Juliet") 119 | assert s.current_position == 2 120 | s.state.global_boolean = True 121 | s.run_sentence("If not, let us proceed to scene I.", "Juliet") 122 | assert s.current_position == 2 123 | s.state.global_boolean = False 124 | s.run_sentence("If so, let us proceed to scene I.", "Juliet") 125 | assert s.current_position == 2 126 | s.state.global_boolean = False 127 | s.run_sentence("If not, let us proceed to scene I.", "Juliet") 128 | assert s.current_position == 0 129 | 130 | captured = capsys.readouterr() 131 | assert captured.out == "" 132 | assert captured.err == "" 133 | 134 | 135 | def test_goto_based_on_numeral_not_order(capsys): 136 | s = Shakespeare( 137 | """ 138 | Test. 139 | 140 | Romeo, a test. 141 | Juliet, a test. 142 | 143 | Act I: Nothing to see here. 144 | Scene III: These are not the actors you're looking for. 145 | 146 | [Enter Romeo and Juliet] 147 | 148 | Juliet: Are you as good as nothing? 149 | 150 | Scene I: Still nothing. 151 | 152 | [A pause] 153 | 154 | Scene II: Nothing strikes back. 155 | 156 | [A pause] 157 | """ 158 | ) 159 | 160 | assert s.current_position == 0 161 | s.step_forward() 162 | assert s.current_position == 1 163 | s.run_sentence("Let us return to scene I.", "Juliet") 164 | assert s.current_position == 2 165 | s.run_sentence("Let us return to scene III.", "Juliet") 166 | assert s.current_position == 0 167 | s.run_sentence("Let us return to scene II.", "Juliet") 168 | assert s.current_position == 3 169 | 170 | captured = capsys.readouterr() 171 | assert captured.out == "" 172 | assert captured.err == "" 173 | 174 | 175 | def test_errors_on_goto_nonexistent(capsys): 176 | s = Shakespeare(SAMPLE_PLAY) 177 | 178 | assert s.current_position == 0 179 | s.step_forward() 180 | assert s.current_position == 1 181 | with pytest.raises(ShakespeareRuntimeError) as exc: 182 | s.run_sentence("Let us proceed to scene IV.", "Juliet") 183 | assert "does not exist" in str(exc.value).lower() 184 | assert ">>Let us proceed to scene IV.<<" in str(exc.value) 185 | assert exc.value.interpreter == s 186 | assert s.current_position == 1 187 | 188 | captured = capsys.readouterr() 189 | assert captured.out == "" 190 | assert captured.err == "" 191 | 192 | 193 | def test_duplicate_scene_numbers(capsys): 194 | with pytest.raises(ShakespeareRuntimeError) as exc: 195 | s = Shakespeare( 196 | """ 197 | Test. 198 | 199 | Romeo, a test. 200 | Juliet, a test. 201 | 202 | Act I: Nothing to see here. 203 | Scene III: These are not the actors you're looking for. 204 | 205 | [Enter Romeo and Juliet] 206 | 207 | Juliet: Are you as good as nothing? 208 | 209 | Scene I: Still nothing. 210 | 211 | [A pause] 212 | 213 | Scene III: Nothing strikes back. 214 | 215 | [A pause] 216 | """ 217 | ) 218 | assert "is not unique" in str(exc.value).lower() 219 | assert "Scene >>III<<: Nothing strikes back." in str(exc.value) 220 | assert exc.value.interpreter == None 221 | 222 | 223 | def test_duplicate_act_numbers(capsys): 224 | with pytest.raises(ShakespeareRuntimeError) as exc: 225 | s = Shakespeare( 226 | """ 227 | Test. 228 | 229 | Romeo, a test. 230 | Juliet, a test. 231 | 232 | Act I: Nothing to see here. 233 | Scene III: These are not the actors you're looking for. 234 | 235 | [Enter Romeo and Juliet] 236 | 237 | Juliet: Are you as good as nothing? 238 | 239 | Scene I: Still nothing. 240 | 241 | [A pause] 242 | 243 | Act I: Nothing strikes back. 244 | 245 | Scene I: This doesn't matter. 246 | 247 | [A pause] 248 | """ 249 | ) 250 | assert "is not unique" in str(exc.value).lower() 251 | assert "Act >>I<<: Nothing strikes back." in str(exc.value) 252 | assert exc.value.interpreter == None 253 | -------------------------------------------------------------------------------- /shakespearelang/shakespeare.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | """ 4 | Shakespeare -- An interpreter for the Shakespeare Programming Language 5 | """ 6 | 7 | from ._parser import shakespeareParser 8 | from tatsu.exceptions import FailedParse 9 | from .errors import ShakespeareRuntimeError, ShakespeareParseError 10 | from ._utils import parseinfo_context, normalize_name 11 | from ._state import State 12 | from ._preprocess import Play 13 | from .settings import Settings 14 | from ._operation import operations_from_event, operation_from_sentence, Goto, Breakpoint 15 | from ._expression import expression_from_ast 16 | import math 17 | from tatsu.ast import AST 18 | from functools import wraps 19 | from typing import Callable, Literal, Union 20 | 21 | 22 | class Shakespeare: 23 | """ 24 | Interpreter for the Shakespeare Programming Language. 25 | """ 26 | 27 | def __init__( 28 | self, 29 | play: Union[str, AST], 30 | input_style: Literal["basic", "interactive"] = "basic", 31 | output_style: Literal["basic", "verbose", "debug"] = "basic", 32 | ): 33 | """ 34 | Arguments: 35 | play: The AST or source code of the SPL play to be interpreted. Must 36 | be provided and cannot be changed after initialization of the 37 | interpreter. 38 | input_style: 'basic' is the default and best for piped input. 39 | 'interactive' is nicer when getting input from a human. 40 | This is passed directly along to the [Settings][shakespearelang.Settings] 41 | instance for this interpreter. To change after initialization, 42 | modify that instance at the .settings property of the interpreter. 43 | output_style: The output style to initialize the interpreter with. 44 | 'basic' is the default and outputs exactly what the SPL play generated. 45 | 'verbose' prefixes output and shows visible representations of 46 | whitespace characters. 'debug' is like 'verbose' but with debug output 47 | from the interpreter. 48 | This is passed directly along to the [Settings][shakespearelang.Settings] 49 | instance for this interpreter. To change after initialization, 50 | modify that instance at the .settings property of the interpreter. 51 | """ 52 | self.settings = Settings(input_style, output_style) 53 | self.parser = shakespeareParser() 54 | ast = self._parse_if_necessary(play, "play") 55 | self.play = Play(ast) 56 | self.state = State(ast.dramatis_personae) 57 | 58 | self.current_position = 0 59 | 60 | # DECORATORS 61 | 62 | def _add_interpreter_context_to_errors(func): 63 | @wraps(func) 64 | def inner_function(self, *args, **kwargs): 65 | try: 66 | return func(self, *args, **kwargs) 67 | except ShakespeareRuntimeError as exc: 68 | if not exc.interpreter: 69 | exc.interpreter = self 70 | raise exc 71 | 72 | return inner_function 73 | 74 | def _parse_first_argument(rule_name): 75 | def decorator(func): 76 | @wraps(func) 77 | def inner_function(self, first_arg, *args, **kwargs): 78 | parsed = self._parse_if_necessary(first_arg, rule_name) 79 | 80 | try: 81 | return func(self, parsed, *args, **kwargs) 82 | except ShakespeareRuntimeError as exc: 83 | if not exc.parseinfo: 84 | exc.parseinfo = parsed.parseinfo 85 | raise exc 86 | 87 | return inner_function 88 | 89 | return decorator 90 | 91 | # PUBLIC METHODS 92 | 93 | @_add_interpreter_context_to_errors 94 | def run(self, breakpoint_callback: Callable[[], None] = lambda: None) -> None: 95 | """ 96 | Execute the entire SPL play, optionally pausing at breakpoints. 97 | 98 | Arguments: 99 | breakpoint_callback: An optional callback, to be called if a debug 100 | breakpoint is hit. After the callback returns, execution 101 | continues. The default is to do nothing. 102 | """ 103 | while not self.play_over(): 104 | if isinstance(self._next_operation(), Breakpoint): 105 | self._advance_position() 106 | breakpoint_callback() 107 | else: 108 | self.step_forward() 109 | 110 | @_add_interpreter_context_to_errors 111 | def play_over(self) -> bool: 112 | """ 113 | Returns: 114 | Whether the play has finished. 115 | """ 116 | return self.current_position >= len(self.play.operations) 117 | 118 | @_add_interpreter_context_to_errors 119 | def step_forward(self) -> None: 120 | """ 121 | Run the next event in the play. 122 | """ 123 | operation_to_run = self._next_operation() 124 | if isinstance(operation_to_run, Breakpoint): 125 | self._advance_position() 126 | return 127 | 128 | if self.settings.output_style == "debug": 129 | print( 130 | f"----------\nat line {operation_to_run.ast_node.parseinfo.line}\n-----\n" 131 | + parseinfo_context(operation_to_run.ast_node.parseinfo) 132 | + "-----\n" 133 | + str(self.state) 134 | + "\n----------" 135 | ) 136 | 137 | pos_before_operation = self.current_position 138 | self._run_operation(operation_to_run) 139 | if self.current_position == pos_before_operation: 140 | self._advance_position() 141 | 142 | @_add_interpreter_context_to_errors 143 | def next_operation_text(self) -> str: 144 | """ 145 | Returns: 146 | The SPL source code of the next operation (sentence or event) 147 | to run in the play, with context before and after. 148 | """ 149 | current_operation = self._next_operation() 150 | return parseinfo_context(current_operation.ast_node.parseinfo) 151 | 152 | @_add_interpreter_context_to_errors 153 | @_parse_first_argument("event") 154 | def run_event(self, event: Union[str, AST]) -> None: 155 | """ 156 | Run an event in the current execution context. 157 | 158 | Arguments: 159 | event: A string or AST representation of an event (line, entrance, 160 | exit, etc). 161 | """ 162 | operations = operations_from_event(event) 163 | for operation in operations: 164 | self._run_operation(operation) 165 | 166 | @_add_interpreter_context_to_errors 167 | @_parse_first_argument("sentence") 168 | def run_sentence(self, sentence: Union[str, AST], character: str): 169 | """ 170 | Run a sentence in the current execution context. 171 | 172 | Arguments: 173 | sentence: A string or AST representation of a sentence. 174 | character: The name of the character speaking the sentence. 175 | """ 176 | operation = operation_from_sentence(sentence, character) 177 | self._run_operation(operation) 178 | 179 | @_add_interpreter_context_to_errors 180 | @_parse_first_argument("value") 181 | def evaluate_expression(self, expression: Union[str, AST], character: str) -> int: 182 | """ 183 | Evaluate an expression in the current execution context. 184 | 185 | Arguments: 186 | expression: A string or AST representation of an expression. 187 | character: The name of the character speaking the expression. 188 | 189 | Returns: 190 | The integer value of the expression. 191 | """ 192 | expression = expression_from_ast(expression, character) 193 | return expression.evaluate(self.state) 194 | 195 | def parse(self, item, rule_name): 196 | try: 197 | return self.parser.parse(item, rule_name=rule_name) 198 | except FailedParse as parseException: 199 | raise ShakespeareParseError(parseException) from None 200 | 201 | # HELPERS 202 | 203 | def _run_operation(self, operation): 204 | if isinstance(operation, Goto): 205 | operation.run(self.state, self, self.play, self.settings) 206 | else: 207 | operation.run(self.state, self.settings) 208 | 209 | def _parse_if_necessary(self, item, rule_name): 210 | if not isinstance(item, str): 211 | return item 212 | return self.parse(item, rule_name) 213 | 214 | def _next_operation(self): 215 | return self.play.operations[self.current_position] 216 | 217 | def _advance_position(self): 218 | self.current_position += 1 219 | -------------------------------------------------------------------------------- /shakespearelang/_operation.py: -------------------------------------------------------------------------------- 1 | from ._utils import normalize_name 2 | from ._expression import expression_from_ast 3 | from .errors import ShakespeareRuntimeError, ShakespeareParseError 4 | from tatsu.ast import AST 5 | 6 | 7 | class Operation: 8 | def __init__(self, ast_node: AST): 9 | self.ast_node = ast_node 10 | self._setup(ast_node) 11 | 12 | def _setup(self, ast_node): 13 | pass 14 | 15 | def run(self, state, settings): 16 | try: 17 | self._run_logic(state, settings) 18 | except ShakespeareRuntimeError as exc: 19 | if not exc.parseinfo: 20 | exc.parseinfo = self.ast_node.parseinfo 21 | raise exc 22 | 23 | def _run_logic(self, state, settings): 24 | pass 25 | 26 | 27 | class Entrance(Operation): 28 | def _setup(self, ast_node: AST): 29 | self.characters = [normalize_name(c) for c in ast_node.characters] 30 | 31 | def _run_logic(self, state, settings): 32 | if settings.output_style in ["verbose", "debug"]: 33 | print(f"Enter {', '.join(self.characters)}") 34 | state.enter_characters(self.characters) 35 | 36 | 37 | class Exit(Operation): 38 | def _setup(self, ast_node: AST): 39 | self.character = normalize_name(ast_node.character) 40 | 41 | def _run_logic(self, state, settings): 42 | if settings.output_style in ["verbose", "debug"]: 43 | print(f"Exit {self.character}") 44 | state.exit_character(self.character) 45 | 46 | 47 | class Exeunt(Operation): 48 | def _setup(self, ast_node: AST): 49 | if ast_node.characters: 50 | self.characters = [normalize_name(c) for c in ast_node.characters] 51 | else: 52 | self.characters = None 53 | 54 | def _run_logic(self, state, settings): 55 | if self.characters is not None: 56 | if settings.output_style in ["verbose", "debug"]: 57 | print(f"Exeunt {', '.join(self.characters)}") 58 | state.exeunt_characters(self.characters) 59 | else: 60 | if settings.output_style in ["verbose", "debug"]: 61 | print("Exeunt all") 62 | state.exeunt_all() 63 | 64 | 65 | class Breakpoint(Operation): 66 | pass 67 | 68 | 69 | class SentenceOperation(Operation): 70 | def __init__(self, ast_node: AST, character: str): 71 | self.ast_node = ast_node 72 | self.op_ast_node = ast_node.operation 73 | self.character = normalize_name(character) 74 | self.has_condition = ast_node.condition is not None 75 | if self.has_condition: 76 | self.condition_type_positive = ( 77 | ast_node.condition.parseinfo.rule == "positive_if" 78 | ) 79 | else: 80 | self.condition_type_positive = None 81 | self._setup() 82 | 83 | def _setup(self): 84 | pass 85 | 86 | def run(self, state, settings): 87 | state.assert_character_on_stage(self.character) 88 | 89 | if self.has_condition and self.condition_type_positive != state.global_boolean: 90 | if settings.output_style in ["verbose", "debug"]: 91 | print( 92 | f"Not executing conditional {type(self).__name__.lower()}, global boolean is {state.global_boolean}" 93 | ) 94 | else: 95 | try: 96 | self._run_logic(state, settings) 97 | except ShakespeareRuntimeError as exc: 98 | if not exc.parseinfo: 99 | exc.parseinfo = self.ast_node.parseinfo 100 | raise exc 101 | 102 | 103 | class Question(SentenceOperation): 104 | _COMPARATIVE_TYPE_HANDLERS = { 105 | "positive_comparative": lambda a, b: a > b, 106 | "negative_comparative": lambda a, b: a < b, 107 | "neutral_comparative": lambda a, b: a == b, 108 | } 109 | 110 | def _setup(self): 111 | self.first_value = expression_from_ast( 112 | self.op_ast_node.first_value, self.character 113 | ) 114 | self.second_value = expression_from_ast( 115 | self.op_ast_node.second_value, self.character 116 | ) 117 | comparative_rule = self.op_ast_node.comparative.parseinfo.rule 118 | if comparative_rule not in self._COMPARATIVE_TYPE_HANDLERS: 119 | raise ShakespeareRuntimeError( 120 | f"Unknown comparative type: {comparative_rule}" 121 | ) 122 | self.comparison = self._COMPARATIVE_TYPE_HANDLERS[comparative_rule] 123 | 124 | def _run_logic(self, state, settings): 125 | result = self._evaluate(state) 126 | 127 | if settings.output_style in ["verbose", "debug"]: 128 | print(f"Setting global boolean to {result}") 129 | 130 | state.global_boolean = result 131 | 132 | def _evaluate(self, state) -> bool: 133 | return self.comparison( 134 | self.first_value.evaluate(state), self.second_value.evaluate(state) 135 | ) 136 | 137 | 138 | class Assignment(SentenceOperation): 139 | def _setup(self): 140 | self.value = expression_from_ast(self.op_ast_node.value, self.character) 141 | 142 | def _run_logic(self, state, settings): 143 | character_opposite = state.character_opposite(self.character) 144 | value = self.value.evaluate(state) 145 | state.character_by_name(character_opposite).value = value 146 | 147 | if settings.output_style in ["verbose", "debug"]: 148 | print(f"{character_opposite} set to {value}") 149 | 150 | 151 | class Input(SentenceOperation): 152 | def _setup(self): 153 | self.input_type = "number" if self.op_ast_node.input_number else "char" 154 | 155 | def _run_logic(self, state, settings): 156 | character_to_set = state.character_opposite(self.character) 157 | if self.input_type == "number": 158 | value = settings.input_manager.consume_numeric_input() 159 | else: 160 | value = settings.input_manager.consume_character_input() 161 | 162 | if settings.output_style in ["verbose", "debug"]: 163 | print(f"Setting {character_to_set} to input value {repr(value)}") 164 | 165 | state.character_by_name(character_to_set).value = value 166 | 167 | 168 | class Output(SentenceOperation): 169 | def _setup(self): 170 | self.output_type = "number" if self.op_ast_node.output_number else "char" 171 | 172 | def _run_logic(self, state, settings): 173 | character_to_output = state.character_opposite(self.character) 174 | value = state.character_by_name(character_to_output).value 175 | if settings.output_style in ["verbose", "debug"]: 176 | print(f"Outputting {character_to_output}") 177 | if self.output_type == "number": 178 | settings.output_manager.output_number(value) 179 | else: 180 | settings.output_manager.output_character(value) 181 | 182 | 183 | class Push(SentenceOperation): 184 | def _setup(self): 185 | self.value = expression_from_ast(self.op_ast_node.value, self.character) 186 | 187 | def _run_logic(self, state, settings): 188 | pushing_character = state.character_opposite(self.character) 189 | value = self.value.evaluate(state) 190 | state.character_by_name(pushing_character).push(value) 191 | 192 | if settings.output_style in ["verbose", "debug"]: 193 | print(f"{pushing_character} pushed {value}") 194 | 195 | 196 | class Pop(SentenceOperation): 197 | def _run_logic(self, state, settings): 198 | popping_character = state.character_opposite(self.character) 199 | state.character_by_name(popping_character).pop() 200 | 201 | if settings.output_style in ["verbose", "debug"]: 202 | print(f"Popping stack of {popping_character}") 203 | 204 | 205 | class Goto(SentenceOperation): 206 | def _setup(self): 207 | self.destination = self.op_ast_node.destination.value 208 | 209 | def run(self, state, interpreter, play, settings): 210 | state.assert_character_on_stage(self.character) 211 | 212 | if self.has_condition and self.condition_type_positive != state.global_boolean: 213 | if settings.output_style in ["verbose", "debug"]: 214 | print( 215 | f"Not jumping to Scene {self.destination} because global boolean is {state.global_boolean}" 216 | ) 217 | return 218 | 219 | if settings.output_style in ["verbose", "debug"]: 220 | print(f"Jumping to Scene {self.destination}") 221 | act = play.get_act(interpreter.current_position) 222 | if self.destination not in play.scene_indices[act]: 223 | raise ShakespeareRuntimeError(f"Scene {self.destination} does not exist.") 224 | new_position = play.scene_indices[act][self.destination] 225 | interpreter.current_position = new_position 226 | 227 | 228 | _OPERATIONS_CONSTRUCTORS = { 229 | "entrance": Entrance, 230 | "exit": Exit, 231 | "exeunt": Exeunt, 232 | "breakpoint": Breakpoint, 233 | "question": Question, 234 | "assignment": Assignment, 235 | "input": Input, 236 | "output": Output, 237 | "push": Push, 238 | "pop": Pop, 239 | "goto": Goto, 240 | } 241 | 242 | 243 | def operations_from_event(event: AST): 244 | rule = event.parseinfo.rule 245 | if rule == "line": 246 | return [operation_from_sentence(s, event.character) for s in event.contents] 247 | else: 248 | return [_OPERATIONS_CONSTRUCTORS[rule](event)] 249 | 250 | 251 | def operation_from_sentence(sentence: AST, character: str): 252 | sentence_operation_rule = sentence.operation.parseinfo.rule 253 | return _OPERATIONS_CONSTRUCTORS[sentence_operation_rule](sentence, character) 254 | -------------------------------------------------------------------------------- /shakespearelang/tests/sample_plays/sierpinski.spl: -------------------------------------------------------------------------------- 1 | The Saga of Sierpinski. 2 | 3 | Mark Antony, the star of the show. 4 | Mariana, Mark Antony's other half. 5 | Pericles, a temporary foil to Mark Antony. 6 | Pinch, Pericles' other half. 7 | Regan, another temporary foil. 8 | Robin, Regan's other half. 9 | Oberon, the count-to-be. 10 | Octavia, who lives in a loop--married to Oberon. 11 | Sebastian, a rather spacey fellow with an indented face. 12 | Slender, Sebastian's other half. 13 | 14 | Act I: A Triangle. 15 | 16 | Scene I: Receiving Instructions from On High. 17 | 18 | [Enter Oberon and Mark Antony] 19 | 20 | Oberon: 21 | You pretty little warm thing! Thou art as prompt as the difference 22 | between the square of thyself and your golden hair. Speak your mind. 23 | Remember a flirt-gill! You are the product of the sum of your nose and 24 | thy rich grandmother and the difference between a bold mighty brave good roman 25 | and the cunning moon. 26 | 27 | Mark Antony: 28 | Listen to your heart! 29 | 30 | [Exeunt] 31 | 32 | Scene II: Mark Antony's Rage. 33 | 34 | [Enter Mark Antony and Mariana] 35 | 36 | Mark Antony: 37 | You are a flirt-gill! Remember thyself! 38 | 39 | [Exit Mariana] 40 | 41 | [Enter Octavia] 42 | 43 | Mark Antony: 44 | You are as bottomless as nothing! 45 | 46 | [Exit Octavia] 47 | 48 | [Enter Slender] 49 | 50 | Mark Antony: 51 | You are as damned as nothing! 52 | 53 | [Exit Slender] 54 | 55 | Scene III: Mark Antony's Friendship. 56 | 57 | [Enter Sebastian] 58 | 59 | Mark Antony: 60 | Remember nothing! You are as fine as the big lovely sweet delicious rich plum. 61 | 62 | [Exit Sebastian] 63 | 64 | Scene IV: A Bold Question. 65 | 66 | [Exeunt] 67 | 68 | [Enter Octavia and Oberon] 69 | 70 | Octavia: 71 | Am I as trustworthy as you? If so, let us proceed to scene XXVI. 72 | 73 | [Exeunt] 74 | 75 | Scene V: Mark Antony's Wrath. 76 | 77 | [Enter Mark Antony and Pericles] 78 | 79 | Mark Antony: 80 | You are as half-witted as a pig! 81 | 82 | [Exit Pericles] 83 | 84 | [Enter Pinch] 85 | 86 | Mark Antony: 87 | You are as vile as starvation! 88 | 89 | [Exit Pinch] 90 | 91 | [Enter Regan] 92 | 93 | Mark Antony: 94 | You are as bottomless as zero! Remember yourself! 95 | 96 | [Exit Regan] 97 | 98 | [Enter Robin] 99 | 100 | Mark Antony: 101 | You are as stupid as nothing! Remember thyself! 102 | 103 | Scene VI: An Option is Offered. 104 | 105 | [Exeunt] 106 | 107 | [Enter Octavia and Sebastian] 108 | 109 | Octavia: 110 | Am I as tiny as nothing? If so, let us proceed to scene IX. 111 | 112 | [Exit Octavia] 113 | 114 | [Enter Slender] 115 | 116 | Scene VII: The Loan. 117 | 118 | Sebastian: 119 | Am I as cunning as nothing? If so, let us proceed to scene VIII. 120 | 121 | Sebastian: 122 | Remember thyself! You are as honest as I. 123 | 124 | Slender: 125 | Recall the interior space of life. Let us return to scene VII. 126 | 127 | Scene VIII: The Loan's Return, With Interest. 128 | 129 | Slender: 130 | Am I as cunning as nothing? If so, let us proceed to scene IX. Remember thyself. 131 | You are as honest as I! Remember yourself. 132 | 133 | Sebastian: Recall what I have given you! Let us return to scene VIII! 134 | 135 | Scene IX: Room is Made. 136 | 137 | [Exeunt] 138 | 139 | [Enter Slender and Sebastian] 140 | 141 | Sebastian: 142 | Am I as normal as nothing? If so, let us proceed to scene X. Remember thyself. 143 | Thou art as bold as I! 144 | 145 | [Exit Slender] 146 | 147 | [Enter Pinch] 148 | 149 | Sebastian: 150 | Remember thyself! Thou be as brave as I. 151 | 152 | [Exit Pinch] 153 | 154 | [Enter Slender] 155 | 156 | Slender: 157 | Recall what comes next. Let us return to scene IX. 158 | 159 | Scene X: Mark Antony's Crisis. 160 | 161 | [Exeunt] 162 | 163 | [Enter Mark Antony and Pinch] 164 | 165 | Mark Antony: 166 | Am I as cowardly as nothing? If so, let us proceed to scene XI. 167 | Am I as horrid as the plague? If so, let us proceed to scene XII. 168 | Remember thyself! Thou art as beautiful as I. 169 | 170 | [Exit Pinch] 171 | 172 | [Enter Mariana] 173 | 174 | Mark Antony: 175 | Remember yourself. You are as normal as me! 176 | 177 | Mariana: 178 | Recall your deepest fears! Let us return to scene X. 179 | 180 | Scene XI: The Confession of Shallowness. 181 | 182 | [Exeunt] 183 | 184 | [Enter Mark Antony and Mariana] 185 | 186 | Mark Antony: 187 | Remember thyself. You are as beautiful as me! 188 | 189 | Mariana: 190 | Recall your vanity! 191 | 192 | Scene XII: More Room is Made. 193 | 194 | [Exeunt] 195 | 196 | [Enter Sebastian and Slender] 197 | 198 | Slender: 199 | Am I as blue as nothing? If so, we shall proceed to scene XIII. Remember thyself! 200 | You are as healthy as me. 201 | 202 | [Exit Sebastian] 203 | 204 | [Enter Pinch] 205 | 206 | Slender: 207 | Remember thyself. You are as honest as me! 208 | 209 | [Exit Pinch] 210 | 211 | [Enter Sebastian] 212 | 213 | Sebastian: 214 | Recall what lies ahead. We must return to scene XII. 215 | 216 | Scene XIII: An Assessment. 217 | 218 | [Exeunt] 219 | 220 | [Enter Mark Antony and Pinch] 221 | 222 | Mark Antony: 223 | Remember thyself. You are nothing! 224 | 225 | [Exit Pinch] 226 | 227 | [Enter Mariana] 228 | 229 | Mark Antony: 230 | Am I as horrid as a flirt-gill? If not, we must return to scene IX. 231 | 232 | [Exit Mariana] 233 | 234 | [Enter Pinch] 235 | 236 | Mark Antony: 237 | Recall something, anything! 238 | 239 | Scene XIV: Sebastian and Slender Find Balance. 240 | 241 | [Exeunt] 242 | 243 | [Enter Sebastian and Slender] 244 | 245 | Slender: 246 | Am I as good as nothing? If so, let us proceed to scene XV. Remember thyself. 247 | You are as mighty as me! 248 | 249 | Sebastian: 250 | Recall what is required of us. We shall return to scene XIV. 251 | 252 | Scene XV: Mark Antony and Mariana Find Balance. 253 | 254 | [Exeunt] 255 | 256 | [Enter Mark Antony and Mariana] 257 | 258 | Mariana: 259 | Am I as miserable as a hog? If so, let us proceed to scene XVI. 260 | Remember thyself. You are as honest as me! 261 | 262 | Mark Antony: 263 | Recall the price to pay. Let us return to scene XV. 264 | 265 | Scene XVI: A Short Speech Foretells A New Start. 266 | 267 | [Exeunt] 268 | 269 | [Enter Mark Antony and Pinch] 270 | 271 | Mark Antony: 272 | Remember thyself. You are nothing. 273 | 274 | Scene XVII: Mark Antony's New Crisis. 275 | 276 | [Exeunt] 277 | 278 | [Enter Mark Antony and Pinch] 279 | 280 | Mark Antony: 281 | Am I as lying as nothing? If so, let us proceed to scene XVIII. 282 | Am I as infected as your goat? If so, let us proceed to scene XIX. 283 | Remember thyself. Thou art as vile as I! 284 | 285 | [Exit Pinch] 286 | 287 | [Enter Robin] 288 | 289 | Mark Antony: 290 | Remember thyself. You are as loving as me! 291 | 292 | [Exit Robin] 293 | 294 | [Enter Mariana] 295 | 296 | Mark Antony: 297 | Remember thyself. You are as trustworthy as me! 298 | 299 | Mariana: 300 | Recall the cycles of life! We must return to scene XVII. 301 | 302 | Scene XVIII: An Interlude. 303 | 304 | [Exeunt] 305 | 306 | [Enter Mark Antony and Mariana] 307 | 308 | Mark Antony: 309 | Remember thyself. Thou art as loving as I! 310 | 311 | Mariana: 312 | Recall what you'd rather forget! 313 | 314 | Scene XIX: Taking Some Space. 315 | 316 | [Exeunt] 317 | 318 | [Enter Mark Antony and Pinch] 319 | 320 | Mark Antony: 321 | Remember thyself. You are as proud as twice my mighty noble handsome cute chihuahua. 322 | 323 | Scene XX: Regan and Robin Find Balance. 324 | 325 | [Exeunt] 326 | 327 | [Enter Regan and Robin] 328 | 329 | Robin: 330 | Am I as good as nothing? If so, let us proceed to scene XXI. 331 | Remember yourself. You are as gentle as myself! 332 | 333 | Regan: 334 | Recall our obligations. We must return to scene XX. 335 | 336 | Scene XXI: The Reprise. 337 | 338 | [Exeunt] 339 | 340 | [Enter Regan and Pinch] 341 | 342 | Regan: 343 | Am I as good as nothing? If so, let us proceed to scene XXII. 344 | Remember thyself. You are as large as me! 345 | 346 | Pinch: 347 | Recall what we've already done. We shall return to scene XXI. 348 | 349 | Scene XXII: A New Assessment. 350 | 351 | Pinch: Remember thyself! 352 | 353 | [Exeunt] 354 | 355 | [Enter Mark Antony and Pinch] 356 | 357 | Mark Antony: 358 | Remember thyself. You are as stuffed as nothing. 359 | 360 | [Exit Pinch] 361 | 362 | [Enter Mariana] 363 | 364 | Mark Antony: 365 | Am I as smelly as a toad? If not, let us return to scene XVII. 366 | 367 | [Exit Mariana] 368 | 369 | [Enter Pinch] 370 | 371 | Mark Antony: 372 | Recall our earlier experiences. 373 | 374 | Scene XXIII: Mariana Makes Herself Clear. 375 | 376 | [Exeunt] 377 | 378 | [Enter Mark Antony and Mariana] 379 | 380 | Mariana: 381 | Am I as horrible as a famine? If so, let us proceed to scene XXIV. 382 | 383 | Mark Antony: 384 | Recall thy past! We shall return to scene XXIII. 385 | 386 | Scene XXIV: A Triumphant Return. 387 | 388 | [Exeunt] 389 | 390 | [Enter Mark Antony and Pinch] 391 | 392 | Pinch: 393 | Am I as hairy as thine goat? If so, let us proceed to scene XXV. 394 | Remember thyself! You are me. 395 | 396 | Mark Antony: 397 | Recall what I have lost! Let us return to scene XXIV! 398 | 399 | Scene XXV: The Beginning of The End. 400 | 401 | [Exeunt] 402 | 403 | [Enter Octavia and Oberon] 404 | 405 | Oberon: You are as noble as the sum of yourself and the moon. Let us return to scene IV. 406 | 407 | Scene XXVI: When All Becomes Clear. 408 | 409 | [Exeunt] 410 | 411 | [Enter Mark Antony and Mariana] 412 | 413 | Mark Antony: 414 | Am I as good as nothing? If so, let us proceed to scene XXVII. 415 | Am I as rotten as Microsoft? If so, let us proceed to scene XXVIII. 416 | 417 | Mariana: 418 | Speak your mind! Recall thy story. Let us return to scene XXVI. 419 | 420 | Scene XXVII: A New Start. 421 | 422 | Mariana: 423 | You are as furry as the sum of my happy healthy mighty horse and your large 424 | hamster! Speak your mind. Recall thy story. We shall return to Scene XXVI. 425 | 426 | Scene XXVIII: Fin. 427 | 428 | Mark Antony: 429 | You are as cunning as the sum of a rich proud noble roman and a huge stone wall! 430 | Speak your mind. 431 | 432 | [Exeunt] 433 | -------------------------------------------------------------------------------- /shakespearelang/shakespeare.ebnf: -------------------------------------------------------------------------------- 1 | @@ignorecase :: True 2 | @@parseinfo :: True 3 | 4 | be = "am" | 5 | "are" | 6 | "art" | 7 | "be" | 8 | "is"; 9 | 10 | article = "a" | 11 | "an" | 12 | "the"; 13 | 14 | first_person = "I" | 15 | "me"; 16 | 17 | first_person_reflexive = "myself"; 18 | 19 | first_person_possessive = "mine" | "my"; 20 | 21 | second_person = "thee" | 22 | "thou" | 23 | "you"; 24 | 25 | second_person_reflexive = "thyself" | "yourself"; 26 | 27 | second_person_possessive = "thine" | "thy" | "your"; 28 | 29 | third_person_possessive = "his" | "her" | "its" | "their"; 30 | 31 | possessive = first_person_possessive | second_person_possessive | third_person_possessive; 32 | 33 | positive_comparative = comparison:("better" | "bigger" | "fresher" | "friendlier" | "nicer" | "jollier" | "more" positive_adjective) "than"; 34 | 35 | negative_comparative = comparison:("punier" | 36 | "smaller" | 37 | "worse" | "more" negative_adjective) "than"; 38 | 39 | negative_adjective = "bad" | 40 | "cowardly" | 41 | "cursed" | 42 | "damned" | 43 | "dirty" | 44 | "disgusting" | 45 | "distasteful" | 46 | "dusty" | 47 | "evil" | 48 | "fat-kidneyed" | 49 | "fatherless" | 50 | "fat" | 51 | "foul" | 52 | "hairy" | 53 | "half-witted" | 54 | "horrible" | 55 | "horrid" | 56 | "infected" | 57 | "lying" | 58 | "miserable" | 59 | "misused" | 60 | "oozing" | 61 | "rotten" | 62 | "rotten" | 63 | "smelly" | 64 | "snotty" | 65 | "sorry" | 66 | "stinking" | 67 | "stuffed" | 68 | "stupid" | 69 | "vile" | 70 | "villainous" | 71 | "worried"; 72 | 73 | neutral_adjective = "big" | 74 | "black" | 75 | "blue" | 76 | "bluest" | 77 | "bottomless" | 78 | "furry" | 79 | "green" | 80 | "hard" | 81 | "huge" | 82 | "large" | 83 | "little" | 84 | "normal" | 85 | "old" | 86 | "purple" | 87 | "red" | 88 | "rural" | 89 | "small" | 90 | "tiny" | 91 | "white" | 92 | "yellow"; 93 | 94 | positive_adjective = "amazing" | 95 | "beautiful" | 96 | "blossoming" | 97 | "bold" | 98 | "brave" | 99 | "charming" | 100 | "clearest" | 101 | "cunning" | 102 | "cute" | 103 | "delicious" | 104 | "embroidered" | 105 | "fair" | 106 | "fine" | 107 | "gentle" | 108 | "golden" | 109 | "good" | 110 | "handsome" | 111 | "happy" | 112 | "healthy" | 113 | "honest" | 114 | "lovely" | 115 | "loving" | 116 | "mighty" | 117 | "noble" | 118 | "peaceful" | 119 | "pretty" | 120 | "prompt" | 121 | "proud" | 122 | "reddest" | 123 | "rich" | 124 | "smooth" | 125 | "sunny" | 126 | "sweet" | 127 | "sweetest" | 128 | "trustworthy" | 129 | "warm"; 130 | 131 | negative_noun = "Hell" | 132 | "Microsoft" | 133 | "bastard" | 134 | "beggar" | 135 | "blister" | 136 | "codpiece" | 137 | "coward" | 138 | "curse" | 139 | "death" | 140 | "devil" | 141 | "draught" | 142 | "famine" | 143 | "flirt-gill" | 144 | "goat" | 145 | "hate" | 146 | "hog" | 147 | "hound" | 148 | "leech" | 149 | "lie" | 150 | "pig" | 151 | "plague" | 152 | "starvation" | 153 | "toad" | 154 | "war" | 155 | "wolf"; 156 | 157 | neutral_noun = "animal" | 158 | "aunt" | 159 | "brother" | 160 | "cat" | 161 | "chihuahua" | 162 | "cousin" | 163 | "cow" | 164 | "daughter" | 165 | "door" | 166 | "face" | 167 | "father" | 168 | "fellow" | 169 | "granddaughter" | 170 | "grandfather" | 171 | "grandmother" | 172 | "grandson" | 173 | "hair" | 174 | "hamster" | 175 | "horse" | 176 | "lamp" | 177 | "lantern" | 178 | "mistletoe" | 179 | "moon" | 180 | "morning" | 181 | "mother" | 182 | "nephew" | 183 | "niece" | 184 | "nose" | 185 | "purse" | 186 | "road" | 187 | "roman" | 188 | "sister" | 189 | "sky" | 190 | "son" | 191 | "squirrel" | 192 | "stone" "wall" | 193 | "thing" | 194 | "town" | 195 | "tree" | 196 | "uncle" | 197 | "wind"; 198 | 199 | positive_noun = "Heaven" | 200 | "King" | 201 | "Lord" | 202 | "angel" | 203 | "flower" | 204 | "happiness" | 205 | "joy" | 206 | "plum" | 207 | "summer's" "day" | 208 | "hero" | 209 | "rose" | 210 | "kingdom" | 211 | "pony"; 212 | 213 | character = "Achilles" | 214 | "Adonis" | 215 | "Adriana" | 216 | "Aegeon" | 217 | "Aemilia" | 218 | "Agamemnon" | 219 | "Agrippa" | 220 | "Ajax" | 221 | "Alonso" | 222 | "Andromache" | 223 | "Angelo" | 224 | "Antiochus" | 225 | "Antonio" | 226 | "Arthur" | 227 | "Autolycus" | 228 | "Balthazar" | 229 | "Banquo" | 230 | "Beatrice" | 231 | "Benedick" | 232 | "Benvolio" | 233 | "Bianca" | 234 | "Brabantio" | 235 | "Brutus" | 236 | "Capulet" | 237 | "Cassandra" | 238 | "Cassius" | 239 | "Christopher" "Sly" | 240 | "Cicero" | 241 | "Claudio" | 242 | "Claudius" | 243 | "Cleopatra" | 244 | "Cordelia" | 245 | "Cornelius" | 246 | "Cressida" | 247 | "Cymberline" | 248 | "Demetrius" | 249 | "Desdemona" | 250 | "Dionyza" | 251 | "Doctor" "Caius" | 252 | "Dogberry" | 253 | "Don" "John" | 254 | "Don" "Pedro" | 255 | "Donalbain" | 256 | "Dorcas" | 257 | "Duncan" | 258 | "Egeus" | 259 | "Emilia" | 260 | "Escalus" | 261 | "Falstaff" | 262 | "Fenton" | 263 | "Ferdinand" | 264 | "Ford" | 265 | "Fortinbras" | 266 | "Francisca" | 267 | "Friar" "John" | 268 | "Friar" "Laurence" | 269 | "Gertrude" | 270 | "Goneril" | 271 | "Hamlet" | 272 | "Hecate" | 273 | "Hector" | 274 | "Helen" | 275 | "Helena" | 276 | "Hermia" | 277 | "Hermonie" | 278 | "Hippolyta" | 279 | "Horatio" | 280 | "Imogen" | 281 | "Isabella" | 282 | "John" "of" "Gaunt" | 283 | "John" "of" "Lancaster" | 284 | "Julia" | 285 | "Juliet" | 286 | "Julius" "Caesar" | 287 | "King" "Henry" | 288 | "King" "John" | 289 | "King" "Lear" | 290 | "King" "Richard" | 291 | "Lady" "Capulet" | 292 | "Lady" "Macbeth" | 293 | "Lady" "Macduff" | 294 | "Lady" "Montague" | 295 | "Lennox" | 296 | "Leonato" | 297 | "Luciana" | 298 | "Lucio" | 299 | "Lychorida" | 300 | "Lysander" | 301 | "Macbeth" | 302 | "Macduff" | 303 | "Malcolm" | 304 | "Mariana" | 305 | "Mark" "Antony" | 306 | "Mercutio" | 307 | "Miranda" | 308 | "Mistress" "Ford" | 309 | "Mistress" "Overdone" | 310 | "Mistress" "Page" | 311 | "Montague" | 312 | "Mopsa" | 313 | "Oberon" | 314 | "Octavia" | 315 | "Octavius" "Caesar" | 316 | "Olivia" | 317 | "Ophelia" | 318 | "Orlando" | 319 | "Orsino" | 320 | "Othello" | 321 | "Page" | 322 | "Pantino" | 323 | "Paris" | 324 | "Pericles" | 325 | "Pinch" | 326 | "Polonius" | 327 | "Pompeius" | 328 | "Portia" | 329 | "Priam" | 330 | "Prince" "Henry" | 331 | "Prospero" | 332 | "Proteus" | 333 | "Publius" | 334 | "Puck" | 335 | "Queen" "Elinor" | 336 | "Regan" | 337 | "Robin" | 338 | "Romeo" | 339 | "Rosalind" | 340 | "Sebastian" | 341 | "Shallow" | 342 | "Shylock" | 343 | "Slender" | 344 | "Solinus" | 345 | "Stephano" | 346 | "Thaisa" | 347 | "The" "Abbot" "of" "Westminster" | 348 | "The" "Apothecary" | 349 | "The" "Archbishop" "of" "Canterbury" | 350 | "The" "Duke" "of" "Milan" | 351 | "The" "Duke" "of" "Venice" | 352 | "The" "Ghost" | 353 | "Theseus" | 354 | "Thurio" | 355 | "Timon" | 356 | "Titania" | 357 | "Titus" | 358 | "Troilus" | 359 | "Tybalt" | 360 | "Ulysses" | 361 | "Valentine" | 362 | "Venus" | 363 | "Vincentio" | 364 | "Viola"; 365 | 366 | nothing = nothing_word:("nothing" | "zero"); 367 | 368 | positive_or_neutral_adjective = positive_adjective | neutral_adjective; 369 | 370 | positive_or_neutral_noun = positive_noun | neutral_noun; 371 | 372 | neutral_comparative = "as" comparison:(negative_adjective | positive_or_neutral_adjective) "as"; 373 | 374 | negative_noun_phrase = [article | possessive] adjectives:{(negative_adjective | neutral_adjective)}* noun:negative_noun; 375 | 376 | positive_noun_phrase = [article | possessive] adjectives:{positive_or_neutral_adjective}* noun:positive_or_neutral_noun; 377 | 378 | noun_phrase = negative_noun_phrase | 379 | positive_noun_phrase; 380 | 381 | first_person_value = first_person_word:(first_person | first_person_reflexive); 382 | 383 | second_person_value = second_person_word:(second_person | second_person_reflexive); 384 | 385 | character_name = name:character; 386 | 387 | value = expression | 388 | first_person_value | 389 | second_person_value | 390 | noun_phrase | 391 | character_name | 392 | nothing; 393 | 394 | binary_operation = "the" "difference" "between" | 395 | "the" "product" "of" | 396 | "the" "quotient" "between" | 397 | "the" "remainder" "of" "the" "quotient" "between" | 398 | "the" "sum" "of"; 399 | 400 | binary_expression = operation:binary_operation first_value:value 'and' second_value:value; 401 | 402 | unary_operation = "the" "cube" "of" | 403 | "the" "factorial" "of" | 404 | "the" "square" "of" | 405 | "the" "square" "root" "of" | 406 | "twice"; 407 | 408 | unary_expression = operation:unary_operation value:value; 409 | 410 | expression = binary_expression | unary_expression; 411 | 412 | negative_if = if:("If" "not" ","); 413 | 414 | positive_if = if:("If" "so" ","); 415 | 416 | question = be first_value:value comparative:(positive_comparative | neutral_comparative | negative_comparative) second_value:value "?"; 417 | 418 | assignment = second_person [be] ["as" [(positive_or_neutral_adjective | negative_adjective) "as"]] value:value ("!" | "."); 419 | 420 | let_us = "Let" "us" | "We" "shall" | "We" "must"; 421 | 422 | proceed_to = "proceed" "to" | "return" "to"; 423 | 424 | roman_numeral = value:?/(?i)M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})/?; 425 | 426 | goto = let_us proceed_to "scene" destination:roman_numeral ("!" | "."); 427 | 428 | output = (output_number:("open" second_person_possessive "heart") | output_char:("speak" second_person_possessive "mind")) ("!" | "."); 429 | 430 | input = (input_number:("listen" "to" second_person_possessive "heart") | input_char:("open" second_person_possessive "mind")) ("!" | "."); 431 | 432 | push = "Remember" value:value ("!" | "."); 433 | 434 | pop = "Recall" recall_string:text_before_punctuation ("!" | "."); 435 | 436 | sentence = condition:[negative_if | positive_if] operation:(question | assignment | goto | output | input | push | pop); 437 | 438 | line_contents = @+:sentence {?"\s*" @+:sentence}; 439 | 440 | line = character:character ":" contents:line_contents; 441 | 442 | character_list = @+:character {"," @+:character}* "and" @+:character | @+:character; 443 | 444 | breakpoint = dummy:("[" "A" "pause" "]"); 445 | 446 | entrance = "[" "Enter" characters:character_list "]"; 447 | 448 | exit = "[" "Exit" character:character "]"; 449 | 450 | exeunt = "[" action:"Exeunt" [characters:character_list] "]"; 451 | 452 | event = line | breakpoint | entrance | exit | exeunt; 453 | 454 | repl_input = !(character ":") display_character:character $ | !(character ":" /.*\./) [expression_character:character ":"] value:value $ | event:event $ | sentences:({sentence}+) $; 455 | 456 | text_before_punctuation = ?/[^!\.]*/?; 457 | 458 | scene = "Scene" ?"\s*" number:roman_numeral ":" name:text_before_punctuation ("!" | ".") events:({event}*); 459 | 460 | act = "Act" ?"\s*" number:roman_numeral ":" name:text_before_punctuation ("!" | ".") scenes:({scene}*); 461 | 462 | dramatis_persona = character:character ',' text_before_punctuation ("!" | "."); 463 | 464 | dramatis_personae = {?"\s*" @+:dramatis_persona}*; 465 | 466 | play = title:text_before_punctuation ("!" | ".") dramatis_personae:dramatis_personae acts:({act}*) $; 467 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_enter_and_exit.py: -------------------------------------------------------------------------------- 1 | from shakespearelang import Shakespeare 2 | from shakespearelang.errors import ShakespeareRuntimeError 3 | import pytest 4 | 5 | 6 | def test_enter_one(): 7 | s = Shakespeare( 8 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 9 | ) 10 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 11 | 12 | s.run_event("[Enter Juliet]") 13 | assert_on_stage(s, ["Juliet"]) 14 | assert_off_stage(s, ["Romeo", "The Ghost", "Demetrius"]) 15 | 16 | 17 | def test_enter_two(): 18 | s = Shakespeare( 19 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 20 | ) 21 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 22 | 23 | s.run_event("[Enter Demetrius and Romeo]") 24 | assert_on_stage(s, ["Demetrius", "Romeo"]) 25 | assert_off_stage(s, ["Juliet", "The Ghost"]) 26 | 27 | 28 | def test_enter_three(): 29 | s = Shakespeare( 30 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 31 | ) 32 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 33 | 34 | s.run_event("[Enter Demetrius, The Ghost and Romeo]") 35 | assert_on_stage(s, ["Demetrius", "The Ghost", "Romeo"]) 36 | assert_off_stage(s, ["Juliet"]) 37 | 38 | 39 | def test_enter_four(): 40 | s = Shakespeare( 41 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 42 | ) 43 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 44 | 45 | s.run_event("[Enter Demetrius, The Ghost, Juliet and Romeo]") 46 | assert_on_stage(s, ["Demetrius", "The Ghost", "Juliet", "Romeo"]) 47 | assert_off_stage(s, []) 48 | 49 | 50 | def test_exit(): 51 | s = Shakespeare( 52 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 53 | ) 54 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 55 | 56 | s.run_event("[Enter Demetrius, The Ghost, Juliet and Romeo]") 57 | assert_on_stage(s, ["Demetrius", "The Ghost", "Juliet", "Romeo"]) 58 | assert_off_stage(s, []) 59 | 60 | s.run_event("[Exit Demetrius]") 61 | assert_on_stage(s, ["The Ghost", "Juliet", "Romeo"]) 62 | assert_off_stage(s, ["Demetrius"]) 63 | 64 | 65 | def test_exeunt_two(): 66 | s = Shakespeare( 67 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 68 | ) 69 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 70 | 71 | s.run_event("[Enter Demetrius, The Ghost, Juliet and Romeo]") 72 | assert_on_stage(s, ["Demetrius", "The Ghost", "Juliet", "Romeo"]) 73 | assert_off_stage(s, []) 74 | 75 | s.run_event("[Exeunt Demetrius and Romeo]") 76 | assert_on_stage(s, ["The Ghost", "Juliet"]) 77 | assert_off_stage(s, ["Demetrius", "Romeo"]) 78 | 79 | 80 | def test_exeunt_three(): 81 | s = Shakespeare( 82 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 83 | ) 84 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 85 | 86 | s.run_event("[Enter Demetrius, The Ghost, Juliet and Romeo]") 87 | assert_on_stage(s, ["Demetrius", "The Ghost", "Juliet", "Romeo"]) 88 | assert_off_stage(s, []) 89 | 90 | s.run_event("[Exeunt Demetrius, The Ghost and Romeo]") 91 | assert_on_stage(s, ["Juliet"]) 92 | assert_off_stage(s, ["Demetrius", "Romeo", "The Ghost"]) 93 | 94 | 95 | def test_exeunt_four_explicit(): 96 | s = Shakespeare( 97 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 98 | ) 99 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 100 | 101 | s.run_event("[Enter Demetrius, The Ghost, Juliet and Romeo]") 102 | assert_on_stage(s, ["Demetrius", "The Ghost", "Juliet", "Romeo"]) 103 | assert_off_stage(s, []) 104 | 105 | s.run_event("[Exeunt Demetrius, The Ghost, Juliet and Romeo]") 106 | assert_on_stage(s, []) 107 | assert_off_stage(s, ["Demetrius", "Romeo", "The Ghost", "Juliet"]) 108 | 109 | 110 | def test_exeunt_four_implicit(): 111 | s = Shakespeare( 112 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 113 | ) 114 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 115 | 116 | s.run_event("[Enter Demetrius, The Ghost, Juliet and Romeo]") 117 | assert_on_stage(s, ["Demetrius", "The Ghost", "Juliet", "Romeo"]) 118 | assert_off_stage(s, []) 119 | 120 | s.run_event("[Exeunt]") 121 | assert_on_stage(s, []) 122 | assert_off_stage(s, ["Demetrius", "Romeo", "The Ghost", "Juliet"]) 123 | 124 | 125 | def test_complex_shuffle(): 126 | s = Shakespeare( 127 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 128 | ) 129 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 130 | 131 | s.run_event("[Enter Demetrius and Juliet]") 132 | assert_on_stage(s, ["Demetrius", "Juliet"]) 133 | assert_off_stage(s, ["The Ghost", "Romeo"]) 134 | 135 | s.run_event("[Exit Juliet]") 136 | assert_on_stage(s, ["Demetrius"]) 137 | assert_off_stage(s, ["The Ghost", "Romeo", "Juliet"]) 138 | 139 | s.run_event("[Enter Romeo]") 140 | assert_on_stage(s, ["Demetrius", "Romeo"]) 141 | assert_off_stage(s, ["The Ghost", "Juliet"]) 142 | 143 | s.run_event("[Enter The Ghost]") 144 | assert_on_stage(s, ["Demetrius", "Romeo", "The Ghost"]) 145 | assert_off_stage(s, ["Juliet"]) 146 | 147 | s.run_event("[Exeunt Demetrius and The Ghost]") 148 | assert_on_stage(s, ["Romeo"]) 149 | assert_off_stage(s, ["Juliet", "Demetrius", "The Ghost"]) 150 | 151 | s.run_event("[Enter Juliet]") 152 | assert_on_stage(s, ["Romeo", "Juliet"]) 153 | assert_off_stage(s, ["Demetrius", "The Ghost"]) 154 | 155 | s.run_event("[Exeunt]") 156 | assert_on_stage(s, []) 157 | assert_off_stage(s, ["Demetrius", "The Ghost", "Romeo", "Juliet"]) 158 | 159 | 160 | def test_errors_on_duplicate_entrance(): 161 | s = Shakespeare( 162 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 163 | ) 164 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 165 | 166 | s.run_event("[Enter Juliet]") 167 | assert_on_stage(s, ["Juliet"]) 168 | assert_off_stage(s, ["Romeo", "The Ghost", "Demetrius"]) 169 | 170 | with pytest.raises(ShakespeareRuntimeError) as exc: 171 | s.run_event("[Enter Juliet]") 172 | assert "already on stage" in str(exc.value).lower() 173 | assert ">>[Enter Juliet]<<" in str(exc.value) 174 | assert exc.value.interpreter == s 175 | assert_on_stage(s, ["Juliet"]) 176 | assert_off_stage(s, ["Romeo", "The Ghost", "Demetrius"]) 177 | 178 | 179 | def test_errors_on_duplicate_exit(): 180 | s = Shakespeare( 181 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 182 | ) 183 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 184 | 185 | s.run_event("[Enter Juliet and Romeo]") 186 | assert_on_stage(s, ["Juliet", "Romeo"]) 187 | assert_off_stage(s, ["The Ghost", "Demetrius"]) 188 | 189 | s.run_event("[Exit Juliet]") 190 | assert_on_stage(s, ["Romeo"]) 191 | assert_off_stage(s, ["Juliet", "The Ghost", "Demetrius"]) 192 | 193 | with pytest.raises(ShakespeareRuntimeError) as exc: 194 | s.run_event("[Exit Juliet]") 195 | assert "not on stage" in str(exc.value).lower() 196 | assert ">>[Exit Juliet]<<" in str(exc.value) 197 | assert exc.value.interpreter == s 198 | assert_on_stage(s, ["Romeo"]) 199 | assert_off_stage(s, ["Juliet", "The Ghost", "Demetrius"]) 200 | 201 | 202 | def test_errors_on_exit_before_entrance(): 203 | s = Shakespeare( 204 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 205 | ) 206 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 207 | 208 | with pytest.raises(ShakespeareRuntimeError) as exc: 209 | s.run_event("[Exit Juliet]") 210 | assert "not on stage" in str(exc.value).lower() 211 | assert ">>[Exit Juliet]<<" in str(exc.value) 212 | assert exc.value.interpreter == s 213 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 214 | 215 | 216 | def test_errors_on_partial_duplicate_entrance(): 217 | s = Shakespeare( 218 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 219 | ) 220 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 221 | 222 | s.run_event("[Enter Juliet]") 223 | assert_on_stage(s, ["Juliet"]) 224 | assert_off_stage(s, ["Romeo", "The Ghost", "Demetrius"]) 225 | 226 | with pytest.raises(ShakespeareRuntimeError) as exc: 227 | s.run_event("[Enter The Ghost and Juliet]") 228 | assert "already on stage" in str(exc.value).lower() 229 | assert ">>[Enter The Ghost and Juliet]<<" in str(exc.value) 230 | assert exc.value.interpreter == s 231 | assert_on_stage(s, ["Juliet"]) 232 | assert_off_stage(s, ["Romeo", "The Ghost", "Demetrius"]) 233 | 234 | 235 | def test_errors_on_partial_duplicate_exeunt(): 236 | s = Shakespeare( 237 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 238 | ) 239 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 240 | 241 | s.run_event("[Enter Juliet and Romeo]") 242 | assert_on_stage(s, ["Juliet", "Romeo"]) 243 | assert_off_stage(s, ["The Ghost", "Demetrius"]) 244 | 245 | s.run_event("[Exit Juliet]") 246 | assert_on_stage(s, ["Romeo"]) 247 | assert_off_stage(s, ["Juliet", "The Ghost", "Demetrius"]) 248 | 249 | with pytest.raises(ShakespeareRuntimeError) as exc: 250 | s.run_event("[Exeunt Romeo and Juliet]") 251 | assert "not on stage" in str(exc.value).lower() 252 | assert ">>[Exeunt Romeo and Juliet]<<" in str(exc.value) 253 | assert exc.value.interpreter == s 254 | assert_on_stage(s, ["Romeo"]) 255 | assert_off_stage(s, ["Juliet", "The Ghost", "Demetrius"]) 256 | 257 | 258 | def test_errors_on_partial_exeunt_before_entrance(): 259 | s = Shakespeare( 260 | "Foo. Juliet, a test. Romeo, a test. The Ghost, a test. Demetrius, a test." 261 | ) 262 | assert_off_stage(s, ["Juliet", "Romeo", "The Ghost", "Demetrius"]) 263 | 264 | s.run_event("[Enter Juliet and Romeo]") 265 | assert_on_stage(s, ["Juliet", "Romeo"]) 266 | assert_off_stage(s, ["The Ghost", "Demetrius"]) 267 | 268 | with pytest.raises(ShakespeareRuntimeError) as exc: 269 | s.run_event("[Exeunt Juliet and Demetrius]") 270 | assert "not on stage" in str(exc.value).lower() 271 | assert ">>[Exeunt Juliet and Demetrius]<<" in str(exc.value) 272 | assert exc.value.interpreter == s 273 | assert_on_stage(s, ["Juliet", "Romeo"]) 274 | assert_off_stage(s, ["The Ghost", "Demetrius"]) 275 | 276 | 277 | def assert_on_stage(s, l): 278 | assert sorted([c for c in s.state._characters_on_stage]) == sorted(l) 279 | 280 | 281 | def assert_off_stage(s, l): 282 | assert sorted( 283 | [c for c in s.state.characters if c not in s.state._characters_on_stage] 284 | ) == sorted(l) 285 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_console.py: -------------------------------------------------------------------------------- 1 | from tatsu.exceptions import FailedParse 2 | from io import StringIO 3 | import pytest 4 | import pexpect 5 | from textwrap import dedent 6 | from .utils import expect_interaction, expect_output_exactly 7 | 8 | STANDARD_REPL_BEGINNING = """ 9 | A REPL-tastic Adventure. 10 | 11 | Romeo, a player. 12 | Juliet, a player. 13 | 14 | Act I: All the World. 15 | Scene I: A Stage. 16 | 17 | [Enter Romeo and Juliet] 18 | 19 | >> """ 20 | 21 | 22 | def test_errors_on_nonsense_characters(): 23 | cli = pexpect.spawn("shakespeare --characters='Foobar,Not Real'") 24 | cli.setecho(False) 25 | cli.waitnoecho() 26 | 27 | expect_output_exactly( 28 | cli, 29 | dedent( 30 | """ 31 | A REPL-tastic Adventure. 32 | 33 | Foobar, a player. 34 | Not Real, a player. 35 | 36 | Act I: All the World. 37 | Scene I: A Stage. 38 | 39 | [Enter Foobar and Not Real] 40 | 41 | SPL parse error: failed to parse character 42 | at line 4 43 | ----- context ----- 44 | 45 | A REPL-tastic Adventure. 46 | 47 | ∨ 48 | Foobar, a player. 49 | ∧ 50 | Not Real, a player. 51 | 52 | Act I: All the World. 53 | Scene I: A Stage. 54 | 55 | ----- details ----- 56 | parsing stack: character, dramatis_persona, dramatis_personae, play 57 | full error message: 58 | expecting one of: 'Achilles' 'Adonis' 'Adriana' 'Aegeon''Aemilia' 'Agamemnon' 'Agrippa' 'Ajax''Alonso' 'Andromache' 'Angelo''Antiochus' 'Antonio' 'Arthur''Autolycus' 'Balthazar' 'Banquo''Beatrice' 'Benedick' 'Benvolio''Bianca' 'Brabantio' 'Brutus' 'Capulet''Cassandra' 'Cassius' 'Christopher''Cicero' 'Claudio' 'Claudius''Cleopatra' 'Cordelia' 'Cornelius''Cressida' 'Cymberline' 'Demetrius''Desdemona' 'Dionyza' 'Doctor''Dogberry' 'Don' 'Donalbain' 'Dorcas''Duncan' 'Egeus' 'Emilia' 'Escalus''Falstaff' 'Fenton' 'Ferdinand' 'Ford''Fortinbras' 'Francisca' 'Friar''Gertrude' 'Goneril' 'Hamlet' 'Hecate''Hector' 'Helen' 'Helena' 'Hermia''Hermonie' 'Hippolyta' 'Horatio''Imogen' 'Isabella' 'John' 'Julia''Juliet' 'Julius' 'King' 'Lady' 'Lennox''Leonato' 'Luciana' 'Lucio' 'Lychorida''Lysander' 'Macbeth' 'Macduff' 'Malcolm''Mariana' 'Mark' 'Mercutio' 'Miranda''Mistress' 'Montague' 'Mopsa' 'Oberon''Octavia' 'Octavius' 'Olivia' 'Ophelia''Orlando' 'Orsino' 'Othello' 'Page''Pantino' 'Paris' 'Pericles' 'Pinch''Polonius' 'Pompeius' 'Portia' 'Priam''Prince' 'Prospero' 'Proteus' 'Publius''Puck' 'Queen' 'Regan' 'Robin' 'Romeo''Rosalind' 'Sebastian' 'Shallow''Shylock' 'Slender' 'Solinus' 'Stephano''Thaisa' 'The' 'Theseus' 'Thurio''Timon' 'Titania' 'Titus' 'Troilus''Tybalt' 'Ulysses' 'Valentine' 'Venus''Vincentio' 'Viola' 59 | """ 60 | ), 61 | eof=True, 62 | ) 63 | 64 | 65 | def test_works_with_arbitrary_characters(): 66 | cli = pexpect.spawn("shakespeare --characters='Lady Capulet,The Ghost,Horatio'") 67 | cli.setecho(False) 68 | cli.waitnoecho() 69 | 70 | expect_output_exactly( 71 | cli, 72 | dedent( 73 | """ 74 | A REPL-tastic Adventure. 75 | 76 | Lady Capulet, a player. 77 | The Ghost, a player. 78 | Horatio, a player. 79 | 80 | Act I: All the World. 81 | Scene I: A Stage. 82 | 83 | [Enter Lady Capulet, The Ghost and Horatio] 84 | 85 | >> """ 86 | ), 87 | ) 88 | expect_interaction(cli, "[Exeunt]", "Exeunt all") 89 | expect_interaction(cli, "[Enter Lady Capulet]", "Enter Lady Capulet") 90 | expect_interaction(cli, "[Enter Horatio]", "Enter Horatio") 91 | expect_interaction(cli, "exit", "", prompt=False) 92 | expect_output_exactly(cli, "", eof=True) 93 | 94 | 95 | def test_runs_noop(): 96 | cli = pexpect.spawn("shakespeare") 97 | cli.setecho(False) 98 | cli.waitnoecho() 99 | 100 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 101 | expect_interaction(cli, "exit", "", prompt=False) 102 | expect_output_exactly(cli, "", eof=True) 103 | 104 | 105 | def test_runs_next(): 106 | cli = pexpect.spawn("shakespeare") 107 | cli.setecho(False) 108 | cli.waitnoecho() 109 | 110 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 111 | expect_interaction(cli, "next", "", prompt=False) 112 | expect_output_exactly(cli, "", eof=True) 113 | 114 | 115 | def test_runs_continue(): 116 | cli = pexpect.spawn("shakespeare") 117 | cli.setecho(False) 118 | cli.waitnoecho() 119 | 120 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 121 | expect_interaction(cli, "continue", "", prompt=False) 122 | expect_output_exactly(cli, "", eof=True) 123 | 124 | 125 | def test_display_parse_error(): 126 | cli = pexpect.spawn("shakespeare") 127 | cli.setecho(False) 128 | cli.waitnoecho() 129 | 130 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 131 | expect_interaction( 132 | cli, 133 | "foobar", 134 | dedent( 135 | """\ 136 | SPL parse error: failed to parse character 137 | at line 1 138 | ----- context ----- 139 | ∨ 140 | foobar 141 | ∧ 142 | 143 | ----- details ----- 144 | parsing stack: character, repl_input 145 | full error message: 146 | expecting one of: 'Achilles' 'Adonis' 'Adriana' 'Aegeon''Aemilia' 'Agamemnon' 'Agrippa' 'Ajax''Alonso' 'Andromache' 'Angelo''Antiochus' 'Antonio' 'Arthur''Autolycus' 'Balthazar' 'Banquo''Beatrice' 'Benedick' 'Benvolio''Bianca' 'Brabantio' 'Brutus' 'Capulet''Cassandra' 'Cassius' 'Christopher''Cicero' 'Claudio' 'Claudius''Cleopatra' 'Cordelia' 'Cornelius''Cressida' 'Cymberline' 'Demetrius''Desdemona' 'Dionyza' 'Doctor''Dogberry' 'Don' 'Donalbain' 'Dorcas''Duncan' 'Egeus' 'Emilia' 'Escalus''Falstaff' 'Fenton' 'Ferdinand' 'Ford''Fortinbras' 'Francisca' 'Friar''Gertrude' 'Goneril' 'Hamlet' 'Hecate''Hector' 'Helen' 'Helena' 'Hermia''Hermonie' 'Hippolyta' 'Horatio''Imogen' 'Isabella' 'John' 'Julia''Juliet' 'Julius' 'King' 'Lady' 'Lennox''Leonato' 'Luciana' 'Lucio' 'Lychorida''Lysander' 'Macbeth' 'Macduff' 'Malcolm''Mariana' 'Mark' 'Mercutio' 'Miranda''Mistress' 'Montague' 'Mopsa' 'Oberon''Octavia' 'Octavius' 'Olivia' 'Ophelia''Orlando' 'Orsino' 'Othello' 'Page''Pantino' 'Paris' 'Pericles' 'Pinch''Polonius' 'Pompeius' 'Portia' 'Priam''Prince' 'Prospero' 'Proteus' 'Publius''Puck' 'Queen' 'Regan' 'Robin' 'Romeo''Rosalind' 'Sebastian' 'Shallow''Shylock' 'Slender' 'Solinus' 'Stephano''Thaisa' 'The' 'Theseus' 'Thurio''Timon' 'Titania' 'Titus' 'Troilus''Tybalt' 'Ulysses' 'Valentine' 'Venus''Vincentio' 'Viola'""" 147 | ), 148 | ) 149 | expect_interaction(cli, "exit", "", prompt=False) 150 | expect_output_exactly(cli, "", eof=True) 151 | 152 | 153 | def test_display_runtime_error(): 154 | cli = pexpect.spawn("shakespeare") 155 | cli.setecho(False) 156 | cli.waitnoecho() 157 | 158 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 159 | expect_interaction( 160 | cli, 161 | "Juliet: You are as good as the quotient between a pig and nothing.", 162 | dedent( 163 | """\ 164 | SPL runtime error: Cannot divide by zero 165 | at line 1 166 | ----- context ----- 167 | Juliet: You are as good as >>the quotient between a pig and nothing<<. 168 | 169 | ----- state ----- 170 | global boolean = False 171 | on stage: 172 | Romeo = 0 () 173 | Juliet = 0 () 174 | off stage:""" 175 | ), 176 | ) 177 | expect_interaction(cli, "exit", "", prompt=False) 178 | expect_output_exactly(cli, "", eof=True) 179 | 180 | 181 | def test_display_repl_specific_error(): 182 | cli = pexpect.spawn("shakespeare") 183 | cli.setecho(False) 184 | cli.waitnoecho() 185 | 186 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 187 | expect_interaction(cli, "You are as good as nothing.", "Who's saying this?") 188 | expect_interaction(cli, "exit", "", prompt=False) 189 | expect_output_exactly(cli, "", eof=True) 190 | 191 | 192 | def test_last_character_speaking(): 193 | cli = pexpect.spawn("shakespeare") 194 | cli.setecho(False) 195 | cli.waitnoecho() 196 | 197 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 198 | expect_interaction(cli, "Juliet: You are as good as nothing.", "Romeo set to 0") 199 | expect_interaction(cli, "You are nothing.", "Romeo set to 0") 200 | expect_interaction(cli, "Romeo: You are nothing.", "Juliet set to 0") 201 | expect_interaction(cli, "You are nothing.", "Juliet set to 0") 202 | expect_interaction(cli, "The sum of thyself and a pig", "-1") 203 | expect_interaction(cli, "Juliet: The sum of thyself and a fat pig", "-2") 204 | expect_interaction(cli, "exit", "", prompt=False) 205 | expect_output_exactly(cli, "", eof=True) 206 | 207 | 208 | def test_detailed_logging(): 209 | cli = pexpect.spawn("shakespeare") 210 | cli.setecho(False) 211 | cli.waitnoecho() 212 | 213 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 214 | cli.sendline( 215 | "Juliet: You are as good as a good good good good good good animal. Speak your mind! Listen to your heart!" 216 | ) 217 | expect_output_exactly( 218 | cli, 219 | dedent( 220 | """\ 221 | Romeo set to 64 222 | Outputting Romeo 223 | Outputting character: '@' 224 | Taking input number: """ 225 | ), 226 | ) 227 | cli.sendline("10") 228 | expect_output_exactly(cli, "Setting Romeo to input value 10\n>> ") 229 | expect_interaction( 230 | cli, "Open your heart!", "Outputting Romeo\nOutputting number: 10" 231 | ) 232 | expect_interaction(cli, "exit", "", prompt=False) 233 | expect_output_exactly(cli, "", eof=True) 234 | 235 | 236 | def test_breakpoint(): 237 | cli = pexpect.spawn("shakespeare") 238 | cli.setecho(False) 239 | cli.waitnoecho() 240 | 241 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 242 | expect_interaction(cli, "[A pause]", "") 243 | expect_interaction(cli, "exit", "", prompt=False) 244 | expect_output_exactly(cli, "", eof=True) 245 | 246 | 247 | def test_expression(): 248 | cli = pexpect.spawn("shakespeare") 249 | cli.setecho(False) 250 | cli.waitnoecho() 251 | 252 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 253 | expect_interaction(cli, "Romeo: The sum of a furry animal and a pig", "1") 254 | expect_interaction(cli, "[Exit Romeo]", "Exit Romeo") 255 | expect_interaction( 256 | cli, 257 | "Romeo: The sum of a furry animal and a pig", 258 | dedent( 259 | """\ 260 | SPL runtime error: Romeo is not on stage! 261 | at line 1 262 | ----- context ----- 263 | Romeo: >>The sum of a furry animal and a pig<< 264 | 265 | ----- state ----- 266 | global boolean = False 267 | on stage: 268 | Juliet = 0 () 269 | off stage: 270 | Romeo = 0 ()""" 271 | ), 272 | ) 273 | expect_interaction(cli, "exit", "", prompt=False) 274 | expect_output_exactly(cli, "", eof=True) 275 | 276 | 277 | def test_display_character(): 278 | cli = pexpect.spawn("shakespeare") 279 | cli.setecho(False) 280 | cli.waitnoecho() 281 | 282 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 283 | expect_interaction( 284 | cli, 285 | "Juliet: Remember thyself! You are a pig! Remember bad Hell! Remember a good animal!", 286 | dedent( 287 | """\ 288 | Romeo pushed 0 289 | Romeo set to -1 290 | Romeo pushed -2 291 | Romeo pushed 2""" 292 | ), 293 | ) 294 | expect_interaction(cli, "Romeo", "-1 (2 -2 0)") 295 | expect_interaction(cli, "exit", "", prompt=False) 296 | expect_output_exactly(cli, "", eof=True) 297 | 298 | 299 | def test_display_state(): 300 | cli = pexpect.spawn("shakespeare") 301 | cli.setecho(False) 302 | cli.waitnoecho() 303 | 304 | expect_output_exactly(cli, STANDARD_REPL_BEGINNING) 305 | expect_interaction( 306 | cli, 307 | "Juliet: Remember thyself! You are a pig!", 308 | "Romeo pushed 0\nRomeo set to -1", 309 | ) 310 | expect_interaction( 311 | cli, 312 | "state", 313 | dedent( 314 | """\ 315 | global boolean = False 316 | on stage: 317 | Romeo = -1 (0) 318 | Juliet = 0 () 319 | off stage:""" 320 | ), 321 | ) 322 | expect_interaction(cli, "exit", "", prompt=False) 323 | expect_output_exactly(cli, "", eof=True) 324 | -------------------------------------------------------------------------------- /shakespearelang/tests/sample_plays/parse_everything.spl: -------------------------------------------------------------------------------- 1 | Trying to Parse the World. 2 | 3 | Achilles, a character. 4 | Adonis, a character. 5 | Adriana, a character. 6 | Aegeon, a character. 7 | Aemilia, a character. 8 | Agamemnon, a character. 9 | Agrippa, a character. 10 | Ajax, a character. 11 | Alonso, a character. 12 | Andromache, a character. 13 | Angelo, a character. 14 | Antiochus, a character. 15 | Antonio, a character. 16 | Arthur, a character. 17 | Autolycus, a character. 18 | Balthazar, a character. 19 | Banquo, a character. 20 | Beatrice, a character. 21 | Benedick, a character. 22 | Benvolio, a character. 23 | Bianca, a character. 24 | Brabantio, a character. 25 | Brutus, a character. 26 | Capulet, a character. 27 | Cassandra, a character. 28 | Cassius, a character. 29 | Christopher Sly, a character. 30 | Cicero, a character. 31 | Claudio, a character. 32 | Claudius, a character. 33 | Cleopatra, a character. 34 | Cordelia, a character. 35 | Cornelius, a character. 36 | Cressida, a character. 37 | Cymberline, a character. 38 | Demetrius, a character. 39 | Desdemona, a character. 40 | Dionyza, a character. 41 | Doctor Caius, a character. 42 | Dogberry, a character. 43 | Don John, a character. 44 | Don Pedro, a character. 45 | Donalbain, a character. 46 | Dorcas, a character. 47 | Duncan, a character. 48 | Egeus, a character. 49 | Emilia, a character. 50 | Escalus, a character. 51 | Falstaff, a character. 52 | Fenton, a character. 53 | Ferdinand, a character. 54 | Ford, a character. 55 | Fortinbras, a character. 56 | Francisca, a character. 57 | Friar John, a character. 58 | Friar Laurence, a character. 59 | Gertrude, a character. 60 | Goneril, a character. 61 | Hamlet, a character. 62 | Hecate, a character. 63 | Hector, a character. 64 | Helen, a character. 65 | Helena, a character. 66 | Hermia, a character. 67 | Hermonie, a character. 68 | Hippolyta, a character. 69 | Horatio, a character. 70 | Imogen, a character. 71 | Isabella, a character. 72 | John of Gaunt, a character. 73 | John of Lancaster, a character. 74 | Julia, a character. 75 | Juliet, a character. 76 | Julius Caesar, a character. 77 | King Henry, a character. 78 | King John, a character. 79 | King Lear, a character. 80 | King Richard, a character. 81 | Lady Capulet, a character. 82 | Lady Macbeth, a character. 83 | Lady Macduff, a character. 84 | Lady Montague, a character. 85 | Lennox, a character. 86 | Leonato, a character. 87 | Luciana, a character. 88 | Lucio, a character. 89 | Lychorida, a character. 90 | Lysander, a character. 91 | Macbeth, a character. 92 | Macduff, a character. 93 | Malcolm, a character. 94 | Mariana, a character. 95 | Mark Antony, a character. 96 | Mercutio, a character. 97 | Miranda, a character. 98 | Mistress Ford, a character. 99 | Mistress Overdone, a character. 100 | Mistress Page, a character. 101 | Montague, a character. 102 | Mopsa, a character. 103 | Oberon, a character. 104 | Octavia, a character. 105 | Octavius Caesar, a character. 106 | Olivia, a character. 107 | Ophelia, a character. 108 | Orlando, a character. 109 | Orsino, a character. 110 | Othello, a character. 111 | Page, a character. 112 | Pantino, a character. 113 | Paris, a character. 114 | Pericles, a character. 115 | Pinch, a character. 116 | Polonius, a character. 117 | Pompeius, a character. 118 | Portia, a character. 119 | Priam, a character. 120 | Prince Henry, a character. 121 | Prospero, a character. 122 | Proteus, a character. 123 | Publius, a character. 124 | Puck, a character. 125 | Queen Elinor, a character. 126 | Regan, a character. 127 | Robin, a character. 128 | Romeo, a character. 129 | Rosalind, a character. 130 | Sebastian, a character. 131 | Shallow, a character. 132 | Shylock, a character. 133 | Slender, a character. 134 | Solinus, a character. 135 | Stephano, a character. 136 | Thaisa, a character. 137 | The Abbot of Westminster, a character. 138 | The Apothecary, a character. 139 | The Archbishop of Canterbury, a character. 140 | The Duke 141 | of Milan, a character with flaws. 142 | The Duke 143 | of Venice, a character with flaws. 144 | The Ghost, a 145 | character with 146 | strange absences. 147 | Theseus, a character. 148 | Thurio, a character. 149 | Timon, a character. 150 | Titania, a character. 151 | Titus, a character. 152 | Troilus, a character. 153 | Tybalt, a character. 154 | Ulysses, a character. 155 | Valentine, a character. 156 | Venus, a character. 157 | Vincentio, a character. 158 | Viola, a character. 159 | 160 | act MCCCXXXV: empty. 161 | 162 | act MCCCXXXVI: not even real! 163 | 164 | sceNe MMmMCcxLiI: this is here. 165 | 166 | sceNe MMmMCcxLI: this is here. 167 | 168 | sceNe MCCCxXxVII: this is here. 169 | 170 | act MCCCXXXVII: The 171 | Only Act. 172 | 173 | sceNe MMmMCcxLiI: The Prince's 174 | Speech. 175 | 176 | [ENTER pinch] 177 | 178 | [exit PINCH] 179 | 180 | [eNtEr 181 | HaMlEt 182 | and jUlIeT] 183 | 184 | JULIET: 185 | 186 | thee 187 | AM 188 | my 189 | tOaD. 190 | 191 | tHoU 192 | be 193 | tHe 194 | HELL! 195 | 196 | you 197 | IS 198 | aN 199 | HoUnD 200 | ! 201 | 202 | THEE 203 | are 204 | aS 205 | rIcH 206 | As 207 | Their 208 | stUPId 209 | GOAT 210 | . 211 | 212 | yOu 213 | art 214 | Its 215 | HoUnD 216 | . 217 | 218 | THEE 219 | aS 220 | rIcH 221 | As 222 | a 223 | stUPId 224 | GOAT 225 | . 226 | 227 | THEE 228 | aS 229 | bottomLESS 230 | As 231 | a 232 | stUPId 233 | GOAT 234 | . 235 | 236 | THEE 237 | aS 238 | disGUSTing 239 | As 240 | a 241 | stUPId 242 | GOAT 243 | . 244 | 245 | THeE 246 | a 247 | stUPId 248 | GOAT 249 | . 250 | 251 | jUlIeT: 252 | thou 253 | art 254 | thee 255 | . 256 | 257 | thou 258 | art 259 | thou 260 | . 261 | 262 | thou 263 | art 264 | you 265 | . 266 | 267 | thou 268 | art 269 | thyself 270 | . 271 | 272 | thou 273 | art 274 | yourself 275 | . 276 | 277 | thou 278 | art 279 | I 280 | . 281 | 282 | thou 283 | art 284 | me 285 | . 286 | 287 | thou 288 | art 289 | myself 290 | . 291 | 292 | thou 293 | art 294 | juliet 295 | . 296 | 297 | thou 298 | art 299 | romeo 300 | . 301 | 302 | thou 303 | art 304 | tybalt 305 | . 306 | 307 | thou 308 | art 309 | the 310 | duke 311 | of 312 | milan 313 | . 314 | 315 | thou 316 | art 317 | nothing 318 | ! 319 | 320 | juliet: 321 | 322 | thou 323 | art 324 | the 325 | sum 326 | of 327 | the 328 | sum 329 | of 330 | nothing 331 | and 332 | the 333 | sum 334 | of 335 | the 336 | difference 337 | between 338 | the 339 | product 340 | of 341 | the 342 | factorial 343 | of 344 | a 345 | golden 346 | chihuahua 347 | and 348 | twice 349 | a 350 | chihuahua 351 | and 352 | me 353 | and 354 | the 355 | quotient 356 | between 357 | the 358 | square 359 | root 360 | of 361 | twice 362 | a 363 | golden 364 | chihuahua 365 | and 366 | the 367 | square 368 | of 369 | a 370 | chihuahua 371 | and 372 | the 373 | remainder 374 | of 375 | the 376 | quotient 377 | between 378 | a 379 | golden 380 | chihuahua 381 | and 382 | the 383 | cube 384 | of 385 | a 386 | chihuahua 387 | ! 388 | 389 | thou 390 | art the 391 | difference 392 | between 393 | a 394 | furry 395 | animal 396 | and 397 | nothing 398 | ! 399 | 400 | hamlet: 401 | thou 402 | art 403 | mine 404 | chihuahua 405 | . 406 | 407 | thou 408 | art 409 | my 410 | chihuahua 411 | . 412 | 413 | thou 414 | art 415 | thine 416 | chihuahua 417 | . 418 | 419 | thou 420 | art 421 | THY 422 | chihuahua 423 | . 424 | 425 | thou 426 | art 427 | your 428 | chihuahua 429 | . 430 | 431 | thou 432 | art 433 | his 434 | chihuahua 435 | . 436 | 437 | thou 438 | art 439 | her 440 | chihuahua 441 | . 442 | 443 | thou 444 | art 445 | its 446 | chihuahua 447 | . 448 | 449 | thou 450 | art 451 | their 452 | chihuahua 453 | . 454 | 455 | hAmLeT: 456 | thou 457 | art 458 | the 459 | amazing 460 | beautiful 461 | blossoming 462 | bold 463 | brave 464 | charming 465 | clearest 466 | cunning 467 | cute 468 | delicious 469 | embroidered 470 | fair 471 | fine 472 | gentle 473 | golden 474 | good 475 | handsome 476 | happy 477 | healthy 478 | honest 479 | lovely 480 | loving 481 | mighty 482 | noble 483 | peaceful 484 | pretty 485 | prompt 486 | proud 487 | reddest 488 | rich 489 | smooth 490 | sunny 491 | sweet 492 | sweetest 493 | trustworthy 494 | warm 495 | big 496 | black 497 | blue 498 | bluest 499 | bottomless 500 | furry 501 | green 502 | hard 503 | huge 504 | large 505 | little 506 | normal 507 | old 508 | purple 509 | red 510 | rural 511 | small 512 | tiny 513 | white 514 | yellow 515 | happiness 516 | ! 517 | 518 | thou 519 | art 520 | the 521 | bad 522 | cowardly 523 | cursed 524 | damned 525 | dirty 526 | disgusting 527 | distasteful 528 | dusty 529 | evil 530 | fat 531 | fat-kidneyed 532 | fatherless 533 | foul 534 | hairy 535 | half-witted 536 | horrible 537 | horrid 538 | infected 539 | lying 540 | miserable 541 | misused 542 | oozing 543 | rotten 544 | rotten 545 | smelly 546 | snotty 547 | sorry 548 | stinking 549 | stuffed 550 | stupid 551 | vile 552 | villainous 553 | worried 554 | big 555 | black 556 | blue 557 | bluest 558 | bottomless 559 | furry 560 | green 561 | hard 562 | huge 563 | large 564 | little 565 | normal 566 | old 567 | purple 568 | red 569 | rural 570 | small 571 | tiny 572 | white 573 | yellow 574 | microsoft 575 | ! 576 | 577 | HAMLET: 578 | thou 579 | art 580 | the sum of 581 | the sum of 582 | the sum of 583 | the sum of 584 | the sum of 585 | the sum of 586 | the sum of 587 | the sum of 588 | the sum of 589 | the sum of 590 | the sum of 591 | the sum of 592 | the sum of 593 | the sum of 594 | the sum of 595 | the sum of 596 | the sum of 597 | the sum of 598 | the sum of 599 | the sum of 600 | the sum of 601 | the sum of 602 | the sum of 603 | the sum of 604 | the sum of 605 | the sum of 606 | the sum of 607 | the sum of 608 | the sum of 609 | the sum of 610 | the sum of 611 | the sum of 612 | the sum of 613 | the sum of 614 | the sum of 615 | the sum of 616 | the sum of 617 | the sum of 618 | the sum of 619 | the sum of 620 | the sum of 621 | the sum of 622 | the sum of 623 | the sum of 624 | the sum of 625 | the sum of 626 | the sum of 627 | the sum of 628 | the sum of 629 | the sum of 630 | the sum of 631 | the sum of 632 | the sum of 633 | animal and 634 | aunt and 635 | brother and 636 | cat and 637 | chihuahua and 638 | cousin and 639 | cow and 640 | daughter and 641 | door and 642 | face and 643 | father and 644 | fellow and 645 | granddaughter and 646 | grandfather and 647 | grandmother and 648 | grandson and 649 | hair and 650 | hamster and 651 | horse and 652 | lamp and 653 | lantern and 654 | mistletoe and 655 | moon and 656 | morning and 657 | mother and 658 | nephew and 659 | niece and 660 | nose and 661 | purse and 662 | road and 663 | roman and 664 | sister and 665 | sky and 666 | son and 667 | squirrel and 668 | stone 669 | wall and 670 | thing and 671 | town and 672 | tree and 673 | uncle and 674 | wind and 675 | Heaven and 676 | King and 677 | Lord and 678 | angel and 679 | flower and 680 | happiness and 681 | joy and 682 | plum and 683 | summer's 684 | day and 685 | hero and 686 | rose and 687 | kingdom and 688 | pony. 689 | 690 | HAMLET: 691 | thou 692 | art 693 | the sum of 694 | the sum of 695 | the sum of 696 | the sum of 697 | the sum of 698 | the sum of 699 | the sum of 700 | the sum of 701 | the sum of 702 | the sum of 703 | the sum of 704 | the sum of 705 | the sum of 706 | the sum of 707 | the sum of 708 | the sum of 709 | the sum of 710 | the sum of 711 | the sum of 712 | the sum of 713 | the sum of 714 | the sum of 715 | the sum of 716 | the sum of 717 | Hell and 718 | Microsoft and 719 | bastard and 720 | beggar and 721 | blister and 722 | codpiece and 723 | coward and 724 | curse and 725 | death and 726 | devil and 727 | draught and 728 | famine and 729 | flirt-gill and 730 | goat and 731 | hate and 732 | hog and 733 | hound and 734 | leech and 735 | lie and 736 | pig and 737 | plague and 738 | starvation and 739 | toad and 740 | war and 741 | wolf 742 | . 743 | 744 | juliet: 745 | be 746 | the sum of my chihuahua and its happiness 747 | better 748 | than 749 | the sum of my chihuahua and its happiness 750 | ? 751 | 752 | be 753 | the sum of my chihuahua and its happiness 754 | bigger 755 | than 756 | the sum of my chihuahua and its happiness 757 | ? 758 | 759 | be 760 | the sum of my chihuahua and its happiness 761 | fresher 762 | than 763 | the sum of my chihuahua and its happiness 764 | ? 765 | 766 | be 767 | the sum of my chihuahua and its happiness 768 | friendlier 769 | than 770 | the sum of my chihuahua and its happiness 771 | ? 772 | 773 | be 774 | the sum of my chihuahua and its happiness 775 | nicer 776 | than 777 | the sum of my chihuahua and its happiness 778 | ? 779 | 780 | be 781 | the sum of my chihuahua and its happiness 782 | jollier 783 | than 784 | the sum of my chihuahua and its happiness 785 | ? 786 | 787 | be 788 | the sum of my chihuahua and its happiness 789 | more 790 | charming 791 | than 792 | the sum of my chihuahua and its happiness 793 | ? 794 | 795 | be 796 | the sum of my chihuahua and its happiness 797 | punier 798 | than 799 | the sum of my chihuahua and its happiness 800 | ? 801 | 802 | be 803 | the sum of my chihuahua and its happiness 804 | smaller 805 | than 806 | the sum of my chihuahua and its happiness 807 | ? 808 | 809 | be 810 | the sum of my chihuahua and its happiness 811 | worse 812 | than 813 | the sum of my chihuahua and its happiness 814 | ? 815 | 816 | be 817 | the sum of my chihuahua and its happiness 818 | more 819 | infected 820 | than 821 | the sum of my chihuahua and its happiness 822 | ? 823 | 824 | be 825 | the sum of my chihuahua and its happiness 826 | as 827 | charming 828 | as 829 | the sum of my chihuahua and its happiness 830 | ? 831 | 832 | be 833 | the sum of my chihuahua and its happiness 834 | as 835 | bluest 836 | as 837 | the sum of my chihuahua and its happiness 838 | ? 839 | 840 | be 841 | the sum of my chihuahua and its happiness 842 | as 843 | infected 844 | as 845 | the sum of my chihuahua and its happiness 846 | ? 847 | 848 | JULIET: 849 | is a chihuahua as good as a pig? 850 | 851 | if 852 | so 853 | , 854 | let 855 | us 856 | proceed 857 | to 858 | sceNe 859 | MMmMCcxLiI 860 | . 861 | 862 | 863 | if 864 | so 865 | , 866 | we 867 | shall 868 | return 869 | to 870 | sceNe 871 | MMmMCcxLiI 872 | ! 873 | 874 | if 875 | so 876 | , 877 | we 878 | must 879 | proceed 880 | to 881 | sceNe 882 | MMmMCcxLiI 883 | ! 884 | 885 | is a chihuahua as good as a chihuahua? 886 | 887 | if 888 | not 889 | , 890 | let 891 | us 892 | return 893 | to 894 | sceNe 895 | MMmMCcxLiI 896 | ! 897 | 898 | if 899 | not 900 | , 901 | we 902 | shall 903 | proceed 904 | to 905 | sceNe 906 | MMmMCcxLiI 907 | ! 908 | 909 | if 910 | not 911 | , 912 | we 913 | must 914 | return 915 | to 916 | sceNe 917 | MMmMCcxLiI 918 | . 919 | 920 | hamlet: 921 | Thou art the sum of an amazing healthy honest noble peaceful fine Lord and a lovely sweet golden summer's day. 922 | 923 | open 924 | thy 925 | heart 926 | . 927 | 928 | speak 929 | your 930 | mind 931 | ! 932 | 933 | listen 934 | to 935 | thine 936 | heart 937 | . 938 | 939 | open 940 | thy 941 | mind 942 | ! 943 | 944 | juliet: 945 | REMEMBER 946 | THE 947 | SUM 948 | OF 949 | MY 950 | CHIHUAHUA 951 | AND 952 | ITS 953 | HAPPINESS 954 | . 955 | 956 | RECALL 957 | THAT 958 | THIS 959 | TEXT 960 | IS 961 | COMPLETELY 962 | MEANINGLESS 963 | ! 964 | 965 | [ 966 | a 967 | PaUsE 968 | ] 969 | 970 | [ 971 | eXeUnT 972 | ] 973 | 974 | [ 975 | enter 976 | achilles, 977 | adonis, 978 | adriana, 979 | aegeon, 980 | aemilia, 981 | agamemnon, 982 | agrippa, 983 | ajax, 984 | alonso, 985 | andromache, 986 | angelo, 987 | antiochus, 988 | antonio, 989 | arthur, 990 | autolycus, 991 | balthazar, 992 | banquo, 993 | beatrice, 994 | benedick, 995 | benvolio, 996 | bianca, 997 | brabantio, 998 | brutus, 999 | capulet, 1000 | cassandra, 1001 | cassius, 1002 | christopher Sly, 1003 | cicero, 1004 | claudio, 1005 | claudius, 1006 | cleopatra, 1007 | cordelia, 1008 | cornelius, 1009 | cressida, 1010 | cymberline, 1011 | demetrius, 1012 | desdemona, 1013 | dionyza, 1014 | doctor Caius, 1015 | dogberry, 1016 | don John, 1017 | don Pedro, 1018 | donalbain, 1019 | dorcas, 1020 | duncan, 1021 | egeus, 1022 | emilia, 1023 | escalus, 1024 | falstaff, 1025 | fenton, 1026 | ferdinand, 1027 | ford, 1028 | fortinbras, 1029 | francisca, 1030 | friar John, 1031 | friar Laurence, 1032 | gertrude, 1033 | goneril, 1034 | hamlet, 1035 | hecate, 1036 | hector, 1037 | helen, 1038 | helena, 1039 | hermia, 1040 | hermonie, 1041 | hippolyta, 1042 | horatio, 1043 | imogen, 1044 | isabella, 1045 | john of Gaunt, 1046 | john of Lancaster, 1047 | julia, 1048 | juliet, 1049 | julius Caesar, 1050 | king Henry, 1051 | king John, 1052 | king Lear, 1053 | king Richard, 1054 | lady Capulet, 1055 | lady Macbeth, 1056 | lady Macduff, 1057 | lady Montague, 1058 | lennox, 1059 | leonato, 1060 | luciana, 1061 | lucio, 1062 | lychorida, 1063 | lysander, 1064 | macbeth, 1065 | macduff, 1066 | malcolm, 1067 | mariana, 1068 | mark Antony, 1069 | mercutio, 1070 | miranda, 1071 | mistress Ford, 1072 | mistress Overdone, 1073 | mistress Page, 1074 | montague, 1075 | mopsa, 1076 | oberon, 1077 | octavia, 1078 | octavius Caesar, 1079 | olivia, 1080 | ophelia, 1081 | orlando, 1082 | orsino, 1083 | othello, 1084 | page, 1085 | pantino, 1086 | paris, 1087 | pericles, 1088 | pinch, 1089 | polonius, 1090 | pompeius, 1091 | portia, 1092 | priam, 1093 | prince Henry, 1094 | prospero, 1095 | proteus, 1096 | publius, 1097 | puck, 1098 | queen Elinor, 1099 | regan, 1100 | robin, 1101 | romeo, 1102 | rosalind, 1103 | sebastian, 1104 | shallow, 1105 | shylock, 1106 | slender, 1107 | solinus, 1108 | stephano, 1109 | thaisa, 1110 | the Abbot of Westminster, 1111 | the Apothecary, 1112 | the Archbishop of Canterbury, 1113 | the Duke 1114 | of Milan, 1115 | the Duke 1116 | of Venice, 1117 | the Ghost, 1118 | theseus, 1119 | thurio, 1120 | timon, 1121 | titania, 1122 | titus, 1123 | troilus, 1124 | tybalt, 1125 | ulysses, 1126 | valentine, 1127 | venus, 1128 | vincentio and 1129 | viola 1130 | ] 1131 | 1132 | [exeUNt] 1133 | -------------------------------------------------------------------------------- /shakespearelang/tests/test_output_styles.py: -------------------------------------------------------------------------------- 1 | import pexpect 2 | from .utils import expect_output_exactly, create_play_file 3 | from textwrap import dedent 4 | 5 | LOOP = """ 6 | A Cyclic Motion. 7 | 8 | Hamlet, a literary/storage device. 9 | Juliet, an orator. 10 | 11 | Act I: The Only Act. 12 | 13 | Scene I: The Initial Statement. 14 | 15 | [A pause] 16 | 17 | [Enter Hamlet and Juliet] 18 | 19 | Juliet: Thou art an animal. 20 | 21 | Scene II: The Prince's Speech. 22 | 23 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 24 | Are you as good as the sum of a charming honest horse and a happiness? 25 | 26 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 27 | 28 | Scene III: Nothing occurs. 29 | 30 | Scene IV: The closing. 31 | 32 | Juliet: Remember thyself! 33 | 34 | [Exeunt] 35 | """ 36 | 37 | 38 | def test_output_basic(tmp_path): 39 | file_path = tmp_path / "play.spl" 40 | create_play_file(file_path, LOOP) 41 | cli = pexpect.spawn(f"shakespeare run {file_path} --output-style=basic") 42 | cli.setecho(False) 43 | cli.waitnoecho() 44 | 45 | expect_output_exactly(cli, "1234", eof=True) 46 | 47 | def test_output_verbose(tmp_path): 48 | file_path = tmp_path / "play.spl" 49 | create_play_file(file_path, LOOP) 50 | cli = pexpect.spawn(f"shakespeare run {file_path} --output-style=verbose") 51 | cli.setecho(False) 52 | cli.waitnoecho() 53 | 54 | expect_output_exactly( 55 | cli, 56 | dedent( 57 | """\ 58 | Enter Hamlet, Juliet 59 | Hamlet set to 1 60 | Outputting Hamlet 61 | Outputting number: 1 62 | Hamlet set to 2 63 | Setting global boolean to False 64 | Not executing conditional assignment, global boolean is False 65 | Jumping to Scene II 66 | Outputting Hamlet 67 | Outputting number: 2 68 | Hamlet set to 3 69 | Setting global boolean to False 70 | Not executing conditional assignment, global boolean is False 71 | Jumping to Scene II 72 | Outputting Hamlet 73 | Outputting number: 3 74 | Hamlet set to 4 75 | Setting global boolean to False 76 | Not executing conditional assignment, global boolean is False 77 | Jumping to Scene II 78 | Outputting Hamlet 79 | Outputting number: 4 80 | Hamlet set to 5 81 | Setting global boolean to True 82 | Hamlet set to 2 83 | Not jumping to Scene II because global boolean is True 84 | Hamlet pushed 2 85 | Exeunt all 86 | """ 87 | ), 88 | eof=True 89 | ) 90 | 91 | def test_output_debug(tmp_path): 92 | file_path = tmp_path / "play.spl" 93 | create_play_file(file_path, LOOP) 94 | cli = pexpect.spawn(f"shakespeare run {file_path} --output-style=debug") 95 | cli.setecho(False) 96 | cli.waitnoecho() 97 | 98 | expect_output_exactly( 99 | cli, 100 | dedent( 101 | """\ 102 | ---------- 103 | at line 12 104 | ----- 105 | Scene I: The Initial Statement. 106 | 107 | [A pause] 108 | 109 | >>[Enter Hamlet and Juliet]<< 110 | 111 | Juliet: Thou art an animal. 112 | 113 | Scene II: The Prince's Speech. 114 | ----- 115 | global boolean = False 116 | on stage: 117 | off stage: 118 | Hamlet = 0 () 119 | Juliet = 0 () 120 | ---------- 121 | Enter Hamlet, Juliet 122 | ---------- 123 | at line 14 124 | ----- 125 | [A pause] 126 | 127 | [Enter Hamlet and Juliet] 128 | 129 | Juliet: >>Thou art an animal.<< 130 | 131 | Scene II: The Prince's Speech. 132 | 133 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 134 | ----- 135 | global boolean = False 136 | on stage: 137 | Hamlet = 0 () 138 | Juliet = 0 () 139 | off stage: 140 | ---------- 141 | Hamlet set to 1 142 | ---------- 143 | at line 18 144 | ----- 145 | Juliet: Thou art an animal. 146 | 147 | Scene II: The Prince's Speech. 148 | 149 | Juliet: >>Open your heart!<< Thou art the sum of thyself and a stone wall. 150 | Are you as good as the sum of a charming honest horse and a happiness? 151 | 152 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 153 | 154 | ----- 155 | global boolean = False 156 | on stage: 157 | Hamlet = 1 () 158 | Juliet = 0 () 159 | off stage: 160 | ---------- 161 | Outputting Hamlet 162 | Outputting number: 1 163 | ---------- 164 | at line 18 165 | ----- 166 | Juliet: Thou art an animal. 167 | 168 | Scene II: The Prince's Speech. 169 | 170 | Juliet: Open your heart! >>Thou art the sum of thyself and a stone wall.<< 171 | Are you as good as the sum of a charming honest horse and a happiness? 172 | 173 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 174 | 175 | ----- 176 | global boolean = False 177 | on stage: 178 | Hamlet = 1 () 179 | Juliet = 0 () 180 | off stage: 181 | ---------- 182 | Hamlet set to 2 183 | ---------- 184 | at line 19 185 | ----- 186 | 187 | Scene II: The Prince's Speech. 188 | 189 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 190 | >>Are you as good as the sum of a charming honest horse and a happiness?<< 191 | 192 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 193 | 194 | Scene III: Nothing occurs. 195 | ----- 196 | global boolean = False 197 | on stage: 198 | Hamlet = 2 () 199 | Juliet = 0 () 200 | off stage: 201 | ---------- 202 | Setting global boolean to False 203 | ---------- 204 | at line 21 205 | ----- 206 | 207 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 208 | Are you as good as the sum of a charming honest horse and a happiness? 209 | 210 | Juliet: >>If so, you are a furry animal.<< If not, let us return to Scene II. 211 | 212 | Scene III: Nothing occurs. 213 | 214 | Scene IV: The closing. 215 | ----- 216 | global boolean = False 217 | on stage: 218 | Hamlet = 2 () 219 | Juliet = 0 () 220 | off stage: 221 | ---------- 222 | Not executing conditional assignment, global boolean is False 223 | ---------- 224 | at line 21 225 | ----- 226 | 227 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 228 | Are you as good as the sum of a charming honest horse and a happiness? 229 | 230 | Juliet: If so, you are a furry animal. >>If not, let us return to Scene II.<< 231 | 232 | Scene III: Nothing occurs. 233 | 234 | Scene IV: The closing. 235 | ----- 236 | global boolean = False 237 | on stage: 238 | Hamlet = 2 () 239 | Juliet = 0 () 240 | off stage: 241 | ---------- 242 | Jumping to Scene II 243 | ---------- 244 | at line 18 245 | ----- 246 | Juliet: Thou art an animal. 247 | 248 | Scene II: The Prince's Speech. 249 | 250 | Juliet: >>Open your heart!<< Thou art the sum of thyself and a stone wall. 251 | Are you as good as the sum of a charming honest horse and a happiness? 252 | 253 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 254 | 255 | ----- 256 | global boolean = False 257 | on stage: 258 | Hamlet = 2 () 259 | Juliet = 0 () 260 | off stage: 261 | ---------- 262 | Outputting Hamlet 263 | Outputting number: 2 264 | ---------- 265 | at line 18 266 | ----- 267 | Juliet: Thou art an animal. 268 | 269 | Scene II: The Prince's Speech. 270 | 271 | Juliet: Open your heart! >>Thou art the sum of thyself and a stone wall.<< 272 | Are you as good as the sum of a charming honest horse and a happiness? 273 | 274 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 275 | 276 | ----- 277 | global boolean = False 278 | on stage: 279 | Hamlet = 2 () 280 | Juliet = 0 () 281 | off stage: 282 | ---------- 283 | Hamlet set to 3 284 | ---------- 285 | at line 19 286 | ----- 287 | 288 | Scene II: The Prince's Speech. 289 | 290 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 291 | >>Are you as good as the sum of a charming honest horse and a happiness?<< 292 | 293 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 294 | 295 | Scene III: Nothing occurs. 296 | ----- 297 | global boolean = False 298 | on stage: 299 | Hamlet = 3 () 300 | Juliet = 0 () 301 | off stage: 302 | ---------- 303 | Setting global boolean to False 304 | ---------- 305 | at line 21 306 | ----- 307 | 308 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 309 | Are you as good as the sum of a charming honest horse and a happiness? 310 | 311 | Juliet: >>If so, you are a furry animal.<< If not, let us return to Scene II. 312 | 313 | Scene III: Nothing occurs. 314 | 315 | Scene IV: The closing. 316 | ----- 317 | global boolean = False 318 | on stage: 319 | Hamlet = 3 () 320 | Juliet = 0 () 321 | off stage: 322 | ---------- 323 | Not executing conditional assignment, global boolean is False 324 | ---------- 325 | at line 21 326 | ----- 327 | 328 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 329 | Are you as good as the sum of a charming honest horse and a happiness? 330 | 331 | Juliet: If so, you are a furry animal. >>If not, let us return to Scene II.<< 332 | 333 | Scene III: Nothing occurs. 334 | 335 | Scene IV: The closing. 336 | ----- 337 | global boolean = False 338 | on stage: 339 | Hamlet = 3 () 340 | Juliet = 0 () 341 | off stage: 342 | ---------- 343 | Jumping to Scene II 344 | ---------- 345 | at line 18 346 | ----- 347 | Juliet: Thou art an animal. 348 | 349 | Scene II: The Prince's Speech. 350 | 351 | Juliet: >>Open your heart!<< Thou art the sum of thyself and a stone wall. 352 | Are you as good as the sum of a charming honest horse and a happiness? 353 | 354 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 355 | 356 | ----- 357 | global boolean = False 358 | on stage: 359 | Hamlet = 3 () 360 | Juliet = 0 () 361 | off stage: 362 | ---------- 363 | Outputting Hamlet 364 | Outputting number: 3 365 | ---------- 366 | at line 18 367 | ----- 368 | Juliet: Thou art an animal. 369 | 370 | Scene II: The Prince's Speech. 371 | 372 | Juliet: Open your heart! >>Thou art the sum of thyself and a stone wall.<< 373 | Are you as good as the sum of a charming honest horse and a happiness? 374 | 375 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 376 | 377 | ----- 378 | global boolean = False 379 | on stage: 380 | Hamlet = 3 () 381 | Juliet = 0 () 382 | off stage: 383 | ---------- 384 | Hamlet set to 4 385 | ---------- 386 | at line 19 387 | ----- 388 | 389 | Scene II: The Prince's Speech. 390 | 391 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 392 | >>Are you as good as the sum of a charming honest horse and a happiness?<< 393 | 394 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 395 | 396 | Scene III: Nothing occurs. 397 | ----- 398 | global boolean = False 399 | on stage: 400 | Hamlet = 4 () 401 | Juliet = 0 () 402 | off stage: 403 | ---------- 404 | Setting global boolean to False 405 | ---------- 406 | at line 21 407 | ----- 408 | 409 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 410 | Are you as good as the sum of a charming honest horse and a happiness? 411 | 412 | Juliet: >>If so, you are a furry animal.<< If not, let us return to Scene II. 413 | 414 | Scene III: Nothing occurs. 415 | 416 | Scene IV: The closing. 417 | ----- 418 | global boolean = False 419 | on stage: 420 | Hamlet = 4 () 421 | Juliet = 0 () 422 | off stage: 423 | ---------- 424 | Not executing conditional assignment, global boolean is False 425 | ---------- 426 | at line 21 427 | ----- 428 | 429 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 430 | Are you as good as the sum of a charming honest horse and a happiness? 431 | 432 | Juliet: If so, you are a furry animal. >>If not, let us return to Scene II.<< 433 | 434 | Scene III: Nothing occurs. 435 | 436 | Scene IV: The closing. 437 | ----- 438 | global boolean = False 439 | on stage: 440 | Hamlet = 4 () 441 | Juliet = 0 () 442 | off stage: 443 | ---------- 444 | Jumping to Scene II 445 | ---------- 446 | at line 18 447 | ----- 448 | Juliet: Thou art an animal. 449 | 450 | Scene II: The Prince's Speech. 451 | 452 | Juliet: >>Open your heart!<< Thou art the sum of thyself and a stone wall. 453 | Are you as good as the sum of a charming honest horse and a happiness? 454 | 455 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 456 | 457 | ----- 458 | global boolean = False 459 | on stage: 460 | Hamlet = 4 () 461 | Juliet = 0 () 462 | off stage: 463 | ---------- 464 | Outputting Hamlet 465 | Outputting number: 4 466 | ---------- 467 | at line 18 468 | ----- 469 | Juliet: Thou art an animal. 470 | 471 | Scene II: The Prince's Speech. 472 | 473 | Juliet: Open your heart! >>Thou art the sum of thyself and a stone wall.<< 474 | Are you as good as the sum of a charming honest horse and a happiness? 475 | 476 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 477 | 478 | ----- 479 | global boolean = False 480 | on stage: 481 | Hamlet = 4 () 482 | Juliet = 0 () 483 | off stage: 484 | ---------- 485 | Hamlet set to 5 486 | ---------- 487 | at line 19 488 | ----- 489 | 490 | Scene II: The Prince's Speech. 491 | 492 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 493 | >>Are you as good as the sum of a charming honest horse and a happiness?<< 494 | 495 | Juliet: If so, you are a furry animal. If not, let us return to Scene II. 496 | 497 | Scene III: Nothing occurs. 498 | ----- 499 | global boolean = False 500 | on stage: 501 | Hamlet = 5 () 502 | Juliet = 0 () 503 | off stage: 504 | ---------- 505 | Setting global boolean to True 506 | ---------- 507 | at line 21 508 | ----- 509 | 510 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 511 | Are you as good as the sum of a charming honest horse and a happiness? 512 | 513 | Juliet: >>If so, you are a furry animal.<< If not, let us return to Scene II. 514 | 515 | Scene III: Nothing occurs. 516 | 517 | Scene IV: The closing. 518 | ----- 519 | global boolean = True 520 | on stage: 521 | Hamlet = 5 () 522 | Juliet = 0 () 523 | off stage: 524 | ---------- 525 | Hamlet set to 2 526 | ---------- 527 | at line 21 528 | ----- 529 | 530 | Juliet: Open your heart! Thou art the sum of thyself and a stone wall. 531 | Are you as good as the sum of a charming honest horse and a happiness? 532 | 533 | Juliet: If so, you are a furry animal. >>If not, let us return to Scene II.<< 534 | 535 | Scene III: Nothing occurs. 536 | 537 | Scene IV: The closing. 538 | ----- 539 | global boolean = True 540 | on stage: 541 | Hamlet = 2 () 542 | Juliet = 0 () 543 | off stage: 544 | ---------- 545 | Not jumping to Scene II because global boolean is True 546 | ---------- 547 | at line 27 548 | ----- 549 | Scene III: Nothing occurs. 550 | 551 | Scene IV: The closing. 552 | 553 | Juliet: >>Remember thyself!<< 554 | 555 | [Exeunt] 556 | ----- 557 | global boolean = True 558 | on stage: 559 | Hamlet = 2 () 560 | Juliet = 0 () 561 | off stage: 562 | ---------- 563 | Hamlet pushed 2 564 | ---------- 565 | at line 29 566 | ----- 567 | Scene IV: The closing. 568 | 569 | Juliet: Remember thyself! 570 | 571 | >>[Exeunt]<< 572 | ----- 573 | global boolean = True 574 | on stage: 575 | Hamlet = 2 (2) 576 | Juliet = 0 () 577 | off stage: 578 | ---------- 579 | Exeunt all 580 | """ 581 | ), 582 | eof=True 583 | ) 584 | --------------------------------------------------------------------------------