<commit_before>import common::*;
import lib::llvm::{TypeRef};
import syntax::ast;
import lib::llvm::llvm;
import driver::session::session;
import std::map::hashmap;

export type_of;
export type_of_dtor;
export type_of_explicit_args;
export type_of_fn_from_ty;
export type_of_fn;
export type_of_non_gc_box;

fn type_of_explicit_args(cx: @crate_ctxt,
                         inputs: ~[ty::arg]) -> ~[TypeRef] {
    do vec::map(inputs) |arg| {
        let arg_ty = arg.ty;
        let llty = type_of(cx, arg_ty);
        match ty::resolved_mode(cx.tcx, arg.mode) {
          ast::by_val => llty,
          _ => T_ptr(llty)
        }
    }
}

fn type_of_fn(cx: @crate_ctxt, inputs: ~[ty::arg],
              output: ty::t) -> TypeRef {
    let mut atys: ~[TypeRef] = ~[];

    // Arg 0: Output pointer.
    vec::push(atys, T_ptr(type_of(cx, output)));

    // Arg 1: Environment
    vec::push(atys, T_opaque_box_ptr(cx));

    // ... then explicit args.
    vec::push_all(atys, type_of_explicit_args(cx, inputs));
    return T_fn(atys, llvm::LLVMVoidType());
}

// Given a function type and a count of ty params, construct an llvm type
fn type_of_fn_from_ty(cx: @crate_ctxt, fty: ty::t) -> TypeRef {
    type_of_fn(cx, ty::ty_fn_args(fty), ty::ty_fn_ret(fty))
}

fn type_of_non_gc_box(cx: @crate_ctxt, t: ty::t) -> TypeRef {
    assert !ty::type_needs_infer(t);

    let t_norm = ty::normalize_ty(cx.tcx, t);
    if t != t_norm {
        type_of_non_gc_box(cx, t_norm)
    } else {
        match ty::get(t).struct {
          ty::ty_box(mt) => {
            T_ptr(T_box(cx, type_of(cx, mt.ty)))
          }
          ty::ty_uniq(mt) => {
            T_ptr(T_unique(cx, type_of(cx, mt.ty)))
          }
          _ => {
            cx.sess.bug(~"non-box in type_of_non_gc_box");
          }
        }
    }
}

fn type_of(cx: @crate_ctxt, t: ty::t) -> TypeRef {
    debug!{"type_of %?: %?", t, ty::get(t)};

    // Check the cache.
    if cx.lltypes.contains_key(t) { return cx.lltypes.get(t); }

    // Replace any typedef'd types with their equivalent non-typedef
    // type. This ensures that all LLVM nominal types that contain
    // Rust types are defined as the same LLVM types.  If we don't do
    // this then, e.g. `option<{myfield: bool}>` would be a different
    // type than `option<myrec>`.
    let t_norm = ty::normalize_ty(cx.tcx, t);

    let mut llty;
    if t != t_norm {
        llty = type_of(cx, t_norm);
        cx.lltypes.insert(t, llty);
    } else {
        llty = match ty::get(t).struct {
          ty::ty_nil | ty::ty_bot => T_nil(),
          ty::ty_bool => T_bool(),
          ty::ty_int(t) => T_int_ty(cx, t),
          ty::ty_uint(t) => T_uint_ty(cx, t),
          ty::ty_float(t) => T_float_ty(cx, t),
          ty::ty_estr(ty::vstore_uniq) => {
            T_unique_ptr(T_unique(cx, T_vec(cx, T_i8())))
          }
          ty::ty_enum(did, _) => type_of_enum(cx, did, t),
          ty::ty_estr(ty::vstore_box) => {
            T_box_ptr(T_box(cx, T_vec(cx, T_i8())))
          }
          ty::ty_evec(mt, ty::vstore_box) => {
            T_box_ptr(T_box(cx, T_vec(cx, type_of(cx, mt.ty))))
          }
          ty::ty_box(mt) => T_box_ptr(T_box(cx, type_of(cx, mt.ty))),
          ty::ty_opaque_box => T_box_ptr(T_box(cx, T_i8())),
          ty::ty_uniq(mt) => T_unique_ptr(T_unique(cx, type_of(cx, mt.ty))),
          ty::ty_evec(mt, ty::vstore_uniq) => {
            T_unique_ptr(T_unique(cx, T_vec(cx, type_of(cx, mt.ty))))
          }
          ty::ty_unboxed_vec(mt) => {
            T_vec(cx, type_of(cx, mt.ty))
          }
          ty::ty_ptr(mt) => T_ptr(type_of(cx, mt.ty)),
          ty::ty_rptr(_, mt) => T_ptr(type_of(cx, mt.ty)),

          ty::ty_evec(mt, ty::vstore_slice(_)) => {
            T_struct(~[T_ptr(type_of(cx, mt.ty)),
                      T_uint_ty(cx, ast::ty_u)])
          }

          ty::ty_estr(ty::vstore_slice(_)) => {
            T_struct(~[T_ptr(T_i8()),
                      T_uint_ty(cx, ast::ty_u)])
          }

          ty::ty_estr(ty::vstore_fixed(n)) => {
            T_array(T_i8(), n + 1u /* +1 for trailing null */)
          }

          ty::ty_evec(mt, ty::vstore_fixed(n)) => {
            T_array(type_of(cx, mt.ty), n)
          }

          ty::ty_rec(fields) => {
            let mut tys: ~[TypeRef] = ~[];
            for vec::each(fields) |f| {
                let mt_ty = f.mt.ty;
                vec::push(tys, type_of(cx, mt_ty));
            }
            T_struct(tys)
          }
          ty::ty_fn(_) => T_fn_pair(cx, type_of_fn_from_ty(cx, t)),
          ty::ty_trait(_, _) => T_opaque_trait(cx),
          ty::ty_type => T_ptr(cx.tydesc_type),
          ty::ty_tup(elts) => {
            let mut tys = ~[];
            for vec::each(elts) |elt| {
                vec::push(tys, type_of(cx, elt));
            }
            T_struct(tys)
          }
          ty::ty_opaque_closure_ptr(_) => T_opaque_box_ptr(cx),
          ty::ty_class(*) => {
            // Only create the named struct, but don't fill it in. We fill it
            // in *after* placing it into the type cache. This prevents
            // infinite recursion with recursive class types.

            common::T_named_struct(llvm_type_name(cx, t))
          }
          ty::ty_self => cx.tcx.sess.unimpl(~"type_of: ty_self"),
          ty::ty_var(_) => cx.tcx.sess.bug(~"type_of with ty_var"),
          ty::ty_param(*) => cx.tcx.sess.bug(~"type_of with ty_param"),
          ty::ty_var_integral(_) => {
            cx.tcx.sess.bug(~"type_of shouldn't see a ty_var_integral");
          }
        };

        cx.lltypes.insert(t, llty);

        // If this was a class, fill in the type now.
        match ty::get(t).struct {
          ty::ty_class(did, ts) => {
            // Only instance vars are record fields at runtime.
            let fields = ty::lookup_class_fields(cx.tcx, did);
            let mut tys = do vec::map(fields) |f| {
                let t = ty::lookup_field_type(cx.tcx, did, f.id, ts);
                type_of(cx, t)
            };

            if ty::ty_dtor(cx.tcx, did) != none {
              // resource type
              tys = ~[T_i8(), T_struct(tys)];
            }

            common::set_struct_body(llty, tys);
          }
          _ => {
            // Nothing more to do.
          }
        }
    };

    return llty;
}

// This should only be called from type_of, above, because it
// creates new llvm named struct types lazily that are then
// cached by type_of
fn type_of_enum(cx: @crate_ctxt, did: ast::def_id, t: ty::t)
    -> TypeRef {

    debug!{"type_of_enum %?: %?", t, ty::get(t)};

    // Every enum type has a unique name. When we find our roots
    // for GC and unwinding we will use this name to rediscover
    // the Rust type
    let name = llvm_type_name(cx, t);

    let named_llty = common::T_named_struct(name);

    let lltys = {
        let degen = (*ty::enum_variants(cx.tcx, did)).len() == 1u;
        let size = shape::static_size_of_enum(cx, t);
        if !degen {
            ~[T_enum_discrim(cx), T_array(T_i8(), size)]
        }
        else if size == 0u {
            ~[T_enum_discrim(cx)]
        }
        else {
            ~[T_array(T_i8(), size)]
        }
    };

    common::set_struct_body(named_llty, lltys);
    return named_llty;
}

fn llvm_type_name(cx: @crate_ctxt, t: ty::t) -> ~str {
    let (name, did, tps) = match check ty::get(t).struct {
      ty::ty_enum(did, substs) => (~"enum", did, substs.tps),
      ty::ty_class(did, substs) => (~"class", did, substs.tps)
    };
    return fmt!{
        "%s %s[#%d]",
        name,
        util::ppaux::parameterized(
            cx.tcx,
            ty::item_path_str(cx.tcx, did),
            none,
            tps),
        did.crate
    };
}

fn type_of_dtor(ccx: @crate_ctxt, self_ty: ty::t) -> TypeRef {
    T_fn(~[T_ptr(type_of(ccx, ty::mk_nil(ccx.tcx))),
          T_ptr(type_of(ccx, self_ty))],
         llvm::LLVMVoidType())
}

<commit_msg>rustc: Fix for type_of on recursive enum creating two types instead of one.<commit_after>import common::*;
import lib::llvm::{TypeRef};
import syntax::ast;
import lib::llvm::llvm;
import driver::session::session;
import std::map::hashmap;

export type_of;
export type_of_dtor;
export type_of_explicit_args;
export type_of_fn_from_ty;
export type_of_fn;
export type_of_non_gc_box;

fn type_of_explicit_args(cx: @crate_ctxt,
                         inputs: ~[ty::arg]) -> ~[TypeRef] {
    do vec::map(inputs) |arg| {
        let arg_ty = arg.ty;
        let llty = type_of(cx, arg_ty);
        match ty::resolved_mode(cx.tcx, arg.mode) {
          ast::by_val => llty,
          _ => T_ptr(llty)
        }
    }
}

fn type_of_fn(cx: @crate_ctxt, inputs: ~[ty::arg],
              output: ty::t) -> TypeRef {
    let mut atys: ~[TypeRef] = ~[];

    // Arg 0: Output pointer.
    vec::push(atys, T_ptr(type_of(cx, output)));

    // Arg 1: Environment
    vec::push(atys, T_opaque_box_ptr(cx));

    // ... then explicit args.
    vec::push_all(atys, type_of_explicit_args(cx, inputs));
    return T_fn(atys, llvm::LLVMVoidType());
}

// Given a function type and a count of ty params, construct an llvm type
fn type_of_fn_from_ty(cx: @crate_ctxt, fty: ty::t) -> TypeRef {
    type_of_fn(cx, ty::ty_fn_args(fty), ty::ty_fn_ret(fty))
}

