Skip to content

Commit a47e979

Browse files
Merge branch 'master' into add_list_copy
2 parents 1e92bb8 + 70d5cdb commit a47e979

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+944
-599
lines changed

.travis.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ matrix:
3232
env:
3333
- TRAVIS_RUST_VERSION=stable
3434
- REGULAR_TEST=false
35+
- CODE_COVERAGE=false
3536
script: tests/.travis-runner.sh
3637
- language: python
3738
python: 3.6
@@ -45,6 +46,7 @@ matrix:
4546
env:
4647
- TRAVIS_RUST_VERSION=beta
4748
- REGULAR_TEST=false
49+
- CODE_COVERAGE=false
4850
script: tests/.travis-runner.sh
4951
- name: rustfmt
5052
language: rust
@@ -97,6 +99,22 @@ matrix:
9799
- cargo clippy
98100
env:
99101
- REGULAR_TEST=true
102+
- name: Code Coverage
103+
language: python
104+
python: 3.6
105+
cache:
106+
pip: true
107+
# Because we're using the Python Travis environment, we can't use
108+
# the built-in cargo cacher
109+
directories:
110+
- /home/travis/.cargo
111+
- target
112+
script:
113+
- tests/.travis-runner.sh
114+
env:
115+
- TRAVIS_RUST_VERSION=nightly
116+
- REGULAR_TEST=false
117+
- CODE_COVERAGE=true
100118
allow_failures:
101119
- rust: nightly
102120
env: REGULAR_TEST=true

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
A Python-3 (CPython >= 3.5.0) Interpreter written in Rust :snake: :scream: :metal:.
44

