<commit_before>extern crate nalgebra as na;
extern crate rustracer as rt;
extern crate chrono;
extern crate docopt;
extern crate rustc_serialize;

use std::f32::consts::*;
use std::f32;
use std::path::Path;
use std::sync::Arc;
use na::zero;
use chrono::*;
use docopt::Docopt;

use rt::scene::Scene;
use rt::colour::Colourf;
use rt::camera::Camera;
use rt::integrator::{Integrator, Whitted, AmbientOcclusion};
use rt::{Point, Vector, Transform};
use rt::renderer;

const USAGE: &'static str = "
Toy Ray-Tracer in Rust

Usage:
  rustracer [options]

Options:
  -o <file>, --output=<file>                  Output file name [default: image.png]
  -t N, --threads=N                           Number of worker threads to start [default: 8]
  -i <integrator>, --integrator=<integrator>  Integrator to use [default: whitted].
                                              Valid values: whitted, ao.
";

#[derive(RustcDecodable)]
struct Args {
    flag_output: String,
    flag_threads: usize,
    flag_integrator: IntegratorType,
}

#[derive(RustcDecodable)]
enum IntegratorType {
    Whitted,
    Ao,
}

fn main() {
    // Parse args
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    // let dim = (1216, 1088);
    let dim = (800, 480);
    let camera = Camera::new(Point::new(0.0, 4.0, 0.0), dim, 50.0);
    let integrator: Box<Integrator + Send + Sync> = match args.flag_integrator {
        IntegratorType::Whitted => Box::new(Whitted::new(8)),
        IntegratorType::Ao => Box::new(AmbientOcclusion::new(32, f32::INFINITY)),
    };

    let mut scene = Scene::new(camera, integrator);
    let height = 5.0;

    // scene.push_sphere(Point::new( 0.0, -10004.0, -20.0), 10000.0, Colourf::rgb(0.20, 0.20, 0.20), 0.0, 0.0);
    // scene.push_sphere(4.0,
    //                   Colourf::rgb(1.00, 0.32, 0.36),
    //                   0.8,
    //                   0.0,
    //                   Transform::new(Vector::new(0.0, height, -20.0), zero(), 1.0));
    // scene.push_sphere(2.0,
    //                   Colourf::rgb(0.90, 0.76, 0.46),
    //                   0.0,
    //                   0.0,
    //                   Transform::new(Vector::new(5.0, height - 1.0, -15.0), zero(), 1.0));
    scene.push_mesh(Path::new("models/smooth_suzanne.obj"),
                    "Suzanne",
                    Transform::new(Vector::new(2.0, height, -15.0),
                                   Vector::new(0.0, 0.0, 0.0),
                                   4.0));
    // scene.push_sphere(3.0,
    //                   Colourf::rgb(0.65, 0.77, 0.97),
    //                   0.0,
    //                   0.0,
    //                   Transform::new(Vector::new(5.0, height, -25.0), zero(), 1.0));
    scene.push_sphere(3.0,
                      Colourf::rgb(0.90, 0.90, 0.90),
                      0.0,
                      0.0,
                      Transform::new(Vector::new(-6.5, 4.0, -15.0), zero(), 1.0));
    // scene.push_triangle(Point::new(-1.0, height - 1.0, -5.0),
    //                     Point::new(1.0, height - 1.0, -5.0),
    //                     Point::new(0.0, height + 0.0, -8.0));
    scene.push_plane(Colourf::rgb(1.0, 1.0, 1.0),
                     0.0,
                     0.0,
                     Transform::new(Vector::new(0.0, height - 4.0, 0.0),
                                    Vector::new(-FRAC_PI_2, 0.0, 0.0),
                                    1.0));
    // Light
    // scene.push_sphere(Point::new( 0.0,     20.0, -30.0),     3.0, Colourf::black(),               Some(Colourf::rgb(3.0, 3.0, 3.0)), 0.0, 0.0);
    scene.push_point_light(Point::new(-10.0, 10.0, -5.0),
                           Colourf::rgb(3000.0, 2000.0, 2000.0));
    scene.push_distant_light(-Vector::y() - Vector::z(), Colourf::rgb(3.0, 3.0, 3.0));

    let duration = Duration::span(|| {
        renderer::render(Arc::new(scene), dim, &args.flag_output, args.flag_threads)
    });
    let stats = rt::stats::get_stats();
    println!("Render time                : {}", duration);
    println!("Primary rays               : {}", stats.primary_rays);
    println!("Secondary rays             : {}", stats.secondary_rays);
    println!("Num triangles              : {}", stats.triangles);
    println!("Ray-triangle tests         : {}", stats.ray_triangle_tests);
    println!("Ray-triangle intersections : {}", stats.ray_triangle_isect);
    println!("Fast bounding-box test     : {}", stats.fast_bbox_isect);
}
<commit_msg>Add a few more CLI flags for integrator params<commit_after>extern crate nalgebra as na;
extern crate rustracer as rt;
extern crate chrono;
extern crate docopt;
extern crate rustc_serialize;

use std::f32::consts::*;
use std::f32;
use std::path::Path;
use std::sync::Arc;
use na::zero;
use chrono::*;
use docopt::Docopt;

use rt::scene::Scene;
use rt::colour::Colourf;
use rt::camera::Camera;
use rt::integrator::{Integrator, Whitted, AmbientOcclusion};
use rt::{Point, Vector, Transform};
use rt::renderer;

const USAGE: &'static str =
    "
Toy Ray-Tracer in Rust

Usage:
  rustracer [options]

Options:
  -o <file>, --output=<file>                  \
     Output file name [default: image.png]
  -t N, --threads=N                           Number \
     of worker threads to start [default: 8]
  -i <integrator>, --integrator=<integrator>  \
     Integrator to use [default: whitted].
                                              Valid \
     values: whitted, ao.
  --whitted-max-ray-depth=N                   Maximum ray depth for \
     Whitted integrator. [default: 8]
  --ao-samples=N                              Number of \
     samples for ambient occlusion integrator [default: 16]
";

#[derive(RustcDecodable)]
struct Args {
    flag_output: String,
    flag_threads: usize,
    flag_integrator: IntegratorType,
    flag_whitted_max_ray_depth: u8,
    flag_ao_samples: usize,
}

#[derive(RustcDecodable)]
enum IntegratorType {
    Whitted,
    Ao,
}

fn main() {
    // Parse args
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    // let dim = (1216, 1088);
    let dim = (800, 480);
    let camera = Camera::new(Point::new(0.0, 4.0, 0.0), dim, 50.0);
    let integrator: Box<Integrator + Send + Sync> = match args.flag_integrator {
        IntegratorType::Whitted => {
            println!("Using Whitted integrator with max ray depth of {}",
                     args.flag_whitted_max_ray_depth);
            Box::new(Whitted::new(args.flag_whitted_max_ray_depth))
        }
        IntegratorType::Ao => {
            println!("Using Ambient Occlusion integrator with {} samples",
                     args.flag_ao_samples);
            Box::new(AmbientOcclusion::new(args.flag_ao_samples, f32::INFINITY))
        }
    };

    let mut scene = Scene::new(camera, integrator);
    let height = 5.0;

    // scene.push_sphere(Point::new( 0.0, -10004.0, -20.0), 10000.0, Colourf::rgb(0.20, 0.20, 0.20), 0.0, 0.0);
    // scene.push_sphere(4.0,
    //                   Colourf::rgb(1.00, 0.32, 0.36),
    //                   0.8,
    //                   0.0,
    //                   Transform::new(Vector::new(0.0, height, -20.0), zero(), 1.0));
    // scene.push_sphere(2.0,
    //                   Colourf::rgb(0.90, 0.76, 0.46),
    //                   0.0,
    //                   0.0,
    //                   Transform::new(Vector::new(5.0, height - 1.0, -15.0), zero(), 1.0));
    scene.push_mesh(Path::new("models/smooth_suzanne.obj"),
                    "Suzanne",
                    Transform::new(Vector::new(2.0, height, -15.0),
                                   Vector::new(0.0, 0.0, 0.0),
                                   4.0));
    // scene.push_sphere(3.0,
    //                   Colourf::rgb(0.65, 0.77, 0.97),
    //                   0.0,
    //                   0.0,
    //                   Transform::new(Vector::new(5.0, height, -25.0), zero(), 1.0));
    scene.push_sphere(3.0,
                      Colourf::rgb(0.90, 0.90, 0.90),
                      0.0,
                      0.0,
                      Transform::new(Vector::new(-6.5, 4.0, -15.0), zero(), 1.0));
    // scene.push_triangle(Point::new(-1.0, height - 1.0, -5.0),
    //                     Point::new(1.0, height - 1.0, -5.0),
    //                     Point::new(0.0, height + 0.0, -8.0));
    scene.push_plane(Colourf::rgb(1.0, 1.0, 1.0),
                     0.0,
                     0.0,
                     Transform::new(Vector::new(0.0, height - 4.0, 0.0),
                                    Vector::new(-FRAC_PI_2, 0.0, 0.0),
                                    1.0));
    // Light
    // scene.push_sphere(Point::new( 0.0,     20.0, -30.0),     3.0, Colourf::black(),               Some(Colourf::rgb(3.0, 3.0, 3.0)), 0.0, 0.0);
    scene.push_point_light(Point::new(-10.0, 10.0, -5.0),
                           Colourf::rgb(3000.0, 2000.0, 2000.0));
    scene.push_distant_light(-Vector::y() - Vector::z(), Colourf::rgb(3.0, 3.0, 3.0));

    let duration = Duration::span(|| {
        renderer::render(Arc::new(scene), dim, &args.flag_output, args.flag_threads)
    });
    let stats = rt::stats::get_stats();
    println!("Render time                : {}", duration);
    println!("Primary rays               : {}", stats.primary_rays);
    println!("Secondary rays             : {}", stats.secondary_rays);
    println!("Num triangles              : {}", stats.triangles);
    println!("Ray-triangle tests         : {}", stats.ray_triangle_tests);
    println!("Ray-triangle intersections : {}", stats.ray_triangle_isect);
    println!("Fast bounding-box test     : {}", stats.fast_bbox_isect);
}
<|endoftext|>


<commit_before>extern crate mmap;
extern crate time;
extern crate timely;
extern crate getopts;

use timely::progress::timestamp::RootTimestamp;
use timely::dataflow::*;
use timely::dataflow::operators::*;
use timely::dataflow::channels::pact::Exchange;