fn type_of_non_gc_box(cx: @crate_ctxt, t: ty::t) -> TypeRef {
    assert !ty::type_needs_infer(t);

    let t_norm = ty::normalize_ty(cx.tcx, t);
    if t != t_norm {
        type_of_non_gc_box(cx, t_norm)
    } else {
        match ty::get(t).struct {
          ty::ty_box(mt) => {
            T_ptr(T_box(cx, type_of(cx, mt.ty)))
          }
          ty::ty_uniq(mt) => {
            T_ptr(T_unique(cx, type_of(cx, mt.ty)))
          }
          _ => {
            cx.sess.bug(~"non-box in type_of_non_gc_box");
          }
        }
    }
}

fn type_of(cx: @crate_ctxt, t: ty::t) -> TypeRef {
    debug!{"type_of %?: %?", t, ty::get(t)};

    // Check the cache.
    if cx.lltypes.contains_key(t) { return cx.lltypes.get(t); }

    // Replace any typedef'd types with their equivalent non-typedef
    // type. This ensures that all LLVM nominal types that contain
    // Rust types are defined as the same LLVM types.  If we don't do
    // this then, e.g. `option<{myfield: bool}>` would be a different
    // type than `option<myrec>`.
    let t_norm = ty::normalize_ty(cx.tcx, t);

    let mut llty;
    if t != t_norm {
        llty = type_of(cx, t_norm);
        cx.lltypes.insert(t, llty);
    } else {
        llty = match ty::get(t).struct {
          ty::ty_nil | ty::ty_bot => T_nil(),
          ty::ty_bool => T_bool(),
          ty::ty_int(t) => T_int_ty(cx, t),
          ty::ty_uint(t) => T_uint_ty(cx, t),
          ty::ty_float(t) => T_float_ty(cx, t),
          ty::ty_estr(ty::vstore_uniq) => {
            T_unique_ptr(T_unique(cx, T_vec(cx, T_i8())))
          }
          ty::ty_enum(did, _) => {
            // Only create the named struct, but don't fill it in. We
            // fill it in *after* placing it into the type cache. This
            // avoids creating more than one copy of the enum when one
            // of the enum's variants refers to the enum itself.

            common::T_named_struct(llvm_type_name(cx, t))
          }
          ty::ty_estr(ty::vstore_box) => {
            T_box_ptr(T_box(cx, T_vec(cx, T_i8())))
          }
          ty::ty_evec(mt, ty::vstore_box) => {
            T_box_ptr(T_box(cx, T_vec(cx, type_of(cx, mt.ty))))
          }
          ty::ty_box(mt) => T_box_ptr(T_box(cx, type_of(cx, mt.ty))),
          ty::ty_opaque_box => T_box_ptr(T_box(cx, T_i8())),
          ty::ty_uniq(mt) => T_unique_ptr(T_unique(cx, type_of(cx, mt.ty))),
          ty::ty_evec(mt, ty::vstore_uniq) => {
            T_unique_ptr(T_unique(cx, T_vec(cx, type_of(cx, mt.ty))))
          }
          ty::ty_unboxed_vec(mt) => {
            T_vec(cx, type_of(cx, mt.ty))
          }
          ty::ty_ptr(mt) => T_ptr(type_of(cx, mt.ty)),
          ty::ty_rptr(_, mt) => T_ptr(type_of(cx, mt.ty)),

          ty::ty_evec(mt, ty::vstore_slice(_)) => {
            T_struct(~[T_ptr(type_of(cx, mt.ty)),
                      T_uint_ty(cx, ast::ty_u)])
          }

          ty::ty_estr(ty::vstore_slice(_)) => {
            T_struct(~[T_ptr(T_i8()),
                      T_uint_ty(cx, ast::ty_u)])
          }

          ty::ty_estr(ty::vstore_fixed(n)) => {
            T_array(T_i8(), n + 1u /* +1 for trailing null */)
          }

          ty::ty_evec(mt, ty::vstore_fixed(n)) => {
            T_array(type_of(cx, mt.ty), n)
          }

          ty::ty_rec(fields) => {
            let mut tys: ~[TypeRef] = ~[];
            for vec::each(fields) |f| {
                let mt_ty = f.mt.ty;
                vec::push(tys, type_of(cx, mt_ty));
            }
            T_struct(tys)
          }
          ty::ty_fn(_) => T_fn_pair(cx, type_of_fn_from_ty(cx, t)),
          ty::ty_trait(_, _) => T_opaque_trait(cx),
          ty::ty_type => T_ptr(cx.tydesc_type),
          ty::ty_tup(elts) => {
            let mut tys = ~[];
            for vec::each(elts) |elt| {
                vec::push(tys, type_of(cx, elt));
            }
            T_struct(tys)
          }
          ty::ty_opaque_closure_ptr(_) => T_opaque_box_ptr(cx),
          ty::ty_class(*) => {
            // Only create the named struct, but don't fill it in. We fill it
            // in *after* placing it into the type cache. This prevents
            // infinite recursion with recursive class types.

            common::T_named_struct(llvm_type_name(cx, t))
          }
          ty::ty_self => cx.tcx.sess.unimpl(~"type_of: ty_self"),
          ty::ty_var(_) => cx.tcx.sess.bug(~"type_of with ty_var"),
          ty::ty_param(*) => cx.tcx.sess.bug(~"type_of with ty_param"),
          ty::ty_var_integral(_) => {
            cx.tcx.sess.bug(~"type_of shouldn't see a ty_var_integral");
          }
        };

        cx.lltypes.insert(t, llty);

        // If this was an enum or class, fill in the type now.
        match ty::get(t).struct {
          ty::ty_enum(did, _) => {
              fill_type_of_enum(cx, did, t, llty);
          }
          ty::ty_class(did, ts) => {
            // Only instance vars are record fields at runtime.
            let fields = ty::lookup_class_fields(cx.tcx, did);
            let mut tys = do vec::map(fields) |f| {
                let t = ty::lookup_field_type(cx.tcx, did, f.id, ts);
                type_of(cx, t)
            };

            if ty::ty_dtor(cx.tcx, did) != none {
              // resource type
              tys = ~[T_i8(), T_struct(tys)];
            }

            common::set_struct_body(llty, tys);
          }
          _ => {
            // Nothing more to do.
          }
        }
    };

    return llty;
}

fn fill_type_of_enum(cx: @crate_ctxt, did: ast::def_id, t: ty::t,
                     llty: TypeRef) {

    debug!{"type_of_enum %?: %?", t, ty::get(t)};

    let lltys = {
        let degen = (*ty::enum_variants(cx.tcx, did)).len() == 1u;
        let size = shape::static_size_of_enum(cx, t);
        if !degen {
            ~[T_enum_discrim(cx), T_array(T_i8(), size)]
        }
        else if size == 0u {
            ~[T_enum_discrim(cx)]
        }
        else {
            ~[T_array(T_i8(), size)]
        }
    };

    common::set_struct_body(llty, lltys);
}

fn llvm_type_name(cx: @crate_ctxt, t: ty::t) -> ~str {
    let (name, did, tps) = match check ty::get(t).struct {
      ty::ty_enum(did, substs) => (~"enum", did, substs.tps),
      ty::ty_class(did, substs) => (~"class", did, substs.tps)
    };
    return fmt!{
        "%s %s[#%d]",
        name,
        util::ppaux::parameterized(
            cx.tcx,
            ty::item_path_str(cx.tcx, did),
            none,
            tps),
        did.crate
    };
}

fn type_of_dtor(ccx: @crate_ctxt, self_ty: ty::t) -> TypeRef {
    T_fn(~[T_ptr(type_of(ccx, ty::mk_nil(ccx.tcx))),
          T_ptr(type_of(ccx, self_ty))],
         llvm::LLVMVoidType())
}

<|endoftext|>


<commit_before>#[doc="
    The content task is the main task that runs JavaScript and spawns layout
    tasks.
"]

export ContentTask;
export ControlMsg, ExecuteMsg, ParseMsg, ExitMsg, Timer;
export PingMsg, PongMsg;

import std::arc::{arc, clone};
import comm::{Port, Chan, port, chan, listen, select2};
import task::{spawn, spawn_listener};
import io::{read_whole_file, println};
import result::{ok, err};

import dom::base::{Document, Node, NodeScope, Window, define_bindings};
import dom::event::{Event, ResizeEvent};
import dom::style;
import dom::style::Stylesheet;
import gfx::compositor::Compositor;
import parser::html_lexer::spawn_html_lexer_task;
import parser::html_builder::build_dom;
import layout::layout_task;
import layout_task::{LayoutTask, BuildMsg};

import jsrt = js::rust::rt;
import js::rust::{cx, methods};
import js::global::{global_class, debug_fns};

import either::{Either, Left, Right};

import dom::bindings::utils::rust_box;
import js::rust::compartment;

import resource::resource_task;
import resource_task::{ResourceTask};

import std::net::url::url;
import url_to_str = std::net::url::to_str;
import util::url::make_url;
import task::{task, SingleThreaded};

import js::glue::bindgen::RUST_JSVAL_TO_OBJECT;
import js::JSVAL_NULL;
import js::jsapi::jsval;
import js::jsapi::bindgen::JS_CallFunctionValue;
import ptr::null;

enum ControlMsg {
    ParseMsg(url),
    ExecuteMsg(url),
    Timer(~dom::bindings::window::TimerData),
    ExitMsg
}

enum PingMsg {
    PongMsg
}

type ContentTask = Chan<ControlMsg>;

fn ContentTask<S: Compositor send copy>(layout_task: LayoutTask, +compositor: S, resource_task: ResourceTask) -> ContentTask {
    do task().sched_mode(SingleThreaded).spawn_listener::<ControlMsg> |from_master| {
        Content(layout_task, compositor, from_master, resource_task).start();
    }
}

#[doc="Sends a ping to layout and waits for the response."]
#[allow(non_implicitly_copyable_typarams)]
fn join_layout(scope: NodeScope, layout_task: LayoutTask) {

    if scope.is_reader_forked() {
        listen(|response_from_layout| {
            layout_task.send(layout_task::PingMsg(response_from_layout));
            response_from_layout.recv();
        });
        scope.reader_joined();
    }
}

struct Content<C:Compositor> {
    let compositor: C;
    let layout_task: LayoutTask;
    let from_master: comm::Port<ControlMsg>;
    let event_port: comm::Port<Event>;

    let scope: NodeScope;
    let jsrt: jsrt;
    let cx: cx;

    let mut document: option<@Document>;
    let mut window:   option<@Window>;
    let mut doc_url: option<url>;

    let resource_task: ResourceTask;

    let compartment: option<compartment>;

    new(layout_task: LayoutTask, +compositor: C, from_master: Port<ControlMsg>,
        resource_task: ResourceTask) {
        self.layout_task = layout_task;
        self.compositor = compositor;
        self.from_master = from_master;
        self.event_port = port();

        self.scope = NodeScope();
        self.jsrt = jsrt();
        self.cx = self.jsrt.cx();

        self.document = none;
        self.window   = none;
        self.doc_url  = none;

        self.compositor.add_event_listener(self.event_port.chan());

        self.resource_task = resource_task;

        self.cx.set_default_options_and_version();
        self.cx.set_logging_error_reporter();
        self.compartment = match self.cx.new_compartment(global_class) {
          ok(c) => some(c),
          err(()) => none
        };
    }

