├── .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 |
--------------------------------------------------------------------------------