Skip to content

Commit

Permalink
Merge pull request #197 from byexamples/Issue-176-Support-Rust-PoC
Browse files Browse the repository at this point in the history
Issue 176 support rust
  • Loading branch information
eldipa committed Jul 6, 2021
2 parents 9c3d134 + efc50a0 commit 1405556
Show file tree
Hide file tree
Showing 12 changed files with 561 additions and 12 deletions.
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

0 comments on commit 1405556

Please sign in to comment.