    fn start() {
        while self.handle_msg(select2(self.from_master, self.event_port)) {
            // Go on...
        }
    }

    fn handle_msg(msg: Either<ControlMsg,Event>) -> bool {
        match msg {
            Left(control_msg) => self.handle_control_msg(control_msg),
            Right(event) => self.handle_event(event)
        }
    }

    fn handle_control_msg(control_msg: ControlMsg) -> bool {
        match control_msg {
          ParseMsg(url) => {
            #debug["content: Received url `%s` to parse", url_to_str(url)];

            // Note: we can parse the next document in parallel
            // with any previous documents.
            let stream = spawn_html_lexer_task(copy url, self.resource_task);
            let (root, style_port, js_port) = build_dom(self.scope, stream, url,
                                                        self.resource_task);

            let css_rules = style_port.recv();
            let js_scripts = js_port.recv();

            let css_rules = ~[];
            let js_scripts = ~[];

            // Apply the css rules to the dom tree:
            #debug["css_rules: %?", css_rules];

            #debug["js_scripts: %?", js_scripts];

            let document = Document(root, self.scope, css_rules);
            let window   = Window(self.from_master);
            self.relayout(document, &url);
            self.document = some(@document);
            self.window   = some(@window);
            self.doc_url = some(copy url);

            let compartment = option::expect(self.compartment, ~"TODO error checking");
            compartment.define_functions(debug_fns);
            define_bindings(*compartment,
                            option::get(self.document),
                            option::get(self.window));

            for vec::each(js_scripts) |bytes| {
                self.cx.evaluate_script(compartment.global_obj, bytes, ~"???", 1u);
            }

            return true;
          }

          Timer(timerData) => {
            let compartment = option::expect(self.compartment, ~"TODO error checking");
            let thisValue = if timerData.args.len() > 0 {
                RUST_JSVAL_TO_OBJECT(unsafe { timerData.args.shift() })
            } else {
                compartment.global_obj.ptr
            };
            let _rval = JSVAL_NULL;
            //TODO: support extra args. requires passing a *jsval argv
            JS_CallFunctionValue(self.cx.ptr, thisValue, timerData.funval,
                                 0, null(), ptr::addr_of(_rval));
            self.relayout(*option::get(self.document), &option::get(self.doc_url));
            return true;
          }

          ExecuteMsg(url) => {
            #debug["content: Received url `%s` to execute", url_to_str(url)];

            match read_whole_file(url.path) {
              err(msg) => {
                println(#fmt["Error opening %s: %s", url_to_str(url), msg]);
              }
              ok(bytes) => {
                let compartment = option::expect(self.compartment, ~"TODO error checking");
                compartment.define_functions(debug_fns);
                self.cx.evaluate_script(compartment.global_obj, bytes, url.path, 1u);
              }
            }
            return true;
          }

          ExitMsg => {
            self.layout_task.send(layout_task::ExitMsg);
            return false;
          }
        }
    }

    fn relayout(document: Document, doc_url: &url) {
        #debug("content: performing relayout");

        // Now, join the layout so that they will see the latest
        // changes we have made.
        join_layout(self.scope, self.layout_task);

        // Send new document and relevant styles to layout
        // FIXME: Put CSS rules in an arc or something.
        self.layout_task.send(BuildMsg(document.root, clone(&document.css_rules), copy *doc_url, self.event_port.chan()));

        // Indicate that reader was forked so any further
        // changes will be isolated.
        self.scope.reader_forked();
    }

    fn handle_event(event: Event) -> bool {
        match event {
          ResizeEvent(new_width, new_height) => {
            #debug("content got resize event: %d, %d", new_width, new_height);
            match copy self.document {
                none => {
                    // Nothing to do.
                }
                some(document) => {
                    assert self.doc_url.is_some();
                    self.relayout(*document, &self.doc_url.get());
                }
            }
            return true;
          }
          ReflowEvent => {
            #debug("content got reflow event");
            match copy self.document {
                none => {
                    // Nothing to do.
                }
                some(document) => {
                    assert self.doc_url.is_some();
                    self.relayout(*document, &self.doc_url.get());
                }
            }
            return true;
          }
        }
    }
}
<commit_msg>Make CSS and JS work again.<commit_after>#[doc="
    The content task is the main task that runs JavaScript and spawns layout
    tasks.
"]

export ContentTask;
export ControlMsg, ExecuteMsg, ParseMsg, ExitMsg, Timer;
export PingMsg, PongMsg;

import std::arc::{arc, clone};
import comm::{Port, Chan, port, chan, listen, select2};
import task::{spawn, spawn_listener};
import io::{read_whole_file, println};
import result::{ok, err};

import dom::base::{Document, Node, NodeScope, Window, define_bindings};
import dom::event::{Event, ResizeEvent};
import dom::style;
import dom::style::Stylesheet;
import gfx::compositor::Compositor;
import parser::html_lexer::spawn_html_lexer_task;
import parser::html_builder::build_dom;
import layout::layout_task;
import layout_task::{LayoutTask, BuildMsg};

import jsrt = js::rust::rt;
import js::rust::{cx, methods};
import js::global::{global_class, debug_fns};

import either::{Either, Left, Right};

import dom::bindings::utils::rust_box;
import js::rust::compartment;

import resource::resource_task;
import resource_task::{ResourceTask};

import std::net::url::url;
import url_to_str = std::net::url::to_str;
import util::url::make_url;
import task::{task, SingleThreaded};

import js::glue::bindgen::RUST_JSVAL_TO_OBJECT;
import js::JSVAL_NULL;
import js::jsapi::jsval;
import js::jsapi::bindgen::JS_CallFunctionValue;
import ptr::null;

enum ControlMsg {
    ParseMsg(url),
    ExecuteMsg(url),
    Timer(~dom::bindings::window::TimerData),
    ExitMsg
}

enum PingMsg {
    PongMsg
}

type ContentTask = Chan<ControlMsg>;

fn ContentTask<S: Compositor send copy>(layout_task: LayoutTask, +compositor: S, resource_task: ResourceTask) -> ContentTask {
    do task().sched_mode(SingleThreaded).spawn_listener::<ControlMsg> |from_master| {
        Content(layout_task, compositor, from_master, resource_task).start();
    }
}

#[doc="Sends a ping to layout and waits for the response."]
#[allow(non_implicitly_copyable_typarams)]
fn join_layout(scope: NodeScope, layout_task: LayoutTask) {

    if scope.is_reader_forked() {
        listen(|response_from_layout| {
            layout_task.send(layout_task::PingMsg(response_from_layout));
            response_from_layout.recv();
        });
        scope.reader_joined();
    }
}

struct Content<C:Compositor> {
    let compositor: C;
    let layout_task: LayoutTask;
    let from_master: comm::Port<ControlMsg>;
    let event_port: comm::Port<Event>;

    let scope: NodeScope;
    let jsrt: jsrt;
    let cx: cx;

    let mut document: option<@Document>;
    let mut window:   option<@Window>;
    let mut doc_url: option<url>;

    let resource_task: ResourceTask;

    let compartment: option<compartment>;

    new(layout_task: LayoutTask, +compositor: C, from_master: Port<ControlMsg>,
        resource_task: ResourceTask) {
        self.layout_task = layout_task;
        self.compositor = compositor;
        self.from_master = from_master;
        self.event_port = port();

        self.scope = NodeScope();
        self.jsrt = jsrt();
        self.cx = self.jsrt.cx();

        self.document = none;
        self.window   = none;
        self.doc_url  = none;

        self.compositor.add_event_listener(self.event_port.chan());

        self.resource_task = resource_task;

        self.cx.set_default_options_and_version();
        self.cx.set_logging_error_reporter();
        self.compartment = match self.cx.new_compartment(global_class) {
          ok(c) => some(c),
          err(()) => none
        };
    }

    fn start() {
        while self.handle_msg(select2(self.from_master, self.event_port)) {
            // Go on...
        }
    }

    fn handle_msg(msg: Either<ControlMsg,Event>) -> bool {
        match msg {
            Left(control_msg) => self.handle_control_msg(control_msg),
            Right(event) => self.handle_event(event)
        }
    }