mod typedrw;
mod graphmap;
mod sorting;
use graphmap::GraphMMap;
use sorting::{SegmentList, radix_sort_32};

fn main () {

    let filename = std::env::args().skip(1).next().unwrap();
    let strategy = std::env::args().skip(2).next().unwrap() == "process";

    // currently need timely's full option set to parse args
    let mut opts = getopts::Options::new();
    opts.optopt("w", "workers", "", "");
    opts.optopt("p", "process", "", "");
    opts.optopt("n", "processes", "", "");
    opts.optopt("h", "hostfile", "", "");

    if let Ok(matches) = opts.parse(std::env::args().skip(3)) {

        let workers: usize = matches.opt_str("w").map(|x| x.parse().unwrap_or(1)).unwrap_or(1);

        timely::execute_from_args(std::env::args().skip(2), move |root| {

            let index = root.index() as usize;
            let peers = root.peers() as usize;

            let start = time::precise_time_s();

            let nodes = GraphMMap::new(&filename).nodes();

            let mut segments = SegmentList::new(1024); // list of edge segments

            let mut src = vec![];   // holds ranks
            let mut deg = vec![];   // holds source degrees
            let mut rev = vec![];   // holds (dst, deg) pairs
            let mut trn = vec![];   // holds transposed sources

            let mut going = start;

            let mut input = root.scoped(|builder| {

                let (input, edges) = builder.new_input::<(u32, u32)>();
                let (cycle, ranks) = builder.loop_variable::<(u32, f32)>(20, 1);

                let mut ranks = edges.binary_notify(&ranks,
                                    Exchange::new(|x: &(u32,u32)| x.0 as u64),
                                    Exchange::new(|x: &(u32,f32)| x.0 as u64),
                                    "pagerank",
                                    vec![RootTimestamp::new(0)],
                                    move |input1, input2, output, notificator| {

                    // receive incoming edges (should only be iter 0)
                    input1.for_each(|_iter, data| {
                        segments.push(data.drain(..));
                    });

                    // all inputs received for iter, commence multiplication
                    notificator.for_each(|iter,_| {

                        let now = time::now();

                        if index == 0 { println!("{}:{}:{}.{} starting iteration {}", now.tm_hour, now.tm_min, now.tm_sec, now.tm_nsec, iter.inner); }

                        // if the very first iteration, prepare some stuff.
                        // specifically, transpose edges and sort by destination.
                        if iter.inner == 0 {
                            let (a, b, c) = transpose(segments.finalize(), peers, nodes);
                            deg = a; rev = b; trn = c;
                            src = vec![0.0f32; deg.len()];
                        }

                        // record some timings in order to estimate per-iteration times
                        if iter.inner == 0  { println!("src: {}, dst: {}, edges: {}", src.len(), rev.len(), trn.len()); }
                        if iter.inner == 10 && index == 0 { going = time::precise_time_s(); }
                        if iter.inner == 20 && index == 0 { println!("average: {}", (time::precise_time_s() - going) / 10.0 ); }

                        // prepare src for transmitting to destinations
                        for s in 0..src.len() { src[s] = 0.15 + 0.85 * src[s] / deg[s] as f32; }

                        // wander through destinations
                        let mut trn_slice = &trn[..];
                        let mut session = output.session(&iter);
                        for &(dst, deg) in &rev {
                            let mut accum = 0.0;
                            for &s in &trn_slice[..deg as usize] {
                                // accum += src[s as usize];
                                unsafe { accum += *src.get_unchecked(s as usize); }
                            }
                            trn_slice = &trn_slice[deg as usize..];
                            session.give((dst, accum));
                        }

                        for s in &mut src { *s = 0.0; }
                    });

                    // receive data from workers, accumulate in src
                    input2.for_each(|iter, data| {
                        notificator.notify_at(iter);
                        for &(node, rank) in data.iter() {
                            src[node as usize / peers] += rank;
                        }
                    });
                });

                // optionally, do process-local accumulation
                if strategy {
                    let local_base = workers * (index / workers);
                    let local_index = index % workers;
                    let mut acc = vec![0.0; (nodes / workers) + 1];   // holds ranks
                    ranks = ranks.unary_notify(
                        Exchange::new(move |x: &(u32,f32)| (local_base as u64 + (x.0 as u64 % workers as u64))),
                        "aggregation",
                        vec![],
                        move |input, output, iterator| {
                            input.for_each(|iter, data| {
                                iterator.notify_at(iter);
                                for &(node, rank) in data.iter() {
                                    acc[node as usize / workers] += rank;
                                }
                            });

                            iterator.for_each(|item,_| {
                                output.session(&item)
                                      .give_iterator(acc.drain(..)
                                                        .enumerate()
                                                        .filter(|x| x.1 != 0.0)
                                                        .map(|(u,f)| ((u * workers + local_index) as u32, f)));

                                for _ in 0..(1 + (nodes/workers)) { acc.push(0.0); }
                            });
                        }
                    );
                }

                ranks.connect_loop(cycle);

                input
            });

            // introduce edges into the computation;
            // allow mmaped file to drop
            {
                let graph = GraphMMap::new(&filename);
                for node in 0..graph.nodes() {
                    if node % peers == index {
                        for dst in graph.edges(node) {
                            input.send((node as u32, *dst as u32));
                        }
                    }
                }
            }
            input.close();
            while root.step() { }

            if index == 0 { println!("elapsed: {}", time::precise_time_s() - start); }
        }).unwrap();
    }
    else {
        println!("error parsing arguments");
        println!("usage:\tpagerank <source> (worker|process) [timely options]");
    }
}

// returns [src/peers] degrees, (dst, deg) pairs, and a list of [src/peers] endpoints
fn transpose(mut edges: Vec<Vec<(u32, u32)>>, peers: usize, nodes: usize) -> (Vec<u32>, Vec<(u32, u32)>, Vec<u32>)  {

    let mut deg = vec![0; (nodes as usize / peers) + 1];
    for list in &edges {
        for &(s, _) in list {
            deg[s as usize / peers] += 1;
        }
    }

    radix_sort_32(&mut edges, &mut Vec::new(), &|&(_,d)| d);

    let mut rev = Vec::<(u32,u32)>::with_capacity(deg.len());
    let mut trn = Vec::with_capacity(edges.len() * 1024);
    for list in edges {
        for (s,d) in list {
            let len = rev.len();
            if (len == 0) || (rev[len-1].0 < d) {
                rev.push((d, 0u32));
            }

            let len = rev.len();
            rev[len-1].1 += 1;
            trn.push(s / peers as u32);
        }
    }

    return (deg, rev, trn);
}
<commit_msg>change behavior<commit_after>extern crate mmap;
extern crate time;
extern crate timely;
extern crate getopts;

use timely::progress::timestamp::RootTimestamp;
use timely::dataflow::*;
use timely::dataflow::operators::*;
use timely::dataflow::channels::pact::Exchange;

mod typedrw;
mod graphmap;
mod sorting;
use graphmap::GraphMMap;
use sorting::{SegmentList, radix_sort_32};

fn main () {

    let filename = std::env::args().skip(1).next().unwrap();
    let strategy = std::env::args().skip(2).next().unwrap() == "process";

    // currently need timely's full option set to parse args
    let mut opts = getopts::Options::new();
    opts.optopt("w", "workers", "", "");
    opts.optopt("p", "process", "", "");
    opts.optopt("n", "processes", "", "");
    opts.optopt("h", "hostfile", "", "");

    if let Ok(matches) = opts.parse(std::env::args().skip(3)) {

        let workers: usize = matches.opt_str("w").map(|x| x.parse().unwrap_or(1)).unwrap_or(1);

        timely::execute_from_args(std::env::args().skip(2), move |root| {

            let index = root.index() as usize;
            let peers = root.peers() as usize;

            let start = time::precise_time_s();

            let nodes = GraphMMap::new(&filename).nodes();

            let mut segments = SegmentList::new(1024); // list of edge segments

            let mut src = vec![];   // holds ranks
            let mut deg = vec![];   // holds source degrees
            let mut rev = vec![];   // holds (dst, deg) pairs
            let mut trn = vec![];   // holds transposed sources

            let mut going = start;

            let mut input = root.scoped(|builder| {

                let (input, edges) = builder.new_input::<(u32, u32)>();
                let (cycle, ranks) = builder.loop_variable::<(u32, f32)>(20, 1);

                let mut ranks = edges.binary_notify(&ranks,
                                    Exchange::new(|x: &(u32,u32)| x.0 as u64),
                                    Exchange::new(|x: &(u32,f32)| x.0 as u64),
                                    "pagerank",
                                    vec![RootTimestamp::new(0)],
                                    move |input1, input2, output, notificator| {

                    // receive incoming edges (should only be iter 0)
                    input1.for_each(|_iter, data| {
                        segments.push(data.drain(..));
                    });

                    // all inputs received for iter, commence multiplication
                    notificator.for_each(|iter,_| {

                        let now = time::now();

                        if index == 0 { println!("{}:{}:{}.{} starting iteration {}", now.tm_hour, now.tm_min, now.tm_sec, now.tm_nsec, iter.inner); }

                        // if the very first iteration, prepare some stuff.
                        // specifically, transpose edges and sort by destination.
                        if iter.inner == 0 {
                            let (a, b, c) = transpose(segments.finalize(), peers, nodes);
                            deg = a; rev = b; trn = c;
                            src = vec![0.0f32; deg.len()];
                        }

                        // record some timings in order to estimate per-iteration times
                        if iter.inner == 0  { println!("src: {}, dst: {}, edges: {}", src.len(), rev.len(), trn.len()); }
                        if iter.inner == 10 && index == 0 { going = time::precise_time_s(); }
                        if iter.inner == 20 && index == 0 { println!("average: {}", (time::precise_time_s() - going) / 10.0 ); }

                        // prepare src for transmitting to destinations
                        for s in 0..src.len() { src[s] = (0.15 + 0.85 * src[s]) / deg[s] as f32; }

                        // wander through destinations
                        let mut trn_slice = &trn[..];
                        let mut session = output.session(&iter);
                        for &(dst, deg) in &rev {
                            let mut accum = 0.0;
                            for &s in &trn_slice[..deg as usize] {
                                // accum += src[s as usize];
                                unsafe { accum += *src.get_unchecked(s as usize); }
                            }
                            trn_slice = &trn_slice[deg as usize..];
                            session.give((dst, accum));
                        }

                        for s in &mut src { *s = 0.0; }
                    });

                    // receive data from workers, accumulate in src
                    input2.for_each(|iter, data| {
                        notificator.notify_at(iter);
                        for &(node, rank) in data.iter() {
                            src[node as usize / peers] += rank;
                        }
                    });
                });

                // optionally, do process-local accumulation
                if strategy {
                    let local_base = workers * (index / workers);
                    let local_index = index % workers;
                    let mut acc = vec![0.0; (nodes / workers) + 1];   // holds ranks
                    ranks = ranks.unary_notify(
                        Exchange::new(move |x: &(u32,f32)| (local_base as u64 + (x.0 as u64 % workers as u64))),
                        "aggregation",
                        vec![],
                        move |input, output, iterator| {
                            input.for_each(|iter, data| {
                                iterator.notify_at(iter);
                                for &(node, rank) in data.iter() {
                                    acc[node as usize / workers] += rank;
                                }
                            });

                            iterator.for_each(|item,_| {
                                output.session(&item)
                                      .give_iterator(acc.drain(..)
                                                        .enumerate()
                                                        .filter(|x| x.1 != 0.0)
                                                        .map(|(u,f)| ((u * workers + local_index) as u32, f)));

                                for _ in 0..(1 + (nodes/workers)) { acc.push(0.0); }
                            });
                        }
                    );
                }

                ranks.connect_loop(cycle);

                input
            });

            // introduce edges into the computation;
            // allow mmaped file to drop
            {
                let graph = GraphMMap::new(&filename);
                for node in 0..graph.nodes() {
                    if node % peers == index {
                        for dst in graph.edges(node) {
                            input.send(((node + 1) as u32, *dst as u32));
                        }
                    }
                }
            }
            input.close();
            while root.step() { }

            if index == 0 { println!("elapsed: {}", time::precise_time_s() - start); }
        }).unwrap();
    }
    else {
        println!("error parsing arguments");
        println!("usage:\tpagerank <source> (worker|process) [timely options]");
    }
}

