snail-lang 0.3.7__tar.gz → 0.3.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {snail_lang-0.3.7 → snail_lang-0.3.8}/Cargo.lock +7 -7
- {snail_lang-0.3.7 → snail_lang-0.3.8}/PKG-INFO +1 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-ast/Cargo.toml +1 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-ast/src/ast.rs +0 -4
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-core/Cargo.toml +1 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-error/Cargo.toml +1 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/Cargo.toml +1 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/src/expr.rs +29 -21
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/Cargo.toml +1 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/src/expr.rs +2 -4
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/src/lib.rs +1 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/src/literal.rs +0 -11
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/src/snail.pest +7 -4
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/src/string.rs +0 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/src/util.rs +0 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/tests/parser.rs +104 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/tests/statements.rs +5 -17
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/tests/syntax_expressions.rs +14 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-python/Cargo.toml +1 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/pyproject.toml +1 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/runtime/compact_try.py +1 -1
- {snail_lang-0.3.7 → snail_lang-0.3.8}/Cargo.toml +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/LICENSE +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/README.md +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-ast/README.md +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-ast/src/awk.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-ast/src/lib.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-core/README.md +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-core/src/lib.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-error/README.md +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-error/src/lib.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/README.md +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/src/awk.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/src/constants.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/src/helpers.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/src/lib.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/src/operators.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/src/program.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/src/py_ast.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-lower/src/stmt.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/README.md +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/src/awk.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/src/stmt.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/tests/common.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/tests/errors.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-parser/tests/syntax_strings.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/crates/snail-python/src/lib.rs +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/__init__.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/cli.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/runtime/__init__.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/runtime/regex.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/runtime/structured_accessor.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/runtime/subprocess.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/vendor/__init__.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/vendor/jmespath/LICENSE +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/vendor/jmespath/__init__.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/vendor/jmespath/ast.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/vendor/jmespath/compat.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/vendor/jmespath/exceptions.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/vendor/jmespath/functions.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/vendor/jmespath/lexer.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/vendor/jmespath/parser.py +0 -0
- {snail_lang-0.3.7 → snail_lang-0.3.8}/python/snail/vendor/jmespath/visitor.py +0 -0
|
@@ -485,11 +485,11 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
|
|
485
485
|
|
|
486
486
|
[[package]]
|
|
487
487
|
name = "snail-ast"
|
|
488
|
-
version = "0.3.
|
|
488
|
+
version = "0.3.8"
|
|
489
489
|
|
|
490
490
|
[[package]]
|
|
491
491
|
name = "snail-core"
|
|
492
|
-
version = "0.3.
|
|
492
|
+
version = "0.3.8"
|
|
493
493
|
dependencies = [
|
|
494
494
|
"pyo3",
|
|
495
495
|
"snail-ast",
|
|
@@ -500,14 +500,14 @@ dependencies = [
|
|
|
500
500
|
|
|
501
501
|
[[package]]
|
|
502
502
|
name = "snail-error"
|
|
503
|
-
version = "0.3.
|
|
503
|
+
version = "0.3.8"
|
|
504
504
|
dependencies = [
|
|
505
505
|
"snail-ast",
|
|
506
506
|
]
|
|
507
507
|
|
|
508
508
|
[[package]]
|
|
509
509
|
name = "snail-lower"
|
|
510
|
-
version = "0.3.
|
|
510
|
+
version = "0.3.8"
|
|
511
511
|
dependencies = [
|
|
512
512
|
"pyo3",
|
|
513
513
|
"snail-ast",
|
|
@@ -516,7 +516,7 @@ dependencies = [
|
|
|
516
516
|
|
|
517
517
|
[[package]]
|
|
518
518
|
name = "snail-parser"
|
|
519
|
-
version = "0.3.
|
|
519
|
+
version = "0.3.8"
|
|
520
520
|
dependencies = [
|
|
521
521
|
"pest",
|
|
522
522
|
"pest_derive",
|
|
@@ -526,7 +526,7 @@ dependencies = [
|
|
|
526
526
|
|
|
527
527
|
[[package]]
|
|
528
528
|
name = "snail-proptest"
|
|
529
|
-
version = "0.3.
|
|
529
|
+
version = "0.3.8"
|
|
530
530
|
dependencies = [
|
|
531
531
|
"proptest",
|
|
532
532
|
"pyo3",
|
|
@@ -540,7 +540,7 @@ dependencies = [
|
|
|
540
540
|
|
|
541
541
|
[[package]]
|
|
542
542
|
name = "snail-python"
|
|
543
|
-
version = "0.3.
|
|
543
|
+
version = "0.3.8"
|
|
544
544
|
dependencies = [
|
|
545
545
|
"pyo3",
|
|
546
546
|
"snail-core",
|
|
@@ -612,6 +612,34 @@ pub(crate) fn lower_expr_with_exception(
|
|
|
612
612
|
}
|
|
613
613
|
Expr::Attribute { value, attr, span } => {
|
|
614
614
|
let value = lower_expr_with_exception(builder, value, exception_name)?;
|
|
615
|
+
if attr.chars().all(|ch| ch.is_ascii_digit()) {
|
|
616
|
+
let group_index = attr
|
|
617
|
+
.parse::<i32>()
|
|
618
|
+
.map_err(|_| LowerError::new(format!("Invalid match group index: .{attr}")))?;
|
|
619
|
+
let group_attr = builder
|
|
620
|
+
.call_node(
|
|
621
|
+
"Attribute",
|
|
622
|
+
vec![
|
|
623
|
+
value,
|
|
624
|
+
"group".into_py(builder.py()),
|
|
625
|
+
builder.load_ctx().map_err(py_err_to_lower)?,
|
|
626
|
+
],
|
|
627
|
+
span,
|
|
628
|
+
)
|
|
629
|
+
.map_err(py_err_to_lower)?;
|
|
630
|
+
let index_expr = number_expr(builder, &group_index.to_string(), span)?;
|
|
631
|
+
return builder
|
|
632
|
+
.call_node(
|
|
633
|
+
"Call",
|
|
634
|
+
vec![
|
|
635
|
+
group_attr,
|
|
636
|
+
PyList::new_bound(builder.py(), vec![index_expr]).into_py(builder.py()),
|
|
637
|
+
PyList::empty_bound(builder.py()).into_py(builder.py()),
|
|
638
|
+
],
|
|
639
|
+
span,
|
|
640
|
+
)
|
|
641
|
+
.map_err(py_err_to_lower);
|
|
642
|
+
}
|
|
615
643
|
builder
|
|
616
644
|
.call_node(
|
|
617
645
|
"Attribute",
|
|
@@ -686,19 +714,6 @@ pub(crate) fn lower_expr_with_exception(
|
|
|
686
714
|
)
|
|
687
715
|
.map_err(py_err_to_lower)
|
|
688
716
|
}
|
|
689
|
-
Expr::Set { elements, span } => {
|
|
690
|
-
let mut lowered = Vec::with_capacity(elements.len());
|
|
691
|
-
for element in elements {
|
|
692
|
-
lowered.push(lower_expr_with_exception(builder, element, exception_name)?);
|
|
693
|
-
}
|
|
694
|
-
builder
|
|
695
|
-
.call_node(
|
|
696
|
-
"Set",
|
|
697
|
-
vec![PyList::new_bound(builder.py(), lowered).into_py(builder.py())],
|
|
698
|
-
span,
|
|
699
|
-
)
|
|
700
|
-
.map_err(py_err_to_lower)
|
|
701
|
-
}
|
|
702
717
|
Expr::ListComp {
|
|
703
718
|
element,
|
|
704
719
|
target,
|
|
@@ -1031,7 +1046,7 @@ fn count_placeholders(expr: &Expr, info: &mut PlaceholderInfo) {
|
|
|
1031
1046
|
count_placeholders(index, info);
|
|
1032
1047
|
}
|
|
1033
1048
|
Expr::Paren { expr, .. } => count_placeholders(expr, info),
|
|
1034
|
-
Expr::List { elements, .. } | Expr::Tuple { elements, .. }
|
|
1049
|
+
Expr::List { elements, .. } | Expr::Tuple { elements, .. } => {
|
|
1035
1050
|
for expr in elements {
|
|
1036
1051
|
count_placeholders(expr, info);
|
|
1037
1052
|
}
|
|
@@ -1262,13 +1277,6 @@ fn substitute_placeholder(expr: &Expr, replacement: &Expr) -> Expr {
|
|
|
1262
1277
|
.collect(),
|
|
1263
1278
|
span: span.clone(),
|
|
1264
1279
|
},
|
|
1265
|
-
Expr::Set { elements, span } => Expr::Set {
|
|
1266
|
-
elements: elements
|
|
1267
|
-
.iter()
|
|
1268
|
-
.map(|expr| substitute_placeholder(expr, replacement))
|
|
1269
|
-
.collect(),
|
|
1270
|
-
span: span.clone(),
|
|
1271
|
-
},
|
|
1272
1280
|
Expr::Slice { start, end, span } => Expr::Slice {
|
|
1273
1281
|
start: start
|
|
1274
1282
|
.as_ref()
|
|
@@ -5,8 +5,8 @@ use snail_error::ParseError;
|
|
|
5
5
|
use crate::Rule;
|
|
6
6
|
use crate::literal::{
|
|
7
7
|
parse_dict_comp, parse_dict_literal, parse_list_comp, parse_list_literal, parse_literal,
|
|
8
|
-
parse_regex_literal,
|
|
9
|
-
|
|
8
|
+
parse_regex_literal, parse_slice, parse_structured_accessor, parse_subprocess,
|
|
9
|
+
parse_tuple_literal,
|
|
10
10
|
};
|
|
11
11
|
use crate::util::{error_with_span, expr_span, merge_span, span_from_pair};
|
|
12
12
|
|
|
@@ -57,7 +57,6 @@ pub fn parse_expr_pair(pair: Pair<'_, Rule>, source: &str) -> Result<Expr, Parse
|
|
|
57
57
|
Rule::list_literal => parse_list_literal(pair, source),
|
|
58
58
|
Rule::dict_literal => parse_dict_literal(pair, source),
|
|
59
59
|
Rule::tuple_literal => parse_tuple_literal(pair, source),
|
|
60
|
-
Rule::set_literal => parse_set_literal(pair, source),
|
|
61
60
|
Rule::list_comp => parse_list_comp(pair, source),
|
|
62
61
|
Rule::dict_comp => parse_dict_comp(pair, source),
|
|
63
62
|
Rule::regex => parse_regex_literal(pair, source),
|
|
@@ -586,7 +585,6 @@ fn parse_atom(pair: Pair<'_, Rule>, source: &str) -> Result<Expr, ParseError> {
|
|
|
586
585
|
Rule::list_literal => parse_list_literal(inner_pair, source),
|
|
587
586
|
Rule::dict_literal => parse_dict_literal(inner_pair, source),
|
|
588
587
|
Rule::tuple_literal => parse_tuple_literal(inner_pair, source),
|
|
589
|
-
Rule::set_literal => parse_set_literal(inner_pair, source),
|
|
590
588
|
Rule::list_comp => parse_list_comp(inner_pair, source),
|
|
591
589
|
Rule::dict_comp => parse_dict_comp(inner_pair, source),
|
|
592
590
|
Rule::regex => parse_regex_literal(inner_pair, source),
|
|
@@ -355,7 +355,7 @@ fn validate_expr(expr: &Expr, source: &str) -> Result<(), ParseError> {
|
|
|
355
355
|
Expr::Paren { expr, .. } => {
|
|
356
356
|
validate_expr(expr, source)?;
|
|
357
357
|
}
|
|
358
|
-
Expr::List { elements, .. } | Expr::Tuple { elements, .. }
|
|
358
|
+
Expr::List { elements, .. } | Expr::Tuple { elements, .. } => {
|
|
359
359
|
for expr in elements {
|
|
360
360
|
validate_expr(expr, source)?;
|
|
361
361
|
}
|
|
@@ -45,17 +45,6 @@ pub fn parse_tuple_literal(pair: Pair<'_, Rule>, source: &str) -> Result<Expr, P
|
|
|
45
45
|
Ok(Expr::Tuple { elements, span })
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
pub fn parse_set_literal(pair: Pair<'_, Rule>, source: &str) -> Result<Expr, ParseError> {
|
|
49
|
-
let span = span_from_pair(&pair, source);
|
|
50
|
-
let mut elements = Vec::new();
|
|
51
|
-
for inner in pair.into_inner() {
|
|
52
|
-
if inner.as_rule() == Rule::expr {
|
|
53
|
-
elements.push(crate::expr::parse_expr_pair(inner, source)?);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
Ok(Expr::Set { elements, span })
|
|
57
|
-
}
|
|
58
|
-
|
|
59
48
|
pub fn parse_list_literal(pair: Pair<'_, Rule>, source: &str) -> Result<Expr, ParseError> {
|
|
60
49
|
let span = span_from_pair(&pair, source);
|
|
61
50
|
let mut elements = Vec::new();
|
|
@@ -11,7 +11,11 @@ awk_rule = { block | awk_pattern ~ block? }
|
|
|
11
11
|
awk_pattern = { expr }
|
|
12
12
|
|
|
13
13
|
// Statements: separated by semicolons or newlines
|
|
14
|
-
|
|
14
|
+
// Compound statements (ending with }) don't need trailing separators
|
|
15
|
+
// Simple statements need separators unless they're the last statement
|
|
16
|
+
stmt_list = { stmt_item* ~ final_stmt? }
|
|
17
|
+
stmt_item = _{ compound_stmt ~ stmt_sep* | simple_stmt ~ stmt_sep+ }
|
|
18
|
+
final_stmt = _{ compound_stmt | simple_stmt }
|
|
15
19
|
stmt_sep = _{ ";" ~ NEWLINE* | NEWLINE+ }
|
|
16
20
|
|
|
17
21
|
stmt = _{ compound_stmt | simple_stmt }
|
|
@@ -136,7 +140,8 @@ star_arg = { "*" ~ expr }
|
|
|
136
140
|
kw_star_arg = { "**" ~ expr }
|
|
137
141
|
|
|
138
142
|
// Attribute access and indexing/slicing
|
|
139
|
-
attribute = { "." ~ identifier }
|
|
143
|
+
attribute = { "." ~ (identifier | attribute_index) }
|
|
144
|
+
attribute_index = @{ ASCII_DIGIT+ }
|
|
140
145
|
index = { "[" ~ slice ~ "]" }
|
|
141
146
|
slice = { slice_expr | expr }
|
|
142
147
|
slice_expr = { slice_start? ~ ":" ~ slice_end? }
|
|
@@ -158,7 +163,6 @@ atom = _{
|
|
|
158
163
|
| list_literal
|
|
159
164
|
| dict_comp
|
|
160
165
|
| dict_literal
|
|
161
|
-
| set_literal
|
|
162
166
|
| tuple_literal
|
|
163
167
|
| compound_expr
|
|
164
168
|
| "(" ~ expr ~ ")"
|
|
@@ -188,7 +192,6 @@ list_comp = { "[" ~ expr ~ comp_for ~ "]" }
|
|
|
188
192
|
list_literal = { "[" ~ (expr ~ ("," ~ expr)*)? ~ "]" }
|
|
189
193
|
dict_comp = { "{" ~ expr ~ ":" ~ expr ~ comp_for ~ "}" }
|
|
190
194
|
dict_literal = { "{" ~ (dict_entry ~ ("," ~ dict_entry)*)? ~ "}" }
|
|
191
|
-
set_literal = { "{" ~ expr ~ ("," ~ expr)* ~ ","? ~ "}" }
|
|
192
195
|
dict_entry = { expr ~ ":" ~ expr }
|
|
193
196
|
|
|
194
197
|
// Comprehension clauses
|
|
@@ -324,7 +324,6 @@ pub fn shift_expr_spans(expr: &mut Expr, offset: usize, source: &str) {
|
|
|
324
324
|
| Expr::List { span, .. }
|
|
325
325
|
| Expr::Tuple { span, .. }
|
|
326
326
|
| Expr::Dict { span, .. }
|
|
327
|
-
| Expr::Set { span, .. }
|
|
328
327
|
| Expr::Slice { span, .. } => {
|
|
329
328
|
*span = shift_span(span, offset, source);
|
|
330
329
|
}
|
|
@@ -162,7 +162,6 @@ pub fn expr_span(expr: &snail_ast::Expr) -> &SourceSpan {
|
|
|
162
162
|
| snail_ast::Expr::List { span, .. }
|
|
163
163
|
| snail_ast::Expr::Tuple { span, .. }
|
|
164
164
|
| snail_ast::Expr::Dict { span, .. }
|
|
165
|
-
| snail_ast::Expr::Set { span, .. }
|
|
166
165
|
| snail_ast::Expr::ListComp { span, .. }
|
|
167
166
|
| snail_ast::Expr::DictComp { span, .. }
|
|
168
167
|
| snail_ast::Expr::Slice { span, .. } => span,
|
|
@@ -632,3 +632,107 @@ fn parses_ternary_with_is_not_operator() {
|
|
|
632
632
|
other => panic!("Expected assignment, got {:?}", other),
|
|
633
633
|
}
|
|
634
634
|
}
|
|
635
|
+
|
|
636
|
+
// Tests for compound statement separator behavior (no semicolon needed after })
|
|
637
|
+
|
|
638
|
+
#[test]
|
|
639
|
+
fn parses_if_followed_by_stmt_without_separator() {
|
|
640
|
+
// if statement followed by expression without semicolon
|
|
641
|
+
let source = "if x { y = 1 } z";
|
|
642
|
+
let program = parse_ok(source);
|
|
643
|
+
assert_eq!(program.stmts.len(), 2);
|
|
644
|
+
assert!(matches!(&program.stmts[0], Stmt::If { .. }));
|
|
645
|
+
assert!(matches!(&program.stmts[1], Stmt::Expr { .. }));
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
#[test]
|
|
649
|
+
fn parses_while_followed_by_stmt_without_separator() {
|
|
650
|
+
let source = "while x { y = 1 } z";
|
|
651
|
+
let program = parse_ok(source);
|
|
652
|
+
assert_eq!(program.stmts.len(), 2);
|
|
653
|
+
assert!(matches!(&program.stmts[0], Stmt::While { .. }));
|
|
654
|
+
assert!(matches!(&program.stmts[1], Stmt::Expr { .. }));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
#[test]
|
|
658
|
+
fn parses_for_followed_by_stmt_without_separator() {
|
|
659
|
+
let source = "for i in x { y = 1 } z";
|
|
660
|
+
let program = parse_ok(source);
|
|
661
|
+
assert_eq!(program.stmts.len(), 2);
|
|
662
|
+
assert!(matches!(&program.stmts[0], Stmt::For { .. }));
|
|
663
|
+
assert!(matches!(&program.stmts[1], Stmt::Expr { .. }));
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
#[test]
|
|
667
|
+
fn parses_def_followed_by_stmt_without_separator() {
|
|
668
|
+
let source = "def f() { pass } f()";
|
|
669
|
+
let program = parse_ok(source);
|
|
670
|
+
assert_eq!(program.stmts.len(), 2);
|
|
671
|
+
assert!(matches!(&program.stmts[0], Stmt::Def { .. }));
|
|
672
|
+
assert!(matches!(&program.stmts[1], Stmt::Expr { .. }));
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
#[test]
|
|
676
|
+
fn parses_class_followed_by_stmt_without_separator() {
|
|
677
|
+
let source = "class C { pass } C()";
|
|
678
|
+
let program = parse_ok(source);
|
|
679
|
+
assert_eq!(program.stmts.len(), 2);
|
|
680
|
+
assert!(matches!(&program.stmts[0], Stmt::Class { .. }));
|
|
681
|
+
assert!(matches!(&program.stmts[1], Stmt::Expr { .. }));
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
#[test]
|
|
685
|
+
fn parses_try_followed_by_stmt_without_separator() {
|
|
686
|
+
// Note: using explicit exception type since bare `except { }` is ambiguous with set literals
|
|
687
|
+
let source = "try { x } except Exception { y } z";
|
|
688
|
+
let program = parse_ok(source);
|
|
689
|
+
assert_eq!(program.stmts.len(), 2);
|
|
690
|
+
assert!(matches!(&program.stmts[0], Stmt::Try { .. }));
|
|
691
|
+
assert!(matches!(&program.stmts[1], Stmt::Expr { .. }));
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
#[test]
|
|
695
|
+
fn parses_with_followed_by_stmt_without_separator() {
|
|
696
|
+
let source = "with x { y } z";
|
|
697
|
+
let program = parse_ok(source);
|
|
698
|
+
assert_eq!(program.stmts.len(), 2);
|
|
699
|
+
assert!(matches!(&program.stmts[0], Stmt::With { .. }));
|
|
700
|
+
assert!(matches!(&program.stmts[1], Stmt::Expr { .. }));
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
#[test]
|
|
704
|
+
fn parses_nested_compound_stmts_without_separators() {
|
|
705
|
+
let source = "if a { if b { c } d } e";
|
|
706
|
+
let program = parse_ok(source);
|
|
707
|
+
assert_eq!(program.stmts.len(), 2);
|
|
708
|
+
assert!(matches!(&program.stmts[0], Stmt::If { .. }));
|
|
709
|
+
assert!(matches!(&program.stmts[1], Stmt::Expr { .. }));
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
#[test]
|
|
713
|
+
fn parses_mixed_compound_and_simple_stmts() {
|
|
714
|
+
let source = "a = 1; if b { c = 2 } d = 3; e = 4";
|
|
715
|
+
let program = parse_ok(source);
|
|
716
|
+
assert_eq!(program.stmts.len(), 4);
|
|
717
|
+
assert!(matches!(&program.stmts[0], Stmt::Assign { .. }));
|
|
718
|
+
assert!(matches!(&program.stmts[1], Stmt::If { .. }));
|
|
719
|
+
assert!(matches!(&program.stmts[2], Stmt::Assign { .. }));
|
|
720
|
+
assert!(matches!(&program.stmts[3], Stmt::Assign { .. }));
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
#[test]
|
|
724
|
+
fn parses_consecutive_compound_stmts_without_separators() {
|
|
725
|
+
let source = "if a { b } if c { d } while e { f }";
|
|
726
|
+
let program = parse_ok(source);
|
|
727
|
+
assert_eq!(program.stmts.len(), 3);
|
|
728
|
+
assert!(matches!(&program.stmts[0], Stmt::If { .. }));
|
|
729
|
+
assert!(matches!(&program.stmts[1], Stmt::If { .. }));
|
|
730
|
+
assert!(matches!(&program.stmts[2], Stmt::While { .. }));
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
#[test]
|
|
734
|
+
fn simple_stmt_still_requires_separator() {
|
|
735
|
+
// Two simple statements without separator should fail
|
|
736
|
+
let source = "a b";
|
|
737
|
+
parse_err(source);
|
|
738
|
+
}
|
|
@@ -183,10 +183,10 @@ fn parses_assert_and_del() {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
#[test]
|
|
186
|
-
fn
|
|
187
|
-
let source = "items = [1, 2, 3, 4]\npair = (1, 2)\nsingle = (1,)\nempty = ()\
|
|
186
|
+
fn parses_tuples_and_slices() {
|
|
187
|
+
let source = "items = [1, 2, 3, 4]\npair = (1, 2)\nsingle = (1,)\nempty = ()\nmid = items[1:3]\nhead = items[:2]\ntail = items[2:]\n";
|
|
188
188
|
let program = parse_ok(source);
|
|
189
|
-
assert_eq!(program.stmts.len(),
|
|
189
|
+
assert_eq!(program.stmts.len(), 7);
|
|
190
190
|
|
|
191
191
|
let (_, value) = expect_assign(&program.stmts[0]);
|
|
192
192
|
match value {
|
|
@@ -213,18 +213,6 @@ fn parses_tuples_sets_and_slices() {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
let (_, value) = expect_assign(&program.stmts[4]);
|
|
216
|
-
match value {
|
|
217
|
-
Expr::Set { elements, .. } => {
|
|
218
|
-
assert_eq!(elements.len(), 2);
|
|
219
|
-
match &elements[0] {
|
|
220
|
-
Expr::Bool { value, .. } => assert!(*value),
|
|
221
|
-
other => panic!("Expected True, got {other:?}"),
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
other => panic!("Expected set, got {other:?}"),
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
let (_, value) = expect_assign(&program.stmts[5]);
|
|
228
216
|
match value {
|
|
229
217
|
Expr::Index { value, index, .. } => {
|
|
230
218
|
expect_name(value.as_ref(), "items");
|
|
@@ -239,7 +227,7 @@ fn parses_tuples_sets_and_slices() {
|
|
|
239
227
|
other => panic!("Expected index, got {other:?}"),
|
|
240
228
|
}
|
|
241
229
|
|
|
242
|
-
let (_, value) = expect_assign(&program.stmts[
|
|
230
|
+
let (_, value) = expect_assign(&program.stmts[5]);
|
|
243
231
|
match value {
|
|
244
232
|
Expr::Index { index, .. } => match index.as_ref() {
|
|
245
233
|
Expr::Slice { start, end, .. } => {
|
|
@@ -251,7 +239,7 @@ fn parses_tuples_sets_and_slices() {
|
|
|
251
239
|
other => panic!("Expected index, got {other:?}"),
|
|
252
240
|
}
|
|
253
241
|
|
|
254
|
-
let (_, value) = expect_assign(&program.stmts[
|
|
242
|
+
let (_, value) = expect_assign(&program.stmts[6]);
|
|
255
243
|
match value {
|
|
256
244
|
Expr::Index { index, .. } => match index.as_ref() {
|
|
257
245
|
Expr::Slice { start, end, .. } => {
|
|
@@ -367,3 +367,17 @@ fn parses_call_attribute_index_chain() {
|
|
|
367
367
|
other => panic!("Expected index access, got {other:?}"),
|
|
368
368
|
}
|
|
369
369
|
}
|
|
370
|
+
|
|
371
|
+
#[test]
|
|
372
|
+
fn parses_numeric_attribute_access() {
|
|
373
|
+
let program = parse_ok("value = match.1");
|
|
374
|
+
let (_, value) = expect_assign(&program.stmts[0]);
|
|
375
|
+
|
|
376
|
+
match value {
|
|
377
|
+
Expr::Attribute { value, attr, .. } => {
|
|
378
|
+
expect_name(value.as_ref(), "match");
|
|
379
|
+
assert_eq!(attr, "1");
|
|
380
|
+
}
|
|
381
|
+
other => panic!("Expected attribute access, got {other:?}"),
|
|
382
|
+
}
|
|
383
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|