    fn handle_control_msg(control_msg: ControlMsg) -> bool {
        match control_msg {
          ParseMsg(url) => {
            #debug["content: Received url `%s` to parse", url_to_str(url)];

            // Note: we can parse the next document in parallel
            // with any previous documents.
            let stream = spawn_html_lexer_task(copy url, self.resource_task);
            let (root, style_port, js_port) = build_dom(self.scope, stream, url,
                                                        self.resource_task);

            let css_rules = style_port.recv();
            let js_scripts = js_port.recv();

            // Apply the css rules to the dom tree:
            #debug["css_rules: %?", css_rules];

            #debug["js_scripts: %?", js_scripts];

            let document = Document(root, self.scope, css_rules);
            let window   = Window(self.from_master);
            self.relayout(document, &url);
            self.document = some(@document);
            self.window   = some(@window);
            self.doc_url = some(copy url);

            let compartment = option::expect(self.compartment, ~"TODO error checking");
            compartment.define_functions(debug_fns);
            define_bindings(*compartment,
                            option::get(self.document),
                            option::get(self.window));

            for vec::each(js_scripts) |bytes| {
                self.cx.evaluate_script(compartment.global_obj, bytes, ~"???", 1u);
            }

            return true;
          }

          Timer(timerData) => {
            let compartment = option::expect(self.compartment, ~"TODO error checking");
            let thisValue = if timerData.args.len() > 0 {
                RUST_JSVAL_TO_OBJECT(unsafe { timerData.args.shift() })
            } else {
                compartment.global_obj.ptr
            };
            let _rval = JSVAL_NULL;
            //TODO: support extra args. requires passing a *jsval argv
            JS_CallFunctionValue(self.cx.ptr, thisValue, timerData.funval,
                                 0, null(), ptr::addr_of(_rval));
            self.relayout(*option::get(self.document), &option::get(self.doc_url));
            return true;
          }

          ExecuteMsg(url) => {
            #debug["content: Received url `%s` to execute", url_to_str(url)];

            match read_whole_file(url.path) {
              err(msg) => {
                println(#fmt["Error opening %s: %s", url_to_str(url), msg]);
              }
              ok(bytes) => {
                let compartment = option::expect(self.compartment, ~"TODO error checking");
                compartment.define_functions(debug_fns);
                self.cx.evaluate_script(compartment.global_obj, bytes, url.path, 1u);
              }
            }
            return true;
          }

          ExitMsg => {
            self.layout_task.send(layout_task::ExitMsg);
            return false;
          }
        }
    }

    fn relayout(document: Document, doc_url: &url) {
        #debug("content: performing relayout");

        // Now, join the layout so that they will see the latest
        // changes we have made.
        join_layout(self.scope, self.layout_task);

        // Send new document and relevant styles to layout
        // FIXME: Put CSS rules in an arc or something.
        self.layout_task.send(BuildMsg(document.root, clone(&document.css_rules), copy *doc_url, self.event_port.chan()));

        // Indicate that reader was forked so any further
        // changes will be isolated.
        self.scope.reader_forked();
    }

    fn handle_event(event: Event) -> bool {
        match event {
          ResizeEvent(new_width, new_height) => {
            #debug("content got resize event: %d, %d", new_width, new_height);
            match copy self.document {
                none => {
                    // Nothing to do.
                }
                some(document) => {
                    assert self.doc_url.is_some();
                    self.relayout(*document, &self.doc_url.get());
                }
            }
            return true;
          }
          ReflowEvent => {
            #debug("content got reflow event");
            match copy self.document {
                none => {
                    // Nothing to do.
                }
                some(document) => {
                    assert self.doc_url.is_some();
                    self.relayout(*document, &self.doc_url.get());
                }
            }
            return true;
          }
        }
    }
}
<|endoftext|>


<commit_before>use std::thread;
use std::cell::UnsafeCell;
use std::sync::Arc;
use std::sync::mpsc::{channel, Sender, Receiver, RecvError};
use basetypes::*;
use chess_move::*;
use tt::*;
use position::Position;

pub enum Command {
    Search {
        search_id: usize,
        position: Position,
        depth: u8,
        lower_bound: Value,
        upper_bound: Value,
    },
    Stop,
    Exit,
}

pub enum Report {
    Progress {
        search_id: usize,
        searched_nodes: NodeCount,
        depth: u8,
    },
    Done {
        search_id: usize,
        searched_nodes: NodeCount,
        value: Option<Value>,
    },
}

pub fn run_deepening(tt: Arc<TranspositionTable>,
                     commands: Receiver<Command>,
                     reports: Sender<Report>) {
    let (slave_commands_tx, slave_commands_rx) = channel();
    let (slave_reports_tx, slave_reports_rx) = channel();
    let slave = thread::spawn(move || {
        run(tt, slave_commands_rx, slave_reports_tx);
    });
    let mut pending_command = None;
    loop {
        // If there is a pending command, we take it, otherwise we
        // block and wait to receive a new one.
        let command = match pending_command.take() {
            Some(cmd) => cmd,
            None => commands.recv().or::<RecvError>(Ok(Command::Exit)).unwrap(),
        };

        match command {
            Command::Search { search_id, position, depth, lower_bound, upper_bound } => {
                let mut searched_nodes_final = 0;
                let mut value_final = None;
                'depthloop: for n in 1..(depth + 1) {
                    slave_commands_tx.send(Command::Search {
                                         search_id: n as usize,
                                         position: position.clone(),
                                         depth: n,
                                         lower_bound: lower_bound,
                                         upper_bound: upper_bound,
                                     })
                                     .unwrap();
                    loop {
                        match slave_reports_rx.recv().unwrap() {
                            Report::Progress { searched_nodes, .. } => {
                                reports.send(Report::Progress {
                                           search_id: search_id,
                                           searched_nodes: searched_nodes_final + searched_nodes,
                                           depth: n,
                                       })
                                       .ok();
                                if pending_command.is_none() {
                                    pending_command = match commands.try_recv() {
                                        Ok(cmd) => {
                                            slave_commands_tx.send(Command::Stop).unwrap();
                                            Some(cmd)
                                        }
                                        _ => None,
                                    }
                                }
                            }
                            Report::Done { searched_nodes, value, .. } => {
                                searched_nodes_final += searched_nodes;
                                if n == depth {
                                    value_final = value;
                                }
                                if pending_command.is_some() {
                                    break 'depthloop;
                                }
                                break;
                            }
                        }
                    }
                }
                reports.send(Report::Done {
                           search_id: search_id,
                           searched_nodes: searched_nodes_final,
                           value: value_final,
                       })
                       .ok();
            }
            Command::Stop => {
                slave_commands_tx.send(Command::Stop).unwrap();
                continue;
            }
            Command::Exit => {
                slave_commands_tx.send(Command::Exit).unwrap();
                break;
            }
        }
    }
    slave.join().unwrap();
}

pub fn run(tt: Arc<TranspositionTable>, commands: Receiver<Command>, reports: Sender<Report>) {
    thread_local!(
        static MOVE_STACK: UnsafeCell<MoveStack> = UnsafeCell::new(MoveStack::new())
    );
    MOVE_STACK.with(|s| {
        let mut move_stack = unsafe { &mut *s.get() };
        let mut pending_command = None;
        loop {
            // If there is a pending command, we take it, otherwise we
            // block and wait to receive a new one.
            let command = match pending_command.take() {
                Some(cmd) => cmd,
                None => commands.recv().or::<RecvError>(Ok(Command::Exit)).unwrap(),
            };

            match command {
                Command::Search { search_id, position, depth, lower_bound, upper_bound } => {
                    let mut state = SearchState {
                        tt: &tt,
                        position: position,
                        moves: move_stack,
                        state_stack: Vec::with_capacity(32),
                        reported_nodes: 0,
                        unreported_nodes: 0,
                        report_func: &mut |n| {
                            reports.send(Report::Progress {
                                       search_id: search_id,
                                       searched_nodes: n,
                                       depth: depth,
                                   })
                                   .ok();
                            match commands.try_recv() {
                                Ok(x) => {
                                    pending_command = Some(x);
                                    Err(TerminatedSearch)
                                }
                                _ => Ok(()),
                            }
                        },
                    };
                    let value = search(&mut state, lower_bound, upper_bound, depth).ok();
                    reports.send(Report::Progress {
                               search_id: search_id,
                               searched_nodes: state.node_count(),
                               depth: depth,
                           })
                           .ok();
                    reports.send(Report::Done {
                               search_id: search_id,
                               searched_nodes: state.node_count(),
                               value: value,
                           })
                           .ok();
                    state.clear();
                }
                Command::Stop => continue,
                Command::Exit => break,
            }
        }
    })
}

// Represents a terminated search condition.
struct TerminatedSearch;

enum NodePhase {
    Pristine,
    TriedHashMove,
    GeneratedMoves,
}

struct NodeState {
    phase: NodePhase,
    entry: EntryData,
}

const NODE_COUNT_REPORT_INTERVAL: NodeCount = 10000;

struct SearchState<'a> {
    tt: &'a TranspositionTable,
    position: Position,
    moves: &'a mut MoveStack,
    state_stack: Vec<NodeState>,
    reported_nodes: NodeCount,
    unreported_nodes: NodeCount,
    report_func: &'a mut FnMut(NodeCount) -> Result<(), TerminatedSearch>,
}

impl<'a> SearchState<'a> {
    #[inline(always)]
    pub fn position(&self) -> &Position {
        &self.position
    }

    #[inline(always)]
    pub fn node_count(&self) -> NodeCount {
        self.reported_nodes + self.unreported_nodes
    }

    #[inline]
    pub fn node_begin(&mut self) -> EntryData {
        // Consult the transposition table.
        let entry = if let Some(e) = self.tt.probe(self.position.hash()) {
            e
        } else {
            EntryData::new(0, BOUND_NONE, 0, 0, self.position.evaluate_static())
        };
        self.state_stack.push(NodeState {
            phase: NodePhase::Pristine,
            entry: entry,
        });
        entry
    }

    // Store the updated info in the transposition table.
    #[inline]
    pub fn node_store(&mut self, value: Value, bound: BoundType, depth: u8, best_move: Move) {
        let entry = &self.state_stack.last().unwrap().entry;
        let move16 = match best_move.digest() {
            0 => entry.move16(),
            x => x,
        };
        self.tt.store(self.position.hash(),
                      EntryData::new(value, bound, depth, move16, entry.eval_value()));
    }

    // Go back to the parent node.
    #[inline]
    pub fn node_end(&mut self) {
        if let NodePhase::Pristine = self.state_stack.last().unwrap().phase {
            // For pristine nodes we have not saved the move list
            // yet, so we should not restore it.
        } else {
            self.moves.restore();
        }
        self.state_stack.pop();
    }

    #[inline]
    pub fn do_move(&mut self) -> Option<Move> {
        let state = self.state_stack.last_mut().unwrap();

        if let NodePhase::Pristine = state.phase {
            // We save the move list in the last possible moment,
            // because most of the nodes are leafs.
            self.moves.save();

            // We always try the hash move first.
            state.phase = NodePhase::TriedHashMove;
            if state.entry.move16() != 0 {
                if let Some(m) = self.position.try_move_digest(state.entry.move16()) {
                    if self.position.do_move(m) {
                        return Some(m);
                    }
                }
            }
        }

        if let NodePhase::TriedHashMove = state.phase {
            // After the hash move, we generate all pseudo-legal
            // moves. But we should not forget to remove the already
            // tried hash move from the list.
            self.position.generate_moves(self.moves);
            if state.entry.move16() != 0 {
                self.moves.remove_move(state.entry.move16());
            }
            state.phase = NodePhase::GeneratedMoves;
        }

        // For the last, we spit the generated moves out.
        while let Some(m) = self.moves.remove_best_move() {
            if self.position.do_move(m) {
                return Some(m);
            }
        }
        None
    }

    #[inline]
    pub fn undo_move(&mut self) {
        self.position.undo_move();
    }

    // From time to time, we report how many nodes had been searched
    // since the last report. This also gives an opportunity for the
    // search to be terminated.
    #[inline]
    pub fn report_progress(&mut self, new_nodes: NodeCount) -> Result<(), TerminatedSearch> {
        self.unreported_nodes += new_nodes;
        if self.unreported_nodes > NODE_COUNT_REPORT_INTERVAL {
            self.reported_nodes += self.unreported_nodes;
            self.unreported_nodes = 0;
            try!((*self.report_func)(self.reported_nodes));
        }
        Ok(())
    }

    #[inline]
    pub fn clear(&mut self) {
        self.moves.clear();
        self.state_stack.clear();
        self.reported_nodes = 0;
        self.unreported_nodes = 0;
    }
}