// returns [src/peers] degrees, (dst, deg) pairs, and a list of [src/peers] endpoints
fn transpose(mut edges: Vec<Vec<(u32, u32)>>, peers: usize, nodes: usize) -> (Vec<u32>, Vec<(u32, u32)>, Vec<u32>)  {

    let mut deg = vec![0; (nodes as usize / peers) + 1];
    for list in &edges {
        for &(s, _) in list {
            deg[s as usize / peers] += 1;
        }
    }

    radix_sort_32(&mut edges, &mut Vec::new(), &|&(_,d)| d);

    let mut rev = Vec::<(u32,u32)>::with_capacity(deg.len());
    let mut trn = Vec::with_capacity(edges.len() * 1024);
    for list in edges {
        for (s,d) in list {
            let len = rev.len();
            if (len == 0) || (rev[len-1].0 < d) {
                rev.push((d, 0u32));
            }

            let len = rev.len();
            rev[len-1].1 += 1;
            trn.push(s / peers as u32);
        }
    }

    return (deg, rev, trn);
}
<|endoftext|>


<commit_before>//! Miscellaneous functions for use in parview.

use std;
use na;
use kiss3d;
use serde;
use serde_json;
use toml;
use rustc_serialize;

use std::fs::File;
use std::path::Path;
use std::io;
use std::io::{Read, Write};
use std::error::Error;
use rustc_serialize::Decodable;
use rand::random;
use kiss3d::window::Window;
use flate2::read::GzDecoder;

use objects;
use palette;

/// A random Vec3<f32>, with coordinates in (-0.5, 0.5)
pub fn rand_vec() -> na::Vec3<f32> {
    na::Vec3::new(random::<f32>(), random(), random()) - na::Vec3::new(0.5f32, 0.5f32, 0.5f32)
}

/// Inconsolata font
pub fn inconsolata(size: i32) -> std::rc::Rc<kiss3d::text::Font> {
    let inconsolata_otf: &[u8] = include_bytes!("Inconsolata.otf");
    kiss3d::text::Font::from_memory(inconsolata_otf, size)
}

/// Deserialize a function from a `.json` or `.json.gz` file
pub fn deserialize_by_ext<T: serde::Deserialize>(path: &Path) -> Result<T, serde_json::Error> {
    let mut buf: io::BufReader<File> = io::BufReader::new(try!(File::open(path)));
    // let f = try!(File::open(path));

    //~ let coded = json::from_reader(&mut buf).unwrap();
    //~ let mut decoder =json::Decoder::new(coded);
    //~ let frames: Vec<Frame> = Decodable::decode(&mut decoder).unwrap();

    let ext: Option<&str> = path.extension().and_then(|s| {s.to_str()});

    let coded = match ext {
        Some("gz") => {
            let mut gzbuf = try!(GzDecoder::new(buf));
            try!(serde_json::de::from_reader(&mut gzbuf))
        }
        _ => {
            try!(serde_json::de::from_reader(&mut buf))
        }
    };
    Ok(coded)
}

/// Generate an example json file
pub fn generate_frame(path: &Path) -> Result<Vec<objects::Frame>, serde_json::error::Error> {
    let spheres = (0..16).map(|n| {
        let loc : na::Vec3<f32> = rand_vec();
        let s : f32 = random();
        let names = objects::ObjectID(vec![
            format!("{}", n / 4 + 1),
            format!("{}", n % 4 + 1)
        ]);
        objects::Sphere{loc:(loc.x, loc.y, loc.z), diameter:s*0.2, names:names}
    }).collect();

    let f = objects::Frame { spheres: spheres, spherocylinders: vec![], text: String::new() };

    let mut framevec: Vec<objects::Frame> = vec!();

    for i in (0usize..40usize) {
        let mut f2 = objects::Frame {
            spheres: f.spheres.iter().enumerate().map(|(n, ref s)| {
                    let mut newr = s.diameter;
                    if n == 0 {
                        newr = random::<f32>() * 0.2f32;
                    }
                    let (sx, sy, sz) = s.loc;
                    let na::Vec3{x, y, z} = na::Vec3::new(sx, sy, sz) +
                        (rand_vec() * 0.1f32);
                    objects::Sphere {
                        loc: (x, y, z),
                        diameter: newr,
                        names: s.names.clone()
                    }
                }).collect(),

            spherocylinders: vec![],
            text: format!("Frame {} with {} spheres", i, f.spheres.len()),
        };

        if i > 10 && i < 20 {
            let l = f2.spheres.len();
            f2.spheres.truncate(l - 8);
            f2.text = format!("Frame {} with {} spheres", i, f2.spheres.len());
        }
        framevec.push(f2);
    }

    let mut file = try!(File::create(&path));

    try!(serde_json::ser::to_writer_pretty(&mut file, &framevec));
    Ok(framevec)
}

/// Write a default palette, as an example to be used when creating a new one
pub fn generate_palette(path: &Path) -> Result<palette::Palette, Box<Error>> {
    let mut file: File = try!(File::create(path));
    let mut default_palette = palette::Palette::default();
    let _ = default_palette.assigned.insert(
        objects::ObjectID(vec!("A".into())),
        palette::Color(255, 0, 0),
    );
    let _ = default_palette.assigned.insert(
        objects::ObjectID(vec!("B".into())),
        palette::Color(0, 255, 0),
    );
    let s: String = toml::encode_str(&default_palette);
    try!(write!(file, "{}", s));
    Ok(default_palette)
}

/// Load from a file, using toml-rs and serialize
pub fn load_toml<T: Decodable>(path: &Path) -> Result<T, Box<Error>> {
    let mut file: File = try!(File::open(path));
    let mut s = String::new();
    let _ = try!(file.read_to_string(&mut s));
    let mut parser = toml::Parser::new(s.as_ref());

    let parsed = parser.parse();
    let values = match parsed {
        Some(v) => v,
        None => {
            // We can unwrap here, becase parser.parse() == None means there were errors
            let err: Box<Error> = parser.errors.pop().expect(
                "Toml parser failed, yet provided no error. Unsure how to proceed."
                ).into();
            return Err(err)
        }
    };
    let table: toml::Value = toml::Value::Table(values);
    let mut decoder = toml::Decoder::new(table);
    let p = try!(rustc_serialize::Decodable::decode(&mut decoder));
    Ok(p)
}

/// Draw a cube in the current window. Cube will be a red skeleton.
pub fn draw_cube(window: &mut Window) -> kiss3d::scene::SceneNode {
    let rotations: Vec<Option<na::Vec3<f32>>> = vec!(
        None, Some(na::Vec3::x()), Some(na::Vec3::z()));
    let translations = vec!((-0.5f32, -0.5),
                            (-0.5,0.5),
                            (0.5, -0.5),
                            (0.5, 0.5));
    let translations: Vec<na::Vec3<f32>> = translations
        .iter()
        .map(|&(x,z)| na::Vec3::new(x,0.0,z))
        .collect();

    let mut cube = window.add_group();
    for rot in &rotations {
        for t in &translations {
            let mut caps = cube.add_capsule(0.01, 1.0);
            caps.append_translation(t);
            match *rot {
                Some(r) => {
                    caps.append_rotation(&(r * std::f32::consts::FRAC_PI_2))
                }
                None => {}
            }
            //caps.append_translation(&Vec3::new(-0.5, 0.0, -0.5));
        }
    }
    // TODO: this color should be configurable
    cube.set_color(1.0, 0.0, 0.0);

    cube
}

/// Turn an error into a print message.
pub fn err_print(err: &Error) {
    println!("Description: {}", err.description());
    println!("Debug version: {:?}", err);

    if let Some(e) = err.cause() {
        println!("Cause.");
        err_print(e);
    }
}
<commit_msg>Removed unnecessary parentheses<commit_after>//! Miscellaneous functions for use in parview.

use std;
use na;
use kiss3d;
use serde;
use serde_json;
use toml;
use rustc_serialize;

use std::fs::File;
use std::path::Path;
use std::io;
use std::io::{Read, Write};
use std::error::Error;
use rustc_serialize::Decodable;
use rand::random;
use kiss3d::window::Window;
use flate2::read::GzDecoder;

use objects;
use palette;

/// A random Vec3<f32>, with coordinates in (-0.5, 0.5)
pub fn rand_vec() -> na::Vec3<f32> {
    na::Vec3::new(random::<f32>(), random(), random()) - na::Vec3::new(0.5f32, 0.5f32, 0.5f32)
}