55
[![Build Status](https://travis-ci.org/RustPython/RustPython.svg?branch=master)](https://travis-ci.org/RustPython/RustPython)
6+
[![codecov](https://codecov.io/gh/RustPython/RustPython/branch/master/graph/badge.svg)](https://codecov.io/gh/RustPython/RustPython)
67
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
78
[![Contributors](https://img.shields.io/github/contributors/RustPython/RustPython.svg)](https://github.com/RustPython/RustPython/graphs/contributors)
89
[![Gitter](https://badges.gitter.im/RustPython/Lobby.svg)](https://gitter.im/rustpython/Lobby)

azure-pipelines.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
trigger:
2+
- master
3+
4+
jobs:
5+
6+
- job: 'Test'
7+
pool:
8+
vmImage: 'vs2017-win2016'
9+
strategy:
10+
matrix:
11+
Python37:
12+
python.version: '3.7'
13+
maxParallel: 10
14+
15+
steps:
16+
- task: UsePythonVersion@0
17+
inputs:
18+
versionSpec: '$(python.version)'
19+
architecture: 'x64'
20+
21+
- script: |
22+
"C:\Program Files\Git\mingw64\bin\curl.exe" -sSf -o rustup-init.exe https://win.rustup.rs/
23+
.\rustup-init.exe -y
24+
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
25+
rustc -V
26+
cargo -V
27+
displayName: 'Installing Rust'
28+
29+
- script: |
30+
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
31+
cargo build --verbose --all
32+
displayName: 'Build'
33+
34+
- script: |
35+
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
36+
cargo test --verbose --all
37+
displayName: 'Run tests'
38+
39+
- script: |
40+
pip install pipenv
41+
pushd tests
42+
pipenv install
43+
popd
44+
displayName: 'Install pipenv and python packages'
45+
46+
- script: |
47+
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
48+
cargo build --verbose --release
49+
displayName: 'Build release'
50+
51+
- script: |
52+
set PATH=%PATH%;%USERPROFILE%\.cargo\bin
53+
pushd tests
54+
pipenv run pytest
55+
popd
56+
displayName: 'Run snippet tests'

parser/src/ast.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,14 @@ pub enum Number {
315315

316316
#[derive(Debug, PartialEq)]
317317
pub enum StringGroup {
318-
Constant { value: String },
319-
FormattedValue { value: Box<Expression> },
320-
Joined { values: Vec<StringGroup> },
318+
Constant {
319+
value: String,
320+
},
321+
FormattedValue {
322+
value: Box<Expression>,
323+
spec: String,
324+
},
325+
Joined {
326+
values: Vec<StringGroup>,
327+
},
321328
}

parser/src/parser.rs

Lines changed: 129 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
extern crate lalrpop_util;
22

33
use std::iter;
4-
use std::mem;
54

65
use super::ast;
76
use super::error::ParseError;
@@ -86,74 +85,143 @@ impl From<FStringError>
8685
}
8786
}
8887

88+
enum ParseState {
89+
Text {
90+
content: String,
91+
},
92+
FormattedValue {
93+
expression: String,
94+
spec: Option<String>,
95+
depth: usize,
96+
},
97+
}
98+
8999
pub fn parse_fstring(source: &str) -> Result<ast::StringGroup, FStringError> {
100+
use self::ParseState::*;
101+
90102
let mut values = vec![];
91-
let mut start = 0;
92-
let mut depth = 0;
93-
let mut escaped = false;
94-
let mut content = String::new();
95-
96-
let mut chars = source.char_indices().peekable();
97-
while let Some((pos, ch)) = chars.next() {
98-
match ch {
99-
'{' | '}' if escaped => {
100-
if depth == 0 {
103+
let mut state = ParseState::Text {
104+
content: String::new(),
105+
};
106+
107+
let mut chars = source.chars().peekable();
108+
while let Some(ch) = chars.next() {
109+
state = match state {
110+
Text { mut content } => match ch {
111+
'{' => {
112+
if let Some('{') = chars.peek() {
113+
chars.next();
114+
content.push('{');
115+
Text { content }
116+
} else {
117+
if !content.is_empty() {
118+
values.push(ast::StringGroup::Constant { value: content });
119+
}
120+
121+
FormattedValue {
122+
expression: String::new(),
123+
spec: None,
124+
depth: 0,
125+
}
126+
}
127+
}
128+
'}' => {
129+
if let Some('}') = chars.peek() {
130+
chars.next();
131+
content.push('}');
132+
Text { content }
133+
} else {
134+
return Err(FStringError::UnopenedRbrace);
135+
}
136+
}
137+
_ => {
101138
content.push(ch);
139+
Text { content }
102140
}
103-
escaped = false;
104-
}
105-
'{' => {
106-
if let Some((_, '{')) = chars.peek() {
107-
escaped = true;
108-
continue;
141+
},
142+
143+
FormattedValue {
144+
mut expression,
145+
mut spec,
146+
depth,
147+
} => match ch {
148+
':' if depth == 0 => FormattedValue {
149+
expression,
150+
spec: Some(String::new()),
151+
depth,
152+
},
153+
'{' => {
154+
if let Some('{') = chars.peek() {
155+
expression.push_str("{{");
156+
chars.next();
157+
FormattedValue {
158+
expression,
159+
spec,
160+
depth,
161+
}
162+
} else {
163+
expression.push('{');
164+
FormattedValue {
165+
expression,
166+
spec,
167+
depth: depth + 1,
168+
}
169+
}
109170
}
110-
111-
if depth == 0 {
112-
if !content.is_empty() {
113-
values.push(ast::StringGroup::Constant {
114-
value: mem::replace(&mut content, String::new()),
171+
'}' => {
172+
if let Some('}') = chars.peek() {
173+
expression.push_str("}}");
174+
chars.next();
175+
FormattedValue {
176+
expression,
177+
spec,
178+
depth,
179+
}
180+
} else if depth > 0 {
181+
expression.push('}');
182+
FormattedValue {
183+
expression,
184+
spec,
185+
depth: depth - 1,
186+
}
187+
} else {
188+
values.push(ast::StringGroup::FormattedValue {
189+
value: Box::new(match parse_expression(expression.trim()) {
190+
Ok(expr) => expr,
191+
Err(_) => return Err(FStringError::InvalidExpression),
192+
}),
193+
spec: spec.unwrap_or_default(),
115194
});
195+
Text {
196+
content: String::new(),
197+
}
116198
}
117-
118-
start = pos + 1;
119-
}
120-
121-
depth += 1;
122-
}
123-
'}' => {
124-
if let Some((_, '}')) = chars.peek() {
125-
escaped = true;
126-
continue;
127199
}
128-
129-
if depth == 0 {
130-
return Err(FStringError::UnopenedRbrace);
200+
_ => {
201+
if let Some(spec) = spec.as_mut() {
202+
spec.push(ch)
203+
} else {
204+
expression.push(ch);
205+
}
206+
FormattedValue {
207+
expression,
208+
spec,
209+
depth,
210+
}
131211
}
212+
},
213+
};
214+
}
132215

133-
depth -= 1;
134-
if depth == 0 {
135-
values.push(ast::StringGroup::FormattedValue {
136-
value: Box::new(match parse_expression(source[start..pos].trim()) {
137-
Ok(expr) => expr,
138-
Err(_) => return Err(FStringError::InvalidExpression),
139-
}),
140-
});
141-
}
142-
}
143-
ch => {
144-
if depth == 0 {
145-
content.push(ch);
146-
}
216+
match state {
217+
Text { content } => {
218+
if !content.is_empty() {
219+
values.push(ast::StringGroup::Constant { value: content })
147220
}
148221
}
149-
}
150-
151-
if depth != 0 {
152-
return Err(FStringError::UnclosedLbrace);
153-
}
154-
155-
if !content.is_empty() {
156-
values.push(ast::StringGroup::Constant { value: content })
222+
FormattedValue { .. } => {
223+
return Err(FStringError::UnclosedLbrace);
224+
}
157225
}
158226

159227
Ok(match values.len() {
@@ -581,10 +649,12 @@ mod tests {
581649
ast::StringGroup::Joined {
582650
values: vec![
583651
ast::StringGroup::FormattedValue {
584-
value: Box::new(mk_ident("a"))
652+
value: Box::new(mk_ident("a")),
653+
spec: String::new(),
585654
},
586655
ast::StringGroup::FormattedValue {
587-
value: Box::new(mk_ident("b"))
656+
value: Box::new(mk_ident("b")),
657+
spec: String::new(),
588658
},
589659
ast::StringGroup::Constant {
590660
value: "{foo}".to_owned()

parser/src/python.lalrpop

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -613,17 +613,26 @@ ClassDef: ast::LocatedStatement = {
613613
},
614614
};
615615

616+
Path: ast::Expression = {
617+
<n:Identifier> => ast::Expression::Identifier { name: n },
618+
<p:Path> "." <n:name> => {
619+
ast::Expression::Attribute {
620+
value: Box::new(p),
621+
name: n,
622+
}
623+
},
624+
};
625+
616626
// Decorators:
617627
Decorator: ast::Expression = {
618-
"@" <n:DottedName> <a: ("(" ArgumentList ")")?> "\n" => {
619-
let name = ast::Expression::Identifier { name: n };
628+
"@" <p:Path> <a: ("(" ArgumentList ")")?> "\n" => {
620629
match a {
621630
Some((_, args, _)) => ast::Expression::Call {
622-
function: Box::new(name),
631+
function: Box::new(p),
623632
args: args.0,
624633
keywords: args.1,
625634
},
626-
None => name,
635+
None => p,
627636
}
628637
},
629638
};

0 commit comments

Comments
 (0)