Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 176 support rust #197

Merged
merged 9 commits into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ lang-javascript-test: clean_test
lang-go-test: clean_test
@$(python_bin) test/r.py @test/lang-go.env

lang-rust-test: clean_test
@$(python_bin) test/r.py @test/lang-rust.env

# not supported
lang-elixir-test: clean_test
@$(python_bin) test/r.py @test/lang-elixir.env
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Currently we support:
- [iasm](https://byexamples.github.io/byexample/languages/iasm)
- [PowerShell](https://byexamples.github.io/byexample/languages/powershell)
- [Go](https://byexamples.github.io/byexample/languages/go)
- [Rust](https://byexamples.github.io/byexample/languages/rust)

More languages will be supported in the future. Stay tuned.

Expand Down
8 changes: 8 additions & 0 deletions byexample/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,14 @@ def parse_args(args=None):
help=
"delay in seconds before sending a line to an runner/interpreter; 0 disable this (default)."
)
g.add_argument(
"-x-delayafterprompt",
metavar="<secs>",
default=None,
type=lambda n: None if n == 0 else float(n),
help=
"delay in seconds after the prompt to capture more output; 0 disable this (default)."
)
g.add_argument(
"-x-min-rcount",
metavar="<n>",
Expand Down
2 changes: 1 addition & 1 deletion byexample/modules/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def run(self, example, options):
# so we disable this:
options['geometry'] = self._terminal_default_geometry

# cling's output requeries to be emulated by an ANSI Terminal
# cling's output requires to be emulated by an ANSI Terminal
# so we force this (see _get_output())
options['term'] = 'ansi'

Expand Down
306 changes: 306 additions & 0 deletions byexample/modules/rust.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
r"""
Example:

>> 1 + 2
3

>> fn hello() {
:: println!("hello bla world"); // classic
:: }

>> hello(); // byexample: +norm-ws
hello <...> world

>> let mut j = 2;
>> for i in 0..4 {
:: j += i;
:: }; // extra ; to suppress output check

>> j + 3
11

>> println!("{}", "this\n
:: is a multiline\n
:: string\n");
this
is a multiline
string

>> /* this
:: is a multiline
:: comment */

>> #[derive(Debug)]
:: struct Point {
:: x: f32,
:: y: f32,
:: }

>> let p1 = Point { x: 2.0, y: 3.0 };
>> p1
Point { x: 2.0, y: 3.0 }

>> #[derive(Debug)]
:: struct Child(i32);

>> #[derive(Debug)]
:: struct Parent(Child);

>> let c1 = Child(42);
>> let c2 = Parent(Child(33));

>> c1
Child(42)
>> c2
Parent(Child(33))

Pretty print for arrays and tuple are supported but only
up to 12 elements. This is a restriction of Rust.

>> let array = [1, 2, 3];
>> let tuple = (1, true, 2.3);

>> array
[1, 2, 3]
>> tuple
(1, true, 2.3)

Slices are not supported in the main space but they are okay
in a function
>> let slice: &[i32] = &array[0..2]; // byexample: +skip

>> fn bar(slice: &[i32]) {
:: println!("{:?}", slice);
:: }

>> bar(&array[0..2]);
[1, 2]

>> const PI : f64 = 3.1416;
>> PI
3.1416

Closure are not supported in the main space but they are okay
in a function
>> let i = 4;
>> let closure_explicit = |j: i32| -> i32 { i + j }; // byexample: +skip
>> let closure_implicit = |j | i + j ; // byexample: +skip
>> let one = || 1; // byexample: +skip

>> let k = Box::new(42); // byexample: +skip
>> let stealer = move || { k }; // byexample: +skip
>> println!("{:?}", stealer()); // byexample: +skip

>> fn baz() {
:: let i = 4;
:: let closure_explicit = |j: i32| -> i32 { i + j };
:: let closure_implicit = |j | i + j ;
:: let one = || 1;
::
:: let k = Box::new(42);
:: let stealer = move || { k };
:: println!("{:?} {:?} {:?} {:?}", closure_explicit(2), closure_implicit(2), one(), stealer());
:: }

>> baz();
6 6 1 42


Types
>> type Nanosecs = u64;
>> let a : Nanosecs = 2;
>> a
2

Scopes
>> let y = {
:: let z = 11;
:: i + z // this is an *expression*, the result of the block
:: };
>> y
15

Flow control
>> let mut m = if i < 1 {
:: i + 2
:: } else {
:: i + 8
:: };

>> loop {
:: if m >= 20 {
:: break;
:: }
::
:: m += 1;
:: if m % 2 == 0 {
:: continue;
:: }
::
:: println!("m is odd: {}", m);
:: };
m is odd: 13
m is odd: 15
m is odd: 17
m is odd: 19

>> 'theloop : while true {
:: m -= 1;
:: if m == 0 {
:: break 'theloop;
:: }
:: };

>> m
0

"""

from __future__ import unicode_literals
import sys, time
import byexample.regex as re
from byexample.common import constant
from byexample.parser import ExampleParser
from byexample.runner import ExampleRunner, PexpectMixin, ShebangTemplate
from byexample.finder import ExampleFinder

stability = 'experimental'


class RustPromptFinder(ExampleFinder):
target = 'rust-prompt'

@constant
def example_regex(self):
return re.compile(
r'''
(?P<snippet>
(?:^(?P<indent> [ ]*) (?:>>)[ ] .*) # PS1 line
(?:\n [ ]* :: .*)*) # PS2 lines
\n?
## Want consists of any non-blank lines that do not start with PS1
(?P<expected> (?:(?![ ]*$) # Not a blank line
(?![ ]*(?:>>)) # Not a line starting with PS1
.+$\n? # But any other line
)*)
''', re.MULTILINE | re.VERBOSE
)

def get_language_of(self, *args, **kargs):
return 'rust'

def get_snippet_and_expected(self, match, where):
snippet, expected = ExampleFinder.get_snippet_and_expected(
self, match, where
)

snippet = self._remove_prompts(snippet)
return snippet, expected

def _remove_prompts(self, snippet):
lines = snippet.split("\n")
return '\n'.join(line[3:] for line in lines)


class RustParser(ExampleParser):
language = 'rust'

@constant
def example_options_string_regex(self):
# anything of the form:
# // byexample: +FOO -BAR +ZAZ=42
return re.compile(r'//\s*byexample:\s*([^\n\'"]*)$', re.MULTILINE)


class RustInterpreter(ExampleRunner, PexpectMixin):
language = 'rust'

def __init__(self, verbosity, encoding, **unused):
self.encoding = encoding

PexpectMixin.__init__(self, PS1_re=r'>> ', any_PS_re=r'>> ')

def get_default_cmd(self, *args, **kargs):
return "%e %p %a", {
'e':
"/usr/bin/env",
'p':
"evcxr",
'a': [
"--disable-readline", # no readline
"--opt",
"0", # disable optimizations (reduce exec time)
]
}

def run(self, example, options):
# evcxr's output requeries to be emulated by an ANSI Terminal
# so we force this (see _get_output())
options['term'] = 'ansi'
options['timeout'] = (max(options['timeout'], 8))
return PexpectMixin._run(self, example, options)

def _run_impl(self, example, options):
src = example.source
src = self._strip_and_join_lines_into_one(src, strip=True)
return self._exec_and_wait(src, options, from_example=example)

_SINGLE_LINE_COMMENT_RE = re.compile(r'//[^\n]*$')

def _strip_and_join_lines_into_one(self, src, strip):
# evcxr doesn't support multiline code if the readline is disabled
# so the simplest thing to do is to collaps all the lines into one
# Rust is a language which syntax should not be affected by this
# in contrast to Python **except** when a single-line comment "//"
# is used.
#
# Valid code like this:
# fn foo() { // super
# 42
# }
# It will not work as it will be seen as:
# fn foo () { // super 42 }
#
# The workaround is to strip those comments.
#
_RE = self._SINGLE_LINE_COMMENT_RE
lines = src.split('\n')
if strip:
lines = (_RE.sub('', line) for line in lines)

return ''.join(lines)

def interact(self, example, options):
PexpectMixin.interact(self)

def initialize(self, options):
shebang, tokens = self.get_default_cmd()
shebang = options['shebangs'].get(self.language, shebang)

cmd = ShebangTemplate(shebang).quote_and_substitute(tokens)

options.up()
# evcxr can be quite slow so we increase the timeout by default
options['x']['dfl_timeout'] = (max(options['x']['dfl_timeout'], 30))
self._spawn_interpreter(cmd, options)

# enable sccache (https://github.com/mozilla/sccache) so evcxr
# can speed up the compilation of the examples
self._exec_and_wait(
':sccache 1', options, timeout=options['x']['dfl_timeout']
)
options.down()

def _expect_delayed_output(self, options):
# evcxr runs the example in background so it may return before
# the example finishes. In those cases we want to wait a little
# more to capture all the output
delay = options['x']['delayafterprompt'] or 0
options['x']['delayafterprompt'] = max(delay, 0.25)
return super()._expect_delayed_output(options)

def shutdown(self):
self._shutdown_interpreter()

def cancel(self, example, options):
return self._abort(example, options)
Loading