/// Inconsolata font
pub fn inconsolata(size: i32) -> std::rc::Rc<kiss3d::text::Font> {
    let inconsolata_otf: &[u8] = include_bytes!("Inconsolata.otf");
    kiss3d::text::Font::from_memory(inconsolata_otf, size)
}

/// Deserialize a function from a `.json` or `.json.gz` file
pub fn deserialize_by_ext<T: serde::Deserialize>(path: &Path) -> Result<T, serde_json::Error> {
    let mut buf: io::BufReader<File> = io::BufReader::new(try!(File::open(path)));
    // let f = try!(File::open(path));

    //~ let coded = json::from_reader(&mut buf).unwrap();
    //~ let mut decoder =json::Decoder::new(coded);
    //~ let frames: Vec<Frame> = Decodable::decode(&mut decoder).unwrap();

    let ext: Option<&str> = path.extension().and_then(|s| {s.to_str()});

    let coded = match ext {
        Some("gz") => {
            let mut gzbuf = try!(GzDecoder::new(buf));
            try!(serde_json::de::from_reader(&mut gzbuf))
        }
        _ => {
            try!(serde_json::de::from_reader(&mut buf))
        }
    };
    Ok(coded)
}

/// Generate an example json file
pub fn generate_frame(path: &Path) -> Result<Vec<objects::Frame>, serde_json::error::Error> {
    let spheres = (0..16).map(|n| {
        let loc : na::Vec3<f32> = rand_vec();
        let s : f32 = random();
        let names = objects::ObjectID(vec![
            format!("{}", n / 4 + 1),
            format!("{}", n % 4 + 1)
        ]);
        objects::Sphere{loc:(loc.x, loc.y, loc.z), diameter:s*0.2, names:names}
    }).collect();

    let f = objects::Frame { spheres: spheres, spherocylinders: vec![], text: String::new() };

    let mut framevec: Vec<objects::Frame> = vec!();

    for i in 0usize..40usize {
        let mut f2 = objects::Frame {
            spheres: f.spheres.iter().enumerate().map(|(n, ref s)| {
                    let mut newr = s.diameter;
                    if n == 0 {
                        newr = random::<f32>() * 0.2f32;
                    }
                    let (sx, sy, sz) = s.loc;
                    let na::Vec3{x, y, z} = na::Vec3::new(sx, sy, sz) +
                        (rand_vec() * 0.1f32);
                    objects::Sphere {
                        loc: (x, y, z),
                        diameter: newr,
                        names: s.names.clone()
                    }
                }).collect(),

            spherocylinders: vec![],
            text: format!("Frame {} with {} spheres", i, f.spheres.len()),
        };

        if i > 10 && i < 20 {
            let l = f2.spheres.len();
            f2.spheres.truncate(l - 8);
            f2.text = format!("Frame {} with {} spheres", i, f2.spheres.len());
        }
        framevec.push(f2);
    }

    let mut file = try!(File::create(&path));

    try!(serde_json::ser::to_writer_pretty(&mut file, &framevec));
    Ok(framevec)
}

/// Write a default palette, as an example to be used when creating a new one
pub fn generate_palette(path: &Path) -> Result<palette::Palette, Box<Error>> {
    let mut file: File = try!(File::create(path));
    let mut default_palette = palette::Palette::default();
    let _ = default_palette.assigned.insert(
        objects::ObjectID(vec!("A".into())),
        palette::Color(255, 0, 0),
    );
    let _ = default_palette.assigned.insert(
        objects::ObjectID(vec!("B".into())),
        palette::Color(0, 255, 0),
    );
    let s: String = toml::encode_str(&default_palette);
    try!(write!(file, "{}", s));
    Ok(default_palette)
}

/// Load from a file, using toml-rs and serialize
pub fn load_toml<T: Decodable>(path: &Path) -> Result<T, Box<Error>> {
    let mut file: File = try!(File::open(path));
    let mut s = String::new();
    let _ = try!(file.read_to_string(&mut s));
    let mut parser = toml::Parser::new(s.as_ref());

    let parsed = parser.parse();
    let values = match parsed {
        Some(v) => v,
        None => {
            // We can unwrap here, becase parser.parse() == None means there were errors
            let err: Box<Error> = parser.errors.pop().expect(
                "Toml parser failed, yet provided no error. Unsure how to proceed."
                ).into();
            return Err(err)
        }
    };
    let table: toml::Value = toml::Value::Table(values);
    let mut decoder = toml::Decoder::new(table);
    let p = try!(rustc_serialize::Decodable::decode(&mut decoder));
    Ok(p)
}

/// Draw a cube in the current window. Cube will be a red skeleton.
pub fn draw_cube(window: &mut Window) -> kiss3d::scene::SceneNode {
    let rotations: Vec<Option<na::Vec3<f32>>> = vec!(
        None, Some(na::Vec3::x()), Some(na::Vec3::z()));
    let translations = vec!((-0.5f32, -0.5),
                            (-0.5,0.5),
                            (0.5, -0.5),
                            (0.5, 0.5));
    let translations: Vec<na::Vec3<f32>> = translations
        .iter()
        .map(|&(x,z)| na::Vec3::new(x,0.0,z))
        .collect();

    let mut cube = window.add_group();
    for rot in &rotations {
        for t in &translations {
            let mut caps = cube.add_capsule(0.01, 1.0);
            caps.append_translation(t);
            match *rot {
                Some(r) => {
                    caps.append_rotation(&(r * std::f32::consts::FRAC_PI_2))
                }
                None => {}
            }
            //caps.append_translation(&Vec3::new(-0.5, 0.0, -0.5));
        }
    }
    // TODO: this color should be configurable
    cube.set_color(1.0, 0.0, 0.0);

    cube
}

/// Turn an error into a print message.
pub fn err_print(err: &Error) {
    println!("Description: {}", err.description());
    println!("Debug version: {:?}", err);

    if let Some(e) = err.cause() {
        println!("Cause.");
        err_print(e);
    }
}
<|endoftext|>


<commit_before>use parser::{Noun, ParseError, atom};
use std::error;
use std::fmt;
use std::fmt::Display;

make_error!(NockError, "NockError: {}\n");

impl From<ParseError> for NockError {
    fn from(err: ParseError) -> Self {
        Self::new_with_cause("AST Parse Error", Box::new(err))
    }
}

fn slice_to_noun(nouns: &[Noun]) -> Result<Noun, NockError> {
    if nouns.len() > 1 {
        Ok(Noun::Cell(nouns.iter().cloned().collect()))
    } else if nouns.len() == 1 {
        Ok(nouns[0].clone())
    } else {
        Err(NockError::new("!! Nock Empty Cell"))
    }
}

// true means take the head.
// false means take the tail
fn make_tree_path(addr: u64) -> Vec<bool> {
    // 6 is the head of 3
    // 3 is the tail of 1
    // 1 is the whole tree.

    // 13 is the tail of 6
    // 6 is the head of 3
    // 3 is the tail of 1
    // 1 is the whole tree.
    let mut ret = Vec::new();
    let mut next = addr;
    loop {
        ret.push(next % 2 == 0);
        next = next / 2;
        if next <= 1 {
            break;
        }
    }
    // TODO(jwall): if we could not have to reverse that would be nice.
    ret.iter().rev().cloned().collect()
}

fn fas(subj: &Noun, addr: u64) -> Result<Noun, NockError> {
    if addr == 0 {
        return Err(NockError::new("!! Invalid slot address 0"));
    }
    if addr == 1 {
        return Ok(subj.clone());
    }
    if addr == 2 {
        return Ok(try!(subj.head()).clone());
    }
    if addr == 3 {
        return slice_to_noun(try!(subj.tail()));
    }
    let path = make_tree_path(addr);
    let mut subject = subj.clone();
    for take_head in path {
        subject = if take_head {
            try!(subject.head()).clone()
        } else {
            try!(slice_to_noun(try!(subject.tail())))
        }
    }
    Ok(subject)
}

#[cfg(test)]
#[test]
fn test_simple_fas() {
    // /[1 [531 25 99]] is [531 25 99];
    let cases = vec![(cell!(atom(531), atom(25), atom(99)), 1,
                      cell!(atom(531), atom(25), atom(99))),
                     // /[2 [531 25 99]] is 531;
                     (cell!(atom(531), atom(25), atom(99)), 2,
                      atom(531)),
                     // /[3 [531 25 99]] is [25 99];
                     (cell!(atom(531), atom(25), atom(99)), 3,
                      cell!(atom(25), atom(99))),
                     // /[6 [531 25 99]] is 25;
                     (cell!(atom(531), atom(25), atom(99)), 6,
                      atom(25)),
                     (cell!(atom(531), cell!(atom(25), atom(26)), atom(99)), 6,
                      cell!(atom(25), atom(26))),
                     (cell!(atom(531), cell!(atom(25), atom(26)), atom(99)), 12,
                      atom(25)),
                     (cell!(atom(531), cell!(atom(25), atom(26)), atom(99)), 13,
                      atom(26)),
                     ];
    for (subj, addr, expected) in cases {
        assert_eq!(expected, fas(&subj, addr).unwrap());
    }
    // [12 [531 25 99]] crashes
}

#[cfg(test)]
#[test]
#[should_panic]
fn test_fas_crash() {
    let cell = cell!(atom(531), atom(25), atom(99));
    // We expect an error here so we crash.
    fas(&cell, 12).unwrap();
}

// Returns 1 false for an atom and 0 true for a Noun::Cell.
fn wut(noun: Noun) -> Noun {
    match noun {
        Noun::Atom(_) => atom(1),
        Noun::Cell(_) => atom(0),
    }
}

// lus increments a atom but crashes for a Noun::Cell.
fn lus(noun: Noun) -> Result<Noun, NockError> {
    match noun {
        Noun::Atom(a) => Ok(atom(a + 1)),
        Noun::Cell(_) => Err(NockError::new("!! Can't increment a cell")),
    }
}

fn cmp_noun(a: &Noun, b: &[Noun]) -> Noun {
    let falsy = atom(0);
    let truthy = atom(1);
    match a {
        &Noun::Cell(ref list) => {
            if list.len() != b.len() {
                return falsy;
            }
            for (i, n) in list.iter().enumerate() {
                if cmp_noun(n, &b[i..]) == falsy {
                    return falsy;
                }
            }
            return truthy;
        }
        &Noun::Atom(a) => {
            if b.len() != 1 {
                if let Noun::Atom(b) = b[0] {
                    if a == b {
                        return truthy;
                    }
                }
            }
            return falsy;
        }
    }
}