// Helper function for `run()`. It implements the principal variation
// search algorithm. When returning `Err(TerminatedSearch)`, this
// function may leave un-restored move lists in `moves`.
fn search(state: &mut SearchState,
          mut alpha: Value, // lower bound
          beta: Value, // upper bound
          depth: u8)
          -> Result<Value, TerminatedSearch> {
    assert!(alpha < beta);

    let entry = state.node_begin();

    // Check if the TT entry gives the result.
    if entry.depth() >= depth {
        let value = entry.value();
        let bound = entry.bound();
        if (value >= beta && bound == BOUND_LOWER) || (value <= alpha && bound == BOUND_UPPER) ||
           (bound == BOUND_EXACT) {
            state.node_end();
            return Ok(value);
        }
    }

    // Initial guests for the final result.
    let mut bound = BOUND_UPPER;
    let mut best_move = Move::invalid();

    if depth == 0 {
        // On leaf nodes, do quiescence search.
        let (value, nodes) = state.position()
                                  .evaluate_quiescence(alpha, beta, Some(entry.eval_value()));
        try!(state.report_progress(nodes));

        // See how good this position is.
        if value >= beta {
            alpha = beta;
            bound = BOUND_LOWER;
        } else if value > alpha {
            alpha = value;
            bound = BOUND_EXACT;
        }

    } else {
        // On non-leaf nodes, try moves.
        let mut no_moves_yet = true;
        while let Some(m) = state.do_move() {
            try!(state.report_progress(1));

            // Make a recursive call.
            let value = if no_moves_yet {
                // The first move we analyze with a fully open window
                // (alpha, beta). If this happens to be a good move,
                // it will probably raise `alpha`.
                no_moves_yet = false;
                -try!(search(state, -beta, -alpha, depth - 1))
            } else {
                // For the next moves we first try to prove that they
                // are not better than our current best move. For this
                // purpose we analyze them with a null window (alpha,
                // alpha + 1). This is faster than a full window
                // search. Only if we are certain that the move is
                // better than our current best move, we do a
                // full-window search.
                match -try!(search(state, -alpha - 1, -alpha, depth - 1)) {
                    x if x <= alpha => x,
                    _ => -try!(search(state, -beta, -alpha, depth - 1)),
                }
            };
            state.undo_move();

            // See how good this move was.
            if value >= beta {
                // This move is so good, that the opponent will
                // probably not allow this line of play to
                // happen. Therefore we should not lose any more time
                // on this position.
                alpha = beta;
                bound = BOUND_LOWER;
                best_move = m;
                break;
            }
            if value > alpha {
                // We found a new best move.
                alpha = value;
                bound = BOUND_EXACT;
                best_move = m;
            }
        }

        // Check if we are in a final position (no legal moves).
        if no_moves_yet {
            alpha = state.position.evaluate_final();
            bound = BOUND_EXACT;
        }
    }

    state.node_store(alpha, bound, depth, best_move);
    state.node_end();
    Ok(alpha)
}

#[cfg(test)]
mod tests {
    use super::{search, SearchState};
    use chess_move::*;
    use tt::*;
    use position::Position;

    #[test]
    fn test_search() {
        let p = Position::from_fen("8/8/8/8/3q3k/7n/6PP/2Q2R1K b - - 0 1").ok().unwrap();
        let mut state = SearchState {
            tt: &TranspositionTable::new(),
            position: p,
            moves: &mut MoveStack::new(),
            state_stack: vec![],
            reported_nodes: 0,
            unreported_nodes: 0,
            report_func: &mut |_| Ok(()),
        };
        let value = search(&mut state, -30000, 30000, 2)
                        .ok()
                        .unwrap();
        assert!(value < -300);
        state.clear();
        let value = search(&mut state, -30000, 30000, 4)
                        .ok()
                        .unwrap();
        assert!(value >= 20000);
    }
}
<commit_msg>refactored SearchState<commit_after>use std::thread;
use std::cell::UnsafeCell;
use std::sync::Arc;
use std::sync::mpsc::{channel, Sender, Receiver, RecvError};
use basetypes::*;
use chess_move::*;
use tt::*;
use position::Position;

pub enum Command {
    Search {
        search_id: usize,
        position: Position,
        depth: u8,
        lower_bound: Value,
        upper_bound: Value,
    },
    Stop,
    Exit,
}

pub enum Report {
    Progress {
        search_id: usize,
        searched_nodes: NodeCount,
        depth: u8,
    },
    Done {
        search_id: usize,
        searched_nodes: NodeCount,
        value: Option<Value>,
    },
}

pub fn run_deepening(tt: Arc<TranspositionTable>,
                     commands: Receiver<Command>,
                     reports: Sender<Report>) {
    let (slave_commands_tx, slave_commands_rx) = channel();
    let (slave_reports_tx, slave_reports_rx) = channel();
    let slave = thread::spawn(move || {
        run(tt, slave_commands_rx, slave_reports_tx);
    });
    let mut pending_command = None;
    loop {
        // If there is a pending command, we take it, otherwise we
        // block and wait to receive a new one.
        let command = match pending_command.take() {
            Some(cmd) => cmd,
            None => commands.recv().or::<RecvError>(Ok(Command::Exit)).unwrap(),
        };

        match command {
            Command::Search { search_id, position, depth, lower_bound, upper_bound } => {
                let mut searched_nodes_final = 0;
                let mut value_final = None;
                'depthloop: for n in 1..(depth + 1) {
                    slave_commands_tx.send(Command::Search {
                                         search_id: n as usize,
                                         position: position.clone(),
                                         depth: n,
                                         lower_bound: lower_bound,
                                         upper_bound: upper_bound,
                                     })
                                     .unwrap();
                    loop {
                        match slave_reports_rx.recv().unwrap() {
                            Report::Progress { searched_nodes, .. } => {
                                reports.send(Report::Progress {
                                           search_id: search_id,
                                           searched_nodes: searched_nodes_final + searched_nodes,
                                           depth: n,
                                       })
                                       .ok();
                                if pending_command.is_none() {
                                    pending_command = match commands.try_recv() {
                                        Ok(cmd) => {
                                            slave_commands_tx.send(Command::Stop).unwrap();
                                            Some(cmd)
                                        }
                                        _ => None,
                                    }
                                }
                            }
                            Report::Done { searched_nodes, value, .. } => {
                                searched_nodes_final += searched_nodes;
                                if n == depth {
                                    value_final = value;
                                }
                                if pending_command.is_some() {
                                    break 'depthloop;
                                }
                                break;
                            }
                        }
                    }
                }
                reports.send(Report::Done {
                           search_id: search_id,
                           searched_nodes: searched_nodes_final,
                           value: value_final,
                       })
                       .ok();
            }
            Command::Stop => {
                slave_commands_tx.send(Command::Stop).unwrap();
                continue;
            }
            Command::Exit => {
                slave_commands_tx.send(Command::Exit).unwrap();
                break;
            }
        }
    }
    slave.join().unwrap();
}

pub fn run(tt: Arc<TranspositionTable>, commands: Receiver<Command>, reports: Sender<Report>) {
    thread_local!(
        static MOVE_STACK: UnsafeCell<MoveStack> = UnsafeCell::new(MoveStack::new())
    );
    MOVE_STACK.with(|s| {
        let mut move_stack = unsafe { &mut *s.get() };
        let mut pending_command = None;
        loop {
            // If there is a pending command, we take it, otherwise we
            // block and wait to receive a new one.
            let command = match pending_command.take() {
                Some(cmd) => cmd,
                None => commands.recv().or::<RecvError>(Ok(Command::Exit)).unwrap(),
            };

            match command {
                Command::Search { search_id, position, depth, lower_bound, upper_bound } => {
                    let report_func = &mut |n| {
                        reports.send(Report::Progress {
                                   search_id: search_id,
                                   searched_nodes: n,
                                   depth: depth,
                               })
                               .ok();
                        match commands.try_recv() {
                            Ok(x) => {
                                pending_command = Some(x);
                                Err(TerminatedSearch)
                            }
                            _ => Ok(()),
                        }
                    };
                    let mut search = Search::new(&tt, position, move_stack, report_func);
                    let value = search.run(lower_bound, upper_bound, depth).ok();
                    reports.send(Report::Progress {
                               search_id: search_id,
                               searched_nodes: search.node_count(),
                               depth: depth,
                           })
                           .ok();
                    reports.send(Report::Done {
                               search_id: search_id,
                               searched_nodes: search.node_count(),
                               value: value,
                           })
                           .ok();
                    search.reset();
                }
                Command::Stop => continue,
                Command::Exit => break,
            }
        }
    })
}

// Represents a terminated search condition.
struct TerminatedSearch;

enum NodePhase {
    Pristine,
    TriedHashMove,
    GeneratedMoves,
}

struct NodeState {
    phase: NodePhase,
    entry: EntryData,
}

const NODE_COUNT_REPORT_INTERVAL: NodeCount = 10000;

struct Search<'a> {
    tt: &'a TranspositionTable,
    position: Position,
    moves: &'a mut MoveStack,
    state_stack: Vec<NodeState>,
    reported_nodes: NodeCount,
    unreported_nodes: NodeCount,
    report_func: &'a mut FnMut(NodeCount) -> Result<(), TerminatedSearch>,
}

impl<'a> Search<'a> {
    fn new(tt: &'a TranspositionTable,
           position: Position,
           moves: &'a mut MoveStack,
           report_func: &'a mut FnMut(NodeCount) -> Result<(), TerminatedSearch>)
           -> Search<'a> {
        Search {
            tt: tt,
            position: position,
            moves: moves,
            state_stack: Vec::with_capacity(32),
            reported_nodes: 0,
            unreported_nodes: 0,
            report_func: report_func,
        }
    }

    // Helper function for `run()`. It implements the principal variation
    // search algorithm. When returning `Err(TerminatedSearch)`, this
    // function may leave un-restored move lists in `moves`.
    fn run(&mut self,
           mut alpha: Value, // lower bound
           beta: Value, // upper bound
           depth: u8)
           -> Result<Value, TerminatedSearch> {
        assert!(alpha < beta);

        let entry = self.node_begin();

        // Check if the TT entry gives the result.
        if entry.depth() >= depth {
            let value = entry.value();
            let bound = entry.bound();
            if (value >= beta && bound == BOUND_LOWER) ||
               (value <= alpha && bound == BOUND_UPPER) || (bound == BOUND_EXACT) {
                self.node_end();
                return Ok(value);
            }
        }

        // Initial guests for the final result.
        let mut bound = BOUND_UPPER;
        let mut best_move = Move::invalid();

        if depth == 0 {
            // On leaf nodes, do quiescence search.
            let (value, nodes) = self.position
                                     .evaluate_quiescence(alpha, beta, Some(entry.eval_value()));
            try!(self.report_progress(nodes));

            // See how good this position is.
            if value >= beta {
                alpha = beta;
                bound = BOUND_LOWER;
            } else if value > alpha {
                alpha = value;
                bound = BOUND_EXACT;
            }

        } else {
            // On non-leaf nodes, try moves.
            let mut no_moves_yet = true;
            while let Some(m) = self.do_move() {
                try!(self.report_progress(1));

                // Make a recursive call.
                let value = if no_moves_yet {
                    // The first move we analyze with a fully open window
                    // (alpha, beta). If this happens to be a good move,
                    // it will probably raise `alpha`.
                    no_moves_yet = false;
                    -try!(self.run(-beta, -alpha, depth - 1))
                } else {
                    // For the next moves we first try to prove that they
                    // are not better than our current best move. For this
                    // purpose we analyze them with a null window (alpha,
                    // alpha + 1). This is faster than a full window
                    // search. Only if we are certain that the move is
                    // better than our current best move, we do a
                    // full-window search.
                    match -try!(self.run(-alpha - 1, -alpha, depth - 1)) {
                        x if x <= alpha => x,
                        _ => -try!(self.run(-beta, -alpha, depth - 1)),
                    }
                };
                self.undo_move();

                // See how good this move was.
                if value >= beta {
                    // This move is so good, that the opponent will
                    // probably not allow this line of play to
                    // happen. Therefore we should not lose any more time
                    // on this position.
                    alpha = beta;
                    bound = BOUND_LOWER;
                    best_move = m;
                    break;
                }
                if value > alpha {
                    // We found a new best move.
                    alpha = value;
                    bound = BOUND_EXACT;
                    best_move = m;
                }
            }

            // Check if we are in a final position (no legal moves).
            if no_moves_yet {
                alpha = self.position.evaluate_final();
                bound = BOUND_EXACT;
            }
        }

        self.store(alpha, bound, depth, best_move);
        self.node_end();
        Ok(alpha)
    }

