/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::rc::Rc;

use super::completion_record::*;
use super::environment_record::*;
use super::function::*;
use super::jsobject::*;
use super::jsvalue::*;
use super::reference::*;
use super::runtime::*;

#[derive(Debug)]
pub struct LexicalEnvironment {
    env_record: EnvRecordAddr,
    outer: Option<LexicalEnvAddr>,
}

impl LexicalEnvironment {
    pub fn new(env_record: EnvRecordAddr, outer: Option<LexicalEnvAddr>) -> Self {
        LexicalEnvironment { env_record, outer }
    }
}

impl LexicalEnvironment {
    /// https://262.ecma-international.org/11.0/#sec-getidentifierreference
    pub fn get_identifier_reference(
        run: &mut Runtime,
        lex: Option<LexicalEnvAddr>,
        name: &Rc<JSString>,
        strict: bool,
    ) -> CompletionRecord {
        match lex {
            None => {
                // 1. If lex is the value null, then
                // a. Return a value of type Reference whose base value component is undefined,
                // whose referenced name component is name,
                // and whose strict reference flag is strict.
                Ok(NormalCompletion::Reference(Reference::value(
                    JSValue::Undefined,
                    JSValue::String(name.clone()),
                    strict,
                )))
            }
            Some(lex) => {
                // 2. Let envRec be lex's EnvironmentRecord.
                let env_rec = run.lexical_env(lex).env_record;
                // 3. Let exists be ? envRec.HasBinding(name).
                let exists = (run.env_record(env_rec).methods.has_binding)(run, env_rec, name)?;
                if let NormalCompletion::Value(JSValue::Boolean(true)) = exists {
                    // 4. If exists is true, then
                    // a. Return a value of type Reference whose base value component is envRec,
                    // whose referenced name component is name,
                    // and whose strict reference flag is strict.
                    Ok(NormalCompletion::Reference(Reference::env_rec(
                        env_rec,
                        JSValue::String(name.clone()),
                        strict,
                    )))
                } else {
                    // 5. Else,
                    // a. Let outer be the value of lex's outer environment reference.
                    let outer = run.lexical_env(lex).outer;
                    // b. Return ? GetIdentifierReference(outer, name, strict).
                    Ok(Self::get_identifier_reference(run, outer, name, strict)?)
                }
            }
        }
    }

    /// https://262.ecma-international.org/11.0/#sec-newdeclarativeenvironment
    pub fn new_declarative_environment(
        run: &mut Runtime,
        e: Option<LexicalEnvAddr>,
    ) -> LexicalEnvAddr {
        // 1. Let env be a new Lexical Environment.
        // 2. Let envRec be a new declarative Environment Record containing no bindings.
        // 3. Set env's EnvironmentRecord to envRec.
        // 4. Set the outer lexical environment reference of env to E.
        // 5. Return env.
        let env_rec = run.new_env_record(EnvironmentRecordKind::Declarative);
        run.new_lexical_env(env_rec, e)
    }

    /// https://262.ecma-international.org/11.0/#sec-newobjectenvironment
    pub fn new_object_environment(
        run: &mut Runtime,
        o: ObjectAddr,
        e: Option<LexicalEnvAddr>,
    ) -> LexicalEnvAddr {
        // 1. Let env be a new Lexical Environment.
        // 2. Let envRec be a new object Environment Record containing O as the binding object.
        let env_rec = run.new_env_record(EnvironmentRecordKind::Declarative);
        // 3. Set env's EnvironmentRecord to envRec.
        run.env_record_mut(env_rec).obj.binding_object = Some(o);
        // 4. Set the outer lexical environment reference of env to E.
        // 5. Return env.
        run.new_lexical_env(env_rec, e)
    }

    /// https://262.ecma-international.org/11.0/#sec-newfunctionenvironment
    pub fn new_function_environment(
        run: &mut Runtime,
        f: &JSValue,
        new_target: &JSValue,
    ) -> LexicalEnvAddr {
        // 1. Assert: F is an ECMAScript function.
        let f = *jsvalue_cast!(JSValue::Object, f);
        // 2. Assert: Type(newTarget) is Undefined or Object.
        // 3. Let env be a new Lexical Environment.
        // 4. Let envRec be a new function Environment Record containing no bindings.
        let env_rec = run.new_env_record(EnvironmentRecordKind::Function);
        // 5. set envrec.[[functionobject]] to f.
        run.env_record_mut(env_rec).func.function_object = JSValue::Object(f);
        if let InternalSlotValue::ThisMode(ThisMode::Lexical) = run
            .object(f)
            .get_internal_slot(InternalSlotName::ThisMode)
            .unwrap()
        {
            // 6. If F.[[ThisMode]] is lexical, set envRec.[[ThisBindingStatus]] to lexical.
            run.env_record_mut(env_rec).func.this_binding_status = ThisBindingStatus::Lexical;
        } else {
            // 7. Else, set envRec.[[ThisBindingStatus]] to uninitialized.
            run.env_record_mut(env_rec).func.this_binding_status = ThisBindingStatus::Uninitialized;
        }
        // 8. Let home be F.[[HomeObject]].
        let home = run
            .object(f)
            .get_internal_slot(InternalSlotName::HomeObject)
            .unwrap()
            .get_value();

        // 9. Set envRec.[[HomeObject]] to home.
        run.env_record_mut(env_rec).func.home_object = home.clone();

        // 10. Set envRec.[[NewTarget]] to newTarget.
        run.env_record_mut(env_rec).func.new_target = new_target.clone();

        // 11. Set env's EnvironmentRecord to envRec.
        // 12. Set the outer lexical environment reference of env to F.[[Environment]].
        // 13. Return env.
        run.new_lexical_env(
            env_rec,
            Some(
                run.object(f)
                    .get_internal_slot(InternalSlotName::Environment)
                    .unwrap()
                    .get_lexical_environment(),
            ),
        )
    }