// tis compares a Noun::Cell's head and tail Nouns for equality.
fn tis(noun: Noun) -> Result<Noun, NockError> {
    match noun {
        Noun::Atom(_) => Err(NockError::new("!! Can't compaire Atom like a cell")),
        Noun::Cell(list) => {
            if list.len() >= 2 {
                Ok(cmp_noun(&list[0], &list[1..]))
            } else {
                Err(NockError::new("!! Can't compare a cell of only one Noun"))
            }
        }
    }
}

// evaluates a nock expression of type [subj formula] or [formula] or atom
pub fn eval(noun: Noun) -> Result<Noun, NockError> {
    match &noun {
        &Noun::Atom(_) => nock_internal(&Noun::Atom(0), noun.clone()),
        &Noun::Cell(ref list) => {
            if list.len() >= 2 {
                nock_internal(try!(noun.head()), try!(slice_to_noun(try!(noun.tail()))))
            } else {
                Err(NockError::new("!! Invalid Nock Expression"))
            }
        }
    }
}

// Evaluates a nock formula against a subj.
fn nock_internal(subj: &Noun, formula: Noun) -> Result<Noun, NockError> {
    println!("subject {}", subj);
    println!("formula {}", formula);
    match formula {
        Noun::Atom(_) => return Err(NockError::new(format!("!! Nock Infinite Loop"))),
        cell => {
            match try!(cell.head()) {
                &Noun::Atom(a) => {
                    // We expect an instruction from 0 to 10
                    match a {
                        0 => {
                            let tail = try!(slice_to_noun(try!(cell.tail())));
                            if let Noun::Atom(b) = tail {
                                return fas(subj, b);
                            } else {
                                return Err(NockError::new(format!("!! not a slot index {}", tail)));
                            }
                        }
                        1 => {
                            return Ok(try!(slice_to_noun(try!(cell.tail()))));
                        }
                        2 => {
                            return Ok(try!(nock_internal(subj,
                                                         try!(slice_to_noun(try!(cell.tail()))))));
                        }
                        3 => {
                            return Ok(wut(try!(nock_internal(subj,
                                                             try!(slice_to_noun(
                                                    try!(cell.tail())))))));
                        }
                        4 => {
                            return Ok(try!(lus(try!(slice_to_noun(try!(cell.tail()))))));
                        }
                        5 => {
                            return Ok(try!(tis(try!(slice_to_noun(try!(cell.tail()))))));
                        }
                        // macros
                        6 => {
                            let tail = try!(cell.tail());
                            if tail.len() < 3 {
                                return Err(NockError::new("!! Need 3 Nouns for macro 6"));
                            }
                            let b = tail[0].clone();
                            let c = tail[1].clone();
                            let d = try!(slice_to_noun(&tail[2..]));
                            // *[a 6 b c d]     *[a 2 [0 1] 2 [1 c d] [1 0] 2 [1 2 3] [1 0] 4 4 b]
                            let formula = cell!(atom(2),
                                                // [0 1]
                                                cell!(atom(0), atom(1)),
                                                // 2
                                                atom(2),
                                                // [1 c d]
                                                cell!(atom(1), c, d),
                                                // [1 0]
                                                cell!(atom(1), atom(0)),
                                                // 2
                                                atom(2),
                                                // [1 2 3]
                                                cell!(atom(1), atom(2), atom(3)),
                                                // [1 0]
                                                cell!(atom(1), atom(0)),
                                                // 4 4 b]
                                                atom(4),
                                                atom(4),
                                                b);
                            return nock_internal(subj, formula);
                        }
                        7 => {
                            let tail = try!(cell.tail());
                            if tail.len() < 2 {
                                return Err(NockError::new("!! Need 3 Nouns for macro 6"));
                            }
                            let b = tail[0].clone();
                            let c = tail[1].clone();
                            // *[a 7 b c] -> *[a 2 b 1 c]
                            let formula = cell!(atom(2), b, atom(1), c);
                            return nock_internal(subj, formula);
                        }
                        8 => {
                            let tail = try!(cell.tail());
                            if tail.len() < 2 {
                                return Err(NockError::new("!! Need 3 Nouns for macro 6"));
                            }
                            let b = tail[0].clone();
                            let c = tail[1].clone();
                            // *[a 8 b c]       *[a 7 [[7 [0 1] b] 0 1] c]
                            let formula = cell!(atom(7),
                                                cell!(cell!(atom(7), cell!(atom(0), atom(1)), b),
                                                      atom(0),
                                                      atom(1)),
                                                c);
                            return nock_internal(subj, formula);
                        }
                        9 => {
                            let tail = try!(cell.tail());
                            if tail.len() < 2 {
                                return Err(NockError::new("!! Need 3 Nouns for macro 6"));
                            }
                            let b = tail[0].clone();
                            let c = tail[1].clone();
                            // *[a 9 b c]       *[a 7 c 2 [0 1] 0 b]
                            let formula =
                                cell!(atom(7), c, atom(2), cell!(atom(0), atom(1)), atom(0), b);
                            return nock_internal(subj, formula);
                        }
                        10 => {
                            let tail = try!(cell.tail());
                            if tail.len() < 2 {
                                return Err(NockError::new("!! Need 3 Nouns for macro 6"));
                            }
                            let b = tail[0].clone();
                            let c = tail[1].clone();
                            match b {
                                Noun::Atom(_) => {
                                    // *[a 10 b c]      *[a c]
                                    // b is discarded.
                                    return nock_internal(subj, c);
                                }
                                Noun::Cell(list) => {
                                    let d = c;
                                    // b is discarded.
                                    let c = try!(slice_to_noun(&list[1..]));
                                    // *[a 10 [b c] d]  *[a 8 c 7 [0 3] d]
                                    let formula =
                                        cell!(atom(8), c, atom(7), cell!(atom(0), atom(3)), d);
                                    return nock_internal(subj, formula);
                                }
                            }
                        }
                        _ => {
                            return Err(NockError::new(format!("!! Unknown Nock instruction {}",
                                                              a)));
                        }
                    }
                }
                ref cell => {
                    let head = try!(nock_internal(subj, try!(cell.head()).clone()));
                    let new_formula = try!(slice_to_noun(try!(cell.tail())));
                    let tail_noun = try!(nock_internal(subj, new_formula));
                    return Ok(cell!(head, tail_noun));
                }
            }
        }
    }
}
<commit_msg>Fixes for nock 4 evaluation.<commit_after>use parser::{Noun, ParseError, atom};
use std::error;
use std::fmt;
use std::fmt::Display;

make_error!(NockError, "NockError: {}\n");

impl From<ParseError> for NockError {
    fn from(err: ParseError) -> Self {
        Self::new_with_cause("AST Parse Error", Box::new(err))
    }
}

fn slice_to_noun(nouns: &[Noun]) -> Result<Noun, NockError> {
    if nouns.len() > 1 {
        Ok(Noun::Cell(nouns.iter().cloned().collect()))
    } else if nouns.len() == 1 {
        Ok(nouns[0].clone())
    } else {
        Err(NockError::new("!! Nock Empty Cell"))
    }
}

// true means take the head.
// false means take the tail
fn make_tree_path(addr: u64) -> Vec<bool> {
    // 6 is the head of 3
    // 3 is the tail of 1
    // 1 is the whole tree.

    // 13 is the tail of 6
    // 6 is the head of 3
    // 3 is the tail of 1
    // 1 is the whole tree.
    let mut ret = Vec::new();
    let mut next = addr;
    loop {
        ret.push(next % 2 == 0);
        next = next / 2;
        if next <= 1 {
            break;
        }
    }
    // TODO(jwall): if we could not have to reverse that would be nice.
    ret.iter().rev().cloned().collect()
}

fn fas(subj: &Noun, addr: u64) -> Result<Noun, NockError> {
    if addr == 0 {
        return Err(NockError::new("!! Invalid slot address 0"));
    }
    if addr == 1 {
        return Ok(subj.clone());
    }
    if addr == 2 {
        return Ok(try!(subj.head()).clone());
    }
    if addr == 3 {
        return slice_to_noun(try!(subj.tail()));
    }
    let path = make_tree_path(addr);
    let mut subject = subj.clone();
    for take_head in path {
        subject = if take_head {
            try!(subject.head()).clone()
        } else {
            try!(slice_to_noun(try!(subject.tail())))
        }
    }
    Ok(subject)
}

#[cfg(test)]
#[test]
fn test_simple_fas() {
    // /[1 [531 25 99]] is [531 25 99];
    let cases = vec![(cell!(atom(531), atom(25), atom(99)), 1,
                      cell!(atom(531), atom(25), atom(99))),
                     // /[2 [531 25 99]] is 531;
                     (cell!(atom(531), atom(25), atom(99)), 2,
                      atom(531)),
                     // /[3 [531 25 99]] is [25 99];
                     (cell!(atom(531), atom(25), atom(99)), 3,
                      cell!(atom(25), atom(99))),
                     // /[6 [531 25 99]] is 25;
                     (cell!(atom(531), atom(25), atom(99)), 6,
                      atom(25)),
                     (cell!(atom(531), cell!(atom(25), atom(26)), atom(99)), 6,
                      cell!(atom(25), atom(26))),
                     (cell!(atom(531), cell!(atom(25), atom(26)), atom(99)), 12,
                      atom(25)),
                     (cell!(atom(531), cell!(atom(25), atom(26)), atom(99)), 13,
                      atom(26)),
                     ];
    for (subj, addr, expected) in cases {
        assert_eq!(expected, fas(&subj, addr).unwrap());
    }
    // [12 [531 25 99]] crashes
}

#[cfg(test)]
#[test]
#[should_panic]
fn test_fas_crash() {
    let cell = cell!(atom(531), atom(25), atom(99));
    // We expect an error here so we crash.
    fas(&cell, 12).unwrap();
}

// Returns 1 false for an atom and 0 true for a Noun::Cell.
fn wut(noun: Noun) -> Noun {
    match noun {
        Noun::Atom(_) => atom(1),
        Noun::Cell(_) => atom(0),
    }
}

