diff --git a/Cargo.lock b/Cargo.lock index 04729d30a..5de6ad43a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2185,29 +2185,6 @@ dependencies = [ "rustc-hash 2.1.1", ] -[[package]] -name = "ruff_python_codegen" -version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" -dependencies = [ - "ruff_python_ast", - "ruff_python_literal", - "ruff_python_parser", - "ruff_source_file", - "ruff_text_size", -] - -[[package]] -name = "ruff_python_literal" -version = "0.0.0" -source = "git+https://github.com/astral-sh/ruff.git?tag=0.11.0#2cd25ef6410fb5fca96af1578728a3d828d2d53a" -dependencies = [ - "bitflags 2.8.0", - "itertools 0.14.0", - "ruff_python_ast", - "unic-ucd-category", -] - [[package]] name = "ruff_python_parser" version = "0.0.0" @@ -2323,12 +2300,11 @@ dependencies = [ "num-complex", "num-traits", "ruff_python_ast", - "ruff_python_codegen", - "ruff_python_parser", "ruff_source_file", "ruff_text_size", "rustpython-compiler-core", "rustpython-compiler-source", + "rustpython-literal", "rustpython-wtf8", "thiserror 2.0.11", "unicode_names2", @@ -2386,8 +2362,6 @@ dependencies = [ "lz4_flex", "malachite-bigint", "num-complex", - "ruff_python_ast", - "ruff_python_parser", "ruff_source_file", "rustpython-wtf8", "serde", diff --git a/Cargo.toml b/Cargo.toml index c9f9c9652..ccfec120f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,7 +148,6 @@ ruff_python_parser = { git = "https://github.com/astral-sh/ruff.git", tag = "0.1 ruff_python_ast = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } ruff_text_size = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } ruff_source_file = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } -ruff_python_codegen = { git = "https://github.com/astral-sh/ruff.git", tag = "0.11.0" } ahash = "0.8.11" ascii = "1.1" diff --git a/Lib/test/test_future_stmt/test_future.py b/Lib/test/test_future_stmt/test_future.py index ad9b5d5a8..0e0805103 100644 --- a/Lib/test/test_future_stmt/test_future.py +++ b/Lib/test/test_future_stmt/test_future.py @@ -198,8 +198,6 @@ class AnnotationsFutureTestCase(unittest.TestCase): ) return scope - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_annotations(self): eq = self.assertAnnotationEqual eq('...') @@ -364,8 +362,6 @@ class AnnotationsFutureTestCase(unittest.TestCase): eq('(((a, b)))', '(a, b)') eq("1 + 2 + 3") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_fstring_debug_annotations(self): # f-strings with '=' don't round trip very well, so set the expected # result explicitly. @@ -376,8 +372,6 @@ class AnnotationsFutureTestCase(unittest.TestCase): self.assertAnnotationEqual("f'{x=!a}'", expected="f'x={x!a}'") self.assertAnnotationEqual("f'{x=!s:*^20}'", expected="f'x={x!s:*^20}'") - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_infinity_numbers(self): inf = "1e" + repr(sys.float_info.max_10_exp + 1) infj = f"{inf}j" diff --git a/compiler/codegen/Cargo.toml b/compiler/codegen/Cargo.toml index 2ee927709..53469b9f6 100644 --- a/compiler/codegen/Cargo.toml +++ b/compiler/codegen/Cargo.toml @@ -14,12 +14,11 @@ license.workspace = true # rustpython-parser-core = { workspace = true } rustpython-compiler-core = { workspace = true } rustpython-compiler-source = {workspace = true } +rustpython-literal = {workspace = true } rustpython-wtf8 = { workspace = true } -ruff_python_parser = { workspace = true } ruff_python_ast = { workspace = true } ruff_text_size = { workspace = true } ruff_source_file = { workspace = true } -ruff_python_codegen = { workspace = true } ahash = { workspace = true } bitflags = { workspace = true } diff --git a/compiler/codegen/src/compile.rs b/compiler/codegen/src/compile.rs index 6140d354a..38d5b8fb1 100644 --- a/compiler/codegen/src/compile.rs +++ b/compiler/codegen/src/compile.rs @@ -12,6 +12,7 @@ use crate::{ error::{CodegenError, CodegenErrorType}, ir, symboltable::{self, SymbolFlags, SymbolScope, SymbolTable}, + unparse::unparse_expr, }; use itertools::Itertools; use malachite_bigint::BigInt; @@ -2026,11 +2027,10 @@ impl Compiler<'_> { fn compile_annotation(&mut self, annotation: &Expr) -> CompileResult<()> { if self.future_annotations { - // FIXME: codegen? - let ident = Default::default(); - let codegen = ruff_python_codegen::Generator::new(&ident, Default::default()); self.emit_load_const(ConstantData::Str { - value: codegen.expr(annotation).into(), + value: unparse_expr(annotation, &self.source_code) + .to_string() + .into(), }); } else { self.compile_expression(annotation)?; @@ -3397,7 +3397,9 @@ impl Compiler<'_> { flags: FStringFlags, fstring_elements: &FStringElements, ) -> CompileResult<()> { + let mut element_count = 0; for element in fstring_elements { + element_count += 1; match element { FStringElement::Literal(string) => { if string.value.contains(char::REPLACEMENT_CHARACTER) { @@ -3419,26 +3421,14 @@ impl Compiler<'_> { FStringElement::Expression(fstring_expr) => { let mut conversion = fstring_expr.conversion; - let debug_text_count = match &fstring_expr.debug_text { - None => 0, - Some(DebugText { leading, trailing }) => { - let range = fstring_expr.expression.range(); - let source = self.source_code.get_range(range); - let source = source.to_string(); + if let Some(DebugText { leading, trailing }) = &fstring_expr.debug_text { + let range = fstring_expr.expression.range(); + let source = self.source_code.get_range(range); + let text = [leading, source, trailing].concat(); - self.emit_load_const(ConstantData::Str { - value: leading.to_string().into(), - }); - self.emit_load_const(ConstantData::Str { - value: source.into(), - }); - self.emit_load_const(ConstantData::Str { - value: trailing.to_string().into(), - }); - - 3 - } - }; + self.emit_load_const(ConstantData::Str { value: text.into() }); + element_count += 1; + } match &fstring_expr.format_spec { None => { @@ -3447,7 +3437,9 @@ impl Compiler<'_> { }); // Match CPython behavior: If debug text is present, apply repr conversion. // See: https://github.com/python/cpython/blob/f61afca262d3a0aa6a8a501db0b1936c60858e35/Parser/action_helpers.c#L1456 - if conversion == ConversionFlag::None && debug_text_count > 0 { + if conversion == ConversionFlag::None + && fstring_expr.debug_text.is_some() + { conversion = ConversionFlag::Repr; } } @@ -3458,30 +3450,17 @@ impl Compiler<'_> { self.compile_expression(&fstring_expr.expression)?; - emit!( - self, - Instruction::FormatValue { - conversion: conversion - } - ); - - // concatenate formatted string and debug text (if present) - if debug_text_count > 0 { - emit!( - self, - Instruction::BuildString { - size: debug_text_count + 1 - } - ); - } + let conversion = match conversion { + ConversionFlag::None => bytecode::ConversionFlag::None, + ConversionFlag::Str => bytecode::ConversionFlag::Str, + ConversionFlag::Ascii => bytecode::ConversionFlag::Ascii, + ConversionFlag::Repr => bytecode::ConversionFlag::Repr, + }; + emit!(self, Instruction::FormatValue { conversion }); } } } - let element_count: u32 = fstring_elements - .len() - .try_into() - .expect("BuildString size overflowed"); if element_count == 0 { // ensure to put an empty string on the stack if there aren't any fstring elements self.emit_load_const(ConstantData::Str { diff --git a/compiler/codegen/src/lib.rs b/compiler/codegen/src/lib.rs index d44844543..90e11c5b8 100644 --- a/compiler/codegen/src/lib.rs +++ b/compiler/codegen/src/lib.rs @@ -13,6 +13,7 @@ pub mod error; pub mod ir; mod string_parser; pub mod symboltable; +mod unparse; pub use compile::CompileOpts; use ruff_python_ast::Expr; diff --git a/compiler/codegen/src/unparse.rs b/compiler/codegen/src/unparse.rs new file mode 100644 index 000000000..458ff76fc --- /dev/null +++ b/compiler/codegen/src/unparse.rs @@ -0,0 +1,624 @@ +use ruff_python_ast as ruff; +use ruff_text_size::Ranged; +use rustpython_compiler_source::SourceCode; +use rustpython_literal::escape::{AsciiEscape, UnicodeEscape}; +use std::fmt::{self, Display as _}; + +use ruff::{ + Arguments, BoolOp, Comprehension, ConversionFlag, Expr, Identifier, Operator, Parameter, + ParameterWithDefault, Parameters, +}; + +mod precedence { + macro_rules! precedence { + ($($op:ident,)*) => { + precedence!(@0, $($op,)*); + }; + (@$i:expr, $op1:ident, $($op:ident,)*) => { + pub const $op1: u8 = $i; + precedence!(@$i + 1, $($op,)*); + }; + (@$i:expr,) => {}; + } + precedence!( + TUPLE, TEST, OR, AND, NOT, CMP, // "EXPR" = + BOR, BXOR, BAND, SHIFT, ARITH, TERM, FACTOR, POWER, AWAIT, ATOM, + ); + pub const EXPR: u8 = BOR; +} + +struct Unparser<'a, 'b, 'c> { + f: &'b mut fmt::Formatter<'a>, + source: &'c SourceCode<'c>, +} +impl<'a, 'b, 'c> Unparser<'a, 'b, 'c> { + fn new(f: &'b mut fmt::Formatter<'a>, source: &'c SourceCode<'c>) -> Self { + Unparser { f, source } + } + + fn p(&mut self, s: &str) -> fmt::Result { + self.f.write_str(s) + } + fn p_id(&mut self, s: &Identifier) -> fmt::Result { + self.f.write_str(s.as_str()) + } + fn p_if(&mut self, cond: bool, s: &str) -> fmt::Result { + if cond { + self.f.write_str(s)?; + } + Ok(()) + } + fn p_delim(&mut self, first: &mut bool, s: &str) -> fmt::Result { + self.p_if(!std::mem::take(first), s) + } + fn write_fmt(&mut self, f: fmt::Arguments<'_>) -> fmt::Result { + self.f.write_fmt(f) + } + + fn unparse_expr(&mut self, ast: &Expr, level: u8) -> fmt::Result { + macro_rules! op_prec { + ($op_ty:ident, $x:expr, $enu:path, $($var:ident($op:literal, $prec:ident)),*$(,)?) => { + match $x { + $(<$enu>::$var => (op_prec!(@space $op_ty, $op), precedence::$prec),)* + } + }; + (@space bin, $op:literal) => { + concat!(" ", $op, " ") + }; + (@space un, $op:literal) => { + $op + }; + } + macro_rules! group_if { + ($lvl:expr, $body:block) => {{ + let group = level > $lvl; + self.p_if(group, "(")?; + let ret = $body; + self.p_if(group, ")")?; + ret + }}; + } + match &ast { + Expr::BoolOp(ruff::ExprBoolOp { + op, + values, + range: _range, + }) => { + let (op, prec) = op_prec!(bin, op, BoolOp, And("and", AND), Or("or", OR)); + group_if!(prec, { + let mut first = true; + for val in values { + self.p_delim(&mut first, op)?; + self.unparse_expr(val, prec + 1)?; + } + }) + } + Expr::Named(ruff::ExprNamed { + target, + value, + range: _range, + }) => { + group_if!(precedence::TUPLE, { + self.unparse_expr(target, precedence::ATOM)?; + self.p(" := ")?; + self.unparse_expr(value, precedence::ATOM)?; + }) + } + Expr::BinOp(ruff::ExprBinOp { + left, + op, + right, + range: _range, + }) => { + let right_associative = matches!(op, Operator::Pow); + let (op, prec) = op_prec!( + bin, + op, + Operator, + Add("+", ARITH), + Sub("-", ARITH), + Mult("*", TERM), + MatMult("@", TERM), + Div("/", TERM), + Mod("%", TERM), + Pow("**", POWER), + LShift("<<", SHIFT), + RShift(">>", SHIFT), + BitOr("|", BOR), + BitXor("^", BXOR), + BitAnd("&", BAND), + FloorDiv("//", TERM), + ); + group_if!(prec, { + self.unparse_expr(left, prec + right_associative as u8)?; + self.p(op)?; + self.unparse_expr(right, prec + !right_associative as u8)?; + }) + } + Expr::UnaryOp(ruff::ExprUnaryOp { + op, + operand, + range: _range, + }) => { + let (op, prec) = op_prec!( + un, + op, + ruff::UnaryOp, + Invert("~", FACTOR), + Not("not ", NOT), + UAdd("+", FACTOR), + USub("-", FACTOR) + ); + group_if!(prec, { + self.p(op)?; + self.unparse_expr(operand, prec)?; + }) + } + Expr::Lambda(ruff::ExprLambda { + parameters, + body, + range: _range, + }) => { + group_if!(precedence::TEST, { + if let Some(parameters) = parameters { + self.p("lambda ")?; + self.unparse_arguments(parameters)?; + } else { + self.p("lambda")?; + } + write!(self, ": {}", unparse_expr(body, self.source))?; + }) + } + Expr::If(ruff::ExprIf { + test, + body, + orelse, + range: _range, + }) => { + group_if!(precedence::TEST, { + self.unparse_expr(body, precedence::TEST + 1)?; + self.p(" if ")?; + self.unparse_expr(test, precedence::TEST + 1)?; + self.p(" else ")?; + self.unparse_expr(orelse, precedence::TEST)?; + }) + } + Expr::Dict(ruff::ExprDict { + items, + range: _range, + }) => { + self.p("{")?; + let mut first = true; + for item in items { + self.p_delim(&mut first, ", ")?; + if let Some(k) = &item.key { + write!(self, "{}: ", unparse_expr(k, self.source))?; + } else { + self.p("**")?; + } + self.unparse_expr(&item.value, level)?; + } + self.p("}")?; + } + Expr::Set(ruff::ExprSet { + elts, + range: _range, + }) => { + self.p("{")?; + let mut first = true; + for v in elts { + self.p_delim(&mut first, ", ")?; + self.unparse_expr(v, precedence::TEST)?; + } + self.p("}")?; + } + Expr::ListComp(ruff::ExprListComp { + elt, + generators, + range: _range, + }) => { + self.p("[")?; + self.unparse_expr(elt, precedence::TEST)?; + self.unparse_comp(generators)?; + self.p("]")?; + } + Expr::SetComp(ruff::ExprSetComp { + elt, + generators, + range: _range, + }) => { + self.p("{")?; + self.unparse_expr(elt, precedence::TEST)?; + self.unparse_comp(generators)?; + self.p("}")?; + } + Expr::DictComp(ruff::ExprDictComp { + key, + value, + generators, + range: _range, + }) => { + self.p("{")?; + self.unparse_expr(key, precedence::TEST)?; + self.p(": ")?; + self.unparse_expr(value, precedence::TEST)?; + self.unparse_comp(generators)?; + self.p("}")?; + } + Expr::Generator(ruff::ExprGenerator { + parenthesized: _, + elt, + generators, + range: _range, + }) => { + self.p("(")?; + self.unparse_expr(elt, precedence::TEST)?; + self.unparse_comp(generators)?; + self.p(")")?; + } + Expr::Await(ruff::ExprAwait { + value, + range: _range, + }) => { + group_if!(precedence::AWAIT, { + self.p("await ")?; + self.unparse_expr(value, precedence::ATOM)?; + }) + } + Expr::Yield(ruff::ExprYield { + value, + range: _range, + }) => { + if let Some(value) = value { + write!(self, "(yield {})", unparse_expr(value, self.source))?; + } else { + self.p("(yield)")?; + } + } + Expr::YieldFrom(ruff::ExprYieldFrom { + value, + range: _range, + }) => { + write!(self, "(yield from {})", unparse_expr(value, self.source))?; + } + Expr::Compare(ruff::ExprCompare { + left, + ops, + comparators, + range: _range, + }) => { + group_if!(precedence::CMP, { + let new_lvl = precedence::CMP + 1; + self.unparse_expr(left, new_lvl)?; + for (op, cmp) in ops.iter().zip(comparators) { + self.p(" ")?; + self.p(op.as_str())?; + self.p(" ")?; + self.unparse_expr(cmp, new_lvl)?; + } + }) + } + Expr::Call(ruff::ExprCall { + func, + arguments: Arguments { args, keywords, .. }, + range: _range, + }) => { + self.unparse_expr(func, precedence::ATOM)?; + self.p("(")?; + if let ( + [ + Expr::Generator(ruff::ExprGenerator { + elt, + generators, + range: _range, + .. + }), + ], + [], + ) = (&**args, &**keywords) + { + // make sure a single genexpr doesn't get double parens + self.unparse_expr(elt, precedence::TEST)?; + self.unparse_comp(generators)?; + } else { + let mut first = true; + for arg in args { + self.p_delim(&mut first, ", ")?; + self.unparse_expr(arg, precedence::TEST)?; + } + for kw in keywords { + self.p_delim(&mut first, ", ")?; + if let Some(arg) = &kw.arg { + self.p_id(arg)?; + self.p("=")?; + } else { + self.p("**")?; + } + self.unparse_expr(&kw.value, precedence::TEST)?; + } + } + self.p(")")?; + } + Expr::FString(ruff::ExprFString { value, .. }) => self.unparse_fstring(value)?, + Expr::StringLiteral(ruff::ExprStringLiteral { value, .. }) => { + if value.is_unicode() { + self.p("u")? + } + UnicodeEscape::new_repr(value.to_str().as_ref()) + .str_repr() + .fmt(self.f)? + } + Expr::BytesLiteral(ruff::ExprBytesLiteral { value, .. }) => { + AsciiEscape::new_repr(&value.bytes().collect::>()) + .bytes_repr() + .fmt(self.f)? + } + Expr::NumberLiteral(ruff::ExprNumberLiteral { value, .. }) => { + const { assert!(f64::MAX_10_EXP == 308) }; + let inf_str = "1e309"; + match value { + ruff::Number::Int(int) => int.fmt(self.f)?, + &ruff::Number::Float(fp) => { + if fp.is_infinite() { + self.p(inf_str)? + } else { + self.p(&rustpython_literal::float::to_string(fp))? + } + } + &ruff::Number::Complex { real, imag } => self + .p(&rustpython_literal::float::complex_to_string(real, imag) + .replace("inf", inf_str))?, + } + } + Expr::BooleanLiteral(ruff::ExprBooleanLiteral { value, .. }) => { + self.p(if *value { "True" } else { "False" })? + } + Expr::NoneLiteral(ruff::ExprNoneLiteral { .. }) => self.p("None")?, + Expr::EllipsisLiteral(ruff::ExprEllipsisLiteral { .. }) => self.p("...")?, + Expr::Attribute(ruff::ExprAttribute { value, attr, .. }) => { + self.unparse_expr(value, precedence::ATOM)?; + let period = if let Expr::NumberLiteral(ruff::ExprNumberLiteral { + value: ruff::Number::Int(_), + .. + }) = value.as_ref() + { + " ." + } else { + "." + }; + self.p(period)?; + self.p_id(attr)?; + } + Expr::Subscript(ruff::ExprSubscript { value, slice, .. }) => { + self.unparse_expr(value, precedence::ATOM)?; + let lvl = precedence::TUPLE; + self.p("[")?; + self.unparse_expr(slice, lvl)?; + self.p("]")?; + } + Expr::Starred(ruff::ExprStarred { value, .. }) => { + self.p("*")?; + self.unparse_expr(value, precedence::EXPR)?; + } + Expr::Name(ruff::ExprName { id, .. }) => self.p(id.as_str())?, + Expr::List(ruff::ExprList { elts, .. }) => { + self.p("[")?; + let mut first = true; + for elt in elts { + self.p_delim(&mut first, ", ")?; + self.unparse_expr(elt, precedence::TEST)?; + } + self.p("]")?; + } + Expr::Tuple(ruff::ExprTuple { elts, .. }) => { + if elts.is_empty() { + self.p("()")?; + } else { + group_if!(precedence::TUPLE, { + let mut first = true; + for elt in elts { + self.p_delim(&mut first, ", ")?; + self.unparse_expr(elt, precedence::TEST)?; + } + self.p_if(elts.len() == 1, ",")?; + }) + } + } + Expr::Slice(ruff::ExprSlice { + lower, + upper, + step, + range: _range, + }) => { + if let Some(lower) = lower { + self.unparse_expr(lower, precedence::TEST)?; + } + self.p(":")?; + if let Some(upper) = upper { + self.unparse_expr(upper, precedence::TEST)?; + } + if let Some(step) = step { + self.p(":")?; + self.unparse_expr(step, precedence::TEST)?; + } + } + Expr::IpyEscapeCommand(_) => {} + } + Ok(()) + } + + fn unparse_arguments(&mut self, args: &Parameters) -> fmt::Result { + let mut first = true; + for (i, arg) in args.posonlyargs.iter().chain(&args.args).enumerate() { + self.p_delim(&mut first, ", ")?; + self.unparse_function_arg(arg)?; + self.p_if(i + 1 == args.posonlyargs.len(), ", /")?; + } + if args.vararg.is_some() || !args.kwonlyargs.is_empty() { + self.p_delim(&mut first, ", ")?; + self.p("*")?; + } + if let Some(vararg) = &args.vararg { + self.unparse_arg(vararg)?; + } + for kwarg in args.kwonlyargs.iter() { + self.p_delim(&mut first, ", ")?; + self.unparse_function_arg(kwarg)?; + } + if let Some(kwarg) = &args.kwarg { + self.p_delim(&mut first, ", ")?; + self.p("**")?; + self.unparse_arg(kwarg)?; + } + Ok(()) + } + fn unparse_function_arg(&mut self, arg: &ParameterWithDefault) -> fmt::Result { + self.unparse_arg(&arg.parameter)?; + if let Some(default) = &arg.default { + write!(self, "={}", unparse_expr(default, self.source))?; + } + Ok(()) + } + + fn unparse_arg(&mut self, arg: &Parameter) -> fmt::Result { + self.p_id(&arg.name)?; + if let Some(ann) = &arg.annotation { + write!(self, ": {}", unparse_expr(ann, self.source))?; + } + Ok(()) + } + + fn unparse_comp(&mut self, generators: &[Comprehension]) -> fmt::Result { + for comp in generators { + self.p(if comp.is_async { + " async for " + } else { + " for " + })?; + self.unparse_expr(&comp.target, precedence::TUPLE)?; + self.p(" in ")?; + self.unparse_expr(&comp.iter, precedence::TEST + 1)?; + for cond in &comp.ifs { + self.p(" if ")?; + self.unparse_expr(cond, precedence::TEST + 1)?; + } + } + Ok(()) + } + + fn unparse_fstring_body(&mut self, elements: &[ruff::FStringElement]) -> fmt::Result { + for elem in elements { + self.unparse_fstring_elem(elem)?; + } + Ok(()) + } + + fn unparse_formatted( + &mut self, + val: &Expr, + debug_text: Option<&ruff::DebugText>, + conversion: ConversionFlag, + spec: Option<&ruff::FStringFormatSpec>, + ) -> fmt::Result { + let buffered = to_string_fmt(|f| { + Unparser::new(f, self.source).unparse_expr(val, precedence::TEST + 1) + }); + if let Some(ruff::DebugText { leading, trailing }) = debug_text { + self.p(leading)?; + self.p(self.source.get_range(val.range()))?; + self.p(trailing)?; + } + let brace = if buffered.starts_with('{') { + // put a space to avoid escaping the bracket + "{ " + } else { + "{" + }; + self.p(brace)?; + self.p(&buffered)?; + drop(buffered); + + if conversion != ConversionFlag::None { + self.p("!")?; + let buf = &[conversion as u8]; + let c = std::str::from_utf8(buf).unwrap(); + self.p(c)?; + } + + if let Some(spec) = spec { + self.p(":")?; + self.unparse_fstring_body(&spec.elements)?; + } + + self.p("}")?; + + Ok(()) + } + + fn unparse_fstring_elem(&mut self, elem: &ruff::FStringElement) -> fmt::Result { + match elem { + ruff::FStringElement::Expression(ruff::FStringExpressionElement { + expression, + debug_text, + conversion, + format_spec, + .. + }) => self.unparse_formatted( + expression, + debug_text.as_ref(), + *conversion, + format_spec.as_deref(), + ), + ruff::FStringElement::Literal(ruff::FStringLiteralElement { value, .. }) => { + self.unparse_fstring_str(value) + } + } + } + + fn unparse_fstring_str(&mut self, s: &str) -> fmt::Result { + let s = s.replace('{', "{{").replace('}', "}}"); + self.p(&s) + } + + fn unparse_fstring(&mut self, value: &ruff::FStringValue) -> fmt::Result { + self.p("f")?; + let body = to_string_fmt(|f| { + value.iter().try_for_each(|part| match part { + ruff::FStringPart::Literal(lit) => f.write_str(lit), + ruff::FStringPart::FString(ruff::FString { elements, .. }) => { + Unparser::new(f, self.source).unparse_fstring_body(elements) + } + }) + }); + // .unparse_fstring_body(elements)); + UnicodeEscape::new_repr(body.as_str().as_ref()) + .str_repr() + .write(self.f) + } +} + +pub struct UnparseExpr<'a> { + expr: &'a Expr, + source: &'a SourceCode<'a>, +} + +pub fn unparse_expr<'a>(expr: &'a Expr, source: &'a SourceCode<'a>) -> UnparseExpr<'a> { + UnparseExpr { expr, source } +} + +impl fmt::Display for UnparseExpr<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Unparser::new(f, self.source).unparse_expr(self.expr, precedence::TEST) + } +} + +fn to_string_fmt(f: impl FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result) -> String { + use std::cell::Cell; + struct Fmt(Cell>); + impl) -> fmt::Result> fmt::Display for Fmt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.take().unwrap()(f) + } + } + Fmt(Cell::new(Some(f))).to_string() +} diff --git a/compiler/core/Cargo.toml b/compiler/core/Cargo.toml index e79d93768..837f9e486 100644 --- a/compiler/core/Cargo.toml +++ b/compiler/core/Cargo.toml @@ -10,8 +10,6 @@ license.workspace = true [dependencies] # rustpython-parser-core = { workspace = true, features=["location"] } -ruff_python_ast = { workspace = true } -ruff_python_parser = { workspace = true } ruff_source_file = { workspace = true } rustpython-wtf8 = { workspace = true } diff --git a/compiler/core/src/bytecode.rs b/compiler/core/src/bytecode.rs index 2745a5e9f..2e8ff2901 100644 --- a/compiler/core/src/bytecode.rs +++ b/compiler/core/src/bytecode.rs @@ -5,14 +5,24 @@ use bitflags::bitflags; use itertools::Itertools; use malachite_bigint::BigInt; use num_complex::Complex64; -pub use ruff_python_ast::ConversionFlag; -// use rustpython_parser_core::source_code::{OneIndexed, SourceLocation}; use ruff_source_file::{OneIndexed, SourceLocation}; use rustpython_wtf8::{Wtf8, Wtf8Buf}; use std::marker::PhantomData; use std::{collections::BTreeSet, fmt, hash, mem}; -// pub use rustpython_parser_core::ConversionFlag; +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[repr(i8)] +#[allow(clippy::cast_possible_wrap)] +pub enum ConversionFlag { + /// No conversion + None = -1, // CPython uses -1 + /// Converts by calling `str()`. + Str = b's' as i8, + /// Converts by calling `ascii()`. + Ascii = b'a' as i8, + /// Converts by calling `repr()`. + Repr = b'r' as i8, +} pub trait Constant: Sized { type Name: AsRef; diff --git a/compiler/core/src/mode.rs b/compiler/core/src/mode.rs index 13cea42b1..0c14c3c49 100644 --- a/compiler/core/src/mode.rs +++ b/compiler/core/src/mode.rs @@ -1,10 +1,9 @@ -pub use ruff_python_parser::ModeParseError; - #[derive(Clone, Copy)] pub enum Mode { Exec, Eval, Single, + /// Returns the value of the last statement in the statement list. BlockExpr, } @@ -22,14 +21,12 @@ impl std::str::FromStr for Mode { } } -impl From for ruff_python_parser::Mode { - fn from(mode: Mode) -> Self { - match mode { - Mode::Exec => Self::Module, - Mode::Eval => Self::Expression, - // TODO: Improve ruff API - // ruff does not have an interactive mode - Mode::Single | Mode::BlockExpr => Self::Ipython, - } +/// Returned when a given mode is not valid. +#[derive(Debug)] +pub struct ModeParseError; + +impl std::fmt::Display for ModeParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, r#"mode must be "exec", "eval", or "single""#) } } diff --git a/compiler/literal/src/float.rs b/compiler/literal/src/float.rs index a58a3f48a..e05a105fd 100644 --- a/compiler/literal/src/float.rs +++ b/compiler/literal/src/float.rs @@ -223,6 +223,39 @@ pub fn to_string(value: f64) -> String { } } +pub fn complex_to_string(re: f64, im: f64) -> String { + // integer => drop ., fractional => float_ops + let mut im_part = if im.fract() == 0.0 { + im.to_string() + } else { + to_string(im) + }; + im_part.push('j'); + + // positive empty => return im_part, integer => drop ., fractional => float_ops + let re_part = if re == 0.0 { + if re.is_sign_positive() { + return im_part; + } else { + re.to_string() + } + } else if re.fract() == 0.0 { + re.to_string() + } else { + to_string(re) + }; + let mut result = + String::with_capacity(re_part.len() + im_part.len() + 2 + im.is_sign_positive() as usize); + result.push('('); + result.push_str(&re_part); + if im.is_sign_positive() || im.is_nan() { + result.push('+'); + } + result.push_str(&im_part); + result.push(')'); + result +} + pub fn from_hex(s: &str) -> Option { if let Ok(f) = hexf_parse::parse_hexf64(s, false) { return Some(f); diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 0ebfbbf41..390a2d566 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -119,7 +119,14 @@ fn _compile( mode: Mode, opts: CompileOpts, ) -> Result { - let parsed = parser::parse(source_code.text, parser::Mode::from(mode).into()) + let parser_mode = match mode { + Mode::Exec => parser::Mode::Module, + Mode::Eval => parser::Mode::Expression, + // ruff does not have an interactive mode, which is fine, + // since these are only different in terms of compilation + Mode::Single | Mode::BlockExpr => parser::Mode::Module, + }; + let parsed = parser::parse(source_code.text, parser_mode.into()) .map_err(|err| CompileError::from_ruff_parse_error(err, &source_code))?; let ast = parsed.into_syntax(); compile::compile_top(ast, source_code, mode, opts).map_err(|e| e.into()) diff --git a/vm/src/builtins/complex.rs b/vm/src/builtins/complex.rs index 01dd65f51..a3a6d4d68 100644 --- a/vm/src/builtins/complex.rs +++ b/vm/src/builtins/complex.rs @@ -494,37 +494,7 @@ impl Representable for PyComplex { // TODO: when you fix this, move it to rustpython_common::complex::repr and update // ast/src/unparse.rs + impl Display for Constant in ast/src/constant.rs let Complex64 { re, im } = zelf.value; - // integer => drop ., fractional => float_ops - let mut im_part = if im.fract() == 0.0 { - im.to_string() - } else { - crate::literal::float::to_string(im) - }; - im_part.push('j'); - - // positive empty => return im_part, integer => drop ., fractional => float_ops - let re_part = if re == 0.0 { - if re.is_sign_positive() { - return Ok(im_part); - } else { - re.to_string() - } - } else if re.fract() == 0.0 { - re.to_string() - } else { - crate::literal::float::to_string(re) - }; - let mut result = String::with_capacity( - re_part.len() + im_part.len() + 2 + im.is_sign_positive() as usize, - ); - result.push('('); - result.push_str(&re_part); - if im.is_sign_positive() || im.is_nan() { - result.push('+'); - } - result.push_str(&im_part); - result.push(')'); - Ok(result) + Ok(rustpython_literal::float::complex_to_string(re, im)) } } diff --git a/vm/src/stdlib/ast/other.rs b/vm/src/stdlib/ast/other.rs index 2b9d292c1..f7d698133 100644 --- a/vm/src/stdlib/ast/other.rs +++ b/vm/src/stdlib/ast/other.rs @@ -1,5 +1,6 @@ use super::*; use num_traits::ToPrimitive; +use rustpython_compiler_core::bytecode; impl Node for ruff::ConversionFlag { fn ast_to_object(self, vm: &VirtualMachine, _source_code: &SourceCodeOwned) -> PyObjectRef { @@ -13,7 +14,13 @@ impl Node for ruff::ConversionFlag { ) -> PyResult { i32::try_from_object(vm, object)? .to_u32() - .and_then(ruff::ConversionFlag::from_op_arg) + .and_then(bytecode::ConversionFlag::from_op_arg) + .map(|flag| match flag { + bytecode::ConversionFlag::None => Self::None, + bytecode::ConversionFlag::Str => Self::Str, + bytecode::ConversionFlag::Ascii => Self::Ascii, + bytecode::ConversionFlag::Repr => Self::Repr, + }) .ok_or_else(|| vm.new_value_error("invalid conversion flag".to_owned())) } }