    #[inline(always)]
    pub fn node_count(&self) -> NodeCount {
        self.reported_nodes + self.unreported_nodes
    }

    #[inline]
    fn node_begin(&mut self) -> EntryData {
        // Consult the transposition table.
        let entry = if let Some(e) = self.tt.probe(self.position.hash()) {
            e
        } else {
            EntryData::new(0, BOUND_NONE, 0, 0, self.position.evaluate_static())
        };
        self.state_stack.push(NodeState {
            phase: NodePhase::Pristine,
            entry: entry,
        });
        entry
    }

    // Go back to the parent node.
    #[inline]
    fn node_end(&mut self) {
        if let NodePhase::Pristine = self.state_stack.last().unwrap().phase {
            // For pristine nodes we have not saved the move list
            // yet, so we should not restore it.
        } else {
            self.moves.restore();
        }
        self.state_stack.pop();
    }

    #[inline]
    fn do_move(&mut self) -> Option<Move> {
        let state = self.state_stack.last_mut().unwrap();

        if let NodePhase::Pristine = state.phase {
            // We save the move list in the last possible moment,
            // because most of the nodes are leafs.
            self.moves.save();

            // We always try the hash move first.
            state.phase = NodePhase::TriedHashMove;
            if state.entry.move16() != 0 {
                if let Some(m) = self.position.try_move_digest(state.entry.move16()) {
                    if self.position.do_move(m) {
                        return Some(m);
                    }
                }
            }
        }

        if let NodePhase::TriedHashMove = state.phase {
            // After the hash move, we generate all pseudo-legal
            // moves. But we should not forget to remove the already
            // tried hash move from the list.
            self.position.generate_moves(self.moves);
            if state.entry.move16() != 0 {
                self.moves.remove_move(state.entry.move16());
            }
            state.phase = NodePhase::GeneratedMoves;
        }

        // For the last, we spit the generated moves out.
        while let Some(m) = self.moves.remove_best_move() {
            if self.position.do_move(m) {
                return Some(m);
            }
        }
        None
    }

    #[inline]
    fn undo_move(&mut self) {
        self.position.undo_move();
    }

    // Store the updated info in the transposition table.
    #[inline]
    fn store(&mut self, value: Value, bound: BoundType, depth: u8, best_move: Move) {
        let entry = &self.state_stack.last().unwrap().entry;
        let move16 = match best_move.digest() {
            0 => entry.move16(),
            x => x,
        };
        self.tt.store(self.position.hash(),
                      EntryData::new(value, bound, depth, move16, entry.eval_value()));
    }

    // From time to time, we report how many nodes had been searched
    // since the last report. This also gives an opportunity for the
    // search to be terminated.
    #[inline]
    fn report_progress(&mut self, new_nodes: NodeCount) -> Result<(), TerminatedSearch> {
        self.unreported_nodes += new_nodes;
        if self.unreported_nodes > NODE_COUNT_REPORT_INTERVAL {
            self.reported_nodes += self.unreported_nodes;
            self.unreported_nodes = 0;
            try!((*self.report_func)(self.reported_nodes));
        }
        Ok(())
    }

    #[inline]
    pub fn reset(&mut self) {
        self.moves.clear();
        self.state_stack.clear();
        self.reported_nodes = 0;
        self.unreported_nodes = 0;
    }
}

#[cfg(test)]
mod tests {
    use super::Search;
    use chess_move::*;
    use tt::*;
    use position::Position;

    #[test]
    fn test_search() {
        let p = Position::from_fen("8/8/8/8/3q3k/7n/6PP/2Q2R1K b - - 0 1").ok().unwrap();
        let mut search = Search {
            tt: &TranspositionTable::new(),
            position: p,
            moves: &mut MoveStack::new(),
            state_stack: vec![],
            reported_nodes: 0,
            unreported_nodes: 0,
            report_func: &mut |_| Ok(()),
        };
        let value = search.run(-30000, 30000, 2)
                          .ok()
                          .unwrap();
        assert!(value < -300);
        search.reset();
        let value = search.run(-30000, 30000, 4)
                          .ok()
                          .unwrap();
        assert!(value >= 20000);
    }
}
<|endoftext|>


<commit_before>use std;
use rustc;

import std::fs;
import std::getopts;
import std::getopts::optopt;
import std::getopts::opt_present;
import std::getopts::opt_str;
import std::io;
import std::io::stdout;
import std::vec;
import std::str;
import std::uint;
import std::option;

import rustc::syntax::ast;
import rustc::syntax::fold;
import rustc::syntax::visit;
import rustc::syntax::codemap;
import rustc::syntax::parse::parser;
import rustc::syntax::print::pprust;

fn write_file(filename: &str, content: &str) {
    io::file_writer(filename, [io::create, io::truncate]).write_str(content);
    // Work around https://github.com/graydon/rust/issues/726
    std::run::run_program("chmod", ["644", filename]);
}

fn file_contains(filename: &str, needle: &str) -> bool {
    let contents = io::read_whole_file_str(filename);
    ret str::find(contents, needle) != -1;
}

fn contains(haystack: &str, needle: &str) -> bool {
    str::find(haystack, needle) != -1
}

fn find_rust_files(files: &mutable [str], path: &str) {
    if str::ends_with(path, ".rs") {
        if file_contains(path, "xfail-test") {
            //log_err "Skipping " + path + " because it is marked as xfail-test";
        } else { files += [path]; }
    } else if fs::file_is_dir(path)
        && str::find(path, "compile-fail") == -1 {
        for p in fs::list_dir(path) {
            find_rust_files(files, p);
        }
    }
}

fn safe_to_steal(e: ast::expr_) -> bool {
    alt e {

      // pretty-printer precedence issues -- https://github.com/graydon/rust/issues/670
      ast::expr_unary(_, _) {
        false
      }
      ast::expr_lit(lit) {
        alt lit.node {
          ast::lit_str(_) { true }
          ast::lit_char(_) { true }
          ast::lit_int(_) { false }
          ast::lit_uint(_) { false }
          ast::lit_mach_int(_, _) { false }
          ast::lit_float(_) { false }
          ast::lit_mach_float(_, _) { false }
          ast::lit_nil. { true }
          ast::lit_bool(_) { true }
        }
      }
      ast::expr_cast(_, _) { false }
      ast::expr_assert(_) { false }
      ast::expr_binary(_, _, _) { false }
      ast::expr_assign(_, _) { false }
      ast::expr_assign_op(_, _, _) { false }
      ast::expr_fail(option::none.) {
        false
        /* https://github.com/graydon/rust/issues/764 */

      }
      ast::expr_ret(option::none.) { false }
      ast::expr_put(option::none.) { false }

      ast::expr_ret(_) {
        false
        /* lots of code generation issues, such as https://github.com/graydon/rust/issues/770 */

      }
      ast::expr_fail(_) { false }

      _ {
        true
      }
    }
}

fn steal_exprs(crate: &ast::crate) -> [ast::expr] {
    let exprs: @mutable [ast::expr] = @mutable [];
    // "Stash" is not type-parameterized because of the need for safe_to_steal
    fn stash_expr(es: @mutable [ast::expr], e: &@ast::expr) {
        if safe_to_steal(e.node) {
            *es += [*e];
        } else {/* now my indices are wrong :( */ }
    }
    let v =
        visit::mk_simple_visitor(@{visit_expr: bind stash_expr(exprs, _)
                                      with *visit::default_simple_visitor()});
    visit::visit_crate(crate, (), v);;
    *exprs
}

// https://github.com/graydon/rust/issues/652
fn safe_to_replace(e: ast::expr_) -> bool {
    alt e {
      ast::expr_if(_, _, _) { false }
      ast::expr_block(_) { false }
      _ { true }
    }
}

// Replace the |i|th expr (in fold order) of |crate| with |newexpr|.
fn replace_expr_in_crate(crate: &ast::crate, i: uint, newexpr: ast::expr_) ->
   ast::crate {
    let j: @mutable uint = @mutable 0u;
    fn fold_expr_rep(j_: @mutable uint, i_: uint, newexpr_: &ast::expr_,
                     original: &ast::expr_, fld: fold::ast_fold) ->
       ast::expr_ {
        *j_ += 1u;
        if i_ + 1u == *j_ && safe_to_replace(original) {
            newexpr_
        } else { fold::noop_fold_expr(original, fld) }
    }
    let afp =
        {fold_expr: bind fold_expr_rep(j, i, newexpr, _, _)
            with *fold::default_ast_fold()};
    let af = fold::make_fold(afp);
    let crate2: @ast::crate = @af.fold_crate(crate);
    fold::dummy_out(af); // work around a leak (https://github.com/graydon/rust/issues/651)
    ;
    *crate2
}

iter under(n: uint) -> uint {
    let i: uint = 0u;
    while i < n { put i; i += 1u; }
}

fn devnull() -> io::writer { std::io::string_writer().get_writer() }

fn as_str(f: fn(io::writer)) -> str {
    let w = std::io::string_writer();
    f(w.get_writer());
    ret w.get_str();
}

fn check_variants_of_ast(crate: &ast::crate, codemap: &codemap::codemap,
                         filename: &str) {
    let exprs = steal_exprs(crate);
    let exprsL = vec::len(exprs);
    if exprsL < 100u {
        for each i: uint in under(uint::min(exprsL, 20u)) {
            log_err "Replacing... " + pprust::expr_to_str(@exprs[i]);
            for each j: uint in under(uint::min(exprsL, 5u)) {
                log_err "With... " + pprust::expr_to_str(@exprs[j]);
                let crate2 = @replace_expr_in_crate(crate, i, exprs[j].node);
                // It would be best to test the *crate* for stability, but testing the
                // string for stability is easier and ok for now.
                let str3 =
                    as_str(bind pprust::print_crate(codemap, crate2,
                                                    filename,
                                                    io::string_reader(""), _,
                                                    pprust::no_ann()));
                // 1u would be sane here, but the pretty-printer currently has lots of whitespace and paren issues,
                // and https://github.com/graydon/rust/issues/766 is hilarious.
                check_roundtrip_convergence(str3, 7u);
                //check_whole_compiler(str3);
            }
        }
    }
}

