fn print_expr()

in hphp/hack/src/hackc/print_expr/print.rs [199:648]


fn print_expr(
    ctx: &Context<'_>,
    w: &mut dyn Write,
    env: &ExprEnv<'_, '_>,
    ast::Expr(_, _, expr): &ast::Expr,
) -> Result<()> {
    fn adjust_id<'a>(env: &ExprEnv<'_, '_>, id: &'a str) -> Cow<'a, str> {
        let s: Cow<'a, str> = match env.codegen_env {
            Some(env) => {
                if env.is_namespaced
                    && id
                        .as_bytes()
                        .iter()
                        .rposition(|c| *c == b'\\')
                        .map_or(true, |i| i < 1)
                {
                    strip_global_ns(id).into()
                } else {
                    add_ns(id)
                }
            }
            _ => id.into(),
        };
        escaper::escape(s)
    }
    fn print_expr_id<'a>(w: &mut dyn Write, env: &ExprEnv<'_, '_>, s: &'a str) -> Result<()> {
        w.write_all(adjust_id(env, s).as_bytes())
    }
    fn fmt_class_name<'a>(is_class_constant: bool, id: Cow<'a, str>) -> Cow<'a, str> {
        let cn: Cow<'a, str> = if is_xhp(strip_global_ns(&id)) {
            escaper::escape(strip_global_ns(&mangle(id.into())))
                .to_string()
                .into()
        } else {
            escaper::escape(strip_global_ns(&id)).to_string().into()
        };
        if is_class_constant {
            format!("\\\\{}", cn).into()
        } else {
            cn
        }
    }
    fn get_class_name_from_id<'e>(
        ctx: &Context<'_>,
        env: Option<&'e HhasBodyEnv<'_>>,
        should_format: bool,
        is_class_constant: bool,
        id: &'e str,
    ) -> Cow<'e, str> {
        if id == classes::SELF || id == classes::PARENT || id == classes::STATIC {
            let name = ctx.special_class_resolver.resolve(env, id);
            return fmt_class_name(is_class_constant, name);
        }
        fn get<'a>(should_format: bool, is_class_constant: bool, id: &'a str) -> Cow<'a, str> {
            if should_format {
                fmt_class_name(is_class_constant, id.into())
            } else {
                id.into()
            }
        }

        if env.is_some() {
            let alloc = bumpalo::Bump::new();
            let class_id = ClassType::from_ast_name_and_mangle(&alloc, id);
            let id = class_id.unsafe_as_str();
            get(should_format, is_class_constant, id)
                .into_owned()
                .into()
        } else {
            get(should_format, is_class_constant, id)
        }
    }
    fn handle_possible_colon_colon_class_expr(
        ctx: &Context<'_>,
        w: &mut dyn Write,
        env: &ExprEnv<'_, '_>,
        is_array_get: bool,
        e_: &ast::Expr_,
    ) -> Result<Option<()>> {
        match e_.as_class_const() {
            Some((
                ast::ClassId(_, _, ast::ClassId_::CIexpr(ast::Expr(_, _, ast::Expr_::Id(id)))),
                (_, s2),
            )) if is_class(&s2) && !(is_self(&id.1) || is_parent(&id.1) || is_static(&id.1)) => {
                Ok(Some({
                    let s1 = get_class_name_from_id(ctx, env.codegen_env, false, false, &id.1);
                    if is_array_get {
                        print_expr_id(w, env, s1.as_ref())?
                    } else {
                        print_expr_string(w, s1.as_bytes())?
                    }
                }))
            }
            _ => Ok(None),
        }
    }
    use ast::Expr_ as E_;
    match expr {
        E_::Id(id) => print_expr_id(w, env, id.1.as_ref()),
        E_::Lvar(lid) => w.write_all(escaper::escape(&(lid.1).1).as_bytes()),
        E_::Float(f) => {
            if f.contains('E') || f.contains('e') {
                let s = format!(
                    "{:.1E}",
                    f.parse::<f64>()
                        .map_err(|_| Error::fail(format!("ParseFloatError: {}", f)))?
                )
                // to_uppercase() here because s might be "inf" or "nan"
                .to_uppercase();

                lazy_static! {
                    static ref UNSIGNED_EXP: Regex =
                        Regex::new(r"(?P<first>E)(?P<second>\d+)").unwrap();
                    static ref SIGNED_SINGLE_DIGIT_EXP: Regex =
                        Regex::new(r"(?P<first>E[+-])(?P<second>\d$)").unwrap();
                }
                // turn mEn into mE+n
                let s = UNSIGNED_EXP.replace(&s, "${first}+${second}");
                // turn mE+n or mE-n into mE+0n or mE-0n (where n is a single digit)
                let s = SIGNED_SINGLE_DIGIT_EXP.replace(&s, "${first}0${second}");
                w.write_all(s.as_bytes())
            } else {
                w.write_all(f.as_bytes())
            }
        }
        E_::Int(i) => print_expr_int(w, i.as_ref()),
        E_::String(s) => print_expr_string(w, s),
        E_::Null => w.write_all(b"NULL"),
        E_::True => w.write_all(b"true"),
        E_::False => w.write_all(b"false"),
        // For arrays and collections, we are making a conscious decision to not
        // match HHMV has HHVM's emitter has inconsistencies in the pretty printer
        // https://fburl.com/tzom2qoe
        E_::Collection(c) if (c.0).1 == "vec" || (c.0).1 == "dict" || (c.0).1 == "keyset" => {
            w.write_all((c.0).1.as_bytes())?;
            write::square(w, |w| print_afields(ctx, w, env, &c.2))
        }
        E_::Collection(c) => {
            let name = strip_ns((c.0).1.as_str());
            let name = types::fix_casing(name);
            match name {
                "Set" | "Pair" | "Vector" | "Map" | "ImmSet" | "ImmVector" | "ImmMap" => {
                    w.write_all(b"HH\\\\")?;
                    w.write_all(name.as_bytes())?;
                    write::wrap_by_(w, " {", "}", |w| {
                        Ok(if !c.2.is_empty() {
                            w.write_all(b" ")?;
                            print_afields(ctx, w, env, &c.2)?;
                            w.write_all(b" ")?;
                        })
                    })
                }
                _ => Err(
                    Error::fail(format!("Default value for an unknow collection - {}", name))
                        .into(),
                ),
            }
        }
        E_::Shape(fl) => print_expr_darray(ctx, w, env, print_shape_field_name, fl),
        E_::Binop(x) => {
            let (bop, e1, e2) = &**x;
            print_expr(ctx, w, env, e1)?;
            w.write_all(b" ")?;
            print_bop(w, bop)?;
            w.write_all(b" ")?;
            print_expr(ctx, w, env, e2)
        }
        E_::Call(c) => {
            let (e, _, es, unpacked_element) = &**c;
            match e.as_id() {
                Some(ast_defs::Id(_, call_id)) => {
                    w.write_all(lstrip(adjust_id(env, call_id).as_ref(), "\\\\").as_bytes())?
                }
                None => {
                    let buf = print_expr_to_string(ctx, env, e)?;
                    w.write_all(lstrip_bslice(&buf, br"\\"))?
                }
            };
            write::paren(w, |w| {
                write::concat_by(w, ", ", &es, |w, (pk, e)| match pk {
                    ParamKind::Pnormal => print_expr(ctx, w, env, e),
                    ParamKind::Pinout(_) => Err(Error::fail("illegal default value").into()),
                })?;
                match unpacked_element {
                    None => Ok(()),
                    Some(e) => {
                        if !es.is_empty() {
                            w.write_all(b", ")?;
                        }
                        // TODO: Should probably have ... also but we are not doing that in ocaml
                        print_expr(ctx, w, env, e)
                    }
                }
            })
        }
        E_::New(x) => {
            let (cid, _, es, unpacked_element, _) = &**x;
            match cid.2.as_ciexpr() {
                Some(ci_expr) => {
                    w.write_all(b"new ")?;
                    match ci_expr.2.as_id() {
                        Some(ast_defs::Id(_, cname)) => w.write_all(
                            lstrip(
                                &adjust_id(
                                    env,
                                    ClassType::from_ast_name_and_mangle(
                                        &bumpalo::Bump::new(),
                                        cname,
                                    )
                                    .unsafe_as_str(),
                                ),
                                "\\\\",
                            )
                            .as_bytes(),
                        )?,
                        None => {
                            let buf = print_expr_to_string(ctx, env, ci_expr)?;
                            w.write_all(lstrip_bslice(&buf, br"\\"))?
                        }
                    }
                    write::paren(w, |w| {
                        write::concat_by(w, ", ", es, |w, e| print_expr(ctx, w, env, e))?;
                        match unpacked_element {
                            None => Ok(()),
                            Some(e) => {
                                w.write_all(b", ")?;
                                print_expr(ctx, w, env, e)
                            }
                        }
                    })
                }
                None => {
                    match cid.2.as_ci() {
                        Some(id) => {
                            // Xml exprs rewritten as New exprs come
                            // through here.
                            print_xml(ctx, w, env, &id.1, es)
                        }
                        None => Err(Error::NotImpl(format!("{}:{}", file!(), line!())).into()),
                    }
                }
            }
        }
        E_::ClassGet(cg) => {
            match &(cg.0).2 {
                ast::ClassId_::CIexpr(e) => match e.as_id() {
                    Some(id) => w.write_all(
                        get_class_name_from_id(
                            ctx,
                            env.codegen_env,
                            true,  /* should_format */
                            false, /* is_class_constant */
                            &id.1,
                        )
                        .as_bytes(),
                    )?,
                    _ => print_expr(ctx, w, env, e)?,
                },
                _ => return Err(Error::fail("TODO Unimplemented unexpected non-CIexpr").into()),
            }
            w.write_all(b"::")?;
            match &cg.1 {
                ast::ClassGetExpr::CGstring((_, litstr)) => {
                    w.write_all(escaper::escape(litstr).as_bytes())
                }
                ast::ClassGetExpr::CGexpr(e) => print_expr(ctx, w, env, e),
            }
        }
        E_::ClassConst(cc) => {
            if let Some(e1) = (cc.0).2.as_ciexpr() {
                handle_possible_colon_colon_class_expr(ctx, w, env, false, expr)?.map_or_else(
                    || {
                        let s2 = &(cc.1).1;
                        match e1.2.as_id() {
                            Some(ast_defs::Id(_, s1)) => {
                                let s1 =
                                    get_class_name_from_id(ctx, env.codegen_env, true, true, s1);
                                write::concat_str_by(w, "::", [&s1.into(), s2])
                            }
                            _ => {
                                print_expr(ctx, w, env, e1)?;
                                w.write_all(b"::")?;
                                w.write_all(s2.as_bytes())
                            }
                        }
                    },
                    Ok,
                )
            } else {
                Err(Error::fail("TODO: Only expected CIexpr in class_const").into())
            }
        }
        E_::Unop(u) => match u.0 {
            ast::Uop::Upincr => {
                print_expr(ctx, w, env, &u.1)?;
                w.write_all(b"++")
            }
            ast::Uop::Updecr => {
                print_expr(ctx, w, env, &u.1)?;
                w.write_all(b"--")
            }
            _ => {
                print_uop(w, u.0)?;
                print_expr(ctx, w, env, &u.1)
            }
        },
        E_::ObjGet(og) => {
            print_expr(ctx, w, env, &og.0)?;
            w.write_all(match og.2 {
                ast::OgNullFlavor::OGNullthrows => b"->",
                ast::OgNullFlavor::OGNullsafe => b"?->",
            })?;
            print_expr(ctx, w, env, &og.1)
        }
        E_::Clone(e) => {
            w.write_all(b"clone ")?;
            print_expr(ctx, w, env, e)
        }
        E_::ArrayGet(ag) => {
            print_expr(ctx, w, env, &ag.0)?;
            write::square(w, |w| {
                write::option(w, &ag.1, |w, e: &ast::Expr| {
                    handle_possible_colon_colon_class_expr(ctx, w, env, true, &e.2)
                        .transpose()
                        .unwrap_or_else(|| print_expr(ctx, w, env, e))
                })
            })
        }
        E_::String2(ss) => write::concat_by(w, " . ", ss, |w, s| print_expr(ctx, w, env, s)),
        E_::PrefixedString(s) => {
            w.write_all(s.0.as_bytes())?;
            w.write_all(b" . ")?;
            print_expr(ctx, w, env, &s.1)
        }
        E_::Eif(eif) => {
            print_expr(ctx, w, env, &eif.0)?;
            w.write_all(b" ? ")?;
            write::option(w, &eif.1, |w, etrue| print_expr(ctx, w, env, etrue))?;
            w.write_all(b" : ")?;
            print_expr(ctx, w, env, &eif.2)
        }
        E_::Cast(c) => {
            write::paren(w, |w| print_hint(w, false, &c.0))?;
            print_expr(ctx, w, env, &c.1)
        }
        E_::Pipe(p) => {
            print_expr(ctx, w, env, &p.1)?;
            w.write_all(b" |> ")?;
            print_expr(ctx, w, env, &p.2)
        }
        E_::Is(i) => {
            print_expr(ctx, w, env, &i.0)?;
            w.write_all(b" is ")?;
            print_hint(w, true, &i.1)
        }
        E_::As(a) => {
            print_expr(ctx, w, env, &a.0)?;
            w.write_all(if a.2 { b" ?as " } else { b" as " })?;
            print_hint(w, true, &a.1)
        }
        E_::Varray(va) => print_expr_varray(ctx, w, env, &va.1),
        E_::Darray(da) => print_expr_darray(ctx, w, env, print_expr, &da.1),
        E_::Tuple(t) => write::wrap_by_(w, "varray[", "]", |w| {
            // A tuple is represented by a varray when using reflection.
            write::concat_by(w, ", ", t, |w, i| print_expr(ctx, w, env, i))
        }),
        E_::List(l) => write::wrap_by_(w, "list(", ")", |w| {
            write::concat_by(w, ", ", l, |w, i| print_expr(ctx, w, env, i))
        }),
        E_::Yield(y) => {
            w.write_all(b"yield ")?;
            print_afield(ctx, w, env, y)
        }
        E_::Await(e) => {
            w.write_all(b"await ")?;
            print_expr(ctx, w, env, e)
        }
        E_::Import(i) => {
            print_import_flavor(w, &i.0)?;
            w.write_all(b" ")?;
            print_expr(ctx, w, env, &i.1)
        }
        E_::Xml(_) => {
            Err(Error::fail("expected Xml to be converted to New during rewriting").into())
        }
        E_::Efun(f) => print_efun(ctx, w, env, &f.0, &f.1),
        E_::FunctionPointer(fp) => {
            let (fp_id, targs) = &**fp;
            match fp_id {
                ast::FunctionPtrId::FPId(ast::Id(_, sid)) => {
                    w.write_all(lstrip(adjust_id(env, sid).as_ref(), "\\\\").as_bytes())?
                }
                ast::FunctionPtrId::FPClassConst(ast::ClassId(_, _, class_id), (_, meth_name)) => {
                    match class_id {
                        ast::ClassId_::CIexpr(e) => match e.as_id() {
                            Some(id) => w.write_all(
                                get_class_name_from_id(
                                    ctx,
                                    env.codegen_env,
                                    true,  /* should_format */
                                    false, /* is_class_constant */
                                    &id.1,
                                )
                                .as_bytes(),
                            )?,
                            _ => print_expr(ctx, w, env, e)?,
                        },
                        _ => {
                            return Err(Error::fail(
                                "TODO Unimplemented unexpected non-CIexpr in function pointer",
                            )
                            .into());
                        }
                    }
                    w.write_all(b"::")?;
                    w.write_all(meth_name.as_bytes())?
                }
            };
            write::wrap_by_(w, "<", ">", |w| {
                write::concat_by(w, ", ", targs, |w, _targ| w.write_all(b"_"))
            })
        }
        E_::Omitted => Ok(()),
        E_::Lfun(lfun) => {
            if ctx.dump_lambdas {
                let fun_ = &lfun.0;
                write::paren(w, |w| {
                    write::paren(w, |w| {
                        write::concat_by(w, ", ", &fun_.params, |w, param| {
                            print_fparam(ctx, w, env, param)
                        })
                    })?;
                    w.write_all(b" ==> ")?;
                    print_block_(ctx, w, env, &fun_.body.fb_ast)
                })
            } else {
                Err(Error::fail(
                    "expected Lfun to be converted to Efun during closure conversion print_expr",
                )
                .into())
            }
        }
        E_::ETSplice(splice) => {
            w.write_all(b"${")?;
            print_expr(ctx, w, env, splice)?;
            w.write_all(b"}")
        }
        _ => Err(Error::fail(format!("TODO Unimplemented: Cannot print: {:?}", expr)).into()),
    }
}