    /// https://262.ecma-international.org/11.0/#sec-newglobalenvironment
    pub fn new_global_environment(
        run: &mut Runtime,
        g: ObjectAddr,
        this_value: JSValue,
    ) -> LexicalEnvAddr {
        // 1. Let env be a new Lexical Environment.
        // 2. Let objRec be a new object Environment Record containing G as the binding object.
        // 3. Let dclRec be a new declarative Environment Record containing no bindings.

        // 4. Let globalRec be a new global Environment Record.
        let global_rec = run.new_env_record(EnvironmentRecordKind::Global);

        // 5. Set globalRec.[[ObjectRecord]] to objRec.
        run.env_record_mut(global_rec).obj.binding_object = Some(g);

        // 6. Set globalRec.[[GlobalThisValue]] to thisValue.
        run.env_record_mut(global_rec).glob.global_this_value = this_value;

        // 7. Set globalRec.[[DeclarativeRecord]] to dclRec.
        // (Done implicitly by the ctor).

        // 8. Set globalRec.[[VarNames]] to a new empty List.
        // (Done implicitly by the ctor).

        // 9. Set env's EnvironmentRecord to globalRec.
        // 10. Set the outer lexical environment reference of env to null.
        // 11. Return env.
        run.new_lexical_env(global_rec, None)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#[macro_use]
mod jsvalue;
mod completion_record;
mod environment_record;
mod execution_context;
mod function;
mod jsobject;
mod lexical_environment;
mod operations;
mod reference;
pub mod runtime;
pub mod script;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use num::Zero;

use super::jsvalue::*;
use crate::eval::completion_record::CompletionRecord;
use crate::eval::completion_record::NormalCompletion;
use crate::eval::runtime::Runtime;

/// https://262.ecma-international.org/11.0/#array-index
///
/// An integer index is a String-valued property key that is a canonical numeric
/// String (see 7.1.21) and whose numeric value is either +0 or a positive
/// integer `â‰¤ 2**53 - 1`. An array index is an integer index whose numeric value
/// i is in the range `+0 â‰¤ i < 2**32 - 1`.
pub fn string_to_array_index(s: &[u16]) -> Option<u32> {
    const V0: u16 = '0' as u16;
    const V9: u16 = '9' as u16;
    match s {
        // Empty string is invalid
        []
        // Leading "0" is invalid.
        | [V0, _, ..] => return None,
        // A single "0" is just that.
        [V0] => return Some(0u32),
        _ => ()
    }

    let mut res: u32 = 0;
    for ch in s {
        if *ch < V0 || *ch > V9 {
            return None;
        }
        let tmp = (res as u64) * 10 + (*ch - V0) as u64;
        if tmp >= 0xFFFFFFFF {
            return None;
        }
        res = tmp as u32;
    }

    Some(res)
}

/// https://262.ecma-international.org/11.0/#sec-toboolean
pub fn to_boolean(a: &JSValue) -> bool {
    match a {
        JSValue::Undefined | JSValue::Null => false,
        JSValue::Boolean(b) => *b,
        JSValue::String(s) => s.len() != 0,
        JSValue::Object(_) | JSValue::Symbol(_) => true,
        JSValue::Number(n) => !(n.is_zero() || n.is_nan()),
        JSValue::BigInt(bi) => !bi.is_zero(),
    }
}

/// https://262.ecma-international.org/11.0/#sec-toobject
pub fn to_object(run: &mut Runtime, a: &JSValue) -> CompletionRecord {
    match a {
        JSValue::Undefined | JSValue::Null => {
            run.type_error("Cannot convert undefined or null to object")
        }
        JSValue::Boolean(_)
        | JSValue::String(_)
        | JSValue::Symbol(_)
        | JSValue::Number(_)
        | JSValue::BigInt(_) => unimplemented!("ToObject()"),
        JSValue::Object(_) => Ok(NormalCompletion::Value(a.clone())),
    }
}

/// https://262.ecma-international.org/11.0/#sec-ispropertykey
pub fn is_property_key(a: &JSValue) -> bool {
    matches!(a, JSValue::String(_) | JSValue::Symbol(_))
}

/// https://262.ecma-international.org/11.0/#sec-samevalue
pub fn same_value(x: &JSValue, y: &JSValue) -> bool {
    if std::mem::discriminant(x) != std::mem::discriminant(y) {
        return false;
    }
    match (x, y) {
        (JSValue::Number(xn), JSValue::Number(yn)) => number_same_value(*xn, *yn),
        (JSValue::BigInt(xn), JSValue::BigInt(yn)) => bigint_same_value(xn, yn),
        _ => same_value_non_numeric(x, y),
    }
}

/// https://262.ecma-international.org/11.0/#sec-samevaluenonnumeric
pub fn same_value_non_numeric(x: &JSValue, y: &JSValue) -> bool {
    debug_assert!(!matches!(x, JSValue::Number(_) | JSValue::BigInt(_)));
    debug_assert!(std::mem::discriminant(x) == std::mem::discriminant(y));

    match (x, y) {
        (JSValue::Undefined, _) | (JSValue::Null, _) => true,
        (JSValue::String(xs), JSValue::String(ys)) => *xs == *ys,
        (JSValue::Boolean(xb), JSValue::Boolean(yb)) => xb == yb,
        (JSValue::Symbol(xs), JSValue::Symbol(ys)) => *xs == *ys,
        (JSValue::Object(xo), JSValue::Object(yo)) => *xo == *yo,
        _ => panic!("invalid value type"),
    }
}

/// https://262.ecma-international.org/11.0/#sec-numeric-types-number-sameValue
pub fn number_same_value(x: f64, y: f64) -> bool {
    x.to_bits() == y.to_bits()
}

/// https://262.ecma-international.org/11.0/#sec-numeric-types-bigint-sameValue
pub fn bigint_same_value(x: &num::BigInt, y: &num::BigInt) -> bool {
    x.eq(y)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use super::completion_record::CompletionRecord;
use super::completion_record::NormalCompletion;
use super::execution_context::ExecutionContext;
use super::jsvalue::JSValue;
use super::operations::to_object;
use super::runtime::EnvRecordAddr;
use super::runtime::Runtime;

#[derive(Clone, Debug, PartialEq)]
pub enum ReferenceBase {
    Value(JSValue),
    EnvRec(EnvRecordAddr),
}

#[derive(Clone, Debug, PartialEq)]
pub struct Reference {
    base: ReferenceBase,
    name: JSValue,
    strict: bool,
    this_value: Option<JSValue>,
}

impl Reference {
    /// Make a "Reference" type.
    pub fn value(base: JSValue, name: JSValue, strict: bool) -> Self {
        Reference {
            base: ReferenceBase::Value(base),
            name,
            strict,
            this_value: None,
        }
    }

    /// Make a "Reference" type with base environment record.
    pub fn env_rec(base: EnvRecordAddr, name: JSValue, strict: bool) -> Self {
        Reference {
            base: ReferenceBase::EnvRec(base),
            name,
            strict,
            this_value: None,
        }
    }
}

/// https://262.ecma-international.org/11.0/#sec-reference-specification-type
impl Reference {
    pub fn get_base(&self) -> &ReferenceBase {
        &self.base
    }
    pub fn get_referenced_name(&self) -> &JSValue {
        &self.name
    }
    pub fn is_strict_reference(&self) -> bool {
        self.strict
    }
    pub fn has_primitive_base(&self) -> bool {
        matches!(
            self.base,
            ReferenceBase::Value(JSValue::Boolean(_))
                | ReferenceBase::Value(JSValue::String(_))
                | ReferenceBase::Value(JSValue::Symbol(_))
                | ReferenceBase::Value(JSValue::BigInt(_))
                | ReferenceBase::Value(JSValue::Number(_))
        )
    }
    pub fn get_base_value(&self) -> &JSValue {
        match &self.base {
            ReferenceBase::Value(v) => v,
            _ => unreachable!(),
        }
    }
    pub fn get_base_env_record(&self) -> EnvRecordAddr {
        match self.base {
            ReferenceBase::EnvRec(r) => r,
            _ => unreachable!(),
        }
    }
    pub fn is_property_reference(&self) -> bool {
        matches!(self.base, ReferenceBase::Value(JSValue::Object(_))) || self.has_primitive_base()
    }
    pub fn is_unresolvable_reference(&self) -> bool {
        matches!(self.base, ReferenceBase::Value(JSValue::Undefined))
    }
    pub fn is_super_reference(&self) -> bool {
        self.this_value.is_some()
    }

    /// https://262.ecma-international.org/11.0/#sec-getvalue
    pub fn get_value(run: &mut Runtime, v: CompletionRecord) -> CompletionRecord {
        // 1. ReturnIfAbrupt(V).
        let v = v?;
        // 2. If Type(V) is not Reference, return V.
        let v = match v {
            NormalCompletion::Empty => panic!("get_value() value cannot be empty"),
            NormalCompletion::Value(_) => return CompletionRecord::Ok(v),
            NormalCompletion::Reference(r) => r,
        };
        // 4.
        if v.is_unresolvable_reference() {
            return run.reference_error("Unresolvable reference");
        }
        // 5.
        if v.is_property_reference() {
            let base_value = v.get_base_value();
            // 5.a.
            let oaddr = if v.has_primitive_base() {
                NormalCompletion::unwrap_value(to_object(run, base_value)?).cast_object()
            } else {
                base_value.cast_object()
            };
            (run.object(oaddr).methods.get)(run, oaddr, v.get_referenced_name(), v.get_this_value())
        } else {
            let recaddr = v.get_base_env_record();
            (run.env_record(recaddr).methods.get_binding_value)(
                run,
                recaddr,
                v.get_referenced_name().cast_string(),
                v.is_strict_reference(),
            )
        }
    }

    /// https://262.ecma-international.org/11.0/#sec-putvalue
    pub fn put_value(
        run: &mut Runtime,
        v: CompletionRecord,
        w: CompletionRecord,
    ) -> CompletionRecord {
        let v = v?;
        let w = w?;
        let v = if let NormalCompletion::Reference(r) = v {
            r
        } else {
            return run.reference_error("Reference required");
        };
        if v.is_unresolvable_reference() {
            if v.is_strict_reference() {
                return run.reference_error("Unresolvable reference");
            }
            let global_obj = ExecutionContext::get_global_object(run);
            run.set(global_obj, v.get_referenced_name(), w.unwrap_value(), false)
        } else if v.is_property_reference() {
            let base_value = v.get_base_value();
            let oaddr = if v.has_primitive_base() {
                NormalCompletion::unwrap_value(to_object(run, base_value)?).cast_object()
            } else {
                base_value.cast_object()
            };
            let success = (run.object(oaddr).methods.set)(
                run,
                oaddr,
                v.get_referenced_name(),
                w.unwrap_value(),
                v.get_this_value(),
            )?
            .unwrap_value();
            if success == JSValue::Boolean(false) && v.is_strict_reference() {
                return run.type_error(format!("Can't set property {}", v.get_referenced_name()));
            }
            Ok(NormalCompletion::Empty)
        } else {
            let recaddr = v.get_base_env_record();
            (run.env_record(recaddr).methods.set_mutable_binding)(
                run,
                recaddr,
                v.get_referenced_name().cast_string(),
                w.unwrap_value(),
                v.is_strict_reference(),
            )
        }
    }

    pub fn get_this_value(&self) -> &JSValue {
        debug_assert!(self.is_property_reference());
        if self.is_super_reference() {
            self.this_value.as_ref().unwrap()
        } else {
            self.get_base_value()
        }
    }

    pub fn initialize_referenced_binding(
        run: &mut Runtime,
        v: CompletionRecord,
        w: CompletionRecord,
    ) -> CompletionRecord {
        let v = v?;
        let w = w?;
        let v = v.unwrap_reference();
        let recaddr = v.get_base_env_record();
        (run.env_record(recaddr).methods.initialize_binding)(
            run,
            recaddr,
            v.get_referenced_name().cast_string(),
            w.unwrap_value(),
        )
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::fmt::Display;

use juno_ast::NodeRc;
use juno_support::declare_opaque_id;

use super::completion_record::*;
use super::environment_record::*;
use super::execution_context::*;
use super::jsobject::*;
use super::lexical_environment::*;
use super::operations::*;
use super::script::*;
use crate::eval::jsvalue::JSString;
use crate::eval::jsvalue::JSSymbol;
use crate::eval::jsvalue::JSValue;

pub type Realm = ();

#[derive(Debug)]
pub enum ScriptOrModule {
    Script(ScriptRecord),
    Module(),
}

declare_opaque_id!(ObjectAddr);
declare_opaque_id!(EnvRecordAddr);
declare_opaque_id!(LexicalEnvAddr);

pub enum WellKnownSymbol {
    Unscopables,
}

pub struct Runtime {
    objects: Vec<JSObject>,
    env_records: Vec<EnvironmentRecord>,
    contexts: Vec<ExecutionContext>,
    lexical_environments: Vec<LexicalEnvironment>,
    well_known_symbols: Box<[JSValue]>,
    global: JSValue,
}

impl Runtime {
    pub fn new() -> Self {
        let well_known_symbols =
            vec![JSValue::Symbol(JSSymbol::new_with_str("@@unscopables"))].into_boxed_slice();

        let mut run = Runtime {
            objects: Default::default(),
            env_records: Default::default(),
            contexts: Default::default(),
            lexical_environments: Default::default(),
            well_known_symbols,
            global: JSValue::Undefined,
        };

        run.init_global();

        run
    }

    pub fn reference_error<S: Display>(&mut self, msg: S) -> CompletionRecord {
        Err(AbruptCompletion::Throw(JSValue::String(
            JSString::from_str(format!("ReferenceError: {}", msg).as_str()),
        )))
    }
    pub fn type_error<S: Display>(&mut self, msg: S) -> CompletionRecord {
        Err(AbruptCompletion::Throw(JSValue::String(
            JSString::from_str(format!("TypeError: {}", msg).as_str()),
        )))
    }

    pub fn global(&self) -> ObjectAddr {
        jsvalue_cast!(JSValue::Object, self.global)
    }

    pub fn object(&self, addr: ObjectAddr) -> &JSObject {
        &self.objects[addr.as_usize()]
    }
    pub fn object_mut(&mut self, addr: ObjectAddr) -> &mut JSObject {
        &mut self.objects[addr.as_usize()]
    }
    pub fn new_env_record(&mut self, kind: EnvironmentRecordKind) -> EnvRecordAddr {
        let new_addr = EnvRecordAddr::new(self.env_records.len());
        self.env_records.push(EnvironmentRecord::new(kind));
        new_addr
    }
    pub fn env_record(&self, addr: EnvRecordAddr) -> &EnvironmentRecord {
        &self.env_records[addr.as_usize()]
    }
    pub fn env_record_mut(&mut self, addr: EnvRecordAddr) -> &mut EnvironmentRecord {
        &mut self.env_records[addr.as_usize()]
    }
    pub fn new_lexical_env(
        &mut self,
        env_rec: EnvRecordAddr,
        outer: Option<LexicalEnvAddr>,
    ) -> LexicalEnvAddr {
        let new_addr = LexicalEnvAddr::new(self.lexical_environments.len());
        self.lexical_environments
            .push(LexicalEnvironment::new(env_rec, outer));
        new_addr
    }
    pub fn lexical_env(&self, addr: LexicalEnvAddr) -> &LexicalEnvironment {
        &self.lexical_environments[addr.as_usize()]
    }
    pub fn lexical_env_mut(&mut self, addr: LexicalEnvAddr) -> &mut LexicalEnvironment {
        &mut self.lexical_environments[addr.as_usize()]
    }

    pub fn contexts(&self) -> &[ExecutionContext] {
        &self.contexts
    }
    pub fn contexts_mut(&mut self) -> &mut Vec<ExecutionContext> {
        &mut self.contexts
    }

    pub fn well_known_symbol(&self, which: WellKnownSymbol) -> JSValue {
        self.well_known_symbols[which as usize].clone()
    }

    /// https://262.ecma-international.org/11.0/#sec-global-object
    fn init_global(&mut self) {
        let proto = JSObject::ordinary_object_create(self, JSValue::Null, None);
        let global = JSObject::ordinary_object_create(self, JSValue::Object(proto), None);
        self.global = JSValue::Object(global);
    }
}

impl Runtime {
    pub fn is_extensible(&self, oaddr: ObjectAddr) -> bool {
        (self.object(oaddr).methods.is_extensible)(self, oaddr)
    }
}

/// Operations on Objects
/// https://262.ecma-international.org/11.0/#sec-operations-on-objects
impl Runtime {
    /// https://262.ecma-international.org/11.0/#sec-makebasicobject
    pub fn make_basic_object(&mut self, internal_slots_list: &[InternalSlotName]) -> ObjectAddr {
        self.objects
            .push(JSObject::new_basic_object(internal_slots_list));
        ObjectAddr::new(self.objects.len() - 1)
    }

    /// https://262.ecma-international.org/11.0/#sec-get-o-p
    pub fn get(&mut self, oaddr: ObjectAddr, p: &JSValue) -> CompletionRecord {
        debug_assert!(is_property_key(p));
        (self.object(oaddr).methods.get)(self, oaddr, p, &JSValue::Object(oaddr))
    }

    /// https://262.ecma-international.org/11.0/#sec-set-o-p-v-throw
    pub fn set(
        &mut self,
        oaddr: ObjectAddr,
        p: &JSValue,
        v: JSValue,
        throw: bool,
    ) -> CompletionRecord {
        debug_assert!(is_property_key(p));
        let success = (self.object(oaddr).methods.set)(self, oaddr, p, v, &JSValue::Object(oaddr))?;
        if success == NormalCompletion::Value(JSValue::Boolean(false)) && throw {
            self.type_error("property set error")
        } else {
            Ok(NormalCompletion::Value(JSValue::Boolean(true)))
        }
    }

    /// https://262.ecma-international.org/11.0/#sec-createdataproperty
    pub fn create_data_property(&mut self, oaddr: ObjectAddr, p: &JSValue, v: JSValue) -> bool {
        debug_assert!(is_property_key(p));
        let new_desc = PropertyDescriptor {
            value: Some(v),
            writable: Some(true),
            enumerable: Some(true),
            configurable: Some(true),
            ..Default::default()
        };
        (self.object(oaddr).methods.define_own_property)(self, oaddr, p, &new_desc)
    }

    /// https://262.ecma-international.org/11.0/#sec-definepropertyorthrow
    pub fn define_property_or_throw(
        &mut self,
        oaddr: ObjectAddr,
        p: &JSValue,
        desc: &PropertyDescriptor,
    ) -> CompletionRecord {
        debug_assert!(is_property_key(p));
        if !(self.object(oaddr).methods.define_own_property)(self, oaddr, p, desc) {
            self.type_error("DefineProperty error")
        } else {
            Ok(NormalCompletion::Value(JSValue::Boolean(true)))
        }
    }

    /// https://262.ecma-international.org/11.0/#sec-hasproperty
    pub fn has_property(&self, oaddr: ObjectAddr, p: &JSValue) -> bool {
        debug_assert!(is_property_key(p));
        (self.object(oaddr).methods.has_property)(self, oaddr, p)
    }

    /// https://262.ecma-international.org/11.0/#sec-hasownproperty
    pub fn has_own_property(&self, oaddr: ObjectAddr, p: &JSValue) -> bool {
        debug_assert!(is_property_key(p));
        (self.object(oaddr).methods.get_own_property)(self, oaddr, p).is_some()
    }

    /// https://262.ecma-international.org/11.0/#sec-call
    pub fn call(&mut self, _f: &JSValue, _v: &JSValue, _args: &[JSValue]) -> CompletionRecord {
        unimplemented!()
    }
}

pub fn evaluate_program(_run: &mut Runtime, _ast: &NodeRc) -> CompletionRecord {
    todo!()
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use juno_ast::NodeRc;

use super::completion_record::*;
use super::execution_context::*;
use super::jsvalue::*;
use super::runtime::*;

/// https://262.ecma-international.org/11.0/#script-record
#[derive(Debug)]
pub struct ScriptRecord {
    realm: Option<Realm>,
    environment: Option<LexicalEnvAddr>,
    ecmascript_code: NodeRc,
}

impl ScriptRecord {
    pub fn new(ast: NodeRc) -> Self {
        Self {
            realm: None,
            environment: None,
            ecmascript_code: ast,
        }
    }
}

/// https://262.ecma-international.org/11.0/#sec-globaldeclarationinstantiation
pub fn global_declaration_instantiation(_run: &mut Runtime, _ast: &NodeRc) -> CompletionRecord {
    todo!();
}

/// https://262.ecma-international.org/11.0/#sec-runtime-semantics-scriptevaluation
pub fn script_evaluation(run: &mut Runtime, ast: &NodeRc) -> CompletionRecord {
    // Let globalEnv be scriptRecord.[[Realm]].[[GlobalEnv]].

    // Let scriptContext be a new ECMAScript code execution context.
    // Set the Function of scriptContext to null.
    // Set the Realm of scriptContext to scriptRecord.[[Realm]].
    // Set the ScriptOrModule of scriptContext to scriptRecord.
    // Set the VariableEnvironment of scriptContext to globalEnv.
    // Set the LexicalEnvironment of scriptContext to globalEnv.
    let script_context =
        ExecutionContext::new_script(ScriptOrModule::Script(ScriptRecord::new(ast.clone())));

    // Suspend the currently running execution context.
    // Push scriptContext onto the execution context stack; scriptContext is now the running execution context.
    run.contexts_mut().push(script_context);

    // Let scriptBody be scriptRecord.[[ECMAScriptCode]].
    // Let result be GlobalDeclarationInstantiation(scriptBody, globalEnv).
    let result = global_declaration_instantiation(run, ast);

    // If result.[[Type]] is normal, then
    //     Set result to the result of evaluating scriptBody.
    // If result.[[Type]] is normal and result.[[Value]] is empty, then
    //     Set result to NormalCompletion(undefined).
    let result = if result.is_ok() {
        let result = evaluate_program(run, ast);
        if let Ok(NormalCompletion::Empty) = result {
            Ok(NormalCompletion::Value(JSValue::Undefined))
        } else {
            result
        }
    } else {
        result
    };

    // Suspend scriptContext and remove it from the execution context stack.
    run.contexts_mut().pop();
    // Assert: The execution context stack is not empty.
    // Resume the context that is now on the top of the execution context stack as the running execution context.
    // Return Completion(result).
    result
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#[allow(dead_code)]
mod pool;

#[allow(dead_code)]
mod uref;

#[allow(dead_code)]
mod value_list;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! # Pool
//!
//! A pool of MaybeUninit<T>, conceptually similar to a C++ deque, except it
//! doesn't support indexing.
//! Nodes must be freed explicitly and are added to a free list.
//! This is not intended to be a user-visible API.

use std::cell::Cell;
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::ptr;
use std::ptr::NonNull;

pub(crate) struct Pool<T> {
    /// List of all the nodes stored in this context.
    /// Each element is a "chunk" of nodes.
    /// None of the chunks are ever resized after allocation.
    nodes: UnsafeCell<Vec<Vec<MaybeUninit<T>>>>,

    /// List of freed elements.
    free: UnsafeCell<Vec<NonNull<MaybeUninit<T>>>>,

    /// Capacity at which to allocate the next chunk.
    /// Doubles every chunk until reaching [`MAX_CHUNK_CAPACITY`].
    next_chunk_capacity: Cell<usize>,
}

const MIN_CHUNK_CAPACITY: usize = 1 << 10;
const MAX_CHUNK_CAPACITY: usize = MIN_CHUNK_CAPACITY * (1 << 10);

impl<T> Pool<T> {
    pub fn new() -> Self {
        let result = Self {
            nodes: Default::default(),
            free: Default::default(),
            next_chunk_capacity: Cell::new(MIN_CHUNK_CAPACITY),
        };
        result.new_chunk();
        result
    }

    /// Allocate a new `Node` in this `Context`.
    pub fn alloc(&self) -> NonNull<MaybeUninit<T>> {
        let free = unsafe { &mut *self.free.get() };
        let nodes = unsafe { &mut *self.nodes.get() };
        if let Some(entry) = free.pop() {
            entry
        } else {
            let chunk = nodes.last().unwrap();
            if chunk.len() >= chunk.capacity() {
                self.new_chunk();
            }
            let chunk = nodes.last_mut().unwrap();
            chunk.push(MaybeUninit::uninit());
            let len = chunk.len();
            unsafe { NonNull::new_unchecked(chunk.get_unchecked_mut(len - 1)) }
        }
    }

    /// Allocate a new chunk in the pool storage.
    fn new_chunk(&self) {
        let nodes = unsafe { &mut *self.nodes.get() };
        let capacity = self.next_chunk_capacity.get();
        nodes.push(Vec::with_capacity(capacity));

        // Double the capacity if there's room.
        if capacity < MAX_CHUNK_CAPACITY {
            self.next_chunk_capacity.set(capacity * 2);
        }
    }

    unsafe fn free(&self, n: *const T) {
        ptr::drop_in_place(n as *mut T);
        let n = n as *mut MaybeUninit<T>;
        let free = &mut *self.free.get();
        free.push(NonNull::new_unchecked(n as *mut MaybeUninit<T>));
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test1() {
        let pool = Pool::new();
        unsafe {
            let mut n1 = pool.alloc();
            n1.as_mut().write(10);
            let mut n2 = pool.alloc();
            n2.as_mut().write(20);

            pool.free(n1.as_ref().as_ptr());
            let _n3 = pool.alloc();
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! # URef
//!
//! A re-implementation of intrusive_collections::UnsafeRef that implements
//! the `Copy` trait.

use std::borrow::Borrow;
use std::fmt;
use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr::NonNull;

use intrusive_collections::PointerOps;

pub(crate) struct URef<T: ?Sized> {
    ptr: NonNull<T>,
}

impl<T: ?Sized> Copy for URef<T> {}

impl<T: ?Sized> Clone for URef<T> {
    fn clone(&self) -> Self {
        Self { ptr: self.ptr }
    }
}

impl<T: ?Sized> URef<T> {
    pub fn new(val: &T) -> Self {
        unsafe {
            URef {
                ptr: NonNull::new_unchecked(val as *const T as *mut T),
            }
        }
    }

    /// Creates an `URef` from a raw pointer
    #[inline]
    pub unsafe fn from_raw(val: *const T) -> URef<T> {
        URef {
            ptr: NonNull::new_unchecked(val as *mut _),
        }
    }

    /// Converts an `URef` into a raw pointer
    #[inline]
    pub fn into_raw(ptr: Self) -> *mut T {
        ptr.ptr.as_ptr()
    }
}

impl<T: ?Sized> Deref for URef<T> {
    type Target = T;

    #[inline]
    fn deref(&self) -> &T {
        self.as_ref()
    }
}

impl<T: ?Sized> AsRef<T> for URef<T> {
    #[inline]
    fn as_ref(&self) -> &T {
        unsafe { self.ptr.as_ref() }
    }
}

impl<T: ?Sized> Borrow<T> for URef<T> {
    #[inline]
    fn borrow(&self) -> &T {
        self.as_ref()
    }
}

impl<T: fmt::Debug + ?Sized> fmt::Debug for URef<T> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(self.as_ref(), f)
    }
}

unsafe impl<T: ?Sized + Send> Send for URef<T> {}

unsafe impl<T: ?Sized + Sync> Sync for URef<T> {}

pub(crate) struct URefPointerOps<T: ?Sized>(PhantomData<T>);

impl<Pointer> URefPointerOps<Pointer> {
    /// Constructs an instance of `DefaultPointerOps`.
    #[inline]
    pub const fn new() -> URefPointerOps<Pointer> {
        URefPointerOps(PhantomData)
    }
}

impl<Pointer> Clone for URefPointerOps<Pointer> {
    #[inline]
    fn clone(&self) -> Self {
        *self
    }
}

impl<Pointer> Copy for URefPointerOps<Pointer> {}

unsafe impl<T: ?Sized> PointerOps for URefPointerOps<URef<T>> {
    type Value = T;
    type Pointer = URef<T>;

    #[inline]
    unsafe fn from_raw(&self, raw: *const T) -> URef<T> {
        URef::from_raw(raw as *mut T)
    }

    #[inline]
    fn into_raw(&self, ptr: URef<T>) -> *const T {
        URef::into_raw(ptr) as *const T
    }
}

#[macro_export]
macro_rules! uref_intrusive_adapter {
    (@impl
        $(#[$attr:meta])* $vis:vis $name:ident ($($args:tt),*)
        = $pointer:ty: $value:path { $field:ident: $link:ty } $($where_:tt)*
    ) => {
        #[allow(explicit_outlives_requirements)]
        $(#[$attr])*
        $vis struct $name<$($args),*> $($where_)* {
            link_ops: <$link as ::intrusive_collections::DefaultLinkOps>::Ops,
            pointer_ops: $crate::uref::URefPointerOps<$pointer>,
        }
        unsafe impl<$($args),*> Send for $name<$($args),*> $($where_)* {}
        unsafe impl<$($args),*> Sync for $name<$($args),*> $($where_)* {}
        impl<$($args),*> Copy for $name<$($args),*> $($where_)* {}
        impl<$($args),*> Clone for $name<$($args),*> $($where_)* {
            #[inline]
            fn clone(&self) -> Self {
                *self
            }
        }
        impl<$($args),*> Default for $name<$($args),*> $($where_)* {
            #[inline]
            fn default() -> Self {
                Self::NEW
            }
        }
        #[allow(dead_code)]
        impl<$($args),*> $name<$($args),*> $($where_)* {
            pub const NEW: Self = $name {
                link_ops: <$link as ::intrusive_collections::DefaultLinkOps>::NEW,
                pointer_ops: $crate::uref::URefPointerOps::<$pointer>::new(),
            };
            #[inline]
            pub fn new() -> Self {
                Self::NEW
            }
        }
        #[allow(dead_code, unsafe_code)]
        unsafe impl<$($args),*> ::intrusive_collections::Adapter for $name<$($args),*> $($where_)* {
            type LinkOps = <$link as ::intrusive_collections::DefaultLinkOps>::Ops;
            type PointerOps = $crate::uref::URefPointerOps<$pointer>;

            #[inline]
            unsafe fn get_value(&self, link: <Self::LinkOps as ::intrusive_collections::LinkOps>::LinkPtr) -> *const <Self::PointerOps as ::intrusive_collections::PointerOps>::Value {
                ::intrusive_collections::container_of!(link.as_ptr(), $value, $field)
            }
            #[inline]
            unsafe fn get_link(&self, value: *const <Self::PointerOps as ::intrusive_collections::PointerOps>::Value) -> <Self::LinkOps as ::intrusive_collections::LinkOps>::LinkPtr {
                // We need to do this instead of just accessing the field directly
                // to strictly follow the stack borrow rules.
                let ptr = (value as *const u8).add(::intrusive_collections::offset_of!($value, $field));
                core::ptr::NonNull::new_unchecked(ptr as *mut _)
            }
            #[inline]
            fn link_ops(&self) -> &Self::LinkOps {
                &self.link_ops
            }
            #[inline]
            fn link_ops_mut(&mut self) -> &mut Self::LinkOps {
                &mut self.link_ops
            }
            #[inline]
            fn pointer_ops(&self) -> &Self::PointerOps {
                &self.pointer_ops
            }
        }
    };
    (@find_generic
        $(#[$attr:meta])* $vis:vis $name:ident ($($prev:tt)*) > $($rest:tt)*
    ) => {
        uref_intrusive_adapter!(@impl
            $(#[$attr])* $vis $name ($($prev)*) $($rest)*
        );
    };
    (@find_generic
        $(#[$attr:meta])* $vis:vis $name:ident ($($prev:tt)*) $cur:tt $($rest:tt)*
    ) => {
        uref_intrusive_adapter!(@find_generic
            $(#[$attr])* $vis $name ($($prev)* $cur) $($rest)*
        );
    };
    (@find_if_generic
        $(#[$attr:meta])* $vis:vis $name:ident < $($rest:tt)*
    ) => {
        uref_intrusive_adapter!(@find_generic
            $(#[$attr])* $vis $name () $($rest)*
        );
    };
    (@find_if_generic
        $(#[$attr:meta])* $vis:vis $name:ident $($rest:tt)*
    ) => {
        uref_intrusive_adapter!(@impl
            $(#[$attr])* $vis $name () $($rest)*
        );
    };
    ($(#[$attr:meta])* $vis:vis $name:ident $($rest:tt)*) => {
        uref_intrusive_adapter!(@find_if_generic
            $(#[$attr])* $vis $name $($rest)*
        );
    };
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::cell::Cell;
use std::cell::UnsafeCell;
use std::mem;
use std::mem::MaybeUninit;

use intrusive_collections::LinkedList;
use intrusive_collections::LinkedListLink;

use crate::uref::URef;
use crate::uref_intrusive_adapter;

pub(crate) struct Value {
    user_list: UnsafeCell<LinkedList<UseNodeAdapter>>,
}

/// UseNode links a "use" of a value to the value and the owner of the use.
/// A value owns a linked list of `UseNode` linking all uses of the value.
/// An owner owns a vector of `UseNode` representing the operands (values) that
/// are used by that owner.
#[derive(Default)]
pub(crate) struct UseNode {
    /// Link in the user list of ValueType.
    link: LinkedListLink,
    /// The value that is being "used".
    value: Cell<Option<URef<Value>>>,
    /// Who is using the value? (Typically an instruction)
    owner: Cell<Option<URef<Instruction>>>,
}

uref_intrusive_adapter!(UseNodeAdapter = URef<UseNode>: UseNode { link: LinkedListLink } );

#[derive(Default)]
pub(crate) struct ValueList {
    owner: Option<URef<Instruction>>,
    data: Vec<UseNode>,
}

pub(crate) struct Instruction {
    operands: UnsafeCell<ValueList>,
}

/// On destruction, ValueType clears all pointers pointing to it.
impl Drop for Value {
    fn drop(&mut self) {
        let list = self.user_list.get_mut();
        while !list.is_empty() {
            // This removes the element from the list as it clears the pointer.
            list.front().get().unwrap().set_value(None);
        }
    }
}

impl Value {
    fn construct(mem: &mut MaybeUninit<Self>) -> &mut Self {
        mem.write(Value {
            user_list: UnsafeCell::new(Default::default()),
        })
    }

    unsafe fn add_user(&self, user: &UseNode) {
        (&mut *self.user_list.get()).push_back(URef::new(user));
    }
    unsafe fn remove_user(&self, user: &UseNode) {
        (&mut *self.user_list.get())
            .cursor_mut_from_ptr(user)
            .remove();
    }

    unsafe fn replace_user(&self, old_user: &UseNode, new_user: &UseNode) {
        // TODO: this can be implemented more efficiently in a circular list.
        let mut old_cursor = (*self.user_list.get()).cursor_mut_from_ptr(old_user);
        old_cursor.insert_after(URef::new(new_user));
        old_cursor.remove();
    }
}

impl UseNode {
    /// We can't use a constructor, because the UseNode cannot be moved once it is added to a list.
    pub fn init_no_add(&mut self, value: Option<URef<Value>>, owner: Option<URef<Instruction>>) {
        self.value.set(value);
        self.owner.set(owner);
    }

    /// We can't use a constructor, because the UseNode cannot be moved once it is added to a list.
    pub fn init(&mut self, value: Option<URef<Value>>, owner: Option<URef<Instruction>>) {
        self.value.set(value);
        self.owner.set(owner);
        if let Some(value) = value {
            unsafe {
                value.add_user(self);
            }
        }
    }

    pub fn set_value(&self, value: Option<URef<Value>>) {
        if let Some(old_value) = self.value.get() {
            unsafe {
                old_value.remove_user(self);
            }
        }
        self.value.set(value);
        if let Some(value) = value {
            unsafe {
                value.add_user(self);
            }
        }
    }
}

/// Remove the UseNode from the value's user list on destruction.
impl Drop for UseNode {
    fn drop(&mut self) {
        if let Some(old_value) = self.value.get() {
            unsafe {
                old_value.remove_user(self);
            }
        }
    }
}

impl ValueList {
    const INIT_CAPACITY: usize = 4;
    const MAX_CAPACITY: usize = usize::MAX / 2 / mem::size_of::<UseNode>();

    pub fn set_owner(&mut self, owner: &Instruction) {
        self.owner = Some(URef::new(owner));
    }

    pub fn is_empty(&self) -> bool {
        self.data.is_empty()
    }

    pub fn len(&self) -> usize {
        self.data.len()
    }

    pub fn reserve(&mut self, new_cap: usize) {
        if new_cap > self.data.capacity() {
            assert!(new_cap <= Self::MAX_CAPACITY);
            self.grow_to(new_cap);
        }
    }

    pub fn clear(&mut self) {
        self.data.clear();
    }

    pub fn push_back(&mut self, value: Option<URef<Value>>) {
        let index = self.data.len();
        if self.data.capacity() == index {
            self.grow();
        }
        debug_assert!(self.data.capacity() > index);
        self.data.push(Default::default());
        unsafe { self.data.get_unchecked_mut(index) }.init(value, self.owner);
    }

    /// Move all values from the specified list onto our list, resetting their owner.
    pub fn move_from(&mut self, other: &mut ValueList) {
        if self.data.is_empty() {
            // If we are empty, we can just swap the vecs.
            mem::swap(&mut self.data, &mut other.data);
            // Reset the owner of the stolen items.
            for elem in self.data.iter_mut() {
                elem.owner.set(self.owner);
            }
        } else {
            // Slow path.
            for elem in other.data.iter() {
                self.push_back(elem.value.get());
            }
            other.clear();
        }
    }

    /// Grow the list. This is a special operation because the reallocated nodes
    /// must be relinked into their corresponding user lists.
    fn grow(&mut self) {
        let cap = self.data.capacity();
        debug_assert!(cap == self.data.len());

        let new_cap;
        if cap == 0 {
            new_cap = Self::INIT_CAPACITY;
        } else if cap >= Self::MAX_CAPACITY {
            // Admittedly, quite unlikely...
            panic!("Maximum ValueList capacity reached");
        } else {
            new_cap = std::cmp::min(cap * 2, Self::MAX_CAPACITY);
        }

        self.grow_to(new_cap);
    }

    /// Grow the capacity to the specified value. This is a special operation
    /// because the reallocated nodes must be relinked into their corresponding user lists.
    fn grow_to(&mut self, new_cap: usize) {
        debug_assert!(new_cap > self.data.capacity());

        if self.data.capacity() == 0 {
            self.data.reserve(new_cap);
            return;
        }

        let mut new_vec = Vec::<UseNode>::with_capacity(new_cap);

        for old_node in self.data.iter() {
            // TODO: this can be optimized with MaybeUninit.
            new_vec.push(Default::default());
            let len = new_vec.len();
            let new_node = unsafe { new_vec.get_unchecked_mut(len - 1) };

            let value = old_node.value.get();
            new_node.init_no_add(value, old_node.owner.get());
            if let Some(value) = value {
                unsafe { value.replace_user(old_node, new_node) };
            }
        }

        mem::swap(&mut new_vec, &mut self.data);

        // Free the memory now stored in new_vec, without running destructors.
        let p = Box::into_raw(new_vec.into_boxed_slice());
        unsafe { Box::from_raw(p as *mut [mem::ManuallyDrop<UseNode>]) };
    }
}

impl Instruction {
    fn construct(mem: &mut MaybeUninit<Instruction>) -> &mut Self {
        let this = mem.write(Instruction {
            operands: UnsafeCell::new(Default::default()),
        });
        let operands = unsafe { &mut *this.operands.get() };
        operands.set_owner(this);
        this
    }

    fn push_operand<'a>(&'a self, value: &'a mut Value) {
        let operands = unsafe { &mut *self.operands.get() };
        operands.push_back(Some(URef::new(value)));
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::pool::Pool;

    #[test]
    fn test1() {
        let value_pool = Pool::<Value>::new();
        let owner_pool = Pool::<Instruction>::new();

        let new_value = || {
            let mut p = value_pool.alloc();
            Value::construct(unsafe { p.as_mut() })
        };
        let new_owner = || {
            let mut p = owner_pool.alloc();
            Instruction::construct(unsafe { p.as_mut() })
        };

        let o1 = new_owner();
        {
            let v1 = new_value();
            o1.push_operand(v1);
            o1.push_operand(v1);
        }
        let v2 = new_value();
        o1.push_operand(v2);

        //
        // {
        //     let o2 = new_owner();
        //     o2.push_operand(v1);
        // }
        //
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! Passes that operate on Juno's representation of JS.
//!
//! Provides transformation traits and the ability to compose them in a pipeline.

mod manager;
pub use manager::Pass;
pub use manager::PassManager;

mod passes;
pub use passes::*;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use juno::ast::Context;
use juno::ast::GCLock;
use juno::ast::Node;
use juno::ast::NodeRc;
use juno::ast::TransformResult;

use crate::passes::*;

/// Manager to create pipelines of multiple passes over the AST.
#[derive(Default)]
pub struct PassManager {
    passes: Vec<Box<dyn Pass>>,
}

impl PassManager {
    /// Create with an empty pipeline.
    pub fn new() -> Self {
        Default::default()
    }

    /// Add `pass` to the pipeline.
    pub fn add_pass(&mut self, pass: Box<dyn Pass>) {
        self.passes.push(pass)
    }

    /// Pipeline containing a list of standard passes.
    pub fn standard() -> Self {
        Self {
            passes: vec![Box::new(reduce_conditional::ReduceConditional::new())],
        }
    }

    /// Pipeline containing only the Flow type stripping pass.
    pub fn strip_flow() -> Self {
        Self {
            passes: vec![Box::new(strip_flow::StripFlow::new())],
        }
    }

    /// Run the pipeline on `node`, consuming it in the process.
    pub fn run(mut self, ctx: &mut Context, node: NodeRc) -> NodeRc {
        let mut result = node;
        for pass in &mut self.passes {
            {
                let gc = GCLock::new(ctx);
                result = match pass.run(&gc, result.node(&gc)) {
                    TransformResult::Unchanged => result,
                    TransformResult::Removed => {
                        panic!("Program node removed");
                    }
                    TransformResult::Changed(new_node) => NodeRc::from_node(&gc, new_node),
                    TransformResult::Expanded(..) => {
                        panic!("Program node cannot be expanded");
                    }
                };
            }
            ctx.gc();
        }
        result
    }
}

/// A single pass over the AST.
pub trait Pass {
    /// Short name of the pass.
    fn name(&self) -> &'static str;

    /// Description of what the pass is and what it does.
    fn description(&self) -> &'static str;

    /// Execute the pass on the root `node` and return a `TransformResult`.
    fn run<'gc>(
        &mut self,
        gc: &'gc GCLock,
        node: &'gc Node<'gc>,
    ) -> TransformResult<&'gc Node<'gc>>;
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! Pass for optimizing addition of negated second operands.
//!
//! This pass is unsafe: if `x` is a string, then `+` is concatenation.
//!
//! Transforms
//! ```js
//! x + -y
//! ```
//! into
//! ```js
//! x - y
//! ```

use juno::ast::*;

use crate::Pass;

#[derive(Default)]
pub struct AddNegative {}

impl AddNegative {
    pub fn new() -> Self {
        Default::default()
    }
}

impl Pass for AddNegative {
    fn name(&self) -> &'static str {
        "Add negative"
    }
    fn description(&self) -> &'static str {
        "Transforms (x + -y) into (x - y)"
    }
    fn run<'gc>(
        &mut self,
        gc: &'gc GCLock<'_, '_>,
        node: &'gc Node<'gc>,
    ) -> TransformResult<&'gc Node<'gc>> {
        VisitorMut::call(self, gc, node, None)
    }
}

impl<'gc> VisitorMut<'gc> for AddNegative {
    fn call(
        &mut self,
        gc: &'gc GCLock<'_, '_>,
        node: &'gc Node<'gc>,
        _parent: Option<Path<'gc>>,
    ) -> TransformResult<&'gc Node<'gc>> {
        if let Node::BinaryExpression(
            e1 @ BinaryExpression {
                operator: BinaryExpressionOperator::Plus,
                right:
                    Node::UnaryExpression(UnaryExpression {
                        operator: UnaryExpressionOperator::Minus,
                        argument: e2,
                        ..
                    }),
                ..
            },
        ) = node
        {
            let mut builder = builder::BinaryExpression::from_node(e1);
            builder.operator(BinaryExpressionOperator::Minus);
            builder.right(e2);
            return node.replace_with_new(builder::Builder::BinaryExpression(builder), gc, self);
        }
        node.visit_children_mut(gc, self)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

pub mod add_negative;
pub mod reduce_conditional;
pub mod strip_flow;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! Pass for optimizing conditional expressions where the conditional is a literal.
//!
//! Operates on both conditional expressions and `if` statements (with or without `else`).
//! For example, transforms
//! ```js
//! true ? a : b
//! ```
//! into
//! ```js
//! a
//! ```

use juno::ast::*;

use crate::Pass;

#[derive(Default)]
pub struct ReduceConditional {}

impl ReduceConditional {
    pub fn new() -> Self {
        Default::default()
    }
}

impl Pass for ReduceConditional {
    fn name(&self) -> &'static str {
        "Reduce conditional"
    }
    fn description(&self) -> &'static str {
        "Transforms literal conditionals into whichever branch is taken"
    }
    fn run<'gc>(
        &mut self,
        gc: &'gc GCLock<'_, '_>,
        node: &'gc Node<'gc>,
    ) -> TransformResult<&'gc Node<'gc>> {
        VisitorMut::call(self, gc, node, None)
    }
}

impl<'gc> VisitorMut<'gc> for ReduceConditional {
    fn call(
        &mut self,
        lock: &'gc GCLock,
        node: &'gc Node<'gc>,
        _parent: Option<Path<'gc>>,
    ) -> TransformResult<&'gc Node<'gc>> {
        match node {
            Node::ConditionalExpression(ConditionalExpression {
                test: Node::BooleanLiteral(BooleanLiteral { value, .. }),
                consequent,
                alternate,
                ..
            })
            | Node::IfStatement(IfStatement {
                test: Node::BooleanLiteral(BooleanLiteral { value, .. }),
                consequent,
                alternate: Some(alternate),
                ..
            }) => {
                node.replace_with_existing(if *value { consequent } else { alternate }, lock, self)
            }
            Node::IfStatement(IfStatement {
                test: Node::BooleanLiteral(BooleanLiteral { value, .. }),
                consequent,
                alternate: None,
                ..
            }) => {
                if *value {
                    node.replace_with_existing(consequent, lock, self)
                } else {
                    TransformResult::Removed
                }
            }
            _ => node.visit_children_mut(lock, self),
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

//! Pass to strip the Flow type declarations from code.

use juno::ast::*;

use crate::Pass;

#[derive(Default)]
pub struct StripFlow {}

impl StripFlow {
    pub fn new() -> Self {
        Default::default()
    }
}

impl Pass for StripFlow {
    fn name(&self) -> &'static str {
        "Strip Flow"
    }
    fn description(&self) -> &'static str {
        "Strip Flow declarations"
    }

    fn run<'gc>(
        &mut self,
        gc: &'gc GCLock<'_, '_>,
        node: &'gc Node<'gc>,
    ) -> TransformResult<&'gc Node<'gc>> {
        VisitorMut::call(self, gc, node, None)
    }
}

impl<'gc> VisitorMut<'gc> for StripFlow {
    fn call(
        &mut self,
        gc: &'gc GCLock<'_, '_>,
        node: &'gc Node<'gc>,
        _parent: Option<Path<'gc>>,
    ) -> TransformResult<&'gc Node<'gc>> {
        match node {
            Node::TypeCastExpression(TypeCastExpression { expression, .. }) => {
                return node.replace_with_existing(expression, gc, self);
            }

            Node::Identifier(id) => {
                if id.type_annotation.is_some() || id.optional {
                    let mut builder = builder::Identifier::from_node(id);
                    builder.type_annotation(None);
                    builder.optional(false);
                    return node.replace_with_new(builder::Builder::Identifier(builder), gc, self);
                }
            }

            Node::ImportDeclaration(decl) => {
                if decl.import_kind != ImportKind::Value {
                    return TransformResult::Removed;
                }
            }
            Node::ImportSpecifier(spec) => {
                if spec.import_kind != ImportKind::Value {
                    return TransformResult::Removed;
                }
            }
            Node::ExportNamedDeclaration(spec) => {
                if spec.export_kind != ExportKind::Value {
                    return TransformResult::Removed;
                }
            }
            Node::InterfaceDeclaration { .. }
            | Node::TypeAlias { .. }
            | Node::OpaqueType { .. }
            | Node::DeclareTypeAlias { .. }
            | Node::DeclareOpaqueType { .. }
            | Node::DeclareInterface { .. }
            | Node::DeclareClass { .. }
            | Node::DeclareFunction { .. }
            | Node::DeclareVariable { .. }
            | Node::DeclareExportDeclaration { .. }
            | Node::DeclareExportAllDeclaration { .. }
            | Node::DeclareModule { .. }
            | Node::DeclareModuleExports { .. } => return TransformResult::Removed,

            Node::FunctionDeclaration(n) => {
                let mut builder = builder::FunctionDeclaration::from_node(n);
                builder.params(NodeList::from_iter(
                    gc,
                    n.params.iter().filter(|p| match p {
                        Node::Identifier(Identifier { name, .. }) => gc.str(*name) != "this",
                        _ => true,
                    }),
                ));
                builder.type_parameters(None);
                builder.return_type(None);
                builder.predicate(None);
                return node.replace_with_new(
                    builder::Builder::FunctionDeclaration(builder),
                    gc,
                    self,
                );
            }
            Node::FunctionExpression(n) => {
                let mut builder = builder::FunctionExpression::from_node(n);
                builder.params(NodeList::from_iter(
                    gc,
                    n.params.iter().filter(|p| match p {
                        Node::Identifier(Identifier { name, .. }) => gc.str(*name) != "this",
                        _ => true,
                    }),
                ));
                builder.type_parameters(None);
                builder.return_type(None);
                builder.predicate(None);
                return node.replace_with_new(
                    builder::Builder::FunctionExpression(builder),
                    gc,
                    self,
                );
            }
            Node::ArrowFunctionExpression(n) => {
                let mut builder = builder::ArrowFunctionExpression::from_node(n);
                builder.type_parameters(None);
                builder.return_type(None);
                builder.predicate(None);
                return node.replace_with_new(
                    builder::Builder::ArrowFunctionExpression(builder),
                    gc,
                    self,
                );
            }

            Node::ClassDeclaration(n) => {
                let mut builder = builder::ClassDeclaration::from_node(n);
                builder.implements(NodeList::new(gc));
                builder.super_type_parameters(None);
                builder.type_parameters(None);
                return node.replace_with_new(
                    builder::Builder::ClassDeclaration(builder),
                    gc,
                    self,
                );
            }
            Node::ClassExpression(n) => {
                let mut builder = builder::ClassExpression::from_node(n);
                builder.implements(NodeList::new(gc));
                builder.super_type_parameters(None);
                builder.type_parameters(None);
                return node.replace_with_new(builder::Builder::ClassExpression(builder), gc, self);
            }

            Node::ClassProperty(n) => {
                if n.value.is_none() {
                    return TransformResult::Removed;
                }

                let mut builder = builder::ClassProperty::from_node(n);
                builder.type_annotation(None);
                builder.variance(None);
                return node.replace_with_new(builder::Builder::ClassProperty(builder), gc, self);
            }
            Node::ClassPrivateProperty(n) => {
                if n.value.is_none() {
                    return TransformResult::Removed;
                }

                let mut builder = builder::ClassPrivateProperty::from_node(n);
                builder.type_annotation(None);
                builder.variance(None);
                return node.replace_with_new(
                    builder::Builder::ClassPrivateProperty(builder),
                    gc,
                    self,
                );
            }

            Node::CallExpression(n) => {
                let mut builder = builder::CallExpression::from_node(n);
                builder.type_arguments(None);
                return node.replace_with_new(builder::Builder::CallExpression(builder), gc, self);
            }
            Node::NewExpression(n) => {
                let mut builder = builder::NewExpression::from_node(n);
                builder.type_arguments(None);
                return node.replace_with_new(builder::Builder::NewExpression(builder), gc, self);
            }

            Node::EnumDeclaration(n) => {
                return node.replace_with_existing(transform_enum(gc, n), gc, self);
            }
            Node::ExportDefaultDeclaration(ExportDefaultDeclaration {
                metadata,
                declaration: Node::EnumDeclaration(e),
            }) => {
                return node.replace_with_multiple(
                    vec![
                        builder::Builder::from_node(transform_enum(gc, e)),
                        builder::Builder::ExportDefaultDeclaration(
                            builder::ExportDefaultDeclaration::from_template(
                                template::ExportDefaultDeclaration {
                                    metadata: TemplateMetadata {
                                        range: metadata.range,
                                        ..Default::default()
                                    },
                                    declaration: e.id,
                                },
                            ),
                        ),
                    ],
                    gc,
                    self,
                );
            }
            _ => {}
        }
        node.visit_children_mut(gc, self)
    }
}

fn transform_enum<'gc>(gc: &'gc GCLock<'_, '_>, n: &'gc EnumDeclaration<'gc>) -> &'gc Node<'gc> {
    let (method, args) = match n.body {
        Node::EnumStringBody(body)
            if matches!(body.members.head(), Some(Node::EnumDefaultedMember(_))) =>
        {
            let elements = body.members.iter().map(|m| match m {
                Node::EnumDefaultedMember(m) => builder::StringLiteral::build_template(
                    gc,
                    template::StringLiteral {
                        metadata: Default::default(),
                        value: gc.atom_u16(match m.id {
                            Node::Identifier(id) => {
                                gc.str(id.name).encode_utf16().collect::<Vec<u16>>()
                            }
                            _ => unreachable!(),
                        }),
                    },
                ),
                _ => unreachable!(),
            });

            (
                Some("Mirrored"),
                NodeList::from_iter(
                    gc,
                    [builder::ArrayExpression::build_template(
                        gc,
                        template::ArrayExpression {
                            metadata: Default::default(),
                            trailing_comma: false,
                            elements: NodeList::from_iter(gc, elements),
                        },
                    )],
                ),
            )
        }
        Node::EnumSymbolBody(EnumSymbolBody { members, .. })
        | Node::EnumStringBody(EnumStringBody { members, .. })
        | Node::EnumBooleanBody(EnumBooleanBody { members, .. })
        | Node::EnumNumberBody(EnumNumberBody { members, .. }) => (
            None,
            NodeList::from_iter(
                gc,
                [builder::ObjectExpression::build_template(
                    gc,
                    template::ObjectExpression {
                        metadata: Default::default(),
                        properties: NodeList::from_iter(
                            gc,
                            members.iter().map(|m| match m {
                                Node::EnumStringMember(EnumStringMember { metadata, id, init })
                                | Node::EnumNumberMember(EnumNumberMember { metadata, id, init })
                                | Node::EnumBooleanMember(EnumBooleanMember {
                                    metadata,
                                    id,
                                    init,
                                }) => builder::Property::build_template(
                                    gc,
                                    template::Property {
                                        metadata: TemplateMetadata {
                                            range: metadata.range,
                                            ..Default::default()
                                        },
                                        kind: PropertyKind::Init,
                                        computed: false,
                                        method: false,
                                        shorthand: false,
                                        key: id,
                                        value: init,
                                    },
                                ),

                                // Has to be contained in a EnumSymbolBody
                                Node::EnumDefaultedMember(m) => builder::Property::build_template(
                                    gc,
                                    template::Property {
                                        metadata: TemplateMetadata {
                                            range: m.metadata.range,
                                            ..Default::default()
                                        },
                                        kind: PropertyKind::Init,
                                        computed: false,
                                        method: false,
                                        shorthand: false,
                                        key: m.id,
                                        value: builder::CallExpression::build_template(
                                            gc,
                                            template::CallExpression {
                                                metadata: Default::default(),
                                                type_arguments: None,
                                                callee: builder::Identifier::build_template(
                                                    gc,
                                                    template::Identifier {
                                                        metadata: Default::default(),
                                                        name: gc.atom("Symbol"),
                                                        optional: false,
                                                        type_annotation: None,
                                                    },
                                                ),
                                                arguments: NodeList::from_iter(
                                                    gc,
                                                    [builder::StringLiteral::build_template(
                                                        gc,
                                                        template::StringLiteral {
                                                            metadata: Default::default(),
                                                            value: gc.atom_u16(match m.id {
                                                                Node::Identifier(id) => gc
                                                                    .str(id.name)
                                                                    .encode_utf16()
                                                                    .collect::<Vec<u16>>(),
                                                                _ => unreachable!(),
                                                            }),
                                                        },
                                                    )],
                                                ),
                                            },
                                        ),
                                    },
                                ),
                                _ => unreachable!(),
                            }),
                        ),
                    },
                )],
            ),
        ),
        _ => unreachable!(),
    };
    let runtime = builder::CallExpression::build_template(
        gc,
        template::CallExpression {
            metadata: Default::default(),
            type_arguments: None,
            callee: builder::Identifier::build_template(
                gc,
                template::Identifier {
                    metadata: Default::default(),
                    name: gc.atom("require"),
                    optional: false,
                    type_annotation: None,
                },
            ),
            arguments: NodeList::from_iter(
                gc,
                [builder::StringLiteral::build_template(
                    gc,
                    template::StringLiteral {
                        metadata: Default::default(),
                        value: gc
                            .atom_u16("flow-enums-runtime".encode_utf16().collect::<Vec<u16>>()),
                    },
                )],
            ),
        },
    );
    return builder::VariableDeclaration::build_template(
        gc,
        template::VariableDeclaration {
            metadata: TemplateMetadata {
                range: n.metadata.range,
                ..Default::default()
            },
            kind: VariableDeclarationKind::Const,
            declarations: NodeList::from_iter(
                gc,
                [builder::VariableDeclarator::build_template(
                    gc,
                    template::VariableDeclarator {
                        metadata: Default::default(),
                        id: n.id,
                        init: Some(builder::CallExpression::build_template(
                            gc,
                            template::CallExpression {
                                metadata: Default::default(),
                                type_arguments: None,
                                callee: match method {
                                    Some(m) => builder::MemberExpression::build_template(
                                        gc,
                                        template::MemberExpression {
                                            metadata: Default::default(),
                                            computed: false,
                                            object: runtime,
                                            property: builder::Identifier::build_template(
                                                gc,
                                                template::Identifier {
                                                    metadata: Default::default(),
                                                    name: gc.atom(m),
                                                    optional: false,
                                                    type_annotation: None,
                                                },
                                            ),
                                        },
                                    ),
                                    None => runtime,
                                },
                                arguments: args,
                            },
                        )),
                    },
                )],
            ),
        },
    );
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#[macro_use]
mod strip_flow;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

extern crate juno_support;

use juno::ast;
use juno::ast::dump_json;
use juno::ast::NodeRc;
use juno::gen_js;
use juno::hparser;
use juno::hparser::ParserDialect;
use juno_pass::PassManager;
use juno_support::NullTerminatedBuf;

#[test]
fn imports_exports() {
    assert_strip(
        r#"
            import { Something, type SomeType, typeof SomeOtherThing } from 'some-module';
            import type { SomeOtherType } from 'some-module';

            export type { T };
            export { Something };
            export type ONE = { one: number };
            export interface IThing {
                exported: true;
            }

            export type TestReadOnly = {|
                +readOnly: $ReadOnlyArray<>
            |};
        "#,
        r#"
            import { Something } from 'some-module';
            export {Something as Something}
        "#,
    );
}

#[test]
fn declare() {
    assert_strip(
        r#"
            declare function someFunc(): void;

            declare interface ISomething {
                answer: number;
            }

            declare module 'fs' {
                declare function readThing(path: string): string;
            }

            declare type Location = {
                lat: number,
                lon: number
            };

            declare var SOME_CONST: string;

            declare class Baz {
                method(): mixed;
            }

            type T = string;

            declare export opaque type B;
            declare export var x: number;
            declare export function x(): void;
            declare export default T;
            ;
    "#,
        r#";"#,
    )
}

#[test]
fn classes() {
    assert_strip(
        r#"
            interface Foo {
                prop: any;
                method(): mixed;
            }

            interface SillyFoo extends Foo {
               silly: string;
            }
            ;"#,
        r#";"#,
    );

    assert_strip(
        r#"
        class Bar extends Other implements Foo, ISomething {
            answer: number = 42;
            +covariant: number = 42;
            prop: any;
            +propCo: number;

            method(): mixed {
                return;
            }
        }
        "#,
        r#"
        class Bar extends Other {
            answer = 42;
            covariant = 42;
            method() {
                return;
            }
        }
        "#,
    );
    assert_strip(
        r#"
        var SomeClass2 = class Baz implements Foo {
            prop: any;

            method(): mixed {
                return;
            }
        };
        "#,
        r#"
        var SomeClass2 = class Baz {
            method() {
                return;
            }
        };
        "#,
    );
    assert_strip(
        r#"
        class Wrapper<T> {
            get(): T {
                return this.value;
            }

            map<M>(): Wrapper<M> {
                // do something
            }
        }
        "#,
        r#"
        class Wrapper {
            get() {
                return this.value;
            }

            map() {
                // do something
            }
        }
        "#,
    );
    assert_strip(
        r#"
        class StringWrapper extends Wrapper<string> {
            // ...
        }
        "#,
        r#"
        class StringWrapper extends Wrapper {
            // ...
        }
        "#,
    );

    assert_strip(
        r#"
            class MyClass1 {
               declare prop: string;
            }
        "#,
        r#"class MyClass1 {}"#,
    );
    assert_strip(
        r#"
            class TestClassWithDefault<+T: TestReadOnly = TestReadOnly> {
                constructor() {}
            }
        "#,
        r#"
            class TestClassWithDefault {
                constructor() {}
            }
        "#,
    );
}

#[test]
fn functions() {
    assert_strip(
        r#"
            function f2<T, S = T>() {}
            async function test(x: Type, y? , z? : number = 123): string {
                return await (x: any);
            }
            function testit(arg: mixed): boolean %checks {
                return !!arg;
            }
            const f = async <T>(): T => {};
            "#,
        r#"
            function f2() {}
            async function test(x, y, z = 123) {
                return await x;
            }
            function testit(arg) {
                return !!arg;
            }
            const f = async () => {};
        "#,
    );
}

#[test]
fn expressions() {
    assert_strip(
        r#"
        var union: T | U;
        var intersection: T & U;
        var someObj = {
            objMethod(): void {
              // do nothing.
            }
        }
        doSomething<number>(3);
        doSomething <T, U>(3);
        new Event<number>();
    "#,
        r#"
        var union;
        var intersection;
        var someObj = {
            objMethod() {
            // do nothing.
            }
        }
        doSomething(3);
        doSomething(3);
        new Event();
    "#,
    )
}

#[test]
fn opaque() {
    assert_strip(
        r#"
            opaque type A = number;
            opaque type B: string = string;
            declare opaque type A;
            declare opaque type B: string;
            export opaque type A = number;
            ;
        "#,
        r#";"#,
    );
}

#[test]
fn comments() {
    assert_strip(
        r#"
            var X /*: {
                version: string,
            } */ = { version: "42" };

            function method(param /*: string */) /*: number */ {
                // ...
            }

            // Comment type includes are emptied out
            class MyClass2 {
                /*:: prop: string; */
            }
        "#,
        r#"
            var X /*: {
                version: string,
            } */ = { version: "42" };

            function method(param /*: string */) /*: number */ {
                // ...
            }

            // Comment type includes are emptied out
            class MyClass2 {
                /*:: prop: string; */
            }
        "#,
    )
}

#[test]
fn enums() {
    assert_strip(
        r#"enum E {A, B}"#,
        r#"const E = require("flow-enums-runtime").Mirrored(["A", "B"]);"#,
    );
    assert_strip(
        r#"enum E {A = "X", B = "Y"}"#,
        r#"const E = require("flow-enums-runtime")({A: "X", B: "Y"});"#,
    );
    assert_strip(
        r#"enum E {A = 1, B = 2}"#,
        r#"const E = require("flow-enums-runtime")({A: 1, B: 2});"#,
    );
    assert_strip(
        r#"enum E {A = true, B = false}"#,
        r#"const E = require("flow-enums-runtime")({A: true, B: false});"#,
    );
    assert_strip(
        r#"enum E of symbol {A, B}"#,
        r#"const E = require("flow-enums-runtime")({A: Symbol("A"), B: Symbol("B")});"#,
    );

    assert_strip(
        r#"enum E {}"#,
        r#"const E = require("flow-enums-runtime")({});"#,
    );
    assert_strip(
        r#"enum E of symbol {}"#,
        r#"const E = require("flow-enums-runtime")({});"#,
    );

    assert_strip(
        r#"export enum E {A, B}"#,
        r#"export const E = require("flow-enums-runtime").Mirrored(["A", "B"]);"#,
    );
    assert_strip(
        r#"export default enum E {A, B}"#,
        r#"const E = require("flow-enums-runtime").Mirrored(["A", "B"]); export default E;"#,
    );
}

#[test]
fn this_params() {
    assert_strip(
        r#"
            declare function y (this : string) : void
            function z (this : string) {}
            function u (this : string, ...a) {}

            function v (this : string
                , ...a) {}

            function w (this
            : string

                ,
                ...a) {}

            const f = function(this: string) {}
            const g = function(this: string, ...a) {}
            const h = function(this
            : string,
            ...a) {}
        "#,
        r#"
            function z() {}
            function u(...a) {}
            function v(...a) {}

            function w(...a) {}

            const f = function () {};
            const g = function (...a) {};
            const h = function (...a) {};
        "#,
    );
}

fn parse(input: &str) -> (ast::Context, NodeRc) {
    let mut ctx = ast::Context::new();

    let file_id = ctx
        .sm_mut()
        .add_source("input", NullTerminatedBuf::from_str_copy(input));
    let buf = ctx.sm().source_buffer_rc(file_id);
    let parsed = hparser::ParsedJS::parse(
        hparser::ParserFlags {
            dialect: ParserDialect::Flow,
            ..Default::default()
        },
        &buf,
    );

    assert!(!parsed.has_errors(), "{:?}", parsed.first_error());

    let ast = {
        let gc = ast::GCLock::new(&mut ctx);
        NodeRc::from_node(&gc, parsed.to_ast(&gc, file_id).unwrap())
    };
    drop(parsed);
    (ctx, ast)
}

fn assert_strip(input: &str, expected: &str) {
    let (mut ctx_input, ast_input) = parse(input);

    let pm = PassManager::strip_flow();
    let ast_transformed = pm.run(&mut ctx_input, ast_input);

    let mut transformed_json = vec![];
    dump_json(
        &mut transformed_json,
        &mut ctx_input,
        &ast_transformed,
        ast::Pretty::Yes,
    )
    .unwrap();

    let (mut ctx_expected, ast_expected) = parse(expected);
    let mut expected_json = vec![];
    dump_json(
        &mut expected_json,
        &mut ctx_expected,
        &ast_expected,
        ast::Pretty::Yes,
    )
    .unwrap();

    let mut transformed_js = vec![];
    gen_js::generate(
        &mut transformed_js,
        &mut ctx_input,
        &ast_transformed,
        gen_js::Opt::new(),
    )
    .unwrap();

    let mut expected_js = vec![];
    gen_js::generate(
        &mut expected_js,
        &mut ctx_expected,
        &ast_expected,
        gen_js::Opt::new(),
    )
    .unwrap();

    assert_eq!(
        std::str::from_utf8(&expected_json).unwrap(),
        std::str::from_utf8(&transformed_json).unwrap(),
        "AST mismatch:\n Expected Source:\n{}\nGenerated Source:\n{}",
        std::str::from_utf8(&expected_js).unwrap(),
        std::str::from_utf8(&transformed_js).unwrap()
    );
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

fn main() {
    println!("cargo:rerun-if-changed=../../../../include");
    println!("cargo:rerun-if-changed=../../../../lib");
    println!("cargo:rerun-if-changed=../../../../external");
    println!("cargo:rerun-if-changed=../../../../cmake");
    println!("cargo:rerun-if-changed=../../../../CMakeLists.txt");
    let dst = cmake::Config::new("../../../../")
        .build_target("hermesSupport")
        .build();
    println!(
        "cargo:rustc-link-search={}/build/lib/Support",
        dst.display()
    );
    println!("cargo:rustc-link-lib=hermesSupport");
    println!(
        "cargo:rustc-link-search={}/build/external/dtoa",
        dst.display()
    );
    println!("cargo:rustc-link-lib=dtoa");
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::cell::Cell;
use std::cell::UnsafeCell;
use std::collections::HashMap;
use std::fmt::Formatter;
use std::ptr::null;

use crate::HeapSize;

/// Type used to hold a string index internally.
type NumIndex = u32;

/// A string uniquing table - only one copy of a string is stored and all attempts
/// to add the same string again return the same atom. This table is intended to
/// be easily shareable, so it utilizes interior mutability. UnsafeCell<> is safe
/// because we never allow reference to it to escape.
#[derive(Debug, Default)]
pub struct AtomTable(UnsafeCell<Inner>);

/// A string uniquing table - only one copy of a string is stored and all attempts
/// to add the same string again return the same atom.
#[derive(Default)]
struct Inner {
    /// Strings are added here and never removed or mutated.
    strings: Vec<String>,
    /// Maps from a reference inside [`Inner::strings`] to the index in [`Inner::strings`].
    /// Since strings are never removed or modified, the lifetime of the key
    /// is effectively static.
    map: HashMap<&'static str, NumIndex>,

    /// Strings are added here and never removed or mutated.
    strings_u16: Vec<Vec<u16>>,
    /// Maps from a reference inside [`Inner::strings_u16`] to the index in [`Inner::strings_u16`].
    /// Since strings are never removed or modified, the lifetime of the key
    /// is effectively static.
    map_u16: HashMap<&'static [u16], NumIndex>,
}

impl HeapSize for Inner {
    fn heap_size(&self) -> usize {
        self.strings.heap_size()
            + self.map.heap_size()
            + self.strings_u16.heap_size()
            + self.map_u16.heap_size()
    }
}

/// This represents a unique string index in the table.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct Atom(NumIndex);

/// This represents a unique string index in the table.
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct AtomU16(NumIndex);

thread_local! {
    /// Stores the active table used for debug formatting.
    static DEBUG_TABLE: Cell<* const AtomTable> = Cell::new(null());
}

// An implementation of Debug which optionally obtains the Atom value from the
// active debug map.
impl std::fmt::Debug for Atom {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut t = f.debug_tuple("Atom");
        t.field(&self.0);

        // If the debug table is set and the atom is valid in it, add the value
        DEBUG_TABLE.with(|debug_table| {
            let p = debug_table.get();
            if let Some(r) = unsafe { p.as_ref() } {
                if let Some(value) = r.try_str(*self) {
                    t.field(&value);
                }
            }
        });
        t.finish()
    }
}

// An implementation of Debug which optionally obtains the Atom value from the
// active debug map.
impl std::fmt::Debug for AtomU16 {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let mut t = f.debug_tuple("Atom");
        t.field(&self.0);

        // If the debug table is set and the atom is valid in it, add the value
        DEBUG_TABLE.with(|debug_table| {
            let p = debug_table.get();
            if let Some(r) = unsafe { p.as_ref() } {
                if let Some(value) = r.try_str_u16(*self) {
                    t.field(&value);
                }
            }
        });
        t.finish()
    }
}

/// A special value reserved for the invalid atom.
pub const INVALID_ATOM: Atom = Atom(NumIndex::MAX);

impl Inner {
    /// Add a string to the table and return its atom index. The same
    /// string always returns the same index.
    fn add_atom<V: Into<String> + AsRef<str>>(&mut self, value: V) -> Atom {
        if let Some(index) = self.map.get(value.as_ref()) {
            return Atom(*index);
        }
        self.add(value.into())
    }

    /// Perform the actual addition of the owned string.
    fn add(&mut self, owned: String) -> Atom {
        // Remember the index of the new element.
        let index = self.strings.len();
        assert!(index < INVALID_ATOM.0 as usize, "More than 4GB atoms?");

        // Obtain a reference to the existing string on the heap. That reference
        // is valid while `self` is valid.
        let key: *const str = owned.as_str();

        // Push the new string.
        self.strings.push(owned);

        self.map.insert(unsafe { &*key }, index as NumIndex);
        Atom(index as NumIndex)
    }

    /// Return the contents of the specified atom.
    #[inline]
    fn str(&self, ident: Atom) -> &str {
        self.strings[ident.0 as usize].as_str()
    }

    fn try_str(&self, ident: Atom) -> Option<&str> {
        if (ident.0 as usize) < self.strings.len() {
            Some(self.str(ident))
        } else {
            None
        }
    }

    /// Add a string to the table and return its atom index. The same
    /// string always returns the same index.
    fn add_atom_u16<V: Into<Vec<u16>> + AsRef<[u16]>>(&mut self, value: V) -> AtomU16 {
        if let Some(index) = self.map_u16.get(value.as_ref()) {
            return AtomU16(*index);
        }
        self.add_u16(value.into())
    }

    /// Perform the actual addition of the owned string.
    fn add_u16(&mut self, owned: Vec<u16>) -> AtomU16 {
        // Remember the index of the new element.
        let index = self.strings_u16.len();
        assert!(index < INVALID_ATOM.0 as usize, "More than 4GB atoms?");

        // Obtain a reference to the existing string on the heap. That reference
        // is valid while `self` is valid.
        let key: *const [u16] = owned.as_slice();

        // Push the new string.
        self.strings_u16.push(owned);

        self.map_u16.insert(unsafe { &*key }, index as NumIndex);
        AtomU16(index as NumIndex)
    }

    /// Return the contents of the specified atom.
    #[inline]
    fn str_u16(&self, ident: AtomU16) -> &[u16] {
        self.strings_u16[ident.0 as usize].as_slice()
    }

    fn try_str_u16(&self, ident: AtomU16) -> Option<&[u16]> {
        if (ident.0 as usize) < self.strings_u16.len() {
            Some(self.str_u16(ident))
        } else {
            None
        }
    }
}

impl AtomTable {
    /// Create a new empty atom table.
    pub fn new() -> AtomTable {
        Default::default()
    }

    /// Add a string to the table and return its atom index. The same
    /// string always returns the same index.
    pub fn atom<V: Into<String> + AsRef<str>>(&self, value: V) -> Atom {
        unsafe { &mut *self.0.get() }.add_atom(value)
    }

    /// Return the contents of the specified atom.
    #[inline]
    pub fn str(&self, ident: Atom) -> &str {
        unsafe { &*self.0.get() }.str(ident)
    }

    #[inline]
    pub fn try_str(&self, ident: Atom) -> Option<&str> {
        unsafe { &*self.0.get() }.try_str(ident)
    }

    /// Add a string to the table and return its atom index. The same
    /// string always returns the same index.
    pub fn atom_u16<V: Into<Vec<u16>> + AsRef<[u16]>>(&self, value: V) -> AtomU16 {
        unsafe { &mut *self.0.get() }.add_atom_u16(value)
    }

    /// Return the contents of the specified atom.
    #[inline]
    pub fn str_u16(&self, ident: AtomU16) -> &[u16] {
        unsafe { &*self.0.get() }.str_u16(ident)
    }

    #[inline]
    pub fn try_str_u16(&self, ident: AtomU16) -> Option<&[u16]> {
        unsafe { &*self.0.get() }.try_str_u16(ident)
    }

    /// Execute the callback in a context where this table is used for debug
    /// printing of atoms.
    pub fn in_debug_context<R, F: FnOnce() -> R>(&self, f: F) -> R {
        DEBUG_TABLE.with(|debug_table| {
            let prev_table = debug_table.replace(self);
            let res = f();
            debug_assert!(
                debug_table.get() == self,
                "debug context unexpectedly changed"
            );
            debug_table.set(prev_table);
            res
        })
    }

    /// Set a table or nullptr as the Atom debug context. If non-null, debug
    /// printing of atoms will use it. Return the previous debug context.
    ///
    /// # Safety
    /// The table must not be destroyed or moved while it is set.
    pub unsafe fn unsafe_set_debug_context(ptr: *const Self) -> *const Self {
        DEBUG_TABLE.with(|debug_table| debug_table.replace(ptr))
    }
}

impl HeapSize for AtomTable {
    fn heap_size(&self) -> usize {
        unsafe { &*self.0.get() }.heap_size()
    }
}

impl std::ops::Index<Atom> for AtomTable {
    type Output = str;

    fn index(&self, index: Atom) -> &Self::Output {
        self.str(index)
    }
}

impl std::ops::Index<AtomU16> for AtomTable {
    type Output = [u16];

    fn index(&self, index: AtomU16) -> &Self::Output {
        self.str_u16(index)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_tab() {
        let idtab = AtomTable::new();

        let id_foo = idtab.atom("foo");
        let p_foo: *const str = idtab.str(id_foo);
        let id_bar = idtab.atom("bar");
        assert_ne!(id_foo, id_bar);

        assert_eq!(idtab.atom("foo"), id_foo);
        assert_eq!(idtab.atom("bar"), id_bar);

        assert_eq!(idtab.atom(String::from("foo")), id_foo);
        assert_eq!(idtab.atom(String::from("bar")), id_bar);

        assert_eq!(idtab.str(id_foo), "foo");
        assert_eq!(idtab.str(id_bar), "bar");

        assert_eq!(idtab.str(id_foo) as *const str, p_foo);
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

/// Converts a `snake_case` ASCII input string to `camelCase`.
pub fn ascii_snake_to_camel(input: &str) -> String {
    debug_assert!(input.is_ascii(), "no Unicode support");
    let mut output = String::new();
    let mut need_upper = false;
    for c in input.chars() {
        match c {
            '_' => {
                need_upper = true;
            }
            _ => {
                output.push(if need_upper {
                    c.to_ascii_uppercase()
                } else {
                    c
                });
                need_upper = false;
            }
        }
    }
    output
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::mem;

extern "C" {
    /// \param m the number to convert
    /// \param dest output buffer
    /// \param destSize size of dest, at least NUMBER_TO_STRING_BUF_SIZE
    /// \return the length of the generated string (excluding the terminating zero).
    fn hermes_numberToString(m: f64, dest: *mut u8, destSize: usize) -> usize;
}

/// Convert a double number to string, following ES5.1 9.8.1.
pub fn number_to_string(m: f64) -> String {
    // Size of buffer that must be passed to hermes_numberToString.
    const NUMBER_TO_STRING_BUF_SIZE: usize = 32;

    unsafe {
        // Create a mutable buffer but don't deallocate it,
        // because we will build a new `String` from it.
        let mut buf = mem::ManuallyDrop::new(vec![0u8; NUMBER_TO_STRING_BUF_SIZE]);

        let length = hermes_numberToString(m, buf.as_mut_ptr(), NUMBER_TO_STRING_BUF_SIZE);
        String::from_raw_parts(buf.as_mut_ptr(), length, NUMBER_TO_STRING_BUF_SIZE)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_number_to_string() {
        assert_eq!(number_to_string(1.0), "1");
    }
}