// We'd find more bugs if we could take an AST here, but
// - that would find many "false positives" or unimportant bugs
// - that would be tricky, requiring use of tasks or serialization or randomness.
// This seems to find plenty of bugs as it is :)
fn check_whole_compiler(code: &str) {
    let filename = "test.rs";
    write_file(filename, code);
    let p =
        std::run::program_output(
            "/Users/jruderman/code/rust/build/stage1/rustc",
            ["-c", filename]);

    //log_err #fmt("Status: %d", p.status);
    //log_err "Output: " + p.out;
    if p.err != "" {
        if contains(p.err, "argument of incompatible type") {
            log_err "https://github.com/graydon/rust/issues/769";
        } else if contains(p.err,
                           "Cannot create binary operator with two operands of differing type")
         {
            log_err "https://github.com/graydon/rust/issues/770";
        } else if contains(p.err, "May only branch on boolean predicates!") {
            log_err "https://github.com/graydon/rust/issues/770 or https://github.com/graydon/rust/issues/776";
        } else if contains(p.err, "Invalid constantexpr cast!") &&
                      contains(code, "!") {
            log_err "https://github.com/graydon/rust/issues/777";
        } else if contains(p.err,
                           "Both operands to ICmp instruction are not of the same type!")
                      && contains(code, "!") {
            log_err "https://github.com/graydon/rust/issues/777 #issuecomment-1678487";
        } else if contains(p.err, "Ptr must be a pointer to Val type!") &&
                      contains(code, "!") {
            log_err "https://github.com/graydon/rust/issues/779";
        } else if contains(p.err, "Calling a function with bad signature!") &&
                      (contains(code, "iter") || contains(code, "range")) {
            log_err "https://github.com/graydon/rust/issues/771 - calling an iter fails";
        } else if contains(p.err, "Calling a function with a bad signature!")
                      && contains(code, "empty") {
            log_err "https://github.com/graydon/rust/issues/775 - possibly a modification of run-pass/import-glob-crate.rs";
        } else if contains(p.err, "Invalid type for pointer element!") &&
                      contains(code, "put") {
            log_err "https://github.com/graydon/rust/issues/773 - put put ()";
        } else if contains(p.err, "pointer being freed was not allocated") &&
                      contains(p.out, "Out of stack space, sorry") {
            log_err "https://github.com/graydon/rust/issues/768 + https://github.com/graydon/rust/issues/778"
        } else {
            log_err "Stderr: " + p.err;
            fail "Unfamiliar error message";
        }
    } else if contains(p.out, "non-exhaustive match failure") &&
                  contains(p.out, "alias.rs") {
        log_err "https://github.com/graydon/rust/issues/772";
    } else if contains(p.out, "non-exhaustive match failure") &&
                  contains(p.out, "trans.rs") && contains(code, "put") {
        log_err "https://github.com/graydon/rust/issues/774";
    } else if contains(p.out, "Out of stack space, sorry") {
        log_err "Possibly a variant of https://github.com/graydon/rust/issues/768";
    } else if p.status == 256 {
        if !contains(p.out, "error:") {
            fail "Exited with status 256 without a span-error";
        }
    } else if p.status == 11 {
        log_err "What is this I don't even";
    } else if p.status != 0 { fail "Unfamiliar status code"; }
}

fn parse_and_print(code: &str) -> str {
    let filename = "tmp.rs";
    let sess = @{cm: codemap::new_codemap(), mutable next_id: 0};
    //write_file(filename, code);
    let crate = parser::parse_crate_from_source_str(
        filename, code, [], sess);
    ret as_str(bind pprust::print_crate(sess.cm, crate,
                                        filename,
                                        io::string_reader(code), _,
                                        pprust::no_ann()));
}

fn content_is_dangerous_to_modify(code: &str) -> bool {
    let dangerous_patterns =
        ["obj", // not safe to steal; https://github.com/graydon/rust/issues/761
         "#macro", // not safe to steal things inside of it, because they have a special syntax
         "#", // strange representation of the arguments to #fmt, for example
         " be ", // don't want to replace its child with a non-call: "Non-call expression in tail call"
         "@"]; // hangs when compiling: https://github.com/graydon/rust/issues/768

    for p: str in dangerous_patterns { if contains(code, p) { ret true; } }
    ret false;
}

fn content_is_confusing(code: &str) ->
   bool { // https://github.com/graydon/rust/issues/671
          // https://github.com/graydon/rust/issues/669
          // https://github.com/graydon/rust/issues/669
          // https://github.com/graydon/rust/issues/669
          // crazy rules enforced by parser rather than typechecker?
          // more precedence issues
          // more precedence issues?

    let confusing_patterns =
        ["#macro", "][]", "][mutable]", "][mutable ]", "self", "spawn",
         "bind", "\n\n\n\n\n", // https://github.com/graydon/rust/issues/759
         " : ", // https://github.com/graydon/rust/issues/760
         "if ret", "alt ret", "if fail", "alt fail"];

    for p: str in confusing_patterns { if contains(code, p) { ret true; } }
    ret false;
}

fn file_is_confusing(filename: &str) -> bool {

    // https://github.com/graydon/rust/issues/674

    // something to do with () as a lone pattern

    // an issue where -2147483648 gains an
    // extra negative sign each time through,
    // which i can't reproduce using "rustc
    // --pretty normal"???
    let confusing_files =
        ["block-expr-precedence.rs", "nil-pattern.rs",
         "syntax-extension-fmt.rs",
         "newtype.rs"]; // modifying it hits something like https://github.com/graydon/rust/issues/670

    for f in confusing_files { if contains(filename, f) { ret true; } }

    ret false;
}

fn check_roundtrip_convergence(code: &str, maxIters: uint) {

    let i = 0u;
    let new = code;
    let old = code;

    while i < maxIters {
        old = new;
        if content_is_confusing(old) { ret; }
        new = parse_and_print(old);
        if old == new { break; }
        i += 1u;
    }

    if old == new {
        log_err #fmt["Converged after %u iterations", i];
    } else {
        log_err #fmt["Did not converge after %u iterations!", i];
        write_file("round-trip-a.rs", old);
        write_file("round-trip-b.rs", new);
        std::run::run_program("diff",
                              ["-w", "-u", "round-trip-a.rs",
                               "round-trip-b.rs"]);
        fail "Mismatch";
    }
}

fn check_convergence(files: &[str]) {
    log_err #fmt["pp convergence tests: %u files", vec::len(files)];
    for file in files {
        if !file_is_confusing(file) {
            let s = io::read_whole_file_str(file);
            if !content_is_confusing(s) {
                log_err #fmt["pp converge: %s", file];
                // Change from 7u to 2u when https://github.com/graydon/rust/issues/759 is fixed
                check_roundtrip_convergence(s, 7u);
            }
        }
    }
}

fn check_variants(files: &[str]) {
    for file in files {
        if !file_is_confusing(file) {
            let s = io::read_whole_file_str(file);
            if content_is_dangerous_to_modify(s) || content_is_confusing(s) {
                cont;
            }
            log_err "check_variants: " + file;
            let sess = @{cm: codemap::new_codemap(), mutable next_id: 0};
            let crate =
                parser::parse_crate_from_source_str(
                    file,
                    s, [], sess);
            log_err as_str(bind pprust::print_crate(sess.cm, crate,
                                                    file,
                                                    io::string_reader(s), _,
                                                    pprust::no_ann()));
            check_variants_of_ast(*crate, sess.cm, file);
        }
    }
}

fn main(args: [str]) {
    if vec::len(args) != 2u {
        log_err #fmt["usage: %s <testdir>", args[0]];
        ret;
    }
    let files = [];
    let root = args[1];

    find_rust_files(files, root);
    check_convergence(files);
    check_variants(files);
    log_err "Fuzzer done";
}

// Local Variables:
// mode: rust;
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<commit_msg>Fuzzer: remove exclusions for some issues that have been fixed<commit_after>use std;
use rustc;

import std::fs;
import std::getopts;
import std::getopts::optopt;
import std::getopts::opt_present;
import std::getopts::opt_str;
import std::io;
import std::io::stdout;
import std::vec;
import std::str;
import std::uint;
import std::option;

import rustc::syntax::ast;
import rustc::syntax::fold;
import rustc::syntax::visit;
import rustc::syntax::codemap;
import rustc::syntax::parse::parser;
import rustc::syntax::print::pprust;

fn write_file(filename: &str, content: &str) {
    io::file_writer(filename, [io::create, io::truncate]).write_str(content);
    // Work around https://github.com/graydon/rust/issues/726
    std::run::run_program("chmod", ["644", filename]);
}

fn file_contains(filename: &str, needle: &str) -> bool {
    let contents = io::read_whole_file_str(filename);
    ret str::find(contents, needle) != -1;
}

fn contains(haystack: &str, needle: &str) -> bool {
    str::find(haystack, needle) != -1
}

fn find_rust_files(files: &mutable [str], path: &str) {
    if str::ends_with(path, ".rs") {
        if file_contains(path, "xfail-test") {
            //log_err "Skipping " + path + " because it is marked as xfail-test";
        } else { files += [path]; }
    } else if fs::file_is_dir(path)
        && str::find(path, "compile-fail") == -1 {
        for p in fs::list_dir(path) {
            find_rust_files(files, p);
        }
    }
}

fn safe_to_steal(e: ast::expr_) -> bool {
    alt e {

      // pretty-printer precedence issues -- https://github.com/graydon/rust/issues/670
      ast::expr_unary(_, _) {
        false
      }
      ast::expr_lit(lit) {
        alt lit.node {
          ast::lit_str(_) { true }
          ast::lit_char(_) { true }
          ast::lit_int(_) { false }
          ast::lit_uint(_) { false }
          ast::lit_mach_int(_, _) { false }
          ast::lit_float(_) { false }
          ast::lit_mach_float(_, _) { false }
          ast::lit_nil. { true }
          ast::lit_bool(_) { true }
        }
      }
      ast::expr_cast(_, _) { false }
      ast::expr_assert(_) { false }
      ast::expr_binary(_, _, _) { false }
      ast::expr_assign(_, _) { false }
      ast::expr_assign_op(_, _, _) { false }
      ast::expr_fail(option::none.) {
        false
        /* https://github.com/graydon/rust/issues/764 */

      }
      ast::expr_ret(option::none.) { false }
      ast::expr_put(option::none.) { false }

      ast::expr_ret(_) {
        false
        /* lots of code generation issues, such as https://github.com/graydon/rust/issues/770 */

      }
      ast::expr_fail(_) { false }

      _ {
        true
      }
    }
}

fn steal_exprs(crate: &ast::crate) -> [ast::expr] {
    let exprs: @mutable [ast::expr] = @mutable [];
    // "Stash" is not type-parameterized because of the need for safe_to_steal
    fn stash_expr(es: @mutable [ast::expr], e: &@ast::expr) {
        if safe_to_steal(e.node) {
            *es += [*e];
        } else {/* now my indices are wrong :( */ }
    }
    let v =
        visit::mk_simple_visitor(@{visit_expr: bind stash_expr(exprs, _)
                                      with *visit::default_simple_visitor()});
    visit::visit_crate(crate, (), v);;
    *exprs
}

// https://github.com/graydon/rust/issues/652
fn safe_to_replace(e: ast::expr_) -> bool {
    alt e {
      ast::expr_if(_, _, _) { false }
      ast::expr_block(_) { false }
      _ { true }
    }
}