// lus increments a atom but crashes for a Noun::Cell.
fn lus(noun: Noun) -> Result<Noun, NockError> {
    match noun {
        Noun::Atom(a) => Ok(atom(a + 1)),
        Noun::Cell(_) => Err(NockError::new("!! Can't increment a cell")),
    }
}

fn cmp_noun(a: &Noun, b: &[Noun]) -> Noun {
    let falsy = atom(0);
    let truthy = atom(1);
    match a {
        &Noun::Cell(ref list) => {
            if list.len() != b.len() {
                return falsy;
            }
            for (i, n) in list.iter().enumerate() {
                if cmp_noun(n, &b[i..]) == falsy {
                    return falsy;
                }
            }
            return truthy;
        }
        &Noun::Atom(a) => {
            if b.len() != 1 {
                if let Noun::Atom(b) = b[0] {
                    if a == b {
                        return truthy;
                    }
                }
            }
            return falsy;
        }
    }
}

// tis compares a Noun::Cell's head and tail Nouns for equality.
fn tis(noun: Noun) -> Result<Noun, NockError> {
    match noun {
        Noun::Atom(_) => Err(NockError::new("!! Can't compaire Atom like a cell")),
        Noun::Cell(list) => {
            if list.len() >= 2 {
                Ok(cmp_noun(&list[0], &list[1..]))
            } else {
                Err(NockError::new("!! Can't compare a cell of only one Noun"))
            }
        }
    }
}

// evaluates a nock expression of type [subj formula] or [formula] or atom
pub fn eval(noun: Noun) -> Result<Noun, NockError> {
    match &noun {
        &Noun::Atom(_) => nock_internal(&Noun::Atom(0), noun.clone()),
        &Noun::Cell(ref list) => {
            if list.len() >= 2 {
                nock_internal(try!(noun.head()), try!(slice_to_noun(try!(noun.tail()))))
            } else {
                Err(NockError::new("!! Invalid Nock Expression"))
            }
        }
    }
}

// Evaluates a nock formula against a subj.
fn nock_internal(subj: &Noun, formula: Noun) -> Result<Noun, NockError> {
    //println!("subject {}", subj);
    //println!("formula {}", formula);
    match formula {
        Noun::Atom(_) => return Err(NockError::new(format!("!! Nock Infinite Loop"))),
        cell => {
            match try!(cell.head()) {
                &Noun::Atom(a) => {
                    // We expect an instruction from 0 to 10
                    match a {
                        0 => {
                            let tail = try!(slice_to_noun(try!(cell.tail())));
                            if let Noun::Atom(b) = tail {
                                return fas(subj, b);
                            } else {
                                return Err(NockError::new(format!("!! not a slot index {}", tail)));
                            }
                        }
                        1 => {
                            return Ok(try!(slice_to_noun(try!(cell.tail()))));
                        }
                        2 => {
                            return Ok(try!(nock_internal(subj,
                                                         try!(slice_to_noun(try!(cell.tail()))))));
                        }
                        3 => {
                            return Ok(wut(try!(nock_internal(subj,
                                                             try!(slice_to_noun(
                                                    try!(cell.tail())))))));
                        }
                        4 => {
                            let tail_noun = try!(slice_to_noun(try!(cell.tail())));
                            if let Noun::Cell(_) = tail_noun {
                                return Ok(try!(lus(try!(nock_internal(subj, tail_noun)))));
                            }
                            return Ok(try!(lus(tail_noun)));
                        }
                        5 => {
                            return Ok(try!(tis(try!(slice_to_noun(try!(cell.tail()))))));
                        }
                        // macros
                        6 => {
                            let tail = try!(cell.tail());
                            if tail.len() < 3 {
                                return Err(NockError::new("!! Need 3 Nouns for macro 6"));
                            }
                            let b = tail[0].clone();
                            let c = tail[1].clone();
                            let d = try!(slice_to_noun(&tail[2..]));
                            // *[a 6 b c d]     *[a 2 [0 1] 2 [1 c d] [1 0] 2 [1 2 3] [1 0] 4 4 b]
                            let formula = cell!(atom(2),
                                                // [0 1]
                                                cell!(atom(0), atom(1)),
                                                // 2
                                                atom(2),
                                                // [1 c d]
                                                cell!(atom(1), c, d),
                                                // [1 0]
                                                cell!(atom(1), atom(0)),
                                                // 2
                                                atom(2),
                                                // [1 2 3]
                                                cell!(atom(1), atom(2), atom(3)),
                                                // [1 0]
                                                cell!(atom(1), atom(0)),
                                                // 4 4 b]
                                                atom(4),
                                                atom(4),
                                                b);
                            return nock_internal(subj, formula);
                        }
                        7 => {
                            let tail = try!(cell.tail());
                            if tail.len() < 2 {
                                return Err(NockError::new("!! Need 3 Nouns for macro 6"));
                            }
                            let b = tail[0].clone();
                            let c = tail[1].clone();
                            // *[a 7 b c] -> *[a 2 b 1 c]
                            let formula = cell!(atom(2), b, atom(1), c);
                            return nock_internal(subj, formula);
                        }
                        8 => {
                            let tail = try!(cell.tail());
                            if tail.len() < 2 {
                                return Err(NockError::new("!! Need 3 Nouns for macro 6"));
                            }
                            let b = tail[0].clone();
                            let c = tail[1].clone();
                            // *[a 8 b c]       *[a 7 [[7 [0 1] b] 0 1] c]
                            let formula = cell!(atom(7),
                                                cell!(cell!(atom(7), cell!(atom(0), atom(1)), b),
                                                      atom(0),
                                                      atom(1)),
                                                c);
                            return nock_internal(subj, formula);
                        }
                        9 => {
                            let tail = try!(cell.tail());
                            if tail.len() < 2 {
                                return Err(NockError::new("!! Need 3 Nouns for macro 6"));
                            }
                            let b = tail[0].clone();
                            let c = tail[1].clone();
                            // *[a 9 b c]       *[a 7 c 2 [0 1] 0 b]
                            let formula =
                                cell!(atom(7), c, atom(2), cell!(atom(0), atom(1)), atom(0), b);
                            return nock_internal(subj, formula);
                        }
                        10 => {
                            let tail = try!(cell.tail());
                            if tail.len() < 2 {
                                return Err(NockError::new("!! Need 3 Nouns for macro 6"));
                            }
                            let b = tail[0].clone();
                            let c = tail[1].clone();
                            match b {
                                Noun::Atom(_) => {
                                    // *[a 10 b c]      *[a c]
                                    // b is discarded.
                                    return nock_internal(subj, c);
                                }
                                Noun::Cell(list) => {
                                    let d = c;
                                    // b is discarded.
                                    let c = try!(slice_to_noun(&list[1..]));
                                    // *[a 10 [b c] d]  *[a 8 c 7 [0 3] d]
                                    let formula =
                                        cell!(atom(8), c, atom(7), cell!(atom(0), atom(3)), d);
                                    return nock_internal(subj, formula);
                                }
                            }
                        }
                        _ => {
                            return Err(NockError::new(format!("!! Unknown Nock instruction {}",
                                                              a)));
                        }
                    }
                }
                head_formula => {
                    // println!("Computing Distribution");
                    // println!("head: {:?}", head_formula);
                    // println!("tail: {:?}", cell.tail());
                    // FIXME(jwall): We need to handle distribution properly.
                    let head = try!(nock_internal(subj, head_formula.clone()));
                    let new_formula = try!(slice_to_noun(try!(cell.tail())));
                    let tail_noun = try!(nock_internal(subj, new_formula));
                    return Ok(cell!(head, tail_noun));
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2013 The rust-pcre authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[link(name = "pcre", vers = "0.1")];
#[crate_type = "lib"];

use std::libc::{c_int, c_uchar, c_void};
use std::option::{Option};
use std::ptr;
use std::vec;

pub type options = c_int;

pub static PCRE_CASELESS: options = 0x00000001;
pub static PCRE_MULTILINE: options = 0x00000002;
pub static PCRE_DOTALL: options = 0x00000004;
pub static PCRE_EXTENDED: options = 0x00000008;
pub static PCRE_ANCHORED: options = 0x00000010;
pub static PCRE_DOLLAR_ENDONLY: options = 0x00000020;
pub static PCRE_EXTRA: options = 0x00000040;
pub static PCRE_NOTBOL: options = 0x00000080;
pub static PCRE_NOTEOL: options = 0x00000100;
pub static PCRE_UNGREEDY: options = 0x00000200;
pub static PCRE_NOTEMPTY: options = 0x00000400;
pub static PCRE_UTF8: options = 0x00000800;
pub static PCRE_UTF16: options = 0x00000800;
pub static PCRE_UTF32: options = 0x00000800;
pub static PCRE_NO_AUTO_CAPTURE: options = 0x00001000;
pub static PCRE_NO_UTF8_CHECK: options = 0x00002000;
pub static PCRE_NO_UTF16_CHECK: options = 0x00002000;
pub static PCRE_NO_UTF32_CHECK: options = 0x00002000;
pub static PCRE_AUTO_CALLOUT: options = 0x00004000;
pub static PCRE_PARTIAL_SOFT: options = 0x00008000;
pub static PCRE_PARTIAL: options = 0x00008000;
pub static PCRE_NEVER_UTF: options = 0x00010000;
pub static PCRE_DFA_SHORTEST: options = 0x00010000;
pub static PCRE_DFA_RESTART: options = 0x00020000;
pub static PCRE_FIRSTLINE: options = 0x00040000;
pub static PCRE_DUPNAMES: options = 0x00080000;
pub static PCRE_NEWLINE_CR: options = 0x00100000;
pub static PCRE_NEWLINE_LF: options = 0x00200000;
pub static PCRE_NEWLINE_CRLF: options = 0x00300000;
pub static PCRE_NEWLINE_ANY: options = 0x00400000;
pub static PCRE_NEWLINE_ANYCRLF: options = 0x00500000;
pub static PCRE_BSR_ANYCRLF: options = 0x00800000;
pub static PCRE_BSR_UNICODE: options = 0x01000000;
pub static PCRE_JAVASCRIPT_COMPAT: options = 0x02000000;
pub static PCRE_NO_START_OPTIMIZE: options = 0x04000000;
pub static PCRE_NO_START_OPTIMISE: options = 0x04000000;
pub static PCRE_PARTIAL_HARD: options = 0x08000000;
pub static PCRE_NOTEMPTY_ATSTART: options = 0x10000000;
pub static PCRE_UCP: options = 0x20000000;

mod detail {

    use std::c_str::*;
    use std::libc::*;
    use std::ptr;

    pub type fullinfo_field = c_int;
    pub type pcre = c_void;
    pub type pcre_error = c_int;
    pub type pcre_extra = c_void;

    pub static PCRE_ERROR_NOMATCH: pcre_error = -1;
    pub static PCRE_ERROR_NULL: pcre_error = -2;

    pub static PCRE_INFO_CAPTURECOUNT: fullinfo_field = 2;

    mod native {

        use std::libc::*;

        #[link_args = "-lpcre.1"]
        extern {
            static pcre_free: extern "C" fn(ptr: *c_void);

            fn pcre_compile(pattern: *c_char, options: ::options, errptr: *mut *c_char, erroffset: *mut c_int, tableptr: *c_uchar) -> *::detail::pcre;
            fn pcre_exec(code: *::detail::pcre, extra: *::detail::pcre_extra, subject: *c_char, length: c_int, startoffset: c_int, options: ::options, ovector: *mut c_int, ovecsize: c_int) -> ::detail::pcre_error;
            fn pcre_free_study(extra: *::detail::pcre_extra);
            fn pcre_fullinfo(code: *::detail::pcre, extra: *::detail::pcre_extra, what: ::detail::fullinfo_field, where: *mut c_void) -> c_int;
        }

    }

    #[fixed_stack_segment]
    #[inline(never)]
    pub fn pcre_compile(pattern: *c_char, options: ::options, tableptr: *c_uchar) -> *pcre {
        assert!(ptr::is_not_null(pattern));
        let mut err: *c_char = ptr::null();
        let mut erroffset: c_int = 0;
        let code = unsafe { native::pcre_compile(pattern, options, &mut err, &mut erroffset, tableptr) };

        if ptr::is_null(code) {
            // "Otherwise, if  compilation  of  a  pattern fails, pcre_compile() returns
            // NULL, and sets the variable pointed to by errptr to point to a textual
            // error message. This is a static string that is part of the library. You
            // must not try to free it."
            // http://pcre.org/pcre.txt
            let err_cstring = unsafe { CString::new(err, false) };
            match err_cstring.as_str() {
                None          => error!("pcre_compile() failed at offset %u", erroffset as uint),
                Some(err_str) => error!("pcre_compile() failed at offset %u: %s", erroffset as uint, err_str)
            }
            fail!("pcre_compile");
        }
        assert!(ptr::is_not_null(code) && erroffset == 0);

        code
    }

    #[fixed_stack_segment]
    #[inline(never)]
    pub fn pcre_exec(code: *pcre, extra: *pcre_extra, subject: *c_char, length: c_int, startoffset: c_int, options: ::options, ovector: *mut c_int, ovecsize: c_int) -> bool {
        assert!(ptr::is_not_null(code));
        assert!(ovecsize >= 0 && ovecsize % 3 == 0);
        let rc = unsafe { native::pcre_exec(code, extra, subject, length, startoffset, options, ovector, ovecsize) };
        if rc == PCRE_ERROR_NOMATCH {
            return false;
        } else if rc < 0 && rc != PCRE_ERROR_NULL {
            fail!("pcre_exec");
        }

        true
    }

    #[fixed_stack_segment]
    #[inline(never)]
    pub fn pcre_free(ptr: *c_void) {
        native::pcre_free(ptr);
    }

    #[fixed_stack_segment]
    #[inline(never)]
    pub fn pcre_free_study(extra: *pcre_extra) {
        unsafe { native::pcre_free_study(extra); }
    }

    #[fixed_stack_segment]
    #[inline(never)]
    pub fn pcre_fullinfo(code: *pcre, extra: *pcre_extra, what: fullinfo_field, where: *mut c_void) {
        assert!(ptr::is_not_null(code));
        let rc = unsafe { native::pcre_fullinfo(code, extra, what, where) };
        if rc < 0 && rc != PCRE_ERROR_NULL {
            fail!("pcre_fullinfo");
        }
    }

} // End `mod detail`.

pub struct Pcre {

    priv code: *detail::pcre,

    priv extra: *detail::pcre_extra,

    capture_count: uint

}

pub struct Match<'self> {

    priv subject: &'self str,

    priv ovector: ~[c_int]

}

impl Pcre {
    fn compile(pattern: &str) -> Pcre {
        Pcre::compile_with_options(pattern, 0)
    }

    fn compile_with_options(pattern: &str, options: options) -> Pcre {
        do pattern.with_c_str |pattern_c_str| {
            // Use the default character tables.
            let tableptr: *c_uchar = ptr::null();
            let code = detail::pcre_compile(pattern_c_str, options, tableptr);
            assert!(ptr::is_not_null(code));

            let extra: *detail::pcre_extra = ptr::null();

            let mut capture_count: c_int = 0;
            detail::pcre_fullinfo(code, extra, detail::PCRE_INFO_CAPTURECOUNT, &mut capture_count as *mut c_int as *mut c_void);

            Pcre {
                code: code,
                extra: extra,
                capture_count: capture_count as uint
            }
        }
    }

    fn exec<'a>(&self, subject: &'a str) -> Option<Match<'a>> {
        self.exec_from(subject, 0)
    }

    fn exec_from<'a>(&self, subject: &'a str, startoffset: uint) -> Option<Match<'a>> {
        self.exec_from_with_options(subject, startoffset, 0)
    }

    fn exec_from_with_options<'a>(&self, subject: &'a str, startoffset: uint, options: options) -> Option<Match<'a>> {
        let ovecsize = (self.capture_count + 1) * 3;
        let mut ovector: ~[c_int] = vec::from_elem(ovecsize, 0 as c_int);

        unsafe {
            do subject.with_c_str_unchecked |subject_c_str| -> Option<Match<'a>> {
                if detail::pcre_exec(self.code, self.extra, subject_c_str, subject.len() as c_int, startoffset as c_int, options, vec::raw::to_mut_ptr(ovector), ovecsize as c_int) {
                    Some(Match {
                        subject: subject,
                        // TODO: Is it possible to avoid to_owned()?
                        // Probably need multiple lifetime parameters:
                        // https://mail.mozilla.org/pipermail/rust-dev/2013-September/005829.html
                        ovector: ovector.slice_to((self.capture_count + 1) * 2).to_owned()
                    })
                } else {
                    None
                }
            }
        }
    }
}

