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()),
}
}