// Replace the |i|th expr (in fold order) of |crate| with |newexpr|.
fn replace_expr_in_crate(crate: &ast::crate, i: uint, newexpr: ast::expr_) ->
   ast::crate {
    let j: @mutable uint = @mutable 0u;
    fn fold_expr_rep(j_: @mutable uint, i_: uint, newexpr_: &ast::expr_,
                     original: &ast::expr_, fld: fold::ast_fold) ->
       ast::expr_ {
        *j_ += 1u;
        if i_ + 1u == *j_ && safe_to_replace(original) {
            newexpr_
        } else { fold::noop_fold_expr(original, fld) }
    }
    let afp =
        {fold_expr: bind fold_expr_rep(j, i, newexpr, _, _)
            with *fold::default_ast_fold()};
    let af = fold::make_fold(afp);
    let crate2: @ast::crate = @af.fold_crate(crate);
    fold::dummy_out(af); // work around a leak (https://github.com/graydon/rust/issues/651)
    ;
    *crate2
}

iter under(n: uint) -> uint {
    let i: uint = 0u;
    while i < n { put i; i += 1u; }
}

fn devnull() -> io::writer { std::io::string_writer().get_writer() }

fn as_str(f: fn(io::writer)) -> str {
    let w = std::io::string_writer();
    f(w.get_writer());
    ret w.get_str();
}

fn check_variants_of_ast(crate: &ast::crate, codemap: &codemap::codemap,
                         filename: &str) {
    let exprs = steal_exprs(crate);
    let exprsL = vec::len(exprs);
    if exprsL < 100u {
        for each i: uint in under(uint::min(exprsL, 20u)) {
            log_err "Replacing... " + pprust::expr_to_str(@exprs[i]);
            for each j: uint in under(uint::min(exprsL, 5u)) {
                log_err "With... " + pprust::expr_to_str(@exprs[j]);
                let crate2 = @replace_expr_in_crate(crate, i, exprs[j].node);
                // It would be best to test the *crate* for stability, but testing the
                // string for stability is easier and ok for now.
                let str3 =
                    as_str(bind pprust::print_crate(codemap, crate2,
                                                    filename,
                                                    io::string_reader(""), _,
                                                    pprust::no_ann()));
                // 1u would be sane here, but the pretty-printer currently has lots of whitespace and paren issues,
                // and https://github.com/graydon/rust/issues/766 is hilarious.
                check_roundtrip_convergence(str3, 7u);
                //check_whole_compiler(str3);
            }
        }
    }
}

// We'd find more bugs if we could take an AST here, but
// - that would find many "false positives" or unimportant bugs
// - that would be tricky, requiring use of tasks or serialization or randomness.
// This seems to find plenty of bugs as it is :)
fn check_whole_compiler(code: &str) {
    let filename = "test.rs";
    write_file(filename, code);
    let p =
        std::run::program_output(
            "/Users/jruderman/code/rust/build/stage1/rustc",
            ["-c", filename]);

    //log_err #fmt("Status: %d", p.status);
    //log_err "Output: " + p.out;
    if p.err != "" {
        if false {
        } else {
            log_err "Stderr: " + p.err;
            fail "Unfamiliar error message";
        }
    } else if p.status == 256 {
        if !contains(p.out, "error:") {
            fail "Exited with status 256 without a span-error";
        }
    } else if p.status == 11 {
        log_err "What is this I don't even";
    } else if p.status != 0 { fail "Unfamiliar status code"; }
}

fn parse_and_print(code: &str) -> str {
    let filename = "tmp.rs";
    let sess = @{cm: codemap::new_codemap(), mutable next_id: 0};
    //write_file(filename, code);
    let crate = parser::parse_crate_from_source_str(
        filename, code, [], sess);
    ret as_str(bind pprust::print_crate(sess.cm, crate,
                                        filename,
                                        io::string_reader(code), _,
                                        pprust::no_ann()));
}

fn content_is_dangerous_to_modify(code: &str) -> bool {
    let dangerous_patterns =
        ["obj", // not safe to steal; https://github.com/graydon/rust/issues/761
         "#macro", // not safe to steal things inside of it, because they have a special syntax
         "#", // strange representation of the arguments to #fmt, for example
         " be ", // don't want to replace its child with a non-call: "Non-call expression in tail call"
         "@"]; // hangs when compiling: https://github.com/graydon/rust/issues/768

    for p: str in dangerous_patterns { if contains(code, p) { ret true; } }
    ret false;
}

fn content_is_confusing(code: &str) ->
   bool { // https://github.com/graydon/rust/issues/671
          // https://github.com/graydon/rust/issues/669
          // https://github.com/graydon/rust/issues/669
          // https://github.com/graydon/rust/issues/669
          // crazy rules enforced by parser rather than typechecker?
          // more precedence issues
          // more precedence issues?

    let confusing_patterns =
        ["#macro", "][]", "][mutable]", "][mutable ]", "self", "spawn",
         "bind", "\n\n\n\n\n", // https://github.com/graydon/rust/issues/759
         " : ", // https://github.com/graydon/rust/issues/760
         "if ret", "alt ret", "if fail", "alt fail"];

    for p: str in confusing_patterns { if contains(code, p) { ret true; } }
    ret false;
}

fn file_is_confusing(filename: &str) -> bool {

    // https://github.com/graydon/rust/issues/674

    // something to do with () as a lone pattern

    // an issue where -2147483648 gains an
    // extra negative sign each time through,
    // which i can't reproduce using "rustc
    // --pretty normal"???
    let confusing_files =
        ["block-expr-precedence.rs", "nil-pattern.rs",
         "syntax-extension-fmt.rs",
         "newtype.rs"]; // modifying it hits something like https://github.com/graydon/rust/issues/670

    for f in confusing_files { if contains(filename, f) { ret true; } }

    ret false;
}

fn check_roundtrip_convergence(code: &str, maxIters: uint) {

    let i = 0u;
    let new = code;
    let old = code;

    while i < maxIters {
        old = new;
        if content_is_confusing(old) { ret; }
        new = parse_and_print(old);
        if old == new { break; }
        i += 1u;
    }

    if old == new {
        log_err #fmt["Converged after %u iterations", i];
    } else {
        log_err #fmt["Did not converge after %u iterations!", i];
        write_file("round-trip-a.rs", old);
        write_file("round-trip-b.rs", new);
        std::run::run_program("diff",
                              ["-w", "-u", "round-trip-a.rs",
                               "round-trip-b.rs"]);
        fail "Mismatch";
    }
}

fn check_convergence(files: &[str]) {
    log_err #fmt["pp convergence tests: %u files", vec::len(files)];
    for file in files {
        if !file_is_confusing(file) {
            let s = io::read_whole_file_str(file);
            if !content_is_confusing(s) {
                log_err #fmt["pp converge: %s", file];
                // Change from 7u to 2u when https://github.com/graydon/rust/issues/759 is fixed
                check_roundtrip_convergence(s, 7u);
            }
        }
    }
}

fn check_variants(files: &[str]) {
    for file in files {
        if !file_is_confusing(file) {
            let s = io::read_whole_file_str(file);
            if content_is_dangerous_to_modify(s) || content_is_confusing(s) {
                cont;
            }
            log_err "check_variants: " + file;
            let sess = @{cm: codemap::new_codemap(), mutable next_id: 0};
            let crate =
                parser::parse_crate_from_source_str(
                    file,
                    s, [], sess);
            log_err as_str(bind pprust::print_crate(sess.cm, crate,
                                                    file,
                                                    io::string_reader(s), _,
                                                    pprust::no_ann()));
            check_variants_of_ast(*crate, sess.cm, file);
        }
    }
}

fn main(args: [str]) {
    if vec::len(args) != 2u {
        log_err #fmt["usage: %s <testdir>", args[0]];
        ret;
    }
    let files = [];
    let root = args[1];

    find_rust_files(files, root);
    check_convergence(files);
    check_variants(files);
    log_err "Fuzzer done";
}

// Local Variables:
// mode: rust;
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<|endoftext|>


<commit_before>extern crate glob;
extern crate rustc_serialize;

use glob::glob;
use rustc_serialize::json::Json;
use std::env;
use std::fs::File;
use std::result::Result;
use std::io::Read;
use std::collections::HashMap;
use std::string::as_string;

/***

To much 'unstable' to continue yet=(

***/

// const OUTPUT_FILE: &'static str = "output.csv";

fn main() {

	let args: Vec<String>= env::args().collect();

	if args.len() < 3 {
		panic!("Args are: <field name> <folder>");
	}

	let field = &args[1];
	let folder = &args[2];

	let mut frequencies = HashMap::new();

	let search_path = format!("{}/*.json", folder);
	let file_paths = glob(&search_path).unwrap();
	for path in file_paths.filter_map(Result::ok) {

		let path = path.to_str().unwrap();
		let contents = file_contents(path);

		let data = Json::from_str(&contents).ok();
		let data = data.expect("Malformed JSON");
		// let value = data.find(field).expect("Field is missing");
		// let key = value.as_string().expect("Field is not a string");
		let key = get_string_value(&data, field);

		let counter = match frequencies.get(&key) {
			None => 0,
			Some(&x) => x,
		};
		frequencies.insert(key, counter + 1);

		// let counter = value_option.unwrap_or(0);
		// frequencies.insert(key, *counter + 1);
	}

}

fn get_string_value(json: &Json, key: &str) -> String {
	let value = json.find(key).expect("Field is missing");
	let result = value.as_string().expect("Field is not a string");
	as_string(result)
}

fn file_contents(file_path: &str) -> String {
	let mut file = File::open(file_path).ok().expect("Failed to open file");
	let mut contents = String::new();
	file.read_to_string(&mut contents).ok().expect("Failed to read file");
	contents
}<commit_msg>Working stats collection<commit_after>extern crate glob;
extern crate rustc_serialize;

use glob::glob;
use rustc_serialize::json::Json;
use std::env;
use std::fs::File;
use std::result::Result;
use std::io::Read;
use std::collections::HashMap;
use std::error::Error;

// const OUTPUT_FILE: &'static str = "output.csv";

fn main() {
	let args: Vec<String>= env::args().collect();

	if args.len() < 3 {
		panic!("Args are: <field name> <folder>");
	}

	let field = &args[1];
	let folder = &args[2];

	let mut frequencies: HashMap<String, i64> = HashMap::new();

	let search_path = format!("{}/*.json", folder);
	let file_paths = glob(&search_path).unwrap();
	for path in file_paths.filter_map(Result::ok) {
		let path = path.to_str().unwrap();

		let contents = file_contents(path);
		let data = Json::from_str(&contents).ok().expect("Malformed JSON");

		let key = get_string_value(&data, field);
		let counter = match frequencies.get(&key) {
			None => 0,
			Some(&x) => x,
		};
		frequencies.insert(key, counter + 1);
	}

	for (k, v) in frequencies.iter() {
	    println!("{}: {}", k, v);
	}
}

fn get_string_value(json: &Json, key: &str) -> String {
	let value = json.find(key).expect("Field is missing");
	let result = value.as_string().expect("Field is not a string");
	result.to_string()
}

fn file_contents(file_path: &str) -> String {
	let mut file = match File::open(file_path) {
		Err(why) => panic!("Failed to open {}: {}", file_path, Error::description(&why)),
		Ok(file) => file,
	};
	let mut contents = String::new();
	match file.read_to_string(&mut contents) {
		Err(why) => panic!("Failed to read {}: {}", file_path, Error::description(&why)),
		Ok(_) => contents,
	}
}
<|endoftext|>