use std::io::{Read, stdin};
use std::fmt;
use std::fs::File;
use std::sync::Mutex;
use vm::{self, Result, Variants};
use vm::gc::{Gc, Traverseable};
use vm::types::*;
use vm::thread::ThreadInternal;
use vm::thread::Thread;
use vm::api::{Array, FunctionRef, Generic, Hole, VmType, Getable, OpaqueValue, IO, WithVM,
Userdata};
use vm::api::generic::{A, B};
use vm::stack::StackFrame;
use vm::internal::ValuePrinter;
use vm::internal::Value;
use super::{Compiler, Error};
fn print(s: &str) -> IO<()> {
print!("{}", s);
IO::Value(())
}
fn println(s: &str) -> IO<()> {
println!("{}", s);
IO::Value(())
}
struct GluonFile(Mutex<File>);
impl Userdata for GluonFile {}
impl fmt::Debug for GluonFile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "File")
}
}
impl VmType for GluonFile {
type Type = GluonFile;
}
impl Traverseable for GluonFile {
fn traverse(&self, _: &mut Gc) {}
}
fn open_file(s: &str) -> IO<GluonFile> {
match File::open(s) {
Ok(f) => IO::Value(GluonFile(Mutex::new(f))),
Err(err) => IO::Exception(format!("{}", err)),
}
}
fn read_file<'vm>(file: WithVM<'vm, &GluonFile>, count: usize) -> IO<Array<'vm, u8>> {
let WithVM { vm, value: file } = file;
let mut file = file.0.lock().unwrap();
let mut buffer = Vec::with_capacity(count);
unsafe {
buffer.set_len(count);
match file.read(&mut *buffer) {
Ok(bytes_read) => {
let value = {
let mut context = vm.context();
match context.alloc(&buffer[..bytes_read]) {
Ok(value) => value,
Err(err) => return IO::Exception(format!("{}", err)),
}
};
IO::Value(
Getable::from_value(vm, Variants::new(&Value::Array(value))).expect("Array"),
)
}
Err(err) => IO::Exception(format!("{}", err)),
}
}
}
fn read_file_to_string(s: &str) -> IO<String> {
let mut buffer = String::new();
match File::open(s).and_then(|mut file| file.read_to_string(&mut buffer)) {
Ok(_) => IO::Value(buffer),
Err(err) => {
use std::fmt::Write;
buffer.clear();
let _ = write!(&mut buffer, "{}", err);
IO::Exception(buffer)
}
}
}
fn read_char() -> IO<char> {
match stdin().bytes().next() {
Some(result) => {
match result {
Ok(b) => {
::std::char::from_u32(b as u32)
.map(IO::Value)
.unwrap_or_else(|| IO::Exception("Not a valid char".into()))
}
Err(err) => IO::Exception(format!("{}", err)),
}
}
None => IO::Exception("No read".into()),
}
}
fn read_line() -> IO<String> {
let mut buffer = String::new();
match stdin().read_line(&mut buffer) {
Ok(_) => IO::Value(buffer),
Err(err) => {
use std::fmt::Write;
buffer.clear();
let _ = write!(&mut buffer, "{}", err);
IO::Exception(buffer)
}
}
}
fn catch<'vm>(
action: OpaqueValue<&'vm Thread, IO<A>>,
mut catch: FunctionRef<fn(String) -> IO<Generic<A>>>,
) -> IO<Generic<A>> {
let vm = action.vm();
let frame_level = vm.context().stack.get_frames().len();
let mut action: FunctionRef<fn(()) -> Generic<A>> =
unsafe { Getable::from_value(vm, Variants::new(&action.get_value())).unwrap() };
let result = action.call(());
match result {
Ok(value) => IO::Value(value),
Err(err) => {
{
let mut context = vm.context();
let mut stack = StackFrame::current(&mut context.stack);
while stack.stack.get_frames().len() > frame_level {
if stack.exit_scope().is_err() {
return IO::Exception("Unknown error".into());
}
}
}
match catch.call(format!("{}", err)) {
Ok(value) => value,
Err(err) => IO::Exception(format!("{}", err)),
}
}
}
}
fn clear_frames(err: Error, frame_level: usize, mut stack: StackFrame) -> IO<String> {
let fmt = match err {
Error::VM(vm::Error::Panic(_)) => {
let trace = stack.stack.stacktrace(frame_level);
format!("{}\n{}", err, trace)
}
_ => format!("{}", err),
};
while stack.stack.get_frames().len() > frame_level {
if stack.exit_scope().is_err() {
return IO::Exception(fmt);
}
}
IO::Exception(fmt)
}
fn run_expr(WithVM { vm, value: expr }: WithVM<&str>) -> IO<String> {
let frame_level = vm.context().stack.get_frames().len();
let run_result = Compiler::new().run_io_expr::<OpaqueValue<&Thread, Hole>>(vm, "<top>", expr);
let mut context = vm.context();
let stack = StackFrame::current(&mut context.stack);
match run_result {
Ok((value, typ)) => {
let env = vm.global_env().get_env();
unsafe {
IO::Value(format!(
"{} : {}",
ValuePrinter::new(&*env, &typ, value.get_value()).width(80),
typ
))
}
}
Err(err) => clear_frames(err, frame_level, stack),
}
}
fn load_script(WithVM { vm, value: name }: WithVM<&str>, expr: &str) -> IO<String> {
let frame_level = vm.context().stack.get_frames().len();
let run_result = Compiler::new()
.load_script_async(vm, name, expr)
.sync_or_error();
let mut context = vm.context();
let stack = StackFrame::current(&mut context.stack);
match run_result {
Ok(()) => IO::Value(format!("Loaded {}", name)),
Err(err) => clear_frames(err, frame_level, stack),
}
}
pub fn load(vm: &Thread) -> Result<()> {
vm.register_type::<GluonFile>("File", &[])?;
let io_flat_map = vec![
Call(1), PushInt(0), TailCall(2) ,
];
let io_flat_map_type =
<fn(fn(A) -> IO<B>, IO<A>) -> IO<B> as VmType>::make_type(vm);
vm.add_bytecode("io_flat_map", io_flat_map_type, 3, io_flat_map)?;
vm.add_bytecode(
"io_pure",
<fn(A) -> IO<A> as VmType>::make_type(vm),
2,
vec![Pop(1)],
)?;
vm.define_global(
"io",
record!(
open_file => primitive!(1 open_file),
read_file => primitive!(2 read_file),
read_file_to_string => primitive!(1 read_file_to_string),
read_char => primitive!(0 read_char),
read_line => primitive!(0 read_line),
print => primitive!(1 print),
println => primitive!(1 println),
catch =>
primitive!(2 catch),
run_expr => primitive!(1 run_expr),
load_script => primitive!(2 load_script)
),
)?;
Ok(())
}