Skip to content

Commit

Permalink
Merge pull request #49 from plasma-umass/interface-cleanup
Browse files Browse the repository at this point in the history
Interface cleanup: wrapper mode default
  • Loading branch information
nicovank committed Jan 10, 2024
2 parents ed45c1f + bc2b9cf commit bdf6df2
Show file tree
Hide file tree
Showing 58 changed files with 224 additions and 198 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/regression-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
python3 -m pip install .
- name: Check prompts
run: python3 tests/regression.py --platform ubuntu --check
run: python3 -m tests.regression --platform ubuntu --check

check-macos:
runs-on: macos-latest
Expand All @@ -39,7 +39,7 @@ jobs:
python3 -m pip install .
- name: Check prompts
run: python3 tests/regression.py --platform macos --check
run: python3 -m tests.regression --platform macos --check

check-windows:
runs-on: windows-latest
Expand All @@ -53,4 +53,4 @@ jobs:
python3 -m pip install .
- name: Check prompts
run: python3 tests/regression.py --platform windows --check
run: python3 -m tests.regression --platform windows --check
6 changes: 3 additions & 3 deletions .github/workflows/regression-generate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
python3 -m pip install .
- name: Generate current prompts
run: python3 tests/regression.py --platform ubuntu --generate
run: python3 -m tests.regression --platform ubuntu --generate

- name: Commit
run: |
Expand All @@ -58,7 +58,7 @@ jobs:
python3 -m pip install .
- name: Generate current prompts
run: python3 tests/regression.py --platform macos --generate
run: python3 -m tests.regression --platform macos --generate

- name: Commit
run: |
Expand All @@ -85,7 +85,7 @@ jobs:
python3 -m pip install .
- name: Generate current prompts
run: python3 tests/regression.py --platform windows --generate
run: python3 -m tests.regression --platform windows --generate

- name: Commit
run: |
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/sanity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@ jobs:
- name: Update pip
run: python3 -m pip install --upgrade pip

- name: Install
run: python3 -m pip install .
- name: Install dependencies
run: python3 -m pip install mypy

- name: Install in editable mode
run: python3 -m pip install -e .

- name: Install in development mode
run: python3 -m pip install .[dev]