impl Drop for Pcre {
    fn drop(&mut self) {
        detail::pcre_free_study(self.extra);
        self.extra = ptr::null();
        detail::pcre_free(self.code);
        self.code = ptr::null();
    }
}

impl<'self> Match<'self> {

    fn group_start(&self, n: uint) -> uint {
        self.ovector[(n * 2) as uint] as uint
    }

    fn group_end(&self, n: uint) -> uint {
        self.ovector[(n * 2 + 1) as uint] as uint
    }

    fn group_len(&self, n: uint) -> uint {
        let group_offsets = self.ovector.slice_from((n * 2) as uint);
        (group_offsets[1] - group_offsets[0]) as uint
    }

    fn group(&self, n: uint) -> &'self str {
        let group_offsets = self.ovector.slice_from((n * 2) as uint);
        let start = group_offsets[0];
        let end = group_offsets[1];
        self.subject.slice(start as uint, end as uint)
    }

}

#[cfg(test)]
mod tests {

    #[test]
    #[should_fail]
    fn test_compile_nul() {
        // Nul bytes are not allowed in the pattern string.
        ::Pcre::compile("\0abc");
    }

    #[test]
    #[should_fail]
    fn test_compile_bad_pattern() {
        ::Pcre::compile("[");
    }

    #[test]
    fn test_compile_capture_count() {
        let re = ::Pcre::compile("(?:abc)(def)");
        assert_eq!(re.capture_count, 1u);
    }

    #[test]
    fn test_exec_basic() {
        let re = ::Pcre::compile("^...$");
        assert_eq!(re.capture_count, 0u);
        let m = re.exec("abc").unwrap();
        assert_eq!(m.group(0), "abc");
    }

    #[test]
    fn test_exec_no_match() {
        let re = ::Pcre::compile("abc");
        assert!(re.exec("def").is_none());
    }

    #[test]
    fn test_exec_nul_byte() {
        // Nul bytes *are* allowed in subject strings, however.
        let re = ::Pcre::compile("abc\\0def");
        let m = re.exec("abc\0def").unwrap();
        assert_eq!(m.group(0), "abc\0def");
    }

    #[test]
    fn test_exec_from_basic() {
        let re = ::Pcre::compile("abc");
        let subject = "abcabc";
        let m1 = re.exec_from(subject, 1u).unwrap();
        assert_eq!(m1.group_start(0u), 3u);
        assert_eq!(m1.group_end(0u), 6u);
        assert_eq!(m1.group_len(0u), 3u);
        let m2 = re.exec(subject).unwrap();
        assert_eq!(m2.group_start(0u), 0u);
    }
}
<commit_msg>Break apart one `assert` into two<commit_after>// Copyright 2013 The rust-pcre authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[link(name = "pcre", vers = "0.1")];
#[crate_type = "lib"];

use std::libc::{c_int, c_uchar, c_void};
use std::option::{Option};
use std::ptr;
use std::vec;

pub type options = c_int;