- name: Run mypy
run: python3 -m mypy src/**/*.py tests/*.py

- name: Check version through Python
run: python3 -m cwhy --version

Expand Down
32 changes: 9 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,22 @@ python3 -m pip install cwhy
## Usage

### Compiler wrapper mode

This new mode is recommended as CWhy will then operate in the same context as the compiler, and will do a better job
finding the right source files.
The wrapper mode is now default and mandatory, with a slightly modified interface.
CWhy can either be used standalone by passing the full command after the triple dashes `---`, or as part of a build tool
by creating a short executable script wrapping the compiler command.

```bash
# Invoking the compiler directly.
% `cwhy --wrapper` mycode.cpp

# Using fix mode.
% `cwhy --wrapper fix` mycode.cpp
% cwhy --- g++ mycode.cpp

# Using cwhy with Java.
% `cwhy --wrapper --wrapper-compiler=javac` mycode.java
# Using CWhy with Java and an increased timeout.
% cwhy --timeout 180 --- javac MyCode.java

# Invoking with GNU make, using GPT-4.
% CXX=`cwhy --llm=gpt-4 --wrapper` make
# Invoking with GNU Make, using GPT-3.5.
% CXX=`cwhy --llm=gpt-3.5-turbo --wrapper --- c++` make

# Invoking with CMake, using GPT-4 and clang++.
% cmake -DCMAKE_CXX_COMPILER=`cwhy --llm=gpt-4 --wrapper --wrapper-compiler=clang++` ...
% cmake -DCMAKE_CXX_COMPILER=`cwhy --llm=gpt-4 --wrapper --- clang++` ...
```

When running a configuration tool such as CMake or Autoconf, this may greatly increase configuration time, as these
Expand All @@ -61,14 +57,6 @@ configuration time.
% CWHY_DISABLE=1 cmake -DCMAKE_CXX_COMPILER=`cwhy --wrapper` ...
```

### Original mode

Just pipe your compiler's output to `cwhy`.

```bash
% clang++ -g mycode.cpp |& cwhy
```

### Options

These options can be displayed with `cwhy --help`.
Expand All @@ -77,8 +65,6 @@ These options can be displayed with `cwhy --help`.
- `--timeout`: pick a different timeout than the default for API calls.
- `--show-prompt` (debug): print prompts before calling the API.

The wrapper mode specifically also has a `--wrapper-compiler` option to select the underlying compiler to use.

## Examples

### C++
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = [
{ name="Nicolas van Kempen", email="[email protected]" },
{ name="Bryce Adelstein Lelbach", email="[email protected]" }
]
dependencies = ["llm_utils==0.2.2", "openai>=1.3.6", "PyYAML>=6.0.1"]
dependencies = ["llm_utils==0.2.3", "openai>=1.3.6", "PyYAML>=6.0.1", "rich>=13.7.0"]
description = "Explains and proposes fixes for compile-time errors for many programming languages."
readme = "README.md"
requires-python = ">=3.7"
Expand All @@ -26,3 +26,6 @@ cwhy = "cwhy.__main__:main"
[project.urls]
"Homepage" = "https://github.com/plasma-umass/cwhy"
"Bug Tracker" = "https://github.com/plasma-umass/cwhy/issues"

[project.optional-dependencies]
dev = ["types-PyYAML>=6.0.1"]
141 changes: 93 additions & 48 deletions src/cwhy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,56 +6,119 @@
import sys
import tempfile
import textwrap
from typing import Any, Optional

import openai
from rich.console import Console

from . import cwhy


def wrapper(args):
return textwrap.dedent(
f"""
#! {sys.executable}
from cwhy import cwhy
cwhy.wrapper({args})
return (
textwrap.dedent(
f"""
#! {sys.executable}
from cwhy import cwhy
cwhy.wrapper({vars(args)})
"""
).lstrip()
).strip()
+ "\n"
)


class RichArgParser(argparse.ArgumentParser):
def __init__(self, *args: Any, **kwargs: Any):
self.console = Console(highlight=False)
super().__init__(*args, **kwargs)

def _print_message(self, message: Optional[str], file: Any = None) -> None:
if message:
self.console.print(message)


class CWhyArgumentFormatter(argparse.HelpFormatter):
# RawDescriptionHelpFormatter.
def _fill_text(self, text, width, indent):
return "".join(indent + line for line in text.splitlines(keepends=True))

# RawTextHelpFormatter.
def _split_lines(self, text, width):
return text.splitlines()

# ArgumentDefaultsHelpFormatter.
# Ignore if help message is multiline.
def _get_help_string(self, action):
help = action.help
if "\n" not in help and "%(default)" not in action.help:
if action.default is not argparse.SUPPRESS:
defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE]
if action.option_strings or action.nargs in defaulting_nargs:
help += " (default: %(default)s)"
return help


def main():
parser = argparse.ArgumentParser(
description = textwrap.dedent(
rf"""
[b]CWhy[/b]: Explains and proposes fixes for compile-time errors for many programming languages.
[blue][link=https://github.com/plasma-umass/cwhy]https://github.com/plasma-umass/cwhy[/link][/blue]
usage:
[b]cwhy [SUBCOMMAND] \[OPTIONS...] --- COMMAND...[/b]
usage (GNU Make):
[b]CXX=`cwhy --wrapper \[OPTIONS...] --- c++` make[/b]
usage (CMake):
[b]cmake -DCMAKE_CXX_COMPILER=`cwhy --wrapper \[OPTIONS...] --- c++`[/b]
"""
).strip()

parser = RichArgParser(
prog="cwhy",
description="CWhy explains and fixes compiler diagnostic errors.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
add_help=False,
usage=argparse.SUPPRESS,
description=description,
formatter_class=CWhyArgumentFormatter,
)

parser.add_argument(
"--help",
"-h",
action="help",
default=argparse.SUPPRESS,
help="Show this help message and exit.",
)
parser.add_argument(
"--version",
action="version",
version=f"%(prog)s v{importlib.metadata.metadata('cwhy')['Version']}",
default=argparse.SUPPRESS,
help="Print the version of CWhy and exit.",
help="print the version of CWhy and exit",
)

parser.add_argument(
"subcommand",
nargs="?",
default="explain",
choices=["explain", "diff", "converse"],
metavar="subcommand",
help=textwrap.dedent(
"""
explain: explain the diagnostic (default)
diff: \[experimental] generate a diff to fix the diagnostic
converse: \[experimental] interactively converse with CWhy
"""
).strip(),
)

parser.add_argument(
"--llm",
type=str,
default="default",
help="The language model to use, e.g., 'gpt-3.5-turbo' or 'gpt-4'. The default mode tries gpt-4 and falls back to gpt-3.5-turbo.",
help=textwrap.dedent(
"""
the language model to use, e.g., 'gpt-3.5-turbo' or 'gpt-4'
the default mode tries gpt-4 and falls back to gpt-3.5-turbo
"""
).strip(),
)
parser.add_argument(
"--timeout",
type=int,
default=60,
help="The timeout for API calls in seconds.",
help="the timeout for API calls in seconds",
)
# The default maximum context length for `gpt-3.5-turbo` is 4096 tokens.
# We keep 256 tokens for other parts of the prompt, and split the remainder in two
Expand All @@ -64,44 +127,33 @@ def main():
"--max-error-tokens",
type=int,
default=1920,
help="The maximum number of tokens from the error message to send in the prompt.",
help="the maximum number of tokens from the error message to send in the prompt",
)
parser.add_argument(
"--max-code-tokens",
type=int,
default=1920,
help="The maximum number of code locations tokens to send in the prompt.",
help="the maximum number of code locations tokens to send in the prompt",
)

parser.add_argument(
"--show-prompt",
action="store_true",
help="When enabled, only print prompt and exit (for debugging purposes).",
help="when enabled, only print prompt and exit (for debugging purposes)",
)
parser.add_argument(
"--wrapper",
action="store_true",
help="Enable compiler wrapper behavior.",
help="generate a temporary executable used to wrap to compiler command",
)
parser.add_argument(
"--wrapper-compiler",
metavar="COMPILER",
type=str,
default="c++",
help="The underlying compiler. Only enabled with --wrapper.",
"---",
dest="command",
required=True,
help=argparse.SUPPRESS,
nargs=argparse.REMAINDER,
)

subparsers = parser.add_subparsers(title="subcommands", dest="subcommand")

subparsers.add_parser("explain", help="Explain the diagnostic. (default)")
subparsers.add_parser("fix", help="Propose a fix for the diagnostic.")
subparsers.add_parser("diff", help="[experimental] Propose a fix in diff format.")
subparsers.add_parser(
"converse", help="[experimental] A back-and-forth mode with ChatGPT."
)

parser.set_defaults(subcommand="explain")

args = parser.parse_args()

if args.wrapper:
Expand All @@ -112,14 +164,7 @@ def main():
os.chmod(f.name, 0o755)
print(f.name)
else:
stdin = sys.stdin.read()
if not stdin:
return
try:
print(cwhy.main(args, stdin))
except (openai.NotFoundError, openai.RateLimitError, openai.APITimeoutError):
# This type of exceptions should have been handled down the stack.
pass
cwhy.main(args)


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion src/cwhy/conversation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def converse(client, args, diagnostic):
f"""
You are an assistant debugger. The user is having an issue with their code, and you are trying to help them.
A few functions exist to help with this process, namely: {", ".join(available_functions_names)}.
Once you are confident in your answer, explain the diagnostic and possibly provide a way to fix the issue.
Once you are confident in your answer, explain the diagnostic and provide a way to fix the issue if you can.
"""
).strip()
user_message = f"Here is my error message:\n\n```\n{fns.get_truncated_error_message()}\n```\n\nWhat's the problem?"
Expand Down
Loading

0 comments on commit bdf6df2

Please sign in to comment.