pub static PCRE_CASELESS: options = 0x00000001;
pub static PCRE_MULTILINE: options = 0x00000002;
pub static PCRE_DOTALL: options = 0x00000004;
pub static PCRE_EXTENDED: options = 0x00000008;
pub static PCRE_ANCHORED: options = 0x00000010;
pub static PCRE_DOLLAR_ENDONLY: options = 0x00000020;
pub static PCRE_EXTRA: options = 0x00000040;
pub static PCRE_NOTBOL: options = 0x00000080;
pub static PCRE_NOTEOL: options = 0x00000100;
pub static PCRE_UNGREEDY: options = 0x00000200;
pub static PCRE_NOTEMPTY: options = 0x00000400;
pub static PCRE_UTF8: options = 0x00000800;
pub static PCRE_UTF16: options = 0x00000800;
pub static PCRE_UTF32: options = 0x00000800;
pub static PCRE_NO_AUTO_CAPTURE: options = 0x00001000;
pub static PCRE_NO_UTF8_CHECK: options = 0x00002000;
pub static PCRE_NO_UTF16_CHECK: options = 0x00002000;
pub static PCRE_NO_UTF32_CHECK: options = 0x00002000;
pub static PCRE_AUTO_CALLOUT: options = 0x00004000;
pub static PCRE_PARTIAL_SOFT: options = 0x00008000;
pub static PCRE_PARTIAL: options = 0x00008000;
pub static PCRE_NEVER_UTF: options = 0x00010000;
pub static PCRE_DFA_SHORTEST: options = 0x00010000;
pub static PCRE_DFA_RESTART: options = 0x00020000;
pub static PCRE_FIRSTLINE: options = 0x00040000;
pub static PCRE_DUPNAMES: options = 0x00080000;
pub static PCRE_NEWLINE_CR: options = 0x00100000;
pub static PCRE_NEWLINE_LF: options = 0x00200000;
pub static PCRE_NEWLINE_CRLF: options = 0x00300000;
pub static PCRE_NEWLINE_ANY: options = 0x00400000;
pub static PCRE_NEWLINE_ANYCRLF: options = 0x00500000;
pub static PCRE_BSR_ANYCRLF: options = 0x00800000;
pub static PCRE_BSR_UNICODE: options = 0x01000000;
pub static PCRE_JAVASCRIPT_COMPAT: options = 0x02000000;
pub static PCRE_NO_START_OPTIMIZE: options = 0x04000000;
pub static PCRE_NO_START_OPTIMISE: options = 0x04000000;
pub static PCRE_PARTIAL_HARD: options = 0x08000000;
pub static PCRE_NOTEMPTY_ATSTART: options = 0x10000000;
pub static PCRE_UCP: options = 0x20000000;

mod detail {

    use std::c_str::*;
    use std::libc::*;
    use std::ptr;

    pub type fullinfo_field = c_int;
    pub type pcre = c_void;
    pub type pcre_error = c_int;
    pub type pcre_extra = c_void;

    pub static PCRE_ERROR_NOMATCH: pcre_error = -1;
    pub static PCRE_ERROR_NULL: pcre_error = -2;

    pub static PCRE_INFO_CAPTURECOUNT: fullinfo_field = 2;

    mod native {

        use std::libc::*;

        #[link_args = "-lpcre.1"]
        extern {
            static pcre_free: extern "C" fn(ptr: *c_void);

            fn pcre_compile(pattern: *c_char, options: ::options, errptr: *mut *c_char, erroffset: *mut c_int, tableptr: *c_uchar) -> *::detail::pcre;
            fn pcre_exec(code: *::detail::pcre, extra: *::detail::pcre_extra, subject: *c_char, length: c_int, startoffset: c_int, options: ::options, ovector: *mut c_int, ovecsize: c_int) -> ::detail::pcre_error;
            fn pcre_free_study(extra: *::detail::pcre_extra);
            fn pcre_fullinfo(code: *::detail::pcre, extra: *::detail::pcre_extra, what: ::detail::fullinfo_field, where: *mut c_void) -> c_int;
        }

    }

    #[fixed_stack_segment]
    #[inline(never)]
    pub fn pcre_compile(pattern: *c_char, options: ::options, tableptr: *c_uchar) -> *pcre {
        assert!(ptr::is_not_null(pattern));
        let mut err: *c_char = ptr::null();
        let mut erroffset: c_int = 0;
        let code = unsafe { native::pcre_compile(pattern, options, &mut err, &mut erroffset, tableptr) };

        if ptr::is_null(code) {
            // "Otherwise, if  compilation  of  a  pattern fails, pcre_compile() returns
            // NULL, and sets the variable pointed to by errptr to point to a textual
            // error message. This is a static string that is part of the library. You
            // must not try to free it."
            // http://pcre.org/pcre.txt
            let err_cstring = unsafe { CString::new(err, false) };
            match err_cstring.as_str() {
                None          => error!("pcre_compile() failed at offset %u", erroffset as uint),
                Some(err_str) => error!("pcre_compile() failed at offset %u: %s", erroffset as uint, err_str)
            }
            fail!("pcre_compile");
        }
        assert!(ptr::is_not_null(code));
        assert_eq!(erroffset, 0);

        code
    }

    #[fixed_stack_segment]
    #[inline(never)]
    pub fn pcre_exec(code: *pcre, extra: *pcre_extra, subject: *c_char, length: c_int, startoffset: c_int, options: ::options, ovector: *mut c_int, ovecsize: c_int) -> bool {
        assert!(ptr::is_not_null(code));
        assert!(ovecsize >= 0 && ovecsize % 3 == 0);
        let rc = unsafe { native::pcre_exec(code, extra, subject, length, startoffset, options, ovector, ovecsize) };
        if rc == PCRE_ERROR_NOMATCH {
            return false;
        } else if rc < 0 && rc != PCRE_ERROR_NULL {
            fail!("pcre_exec");
        }

        true
    }

    #[fixed_stack_segment]
    #[inline(never)]
    pub fn pcre_free(ptr: *c_void) {
        native::pcre_free(ptr);
    }

    #[fixed_stack_segment]
    #[inline(never)]
    pub fn pcre_free_study(extra: *pcre_extra) {
        unsafe { native::pcre_free_study(extra); }
    }

    #[fixed_stack_segment]
    #[inline(never)]
    pub fn pcre_fullinfo(code: *pcre, extra: *pcre_extra, what: fullinfo_field, where: *mut c_void) {
        assert!(ptr::is_not_null(code));
        let rc = unsafe { native::pcre_fullinfo(code, extra, what, where) };
        if rc < 0 && rc != PCRE_ERROR_NULL {
            fail!("pcre_fullinfo");
        }
    }

} // End `mod detail`.

pub struct Pcre {

    priv code: *detail::pcre,

    priv extra: *detail::pcre_extra,

    capture_count: uint

}

pub struct Match<'self> {

    priv subject: &'self str,

    priv ovector: ~[c_int]

}

impl Pcre {
    fn compile(pattern: &str) -> Pcre {
        Pcre::compile_with_options(pattern, 0)
    }

    fn compile_with_options(pattern: &str, options: options) -> Pcre {
        do pattern.with_c_str |pattern_c_str| {
            // Use the default character tables.
            let tableptr: *c_uchar = ptr::null();
            let code = detail::pcre_compile(pattern_c_str, options, tableptr);
            assert!(ptr::is_not_null(code));

            let extra: *detail::pcre_extra = ptr::null();

            let mut capture_count: c_int = 0;
            detail::pcre_fullinfo(code, extra, detail::PCRE_INFO_CAPTURECOUNT, &mut capture_count as *mut c_int as *mut c_void);

            Pcre {
                code: code,
                extra: extra,
                capture_count: capture_count as uint
            }
        }
    }

    fn exec<'a>(&self, subject: &'a str) -> Option<Match<'a>> {
        self.exec_from(subject, 0)
    }

    fn exec_from<'a>(&self, subject: &'a str, startoffset: uint) -> Option<Match<'a>> {
        self.exec_from_with_options(subject, startoffset, 0)
    }

    fn exec_from_with_options<'a>(&self, subject: &'a str, startoffset: uint, options: options) -> Option<Match<'a>> {
        let ovecsize = (self.capture_count + 1) * 3;
        let mut ovector: ~[c_int] = vec::from_elem(ovecsize, 0 as c_int);

        unsafe {
            do subject.with_c_str_unchecked |subject_c_str| -> Option<Match<'a>> {
                if detail::pcre_exec(self.code, self.extra, subject_c_str, subject.len() as c_int, startoffset as c_int, options, vec::raw::to_mut_ptr(ovector), ovecsize as c_int) {
                    Some(Match {
                        subject: subject,
                        // TODO: Is it possible to avoid to_owned()?
                        // Probably need multiple lifetime parameters:
                        // https://mail.mozilla.org/pipermail/rust-dev/2013-September/005829.html
                        ovector: ovector.slice_to((self.capture_count + 1) * 2).to_owned()
                    })
                } else {
                    None
                }
            }
        }
    }
}

impl Drop for Pcre {
    fn drop(&mut self) {
        detail::pcre_free_study(self.extra);
        self.extra = ptr::null();
        detail::pcre_free(self.code);
        self.code = ptr::null();
    }
}

impl<'self> Match<'self> {

    fn group_start(&self, n: uint) -> uint {
        self.ovector[(n * 2) as uint] as uint
    }

    fn group_end(&self, n: uint) -> uint {
        self.ovector[(n * 2 + 1) as uint] as uint
    }

    fn group_len(&self, n: uint) -> uint {
        let group_offsets = self.ovector.slice_from((n * 2) as uint);
        (group_offsets[1] - group_offsets[0]) as uint
    }

    fn group(&self, n: uint) -> &'self str {
        let group_offsets = self.ovector.slice_from((n * 2) as uint);
        let start = group_offsets[0];
        let end = group_offsets[1];
        self.subject.slice(start as uint, end as uint)
    }

}

#[cfg(test)]
mod tests {

    #[test]
    #[should_fail]
    fn test_compile_nul() {
        // Nul bytes are not allowed in the pattern string.
        ::Pcre::compile("\0abc");
    }

    #[test]
    #[should_fail]
    fn test_compile_bad_pattern() {
        ::Pcre::compile("[");
    }

    #[test]
    fn test_compile_capture_count() {
        let re = ::Pcre::compile("(?:abc)(def)");
        assert_eq!(re.capture_count, 1u);
    }

    #[test]
    fn test_exec_basic() {
        let re = ::Pcre::compile("^...$");
        assert_eq!(re.capture_count, 0u);
        let m = re.exec("abc").unwrap();
        assert_eq!(m.group(0), "abc");
    }

    #[test]
    fn test_exec_no_match() {
        let re = ::Pcre::compile("abc");
        assert!(re.exec("def").is_none());
    }

    #[test]
    fn test_exec_nul_byte() {
        // Nul bytes *are* allowed in subject strings, however.
        let re = ::Pcre::compile("abc\\0def");
        let m = re.exec("abc\0def").unwrap();
        assert_eq!(m.group(0), "abc\0def");
    }

    #[test]
    fn test_exec_from_basic() {
        let re = ::Pcre::compile("abc");
        let subject = "abcabc";
        let m1 = re.exec_from(subject, 1u).unwrap();
        assert_eq!(m1.group_start(0u), 3u);
        assert_eq!(m1.group_end(0u), 6u);
        assert_eq!(m1.group_len(0u), 3u);
        let m2 = re.exec(subject).unwrap();
        assert_eq!(m2.group_start(0u), 0u);
    }
}
<|endoftext|>