diff --git a/Cargo.lock b/Cargo.lock index a766ca2f..bb5eaed1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -228,6 +228,17 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.8", +] + [[package]] name = "indexmap" version = "1.3.2" @@ -310,6 +321,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "maybe-uninit" version = "2.0.0" @@ -627,6 +644,7 @@ dependencies = [ "divrem", "downcast", "git-version", + "hostname", "indexmap", "lazy_static", "libc", @@ -637,6 +655,7 @@ dependencies = [ "ref_thread_local", "rug", "rustyline", + "unicode_reader", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 37b4ef3a..2112dcba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,13 @@ default = ["rug", "prolog_parser/rug"] num = ["num-rug-adapter", "prolog_parser/num"] [dependencies] +cpu-time = "1.0.0" crossterm = "0.16.0" dirs = "2.0.2" divrem = "0.1.0" downcast = "0.10.0" git-version = "0.3.4" +hostname = "0.3.1" indexmap = "1.0.2" lazy_static = "1.4.0" libc = "0.2.62" @@ -31,4 +33,4 @@ prolog_parser = { version = "0.8.56", default-features = false } ref_thread_local = "0.0.0" rug = { version = "1.4.0", optional = true } rustyline = "6.0.0" -cpu-time = "1.0.0" +unicode_reader = "1.0.0" diff --git a/README.md b/README.md index 1df51017..1eb98982 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,10 @@ Extend Scryer Prolog to include the following, among other features: - [x] A _redone_ representation of strings as difference lists of characters, using a packed internal representation. - [x] clp(B) and clp(ℤ) as builtin libraries. -- [ ] Streams and predicates for stream control (_in progress_). +- [x] Streams and predicates for stream control. + - [x] A simple sockets library representing TCP connections as streams. +- [ ] Incremental compilation and loading process, newly written, + primarily in Prolog. (_in progress_) - [ ] A compacting garbage collector satisfying the five properties of "Precise Garbage Collection in Prolog." - [ ] Mode declarations. @@ -372,6 +375,8 @@ The modules that ship with Scryer Prolog are also called Provides *delimited continuations* via `reset/3` and `shift/1`. * [`random`](src/prolog/lib/random.pl) Probabilistic predicates and random number generators. +* [`sockets`](src/prolog/lib/sockets.pl) + Predicates for opening and accepting TCP connections as streams. To read contents of external files, use `phrase_from_file/2` from [`library(pio)`](src/prolog/lib/pio.pl) to apply a DCG to diff --git a/src/main.rs b/src/main.rs index fc38f3f0..83196004 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ extern crate divrem; #[macro_use] extern crate downcast; extern crate git_version; +extern crate hostname; extern crate indexmap; #[macro_use] extern crate lazy_static; diff --git a/src/prolog/clause_types.rs b/src/prolog/clause_types.rs index d05c8559..36f278df 100644 --- a/src/prolog/clause_types.rs +++ b/src/prolog/clause_types.rs @@ -165,8 +165,10 @@ pub enum SystemClauseType { CodesToNumber, CopyTermWithoutAttrVars, CheckCutPoint, + Close, CopyToLiftedHeap, CreatePartialString, + CurrentHostname, CurrentInput, CurrentOutput, DeleteAttribute, @@ -179,7 +181,11 @@ pub enum SystemClauseType { FetchGlobalVar, FetchGlobalVarWithOffset, FileToChars, + FirstStream, + FlushOutput, + GetByte, GetChar, + GetCode, GetSingleChar, ResetAttrVarState, TruncateIfNoLiftedHeapGrowthDiff, @@ -215,8 +221,16 @@ pub enum SystemClauseType { NumberToChars, NumberToCodes, OpDeclaration, + Open, + NextStream, PartialStringTail, + PeekByte, + PeekChar, + PeekCode, PointsToContinuationResetMarker, + PutByte, + PutChar, + PutCode, REPL(REPLCodePtr), ReadQueryTerm, ReadTerm, @@ -233,6 +247,8 @@ pub enum SystemClauseType { SetOutput, StoreGlobalVar, StoreGlobalVarWithOffset, + StreamProperty, + SetStreamPosition, InferenceLevel, CleanUpBlock, EraseBall, @@ -254,6 +270,10 @@ pub enum SystemClauseType { SetSeed, SkipMaxList, Sleep, + SocketClientOpen, + SocketServerOpen, + SocketServerAccept, + SocketServerClose, Succeed, TermAttributedVariables, TermVariables, @@ -291,6 +311,7 @@ impl SystemClauseType { &SystemClauseType::CopyTermWithoutAttrVars => clause_name!("$copy_term_without_attr_vars"), &SystemClauseType::CreatePartialString => clause_name!("$create_partial_string"), &SystemClauseType::CurrentInput => clause_name!("$current_input"), + &SystemClauseType::CurrentHostname => clause_name!("$current_hostname"), &SystemClauseType::CurrentOutput => clause_name!("$current_output"), &SystemClauseType::REPL(REPLCodePtr::CompileBatch) => clause_name!("$compile_batch"), &SystemClauseType::REPL(REPLCodePtr::UseModule) => clause_name!("$use_module"), @@ -303,6 +324,7 @@ impl SystemClauseType { &SystemClauseType::REPL(REPLCodePtr::UseQualifiedModuleFromFile) => { clause_name!("$use_qualified_module_from_file") } + &SystemClauseType::Close => clause_name!("$close"), &SystemClauseType::CopyToLiftedHeap => clause_name!("$copy_to_lh"), &SystemClauseType::DeleteAttribute => clause_name!("$del_attr_non_head"), &SystemClauseType::DeleteHeadAttribute => clause_name!("$del_attr_head"), @@ -316,7 +338,11 @@ impl SystemClauseType { clause_name!("$fetch_global_var_with_offset") } &SystemClauseType::FileToChars => clause_name!("$file_to_chars"), + &SystemClauseType::FirstStream => clause_name!("$first_stream"), + &SystemClauseType::FlushOutput => clause_name!("$flush_output"), + &SystemClauseType::GetByte => clause_name!("$get_byte"), &SystemClauseType::GetChar => clause_name!("$get_char"), + &SystemClauseType::GetCode => clause_name!("$get_code"), &SystemClauseType::GetSingleChar => clause_name!("$get_single_char"), &SystemClauseType::ResetAttrVarState => clause_name!("$reset_attr_var_state"), &SystemClauseType::TruncateIfNoLiftedHeapGrowth => { @@ -346,13 +372,17 @@ impl SystemClauseType { &SystemClauseType::GetSCCCleaner => clause_name!("$get_scc_cleaner"), &SystemClauseType::Halt => clause_name!("$halt"), &SystemClauseType::HeadIsDynamic => clause_name!("$head_is_dynamic"), - &SystemClauseType::OpDeclaration => clause_name!("$op$"), + &SystemClauseType::Open => clause_name!("$open"), + &SystemClauseType::OpDeclaration => clause_name!("$op"), &SystemClauseType::InstallSCCCleaner => clause_name!("$install_scc_cleaner"), &SystemClauseType::InstallInferenceCounter => { clause_name!("$install_inference_counter") } &SystemClauseType::IsPartialString => clause_name!("$is_partial_string"), &SystemClauseType::PartialStringTail => clause_name!("$partial_string_tail"), + &SystemClauseType::PeekByte => clause_name!("$peek_byte"), + &SystemClauseType::PeekChar => clause_name!("$peek_char"), + &SystemClauseType::PeekCode => clause_name!("$peek_code"), &SystemClauseType::LiftedHeapLength => clause_name!("$lh_length"), &SystemClauseType::Maybe => clause_name!("maybe"), &SystemClauseType::CpuNow => clause_name!("$cpu_now"), @@ -365,12 +395,22 @@ impl SystemClauseType { &SystemClauseType::ModuleHeadIsDynamic => clause_name!("$module_head_is_dynamic"), &SystemClauseType::ModuleExists => clause_name!("$module_exists"), &SystemClauseType::ModuleOf => clause_name!("$module_of"), + &SystemClauseType::NextStream => clause_name!("$next_stream"), &SystemClauseType::NoSuchPredicate => clause_name!("$no_such_predicate"), &SystemClauseType::NumberToChars => clause_name!("$number_to_chars"), &SystemClauseType::NumberToCodes => clause_name!("$number_to_codes"), &SystemClauseType::PointsToContinuationResetMarker => { clause_name!("$points_to_cont_reset_marker") } + &SystemClauseType::PutByte => { + clause_name!("$put_byte") + } + &SystemClauseType::PutChar => { + clause_name!("$put_char") + } + &SystemClauseType::PutCode => { + clause_name!("$put_code") + } &SystemClauseType::QuotedToken => { clause_name!("$quoted_token") } @@ -382,6 +422,8 @@ impl SystemClauseType { &SystemClauseType::SetInput => clause_name!("$set_input"), &SystemClauseType::SetOutput => clause_name!("$set_output"), &SystemClauseType::SetSeed => clause_name!("$set_seed"), + &SystemClauseType::StreamProperty => clause_name!("$stream_property"), + &SystemClauseType::SetStreamPosition => clause_name!("$set_stream_position"), &SystemClauseType::StoreGlobalVar => clause_name!("$store_global_var"), &SystemClauseType::StoreGlobalVarWithOffset => { clause_name!("$store_global_var_with_offset") @@ -410,6 +452,10 @@ impl SystemClauseType { &SystemClauseType::SetDoubleQuotes => clause_name!("$set_double_quotes"), &SystemClauseType::SkipMaxList => clause_name!("$skip_max_list"), &SystemClauseType::Sleep => clause_name!("$sleep"), + &SystemClauseType::SocketClientOpen => clause_name!("$socket_client_open"), + &SystemClauseType::SocketServerOpen => clause_name!("$socket_server_open"), + &SystemClauseType::SocketServerAccept => clause_name!("$socket_server_accept"), + &SystemClauseType::SocketServerClose => clause_name!("$socket_server_close"), &SystemClauseType::Succeed => clause_name!("$succeed"), &SystemClauseType::TermAttributedVariables => clause_name!("$term_attributed_variables"), &SystemClauseType::TermVariables => clause_name!("$term_variables"), @@ -450,8 +496,13 @@ impl SystemClauseType { ("$check_cp", 1) => Some(SystemClauseType::CheckCutPoint), ("$compile_batch", 0) => Some(SystemClauseType::REPL(REPLCodePtr::CompileBatch)), ("$copy_to_lh", 2) => Some(SystemClauseType::CopyToLiftedHeap), + ("$close", 2) => Some(SystemClauseType::Close), + ("$current_hostname", 1) => Some(SystemClauseType::CurrentHostname), ("$current_input", 1) => Some(SystemClauseType::CurrentInput), ("$current_output", 1) => Some(SystemClauseType::CurrentOutput), + ("$first_stream", 1) => Some(SystemClauseType::FirstStream), + ("$next_stream", 2) => Some(SystemClauseType::NextStream), + ("$flush_output", 1) => Some(SystemClauseType::FlushOutput), ("$del_attr_non_head", 1) => Some(SystemClauseType::DeleteAttribute), ("$del_attr_head", 1) => Some(SystemClauseType::DeleteHeadAttribute), ("$get_next_db_ref", 2) => Some(SystemClauseType::GetNextDBRef), @@ -462,17 +513,31 @@ impl SystemClauseType { ("$enqueue_attribute_goal", 1) => Some(SystemClauseType::EnqueueAttributeGoal), ("$enqueue_attr_var", 1) => Some(SystemClauseType::EnqueueAttributedVar), ("$partial_string_tail", 2) => Some(SystemClauseType::PartialStringTail), + ("$peek_byte", 2) => Some(SystemClauseType::PeekByte), + ("$peek_char", 2) => Some(SystemClauseType::PeekChar), + ("$peek_code", 2) => Some(SystemClauseType::PeekCode), ("$is_partial_string", 1) => Some(SystemClauseType::IsPartialString), ("$expand_term", 2) => Some(SystemClauseType::ExpandTerm), ("$expand_goal", 2) => Some(SystemClauseType::ExpandGoal), ("$fetch_global_var", 2) => Some(SystemClauseType::FetchGlobalVar), ("$fetch_global_var_with_offset", 3) => Some(SystemClauseType::FetchGlobalVarWithOffset), ("$file_to_chars", 2) => Some(SystemClauseType::FileToChars), - ("$get_char", 1) => Some(SystemClauseType::GetChar), + ("$get_byte", 2) => Some(SystemClauseType::GetByte), + ("$get_char", 2) => Some(SystemClauseType::GetChar), + ("$get_code", 2) => Some(SystemClauseType::GetCode), ("$get_single_char", 1) => Some(SystemClauseType::GetSingleChar), ("$points_to_cont_reset_marker", 1) => { Some(SystemClauseType::PointsToContinuationResetMarker) } + ("$put_byte", 2) => { + Some(SystemClauseType::PutByte) + } + ("$put_char", 2) => { + Some(SystemClauseType::PutChar) + } + ("$put_code", 2) => { + Some(SystemClauseType::PutCode) + } ("$reset_attr_var_state", 0) => Some(SystemClauseType::ResetAttrVarState), ("$truncate_if_no_lh_growth", 1) => { Some(SystemClauseType::TruncateIfNoLiftedHeapGrowth) @@ -503,6 +568,7 @@ impl SystemClauseType { ("$number_to_chars", 2) => Some(SystemClauseType::NumberToChars), ("$number_to_codes", 2) => Some(SystemClauseType::NumberToCodes), ("$op", 3) => Some(SystemClauseType::OpDeclaration), + ("$open", 7) => Some(SystemClauseType::Open), ("$redo_attr_var_binding", 2) => Some(SystemClauseType::RedoAttrVarBinding), ("$remove_call_policy_check", 1) => Some(SystemClauseType::RemoveCallPolicyCheck), ("$remove_inference_counter", 2) => Some(SystemClauseType::RemoveInferenceCounter), @@ -510,6 +576,8 @@ impl SystemClauseType { ("$set_cp", 1) => Some(SystemClauseType::SetCutPoint(temp_v!(1))), ("$set_input", 1) => Some(SystemClauseType::SetInput), ("$set_output", 1) => Some(SystemClauseType::SetOutput), + ("$stream_property", 3) => Some(SystemClauseType::StreamProperty), + ("$set_stream_position", 2) => Some(SystemClauseType::SetStreamPosition), ("$inference_level", 2) => Some(SystemClauseType::InferenceLevel), ("$clean_up_block", 1) => Some(SystemClauseType::CleanUpBlock), ("$erase_ball", 0) => Some(SystemClauseType::EraseBall), @@ -523,8 +591,8 @@ impl SystemClauseType { ("$install_new_block", 1) => Some(SystemClauseType::InstallNewBlock), ("$quoted_token", 1) => Some(SystemClauseType::QuotedToken), ("$nextEP", 3) => Some(SystemClauseType::NextEP), - ("$read_query_term", 2) => Some(SystemClauseType::ReadQueryTerm), - ("$read_term", 2) => Some(SystemClauseType::ReadTerm), + ("$read_query_term", 5) => Some(SystemClauseType::ReadQueryTerm), + ("$read_term", 5) => Some(SystemClauseType::ReadTerm), ("$read_term_from_chars", 2) => Some(SystemClauseType::ReadTermFromChars), ("$reset_block", 1) => Some(SystemClauseType::ResetBlock), ("$reset_cont_marker", 0) => Some(SystemClauseType::ResetContinuationMarker), @@ -538,6 +606,10 @@ impl SystemClauseType { ("$set_seed", 1) => Some(SystemClauseType::SetSeed), ("$skip_max_list", 4) => Some(SystemClauseType::SkipMaxList), ("$sleep", 1) => Some(SystemClauseType::Sleep), + ("$socket_client_open", 7) => Some(SystemClauseType::SocketClientOpen), + ("$socket_server_open", 3) => Some(SystemClauseType::SocketServerOpen), + ("$socket_server_accept", 7) => Some(SystemClauseType::SocketServerAccept), + ("$socket_server_close", 1) => Some(SystemClauseType::SocketServerClose), ("$store_global_var", 2) => Some(SystemClauseType::StoreGlobalVar), ("$store_global_var_with_offset", 2) => Some(SystemClauseType::StoreGlobalVarWithOffset), ("$term_attributed_variables", 2) => Some(SystemClauseType::TermAttributedVariables), @@ -555,7 +627,7 @@ impl SystemClauseType { Some(SystemClauseType::REPL(REPLCodePtr::UseQualifiedModuleFromFile)), ("$variant", 2) => Some(SystemClauseType::Variant), ("$wam_instructions", 3) => Some(SystemClauseType::WAMInstructions), - ("$write_term", 6) => Some(SystemClauseType::WriteTerm), + ("$write_term", 7) => Some(SystemClauseType::WriteTerm), ("$write_term_to_chars", 7) => Some(SystemClauseType::WriteTermToChars), ("$scryer_prolog_version", 1) => Some(SystemClauseType::ScryerPrologVersion), _ => None, diff --git a/src/prolog/examples/echo_server.pl b/src/prolog/examples/echo_server.pl new file mode 100644 index 00000000..4543e60d --- /dev/null +++ b/src/prolog/examples/echo_server.pl @@ -0,0 +1,31 @@ +:- module(echo_server, [echo_server/0, + echo_server/1]). + +:- use_module(library(format)). +:- use_module(library(sockets)). + + +echo_server :- + echo_server('127.0.0.1'). + + +echo_server(Addr) :- + socket_server_open(Addr:Port, ServerSocket), + format("echo_server: connection opened at ~w:~d~n", [Addr, Port]), + socket_server_accept(ServerSocket, Client, Stream, [eof_action(eof_code)]), + format("echo_server: connection accepted from ~a~n", [Client]), + !, + echo_loop(Stream), + socket_server_close(ServerSocket). + + +echo_loop(Stream) :- + read_term(Stream, Term, []), + ( Term == end_of_file -> + true + ; + format("received: ~w~n", [Term]), + !, + echo_loop(Stream) + ). + diff --git a/src/prolog/heap_iter.rs b/src/prolog/heap_iter.rs index de398ccb..28cdaf85 100644 --- a/src/prolog/heap_iter.rs +++ b/src/prolog/heap_iter.rs @@ -48,6 +48,9 @@ impl<'a> HCPreOrderIterator<'a> { HeapCellValue::Stream(_) => { Addr::Stream(h) } + &HeapCellValue::TcpListener(_) => { + Addr::TcpListener(h) + } } } diff --git a/src/prolog/heap_print.rs b/src/prolog/heap_print.rs index 5ddbcdd4..b68a580a 100644 --- a/src/prolog/heap_print.rs +++ b/src/prolog/heap_print.rs @@ -6,6 +6,7 @@ use crate::prolog::heap_iter::*; use crate::prolog::machine::heap::*; use crate::prolog::machine::machine_indices::*; use crate::prolog::machine::machine_state::*; +use crate::prolog::machine::streams::*; use crate::prolog::ordered_float::OrderedFloat; use crate::prolog::rug::{Integer, Rational}; @@ -14,6 +15,7 @@ use indexmap::{IndexMap, IndexSet}; use std::cell::Cell; use std::convert::TryFrom; use std::iter::{FromIterator, once}; +use std::net::{IpAddr, TcpListener}; use std::ops::{Range, RangeFrom}; use std::rc::Rc; @@ -170,10 +172,12 @@ enum TokenOrRedirect { NumberedVar(String), CompositeRedirect(usize, DirectedOp), FunctorRedirect(usize), + IpAddr(IpAddr), Number(Number, Option), Open, Close, Comma, + RawPtr(*const u8), Space, LeftCurly, RightCurly, @@ -643,8 +647,8 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> { } self.state_stack.pop(); - self.state_stack.push(TokenOrRedirect::Open); + self.state_stack.push(TokenOrRedirect::Open); self.state_stack.push(TokenOrRedirect::Atom(name)); true @@ -964,6 +968,18 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> { }); } + #[inline] + fn print_ip_addr(&mut self, ip: IpAddr) { + self.push_char('\''); + self.append_str(&format!("{}", ip)); + self.push_char('\''); + } + + #[inline] + fn print_raw_ptr(&mut self, ptr: *const u8) { + self.append_str(&format!("0x{:x}", ptr as usize)); + } + fn print_number(&mut self, n: Number, op: &Option) { let add_brackets = if let Some(op) = op { op.is_negative_sign() && n.is_positive() @@ -1330,6 +1346,66 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> { } } + fn print_tcp_listener( + &mut self, + iter: &mut HCPreOrderIterator, + tcp_listener: &TcpListener, + max_depth: usize, + ) { + let (ip, port) = + if let Some(addr) = tcp_listener.local_addr().ok() { + (addr.ip(), Number::from(addr.port() as isize)) + } else { + let disconnected_atom = clause_name!("$disconnected_tcp_listener"); + self.state_stack.push(TokenOrRedirect::Atom(disconnected_atom)); + + return; + }; + + if self.format_struct(iter, max_depth, 1, clause_name!("$tcp_listener")) { + let atom = self.state_stack.pop().unwrap(); + + self.state_stack.pop(); + self.state_stack.pop(); + + self.state_stack.push(TokenOrRedirect::Number(port, None)); + self.state_stack.push(TokenOrRedirect::Comma); + self.state_stack.push(TokenOrRedirect::IpAddr(ip)); + + self.state_stack.push(TokenOrRedirect::Open); + self.state_stack.push(atom); + } + } + + fn print_stream( + &mut self, + iter: &mut HCPreOrderIterator, + stream: &Stream, + max_depth: usize, + ) { + if let Some(alias) = &stream.options.alias { + self.print_atom(alias); + } else { + if self.format_struct(iter, max_depth, 1, clause_name!("$stream")) { + let atom = + if stream.is_stdout() || stream.is_stdin() { + TokenOrRedirect::Atom(clause_name!("user")) + } else { + TokenOrRedirect::RawPtr(stream.as_ptr()) + }; + + let stream_root = self.state_stack.pop().unwrap(); + + self.state_stack.pop(); + self.state_stack.pop(); + + self.state_stack.push(atom); + self.state_stack.push(TokenOrRedirect::Open); + self.state_stack.push(stream_root); + } + } + } + fn handle_heap_term( &mut self, iter: &mut HCPreOrderIterator, @@ -1440,15 +1516,10 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> { self.print_number(Number::Rational(n.clone()), &op); } &HeapCellValue::Stream(ref stream) => { - if let Some(alias) = &stream.options.alias { - self.print_atom(alias); - } else { - if stream.is_stdout() || stream.is_stdin() { - self.print_atom(&clause_name!("user")); - } else { - self.format_struct(iter, max_depth, 1, clause_name!("$stream")); - } - } + self.print_stream(iter, stream, max_depth); + } + &HeapCellValue::TcpListener(ref tcp_listener) => { + self.print_tcp_listener(iter, tcp_listener, max_depth); } _ => { unreachable!() @@ -1486,6 +1557,8 @@ impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> { self.handle_heap_term(&mut iter, None, true, max_depth) } TokenOrRedirect::Close => self.push_char(')'), + TokenOrRedirect::IpAddr(ip) => self.print_ip_addr(ip), + TokenOrRedirect::RawPtr(ptr) => self.print_raw_ptr(ptr), TokenOrRedirect::Open => self.push_char('('), TokenOrRedirect::OpenList(delimit) => { if !self.at_cdr(",") { diff --git a/src/prolog/lib/builtins.pl b/src/prolog/lib/builtins.pl index 0cebab9b..798e201a 100644 --- a/src/prolog/lib/builtins.pl +++ b/src/prolog/lib/builtins.pl @@ -41,20 +41,30 @@ user:term_expansion((:- op(Pred, Spec, [Op | OtherOps])), OpResults) :- :- module(builtins, [(=)/2, (\=)/2, (\+)/1, (',')/2, (->)/2, (;)/2, (=..)/2, (:)/2, (:)/3, (:)/4, (:)/5, (:)/6, (:)/7, (:)/8, (:)/9, (:)/10, (:)/11, (:)/12, - abolish/1, asserta/1, assertz/1, atom_chars/2, - atom_codes/2, atom_concat/3, atom_length/2, - bagof/3, catch/3, char_code/2, clause/2, - current_input/1, current_output/1, current_op/3, + abolish/1, asserta/1, assertz/1, + at_end_of_stream/0, at_end_of_stream/1, + atom_chars/2, atom_codes/2, atom_concat/3, + atom_length/2, bagof/3, catch/3, char_code/2, + clause/2, close/1, close/2, current_input/1, + current_output/1, current_op/3, current_predicate/1, current_prolog_flag/2, expand_goal/2, expand_term/2, fail/0, false/0, - findall/3, findall/4, get_char/1, halt/0, - max_arity/1, number_chars/2, number_codes/2, - once/1, op/3, read_term/2, read_term/3, repeat/0, - retract/1, set_prolog_flag/2, set_input/1, - set_output/1, setof/3, sub_atom/5, + findall/3, findall/4, flush_output/0, + flush_output/1, get_byte/1, get_byte/2, + get_char/1, get_char/2, get_code/1, get_code/2, + halt/0, max_arity/1, number_chars/2, + number_codes/2, once/1, op/3, open/3, open/4, + peek_byte/1, peek_byte/2, peek_char/1, + peek_char/2, peek_code/1, peek_code/2, + put_byte/1, put_byte/2, put_code/1, put_code/2, + put_char/1, put_char/2, read_term/2, read_term/3, + repeat/0, retract/1, set_prolog_flag/2, + set_input/1, set_stream_position/2, set_output/1, + setof/3, stream_property/2, sub_atom/5, subsumes_term/2, term_variables/2, throw/1, true/0, unify_with_occurs_check/2, write/1, - write_canonical/1, write_term/2, writeq/1]). + write_canonical/1, write_term/2, write_term/3, + writeq/1]). % the maximum arity flag. needs to be replaced with @@ -312,36 +322,39 @@ get_args([Arg|Args], Func, I0, N) :- '$call_with_default_policy'(I1 is I0 + 1), '$call_with_default_policy'(get_args(Args, Func, I1, N)). -% write, write_canonical, writeq, write_term. -is_write_option(Functor) :- - Functor =.. [Name, Arg], - ( Arg == true -> true - ; Arg == false -> true - ; Name == variable_names -> must_be_var_names_list(Arg) - ; Name == max_depth -> integer(Arg), Arg >= 0 - ; var(Arg) -> throw(error(instantiation_error, write_term/2)) - ; throw(error(domain_error(write_option, Functor), write_term/2)) - ), % 8.14.2.3 e) - ( Name == ignore_ops -> true - ; Name == quoted -> true - ; Name == numbervars -> true - ; Name == variable_names -> true - ; Name == max_depth -> true - ; throw(error(domain_error(write_option, Functor), write_term/2)) - ). % 8.14.2.3 e) +parse_write_options(Options, OptionValues, Stub) :- + DefaultOptions = [ignore_ops-false, max_depth-0, numbervars-false, + quoted-false, variable_names-[]], + parse_options_list(Options, parse_write_options_, DefaultOptions, OptionValues, Stub). -inst_member_or([X|Xs], Y, Z) :- - ( var(X) -> throw(error(instantiation_error, write_term/2)) - ; is_write_option(X) -> ( Y = X, ! ; inst_member_or(Xs, Y, Z) ) - ; throw(error(domain_error(write_option, X), write_term/2)) +parse_write_options_(ignore_ops(IgnoreOps), ignore_ops-IgnoreOps) :- + ( nonvar(IgnoreOps), lists:member(IgnoreOps, [true, false]) + ; + throw(error(domain_error(write_option, ignore_ops(IgnoreOps)), _)) + ). +parse_write_options_(quoted(Quoted), quoted-Quoted) :- + ( nonvar(Quoted), lists:member(Quoted, [true, false]) + ; + throw(error(domain_error(write_option, quoted(Quoted)), _)) + ). +parse_write_options_(numbervars(NumberVars), numbervars-NumberVars) :- + ( nonvar(NumberVars), lists:member(NumberVars, [true, false]) + ; + throw(error(domain_error(write_option, numbervars(NumberVars)), _)) + ). +parse_write_options_(variable_names(VNNames), variable_names-VNNames) :- + must_be_var_names_list(VNNames). +parse_write_options_(max_depth(MaxDepth), max_depth-MaxDepth) :- + ( integer(MaxDepth), MaxDepth >= 0 + ; + throw(error(domain_error(write_option, max_depth(MaxDepth)), _)) ). -inst_member_or([], Y, Y). must_be_var_names_list(VarNames) :- '$skip_max_list'(_, -1, VarNames, Tail), ( Tail == [] -> must_be_var_names_list_(VarNames, VarNames) ; var(Tail) -> throw(error(instantiation_error, write_term/2)) - ; throw(error(domain_error(write_options, variable_names(VarNames)), write_term/2)) + ; throw(error(domain_error(write_option, variable_names(VarNames)), write_term/2)) ). must_be_var_names_list_([], List). @@ -350,36 +363,34 @@ must_be_var_names_list_([VarName | VarNames], List) :- ( VarName = (Atom = _) -> ( atom(Atom) -> must_be_var_names_list_(VarNames, List) ; var(Atom) -> throw(error(instantiation_error, write_term/2)) - ; throw(error(domain_error(write_options, variable_names(List)), write_term/2)) + ; throw(error(domain_error(write_option, variable_names(List)), write_term/2)) ) - ; throw(error(domain_error(write_options, variable_names(List)), write_term/2)) + ; throw(error(domain_error(write_option, variable_names(List)), write_term/2)) ) - ; throw(error(instantiation_error, write_term/2)) % throw(error(domain_error(write_options, variable_names(List)), write_term/2)) + ; throw(error(instantiation_error, write_term/2)) ). -write_term(_, Options) :- - var(Options), throw(error(instantiation_error, write_term/2)). + write_term(Term, Options) :- - '$skip_max_list'(_, -1, Options, Options0), - ( var(Options0) -> throw(error(instantiation_error, write_term/2)) - ; Options0 == [] -> true - ; throw(error(type_error(list, Options), write_term/2)) - ), % 8.14.2.3 c) - inst_member_or(Options, ignore_ops(IgnoreOps), ignore_ops(false)), - inst_member_or(Options, numbervars(NumberVars), numbervars(false)), - inst_member_or(Options, quoted(Quoted), quoted(false)), - inst_member_or(Options, variable_names(VarNames), variable_names([])), - inst_member_or(Options, max_depth(MaxDepth), max_depth(0)), - '$write_term'(Term, IgnoreOps, NumberVars, Quoted, VarNames, MaxDepth). + current_output(Stream), + write_term(Stream, Term, Options). + +write_term(Stream, Term, Options) :- + parse_write_options(Options, [IgnoreOps, MaxDepth, NumberVars, Quoted, VNNames], write_term/3), + '$write_term'(Stream, Term, IgnoreOps, NumberVars, Quoted, VNNames, MaxDepth). + write(Term) :- - '$write_term'(Term, false, true, false, [], 0). + current_output(Stream), + '$write_term'(Stream, Term, false, true, false, [], 0). write_canonical(Term) :- - '$write_term'(Term, true, false, true, [], 0). + current_output(Stream), + '$write_term'(Stream, Term, true, false, true, [], 0). writeq(Term) :- - '$write_term'(Term, false, true, true, [], 0). + current_output(Stream), + '$write_term'(Stream, Term, false, true, true, [], 0). @@ -420,25 +431,16 @@ parse_read_term_options(Options, OptionValues, Stub) :- parse_options_list(Options, parse_read_term_options_, DefaultOptions, OptionValues, Stub). -parse_read_term_options_(singletons(Vars), singletons-Vars) :- - ( '$skip_max_list'(Vars, _, -1, Tail), Tail == [], ! - ; - throw(error(domain_error(read_option, singletons(Vars)), _)) - ). -parse_read_term_options_(variables(Vars), variables-Vars) :- - ( '$skip_max_list'(Vars, _, -1, Tail), Tail == [], ! - ; - throw(error(domain_error(read_option, variables(Vars)), _)) - ). -parse_read_term_options_(variable_names(Vars), variable_names-Vars) :- - ( '$skip_max_list'(Vars, _, -1, Tail), Tail == [], ! - ; - throw(error(domain_error(read_option, variable_names(Vars)), _)) - ). +parse_read_term_options_(singletons(Vars), singletons-Vars). +parse_read_term_options_(variables(Vars), variables-Vars). +parse_read_term_options_(variable_names(Vars), variable_names-Vars). +parse_read_term_options_(E,_) :- + throw(error(domain_error(read_option, E), _)). + read_term(Stream, Term, Options) :- - parse_read_term_options(Options, [Singletons, Variables, VariableNames], read_term/3), + parse_read_term_options(Options, [Singletons, VariableNames, Variables], read_term/3), '$read_term'(Stream, Term, Singletons, Variables, VariableNames). read_term(Term, Options) :- @@ -1004,11 +1006,11 @@ char_code(Char, Code) :- ). get_char(C) :- - ( var(C) -> '$get_char'(C) - ; C == end_of_file -> '$get_char'(C) - ; atom_length(C, 1) -> '$get_char'(C) - ; throw(error(type_error(in_character, C), get_char/1)) - ). + current_input(S), + '$get_char'(S, C). + +get_char(S, C) :- + '$get_char'(S, C). can_be_number(N, PI) :- ( var(N) -> true @@ -1148,3 +1150,182 @@ parse_stream_options_(eof_action(Action), eof_action-Action) :- ). parse_stream_options_(E, _) :- throw(error(domain_error(stream_option, E), _)). % 8.11.5.3i) + + +open(SourceSink, Mode, Stream) :- + open(SourceSink, Mode, Stream, []). + +open(SourceSink, Mode, Stream, StreamOptions) :- + ( var(SourceSink) -> + throw(error(instantiation_error, open/4)) % 8.11.5.3a) + ; var(Mode) -> + throw(error(instantiation_error, open/4)) % 8.11.5.3b) + ; \+ atom(Mode) -> + throw(error(type_error(atom, Mode), open/4)) % 8.11.5.3d) + ; nonvar(Stream) -> + throw(error(type_error(variable, Stream), open/4)) % 8.11.5.3f) + ; + parse_stream_options(StreamOptions, [Alias, EOFAction, Reposition, Type], open/4), + '$open'(SourceSink, Mode, Stream, Alias, EOFAction, Reposition, Type) + ). + + +parse_close_options(Options, OptionValues, Stub) :- + DefaultOptions = [force-false], + parse_options_list(Options, parse_close_options_, DefaultOptions, OptionValues, Stub). + +parse_close_options_(force(Force), force-Force) :- + ( nonvar(Force), lists:member(Force, [true, false]), ! + ; + throw(error(domain_error(close_option, force(Force)), _)) + ). +parse_close_options_(E, _) :- + throw(error(domain_error(close_option, E), _)). + + +close(Stream, CloseOptions) :- + parse_close_options(CloseOptions, [Force], close/2), + '$close'(Stream, CloseOptions). + +close(Stream) :- + '$close'(Stream, []). + + +flush_output(S) :- + '$flush_output'(S). + +flush_output :- + current_output(S), + '$flush_output'(S). + + +get_byte(S, B) :- + '$get_byte'(S, B). + +get_byte(B) :- + current_input(S), + '$get_byte'(S, B). + + +put_char(C) :- + current_output(S), + '$put_char'(S, C). + +put_char(S, C) :- + '$put_char'(S, C). + + +put_byte(C) :- + current_output(S), + '$put_byte'(S, C). + +put_byte(S, C) :- + '$put_byte'(S, C). + + +put_code(C) :- + current_output(S), + '$put_code'(S, C). + +put_code(S, C) :- + '$put_code'(S, C). + + +get_code(C) :- + current_input(S), + '$get_code'(S, C). + +get_code(S, C) :- + '$get_code'(S, C). + + +peek_byte(S, B) :- + '$peek_byte'(S, B). + +peek_byte(B) :- + current_input(S), + '$peek_byte'(S, B). + + +peek_code(C) :- + current_input(S), + '$peek_code'(S, C). + +peek_code(S, C) :- + '$peek_code'(S, C). + + +peek_char(C) :- + current_input(S), + '$peek_char'(S, C). + +peek_char(S, C) :- + '$peek_char'(S, C). + + +check_stream_property(file_name(F), file_name, F) :- + ( var(F) -> true ; atom(F) ). +check_stream_property(mode(M), mode, M) :- + ( var(M) -> true ; lists:member(M, [read, write, append]) ). +check_stream_property(D, direction, D) :- + ( var(D) -> true ; lists:member(D, [input, output, input_output]), ! ). +check_stream_property(alias(A), alias, A) :- + ( var(A) -> true ; atom(A) ). +check_stream_property(position(P), position, P) :- + ( var(P) -> true ; integer(P), P >= 0 ). +check_stream_property(end_of_stream(E), end_of_stream, E) :- + ( var(E) -> true ; lists:member(E, [not, at, past]) ). +check_stream_property(eof_action(A), eof_action, A) :- + ( var(A) -> true ; lists:member(A, [error, eof_code, reset]) ). +check_stream_property(reposition(B), reposition, B) :- + ( var(B) -> true ; lists:member(B, [true, false]) ). +check_stream_property(type(T), type, T) :- + ( var(T) -> true ; lists:member(T, [text, binary]) ). + + +stream_iter_(S, S). +stream_iter_(S, S1) :- + '$next_stream'(S, S0), + stream_iter_(S0, S1). + +stream_iter(S) :- + ( nonvar(S) -> + true + ; '$first_stream'(S0), + stream_iter_(S0, S) + ). + + +stream_property(S, P) :- + ( nonvar(P), \+ check_stream_property(P, _, _) -> + throw(error(domain_error(stream_property, P), stream_property/2)) + ; stream_iter(S), + check_stream_property(P, PropertyName, PropertyValue), + '$stream_property'(S, PropertyName, PropertyValue) + ). + + +at_end_of_stream(S_or_a) :- + ( atom(S_or_a) -> + stream_property(S, alias(A)) + ; S = S_or_a + ), + stream_property(S, end_of_stream(E)), + !, + ( E = at ; E = past ). + +at_end_of_stream :- + current_input(S), + stream_property(S, end_of_stream(E)), + !, + ( E = at ; E = past ). + + +set_stream_position(S_or_a, Position) :- + ( var(Position) -> + throw(error(instantiation_error, set_stream_position/2)) + ; integer(Position), Position >= 0 -> + true + ; throw(error(domain_error(stream_position, Position))) + ), + '$set_stream_position'(S_or_a, Position). diff --git a/src/prolog/lib/charsio.pl b/src/prolog/lib/charsio.pl index 69a36430..e929dc81 100644 --- a/src/prolog/lib/charsio.pl +++ b/src/prolog/lib/charsio.pl @@ -124,21 +124,14 @@ read_term_from_chars(Chars, Term) :- write_term_to_chars(_, Options, _) :- var(Options), instantiation_error(write_term_to_chars/3). write_term_to_chars(Term, Options, Chars) :- - '$skip_max_list'(_, -1, Options, Options0), - ( var(Options0) -> - instantiation_error(write_term_to_chars/3) - ; nonvar(Chars) -> + builtins:parse_write_options(Options, + [IgnoreOps, MaxDepth, NumberVars, Quoted, VNNames], + write_term_to_chars/3), + ( nonvar(Chars) -> throw(error(uninstantiation_error(Chars), write_term_to_chars/3)) - ; Options0 == [] -> - true ; - type_error(list, Options, write_term_to_chars/3) + true ), - builtins:inst_member_or(Options, ignore_ops(IgnoreOps), ignore_ops(false)), - builtins:inst_member_or(Options, numbervars(NumberVars), numbervars(false)), - builtins:inst_member_or(Options, quoted(Quoted), quoted(false)), - builtins:inst_member_or(Options, variable_names(VarNames), variable_names([])), - builtins:inst_member_or(Options, max_depth(MaxDepth), max_depth(0)), term_variables(Term, Vars), extend_var_list(Vars, VarNames, NewVarNames, numbervars), - '$write_term_to_chars'(Term, IgnoreOps, NumberVars, Quoted, NewVarNames, MaxDepth, Chars). + '$write_term_to_chars'(Chars, Term, IgnoreOps, NumberVars, Quoted, NewVarNames, MaxDepth). diff --git a/src/prolog/lib/sockets.pl b/src/prolog/lib/sockets.pl new file mode 100644 index 00000000..a006d6a2 --- /dev/null +++ b/src/prolog/lib/sockets.pl @@ -0,0 +1,58 @@ + +:- module(sockets, [socket_client_open/3, + socket_server_open/2, + socket_server_accept/4, + socket_server_close/1, + current_hostname/1]). + +:- use_module(library(error)). + + +socket_client_open(Addr, Stream, Options) :- + ( var(Addr) -> + throw(error(instantiation_error, socket_client_open/3)) + ; + true + ), + must_be(var, Stream), + must_be(list, Options), + ( Addr = Address:Port, + atom(Address), + ( atom(Port) ; integer(Port) ) -> + true + ; + throw(error(type_error(socket_address, Addr), socket_client_open/3)) + ), + builtins:parse_stream_options(Options, + [Alias, EOFAction, Reposition, Type], + socket_client_open/3), + '$socket_client_open'(Address, Port, Stream, Alias, EOFAction, Reposition, Type). + + +socket_server_open(Addr, ServerSocket) :- + must_be(var, ServerSocket), + ( ( integer(Addr) ; var(Addr) ) -> + '$socket_server_open'([], Addr, ServerSocket) + ; + Addr = Address:Port, + must_be(atom, Address), + can_be(integer, Port), + '$socket_server_open'(Address, Port, ServerSocket) + ). + + +socket_server_accept(ServerSocket, Client, Stream, Options) :- + must_be(var, Client), + must_be(var, Stream), + builtins:parse_stream_options(Options, + [Alias, EOFAction, Reposition, Type], + socket_server_accept/4), + '$socket_server_accept'(ServerSocket, Client, Stream, Alias, EOFAction, Reposition, Type). + + +socket_server_close(ServerSocket) :- + '$socket_server_close'(ServerSocket). + + +current_hostname(HostName) :- + '$current_hostname'(HostName). diff --git a/src/prolog/machine/compile.rs b/src/prolog/machine/compile.rs index f59c8610..c4842978 100644 --- a/src/prolog/machine/compile.rs +++ b/src/prolog/machine/compile.rs @@ -119,7 +119,7 @@ fn load_module_from_file( let mut path_buf = fix_filename(wam.indices.atom_tbl.clone(), path_buf)?; let filename = clause_name!(path_buf.to_string_lossy().to_string(), wam.indices.atom_tbl); - let file_handle = Stream::from(File::open(&path_buf).or_else(|_| { + let file_handle = Stream::from_file_as_input(filename.clone(), File::open(&path_buf).or_else(|_| { Err(SessionError::InvalidFileName(filename.clone())) })?); @@ -614,7 +614,7 @@ fn load_library( ) } None => { - let err = ExistenceError::SourceSink(ModuleSource::Library( + let err = ExistenceError::ModuleSource(ModuleSource::Library( name.clone() )); @@ -705,7 +705,7 @@ impl ListingCompiler { Ok(wam_indices.insert_module(submodule)) } else { - let err = ExistenceError::SourceSink(ModuleSource::File( + let err = ExistenceError::ModuleSource(ModuleSource::File( module_name, )); @@ -743,7 +743,7 @@ impl ListingCompiler { Ok(wam_indices.insert_module(submodule)) } else { - let err = ExistenceError::SourceSink(ModuleSource::File( + let err = ExistenceError::ModuleSource(ModuleSource::File( module_name )); @@ -1077,7 +1077,7 @@ impl ListingCompiler { insert_or_refresh_term_dir_quantum(term_dir, key, term_dirs); } None => { - let err = ExistenceError::SourceSink(ModuleSource::File( + let err = ExistenceError::ModuleSource(ModuleSource::File( module_name, )); @@ -1436,7 +1436,7 @@ pub(super) fn setup_indices( wam.indices.insert_module(module); result } else { - let err = ExistenceError::SourceSink(ModuleSource::Library( + let err = ExistenceError::ModuleSource(ModuleSource::Library( module )); diff --git a/src/prolog/machine/copier.rs b/src/prolog/machine/copier.rs index 08c41944..dd861731 100644 --- a/src/prolog/machine/copier.rs +++ b/src/prolog/machine/copier.rs @@ -1,6 +1,5 @@ use crate::prolog::machine::machine_indices::*; use crate::prolog::machine::stack::*; -use crate::prolog::machine::streams::*; use std::mem; use std::ops::IndexMut; @@ -215,24 +214,6 @@ impl CopyTermState { } } - fn copy_stream(&mut self, addr: usize) { - let threshold = self.target.threshold(); - - let trail_item = mem::replace( - &mut self.target[addr], - HeapCellValue::Addr(Addr::Stream(threshold)), - ); - - self.trail.push(( - Ref::HeapCell(addr), - trail_item, - )); - - self.target.push(HeapCellValue::Stream(Stream::null_stream())); - - self.scan += 1; - } - fn copy_structure(&mut self, addr: usize) { match self.target[addr].context_free_clone() { HeapCellValue::NamedStr(arity, name, fixity) => { @@ -285,11 +266,12 @@ impl CopyTermState { *self.value_at_scan() = HeapCellValue::Addr(addr); } } - Addr::Lis(h) if h >= self.old_h => { - self.scan += 1; - } Addr::Lis(h) => { - self.copy_list(h); + if h >= self.old_h { + self.scan += 1; + } else { + self.copy_list(h); + } } addr @ Addr::AttrVar(_) | addr @ Addr::HeapCell(_) | @@ -303,7 +285,7 @@ impl CopyTermState { self.copy_partial_string(addr, n); } Addr::Stream(h) => { - self.copy_stream(h); + *self.value_at_scan() = self.target[h].context_free_clone(); } _ => { self.scan += 1; diff --git a/src/prolog/machine/heap.rs b/src/prolog/machine/heap.rs index 9d8cbfe7..718cbfe3 100644 --- a/src/prolog/machine/heap.rs +++ b/src/prolog/machine/heap.rs @@ -171,15 +171,18 @@ impl HeapTemplate { &HeapCellValue::NamedStr(arity, ref name, ref op) => { HeapCellValue::NamedStr(arity, name.clone(), op.clone()) } - &HeapCellValue::Rational(ref r) => { - HeapCellValue::Rational(r.clone()) - } &HeapCellValue::PartialString(..) => { HeapCellValue::Addr(Addr::PStrLocation(h, 0)) } + &HeapCellValue::Rational(ref r) => { + HeapCellValue::Rational(r.clone()) + } &HeapCellValue::Stream(_) => { HeapCellValue::Addr(Addr::Stream(h)) } + &HeapCellValue::TcpListener(_) => { + HeapCellValue::Addr(Addr::TcpListener(h)) + } } } @@ -285,18 +288,15 @@ impl HeapTemplate { HeapCellValue::Addr(addr) => { addr } - val @ HeapCellValue::Atom(..) - | val @ HeapCellValue::Integer(_) - | val @ HeapCellValue::DBRef(_) - | val @ HeapCellValue::Rational(_) => { + val @ HeapCellValue::Atom(..) | + val @ HeapCellValue::Integer(_) | + val @ HeapCellValue::DBRef(_) | + val @ HeapCellValue::Rational(_) => { Addr::Con(self.push(val)) } val @ HeapCellValue::NamedStr(..) => { Addr::Str(self.push(val)) } - val @ HeapCellValue::Stream(..) => { - Addr::Stream(self.push(val)) - } HeapCellValue::PartialString(pstr, has_tail) => { let h = self.push(HeapCellValue::PartialString(pstr, has_tail)); @@ -306,6 +306,12 @@ impl HeapTemplate { Addr::Con(h) } + val @ HeapCellValue::Stream(..) => { + Addr::Stream(self.push(val)) + } + val @ HeapCellValue::TcpListener(..) => { + Addr::TcpListener(self.push(val)) + } } } @@ -517,7 +523,7 @@ impl HeapTemplate { pub fn index_addr<'a>(&'a self, addr: &Addr) -> RefOrOwned<'a, HeapCellValue> { match addr { - &Addr::Con(h) | &Addr::Str(h) | &Addr::Stream(h) => { + &Addr::Con(h) | &Addr::Str(h) | &Addr::Stream(h) | &Addr::TcpListener(h) => { RefOrOwned::Borrowed(&self[h]) } addr => { diff --git a/src/prolog/machine/machine_errors.rs b/src/prolog/machine/machine_errors.rs index fd1e9397..93c36a73 100644 --- a/src/prolog/machine/machine_errors.rs +++ b/src/prolog/machine/machine_errors.rs @@ -17,13 +17,13 @@ enum ErrorProvenance { } #[derive(Debug)] -pub(super) struct MachineError { +pub(crate) struct MachineError { stub: MachineStub, location: Option<(usize, usize)>, // line_num, col_num from: ErrorProvenance, } -pub(super) +pub(crate) trait TypeError { fn type_error(self, h: usize, valid_type: ValidType) -> MachineError; } @@ -74,7 +74,7 @@ impl TypeError for Number { } } -pub(super) +pub(crate) trait PermissionError { fn permission_error(self, h: usize, index_str: &'static str, perm: Permission) -> MachineError; } @@ -250,7 +250,7 @@ impl MachineError { from: ErrorProvenance::Constructed, } } - ExistenceError::SourceSink(source) => { + ExistenceError::ModuleSource(source) => { let source_stub = source.as_functor_stub(); let stub = functor!( @@ -265,6 +265,18 @@ impl MachineError { from: ErrorProvenance::Constructed, } } + ExistenceError::SourceSink(culprit) => { + let stub = functor!( + "existence_error", + [atom("source_sink"), addr(culprit)] + ); + + MachineError { + stub, + location: None, + from: ErrorProvenance::Received, + } + } ExistenceError::Stream(culprit) => { let stub = functor!( "existence_error", @@ -454,17 +466,22 @@ pub enum Permission { Create, InputStream, Modify, + Open, OutputStream, + Reposition, } impl Permission { + #[inline] pub fn as_str(self) -> &'static str { match self { Permission::Access => "access", Permission::Create => "create", Permission::InputStream => "input", Permission::Modify => "modify", + Permission::Open => "open", Permission::OutputStream => "output", + Permission::Reposition => "reposition", } } } @@ -475,20 +492,21 @@ pub enum ValidType { Atom, Atomic, // Boolean, - // Byte, + Byte, Callable, Character, Compound, Evaluable, Float, - // InByte, - // InCharacter, + InByte, + InCharacter, Integer, List, // Number, Pair, // PredicateIndicator, // Variable + TcpListener, } impl ValidType { @@ -497,26 +515,28 @@ impl ValidType { ValidType::Atom => "atom", ValidType::Atomic => "atomic", // ValidType::Boolean => "boolean", - // ValidType::Byte => "byte", + ValidType::Byte => "byte", ValidType::Callable => "callable", ValidType::Character => "character", ValidType::Compound => "compound", ValidType::Evaluable => "evaluable", ValidType::Float => "float", - // ValidType::InByte => "in_byte", - // ValidType::InCharacter => "in_character", + ValidType::InByte => "in_byte", + ValidType::InCharacter => "in_character", ValidType::Integer => "integer", ValidType::List => "list", // ValidType::Number => "number", ValidType::Pair => "pair", // ValidType::PredicateIndicator => "predicate_indicator", // ValidType::Variable => "variable" + ValidType::TcpListener => "tcp_listener", } } } #[derive(Debug, Clone, Copy)] pub enum DomainErrorType { + IOMode, NotLessThanZero, Order, Stream, @@ -526,6 +546,7 @@ pub enum DomainErrorType { impl DomainErrorType { pub fn as_str(self) -> &'static str { match self { + DomainErrorType::IOMode => "io_mode", DomainErrorType::NotLessThanZero => "not_less_than_zero", DomainErrorType::Order => "order", DomainErrorType::Stream => "stream", @@ -537,9 +558,9 @@ impl DomainErrorType { // from 7.12.2 f) of 13211-1:1995 #[derive(Debug, Clone, Copy)] pub enum RepFlag { - Character, + // Character, CharacterCode, - // InCharacterCode, + InCharacterCode, MaxArity, // MaxInteger, // MinInteger @@ -548,9 +569,9 @@ pub enum RepFlag { impl RepFlag { pub fn as_str(self) -> &'static str { match self { - RepFlag::Character => "character", + // RepFlag::Character => "character", RepFlag::CharacterCode => "character_code", - // RepFlag::InCharacterCode => "in_character_code", + RepFlag::InCharacterCode => "in_character_code", RepFlag::MaxArity => "max_arity", // RepFlag::MaxInteger => "max_integer", // RepFlag::MinInteger => "min_integer" @@ -681,6 +702,41 @@ impl MachineState { self.check_for_list_pairs(sorted) } + #[inline] + pub(crate) + fn type_error( + &self, + valid_type: ValidType, + culprit: T, + caller: ClauseName, + arity: usize, + ) -> MachineStub { + let stub = MachineError::functor_stub(caller, arity); + let err = MachineError::type_error( + self.heap.h(), + valid_type, + culprit, + ); + + return self.error_form(err, stub); + } + + #[inline] + pub(crate) + fn representation_error( + &self, + rep_flag: RepFlag, + caller: ClauseName, + arity: usize, + ) -> MachineStub { + let stub = MachineError::functor_stub(caller, arity); + let err = MachineError::representation_error( + rep_flag, + ); + + return self.error_form(err, stub); + } + pub(super) fn error_form(&self, err: MachineError, src: MachineStub) -> MachineStub { let location = err.location; @@ -726,8 +782,9 @@ impl MachineState { #[derive(Debug)] pub enum ExistenceError { Module(ClauseName), + ModuleSource(ModuleSource), Procedure(ClauseName, usize), - SourceSink(ModuleSource), + SourceSink(Addr), Stream(Addr), } diff --git a/src/prolog/machine/machine_indices.rs b/src/prolog/machine/machine_indices.rs index f35e76f6..21ff3e0f 100644 --- a/src/prolog/machine/machine_indices.rs +++ b/src/prolog/machine/machine_indices.rs @@ -19,10 +19,11 @@ use indexmap::IndexMap; use std::cell::RefCell; use std::cmp::Ordering; -use std::collections::{BTreeMap, VecDeque}; +use std::collections::{BTreeMap, BTreeSet, VecDeque}; use std::convert::TryFrom; use std::fmt; use std::mem; +use std::net::TcpListener; use std::ops::{Add, AddAssign, Sub, SubAssign}; use std::rc::Rc; @@ -69,6 +70,7 @@ pub enum Addr { StackCell(usize, usize), Str(usize), Stream(usize), + TcpListener(usize), Usize(usize), } @@ -230,7 +232,7 @@ impl Addr { Addr::Lis(_) | Addr::PStrLocation(..) | Addr::Str(_) => { Some(TermOrderCategory::Compound) } - Addr::CutPoint(_) | Addr::Stream(_) => { + Addr::CutPoint(_) | Addr::Stream(_) | Addr::TcpListener(_) => { None } } @@ -388,6 +390,7 @@ pub enum HeapCellValue { Rational(Rc), PartialString(PartialString, bool), // the partial string, a bool indicating whether it came from a Constant. Stream(Stream), + TcpListener(TcpListener), } impl HeapCellValue { @@ -410,6 +413,9 @@ impl HeapCellValue { HeapCellValue::Stream(_) => { Addr::Stream(focus) } + HeapCellValue::TcpListener(_) => { + Addr::TcpListener(focus) + } } } @@ -437,8 +443,11 @@ impl HeapCellValue { &HeapCellValue::PartialString(ref pstr, has_tail) => { HeapCellValue::PartialString(pstr.clone(), has_tail) } - &HeapCellValue::Stream(_) => { - HeapCellValue::Stream(Stream::null_stream()) + &HeapCellValue::Stream(ref stream) => { + HeapCellValue::Stream(stream.clone()) + } + &HeapCellValue::TcpListener(_) => { + HeapCellValue::Atom(clause_name!("$tcp_listener"), None) } } } @@ -815,6 +824,7 @@ impl ModuleStub { pub(crate) type ModuleStubDir = IndexMap; pub(crate) type StreamAliasDir = IndexMap; +pub(crate) type StreamDir = BTreeSet; #[derive(Debug)] pub struct IndexStore { @@ -827,6 +837,7 @@ pub struct IndexStore { pub(super) module_dir: ModuleDir, pub(super) modules: ModuleDir, pub(super) op_dir: OpDir, + pub(super) streams: StreamDir, pub(super) stream_aliases: StreamAliasDir, } @@ -915,6 +926,7 @@ impl IndexStore { op_dir: default_op_dir(), modules: ModuleDir::new(), stream_aliases: StreamAliasDir::new(), + streams: StreamDir::new(), } } diff --git a/src/prolog/machine/machine_state.rs b/src/prolog/machine/machine_state.rs index 5f3557fb..6f6c5365 100644 --- a/src/prolog/machine/machine_state.rs +++ b/src/prolog/machine/machine_state.rs @@ -12,7 +12,6 @@ use crate::prolog::machine::machine_indices::*; use crate::prolog::machine::modules::*; use crate::prolog::machine::stack::*; use crate::prolog::machine::streams::*; -use crate::prolog::read::{PrologStream, readline}; use crate::prolog::rug::Integer; use downcast::Any; @@ -614,88 +613,131 @@ pub struct MachineState { } impl MachineState { - pub(crate) - fn open_parsing_stream( - &self, - stream: Stream, - stub_name: &'static str, - stub_arity: usize, - ) -> Result { - match parsing_stream(stream) { - Ok(stream) => { - Ok(stream) - } - Err(e) => { - let stub = MachineError::functor_stub(clause_name!(stub_name), stub_arity); - let err = MachineError::session_error( - self.heap.h(), - SessionError::from(e), - ); - - Err(self.error_form(err, stub)) - } - } - } - pub(crate) fn read_term( &mut self, - current_input_stream: &mut Stream, + mut stream: Stream, indices: &mut IndexStore, ) -> CallResult { - let mut stream = self.open_parsing_stream( - current_input_stream.clone(), - "read_term", - 2, + self.check_stream_properties( + &mut stream, + StreamType::Text, + Some(self[temp_v!(2)]), + clause_name!("read_term"), + 3, )?; - match self.read( - &mut stream, - indices.atom_tbl.clone(), - &indices.op_dir, - ) { - Ok(term_write_result) => { - let a1 = self[temp_v!(1)]; - self.unify(Addr::HeapCell(term_write_result.heap_loc), a1); + if stream.past_end_of_stream() { + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + } + + let mut orig_stream = stream.clone(); + let mut stream = self.open_parsing_stream(stream, "read_term", 3)?; + + loop { + match self.read( + &mut stream, + indices.atom_tbl.clone(), + &indices.op_dir, + ) { + Ok(term_write_result) => { + let term = self[temp_v!(2)]; + self.unify(Addr::HeapCell(term_write_result.heap_loc), term); + + if self.fail { + return Ok(()); + } + + let mut list_of_var_eqs = vec![]; + + for (var, binding) in term_write_result.var_dict.into_iter() { + let var_atom = clause_name!(var.to_string(), indices.atom_tbl); + + let h = self.heap.h(); + let spec = fetch_atom_op_spec(clause_name!("="), None, &indices.op_dir); + + self.heap.push(HeapCellValue::NamedStr(2, clause_name!("="), spec)); + self.heap.push(HeapCellValue::Atom(var_atom, None)); + self.heap.push(HeapCellValue::Addr(binding)); + + list_of_var_eqs.push(Addr::Str(h)); + } + + let mut var_set: IndexMap = IndexMap::new(); + + for addr in self.acyclic_pre_order_iter(term) { + if let Some(var) = addr.as_var() { + if !var_set.contains_key(&var) { + var_set.insert(var, true); + } else { + var_set.insert(var, false); + } + } + } + + let mut var_list = vec![]; + let mut singleton_var_list = vec![]; + + for addr in self.acyclic_pre_order_iter(term) { + if let Some(var) = addr.as_var() { + if var_set.get(&var) == Some(&true) { + singleton_var_list.push(var.as_addr()); + } + + var_list.push(var.as_addr()); + } + } + + let singleton_addr = self[temp_v!(3)]; + let singletons_offset = + Addr::HeapCell(self.heap.to_list(singleton_var_list.into_iter())); + + self.unify(singletons_offset, singleton_addr); + + if self.fail { + return Ok(()); + } + + let vars_addr = self[temp_v!(4)]; + let vars_offset = + Addr::HeapCell(self.heap.to_list(var_list.into_iter())); + + self.unify(vars_offset, vars_addr); + + if self.fail { + return Ok(()); + } + + let var_names_addr = self[temp_v!(5)]; + let var_names_offset = + Addr::HeapCell(self.heap.to_list(list_of_var_eqs.into_iter())); + + return Ok(self.unify(var_names_offset, var_names_addr)); + } + Err(err) => { + if let ParserError::UnexpectedEOF = err { + self.eof_action( + self[temp_v!(2)], + &mut orig_stream, + clause_name!("read_term"), + 3 + )?; + + if orig_stream.options.eof_action == EOFAction::Reset { + if self.fail == false { + continue; + } else { + return Ok(()); + } + } + } - if self.fail { return Ok(()); } - - let mut list_of_var_eqs = vec![]; - - for (var, binding) in term_write_result.var_dict.into_iter() { - let var_atom = clause_name!(var.to_string(), indices.atom_tbl); - - let h = self.heap.h(); - let spec = fetch_atom_op_spec(clause_name!("="), None, &indices.op_dir); - - self.heap.push(HeapCellValue::NamedStr(2, clause_name!("="), spec)); - self.heap.push(HeapCellValue::Atom(var_atom, None)); - self.heap.push(HeapCellValue::Addr(binding)); - - list_of_var_eqs.push(Addr::Str(h)); - } - - let a2 = self[temp_v!(2)]; - let list_offset = - Addr::HeapCell(self.heap.to_list(list_of_var_eqs.into_iter())); - - Ok(self.unify(list_offset, a2)) - } - Err(err) => { - if let ParserError::UnexpectedEOF = err { - std::process::exit(0); - } - - // reset the input stream after an input failure. - *current_input_stream = readline::input_stream(); - - let h = self.heap.h(); - let syntax_error = MachineError::syntax_error(h, err); - let stub = MachineError::functor_stub(clause_name!("read_term"), 2); - - Err(self.error_form(syntax_error, stub)) } } } @@ -706,10 +748,10 @@ impl MachineState { op_dir: &'a OpDir, ) -> Result>, MachineStub> { - let ignore_ops = self.store(self.deref(self[temp_v!(2)])); - let numbervars = self.store(self.deref(self[temp_v!(3)])); - let quoted = self.store(self.deref(self[temp_v!(4)])); - let max_depth = self.store(self.deref(self[temp_v!(6)])); + let ignore_ops = self.store(self.deref(self[temp_v!(3)])); + let numbervars = self.store(self.deref(self[temp_v!(4)])); + let quoted = self.store(self.deref(self[temp_v!(5)])); + let max_depth = self.store(self.deref(self[temp_v!(7)])); let mut printer = HCPrinter::new(&self, op_dir, PrinterOutputter::new()); @@ -759,7 +801,7 @@ impl MachineState { let stub = MachineError::functor_stub(clause_name!("write_term"), 2); - match self.try_from_list(temp_v!(5), stub) { + match self.try_from_list(temp_v!(6), stub) { Ok(addrs) => { let mut var_names: IndexMap = IndexMap::new(); @@ -792,9 +834,11 @@ impl MachineState { var_names.insert(var, atom); } - _ => unreachable!(), + _ => { + } }, - _ => unreachable!(), + _ => { + } } } diff --git a/src/prolog/machine/machine_state_impl.rs b/src/prolog/machine/machine_state_impl.rs index 42e45b4d..aa7cb911 100644 --- a/src/prolog/machine/machine_state_impl.rs +++ b/src/prolog/machine/machine_state_impl.rs @@ -1400,9 +1400,11 @@ impl MachineState { let addr = self.store(self.deref(addr)); let offset = match addr { - Addr::HeapCell(_) | Addr::StackCell(..) | - Addr::AttrVar(..) | Addr::Stream(_) => { - v + Addr::Stream(_) | Addr::TcpListener(_) => { + 0 + } + Addr::HeapCell(_) | Addr::StackCell(..) | Addr::AttrVar(..) => { + v } Addr::PStrLocation(..) => { if !self.flags.double_quotes.is_atom() { diff --git a/src/prolog/machine/mod.rs b/src/prolog/machine/mod.rs index 03deda01..78e3215a 100644 --- a/src/prolog/machine/mod.rs +++ b/src/prolog/machine/mod.rs @@ -297,7 +297,7 @@ impl Machine { Ok(self.indices.insert_module(module)) } else { - let err = ExistenceError::SourceSink(ModuleSource::File( + let err = ExistenceError::ModuleSource(ModuleSource::File( clause_name!("$toplevel"), )); @@ -315,7 +315,10 @@ impl Machine { if path.is_file() { let file_src = match File::open(&path) { - Ok(file_handle) => Stream::from(file_handle), + Ok(file_handle) => Stream::from_file_as_input( + clause_name!(".scryerrc"), + file_handle, + ), Err(_) => return, }; @@ -409,6 +412,15 @@ impl Machine { ) ); + compile_user_module(&mut wam, + Stream::from(PAIRS), + true, + ListingSource::from_file_and_path( + clause_name!("pairs"), + lib_path.clone(), + ) + ); + compile_user_module(&mut wam, Stream::from(LISTS), true, @@ -451,6 +463,28 @@ impl Machine { wam.compile_scryerrc(); + wam.current_input_stream.options.alias = Some(clause_name!("user_input")); + + wam.indices.stream_aliases.insert( + clause_name!("user_input"), + wam.current_input_stream.clone(), + ); + + wam.indices.streams.insert( + wam.current_input_stream.clone() + ); + + wam.current_output_stream.options.alias = Some(clause_name!("user_output")); + + wam.indices.stream_aliases.insert( + clause_name!("user_output"), + wam.current_output_stream.clone(), + ); + + wam.indices.streams.insert( + wam.current_output_stream.clone() + ); + wam } diff --git a/src/prolog/machine/streams.rs b/src/prolog/machine/streams.rs index d9a83934..68b2ec3b 100644 --- a/src/prolog/machine/streams.rs +++ b/src/prolog/machine/streams.rs @@ -1,14 +1,20 @@ use crate::prolog_parser::ast::*; +use crate::prolog::read::PrologStream; use crate::prolog::read::readline::*; +use crate::prolog::machine::machine_errors::*; +use crate::prolog::machine::machine_indices::*; +use crate::prolog::machine::machine_state::*; +use std::cmp::Ordering; use std::cell::RefCell; use std::error::Error; use std::fmt; use std::fs::File; -use std::io::{stdin, stdout, Cursor, ErrorKind, Read, Write}; +use std::io::{stdout, Cursor, ErrorKind, Read, Seek, SeekFrom, Write}; use std::hash::{Hash, Hasher}; -use std::net::TcpStream; +use std::net::{Shutdown, TcpStream}; +use std::ops::DerefMut; use std::rc::Rc; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -17,6 +23,35 @@ pub enum StreamType { Text, } +impl StreamType { + #[inline] + pub(crate) + fn as_str(&self) -> &'static str { + match self { + StreamType::Binary => "binary_stream", + StreamType::Text => "text_stream", + } + } + + #[inline] + pub(crate) + fn as_property_str(&self) -> &'static str { + match self { + StreamType::Binary => "binary", + StreamType::Text => "text", + } + } + + #[inline] + pub(crate) + fn other(self) -> StreamType { + match self { + StreamType::Binary => StreamType::Text, + StreamType::Text => StreamType::Binary, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum EOFAction { EOFCode, @@ -24,17 +59,59 @@ pub enum EOFAction { Reset, } +pub enum AtEndOfStream { + Not, + End, + Past +} + +impl AtEndOfStream { + #[inline] + pub(crate) + fn as_str(&self) -> &'static str { + match self { + AtEndOfStream::Not => "not", + AtEndOfStream::Past => "past", + AtEndOfStream::End => "end", + } + } +} + +impl EOFAction { + #[inline] + pub(crate) + fn as_str(&self) -> &'static str { + match self { + EOFAction::EOFCode => "eof_code", + EOFAction::Error => "error", + EOFAction::Reset => "reset", + } + } +} + /* all these streams are closed automatically when the instance is * dropped. */ pub enum StreamInstance { Bytes(Cursor>), DynReadSource(Box), - File(File), + InputFile(ClauseName, File), + OutputFile(ClauseName, File, bool), // File, append. Null, ReadlineStream(ReadlineStream), - Stdin, Stdout, - TcpStream(TcpStream), + TcpStream(ClauseName, TcpStream), +} + +impl Drop for StreamInstance { + fn drop(&mut self) { + match self { + StreamInstance::TcpStream(_, ref mut tcp_stream) => { + tcp_stream.shutdown(Shutdown::Both).unwrap(); + } + _ => { + } + } + } } impl fmt::Debug for StreamInstance { @@ -44,25 +121,28 @@ impl fmt::Debug for StreamInstance { write!(fmt, "Bytes({:?})", bytes), &StreamInstance::DynReadSource(_) => write!(fmt, "DynReadSource(_)"), // Hacky solution. - &StreamInstance::File(ref file) => write!(fmt, "File({:?})", file), + &StreamInstance::InputFile(_, ref file) => write!(fmt, "InputFile({:?})", file), + &StreamInstance::OutputFile(_, ref file, _) => write!(fmt, "OutputFile({:?})", file), &StreamInstance::Null => write!(fmt, "Null"), &StreamInstance::ReadlineStream(ref readline_stream) => write!(fmt, "ReadlineStream({:?})", readline_stream), - &StreamInstance::Stdin => write!(fmt, "Stdin"), + // &StreamInstance::Stdin => write!(fmt, "Stdin"), &StreamInstance::Stdout => write!(fmt, "Stdout"), - &StreamInstance::TcpStream(ref tcp_stream) => + &StreamInstance::TcpStream(_, ref tcp_stream) => write!(fmt, "TcpStream({:?})", tcp_stream), } } } #[derive(Debug, Clone)] -struct WrappedStreamInstance(Rc>); +struct WrappedStreamInstance(Rc>); impl WrappedStreamInstance { #[inline] fn new(stream_inst: StreamInstance) -> Self { - WrappedStreamInstance(Rc::new(RefCell::new(stream_inst))) + WrappedStreamInstance( + Rc::new(RefCell::new((false, stream_inst))) + ) } } @@ -91,6 +171,10 @@ impl Hash for WrappedStreamInstance { #[derive(Debug)] enum StreamError { + PeekByteFailed, + PeekByteFromNonPeekableStream, + PeekCharFailed, + PeekCharFromNonPeekableStream, ReadFromOutputStream, WriteToInputStream, FlushToInputStream, @@ -99,6 +183,18 @@ enum StreamError { impl fmt::Display for StreamError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + StreamError::PeekByteFailed => { + write!(f, "peek byte failed!") + } + StreamError::PeekByteFromNonPeekableStream => { + write!(f, "attempted to peek byte from a non-peekable input stream") + } + StreamError::PeekCharFailed => { + write!(f, "peek char failed!") + } + StreamError::PeekCharFromNonPeekableStream => { + write!(f, "attempted to peek char from a non-peekable input stream") + } StreamError::ReadFromOutputStream => { write!(f, "attempted to read from a write-only stream") } @@ -134,60 +230,57 @@ impl Default for StreamOptions { } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Hash)] pub struct Stream { pub options: StreamOptions, stream_inst: WrappedStreamInstance, } +impl PartialOrd for Stream { + #[inline] + fn partial_cmp(&self, other: &Stream) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Stream { + #[inline] + fn cmp(&self, other: &Stream) -> Ordering { + self.as_ptr().cmp(&other.as_ptr()) + } +} + +impl PartialEq for Stream { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.stream_inst == other.stream_inst + } +} + +impl Eq for Stream {} + impl From for Stream { fn from(string: String) -> Self { - Stream { - options: StreamOptions::default(), - stream_inst: WrappedStreamInstance::new( - StreamInstance::Bytes(Cursor::new(string.into_bytes())) - ) - } + Stream::from_inst(StreamInstance::Bytes(Cursor::new(string.into_bytes()))) } } impl From for Stream { fn from(rl_stream: ReadlineStream) -> Self { - Stream { - options: StreamOptions::default(), - stream_inst: WrappedStreamInstance::new( - StreamInstance::ReadlineStream(rl_stream) - ), - } + Stream::from_inst(StreamInstance::ReadlineStream(rl_stream)) } } impl From<&'static str> for Stream { fn from(src: &'static str) -> Stream { - Stream { - options: StreamOptions::default(), - stream_inst: WrappedStreamInstance::new( - StreamInstance::DynReadSource(Box::new(src.as_bytes())) - ), - } - } -} - -impl From for Stream { - fn from(file: File) -> Stream { - Stream { - options: StreamOptions::default(), - stream_inst: WrappedStreamInstance::new( - StreamInstance::File(file) - ), - } + Stream::from_inst(StreamInstance::DynReadSource(Box::new(src.as_bytes()))) } } impl Stream { #[inline] pub(crate) - fn as_ptr(&self) -> *const RefCell { + fn as_ptr(&self) -> *const u8 { let rc = self.stream_inst.0.clone(); let ptr = Rc::into_raw(rc); @@ -196,46 +289,175 @@ impl Stream { let _ = Rc::from_raw(ptr); } - ptr + ptr as *const u8 + } + + #[inline] + pub(crate) + fn position(&mut self) -> Option { + match self.stream_inst.0.borrow_mut().1 { + StreamInstance::InputFile(_, ref mut file) => { + file.seek(SeekFrom::Current(0)).ok() + } + _ => { + None + } + } + } + + #[inline] + pub(crate) + fn set_position(&mut self, position: u64) { + match self.stream_inst.0.borrow_mut().deref_mut() { + (past_end_of_stream, StreamInstance::InputFile(_, ref mut file)) => { + file.seek(SeekFrom::Start(position)).unwrap(); + + if let Ok(metadata) = file.metadata() { + *past_end_of_stream = position > metadata.len(); + } + } + _ => { + } + } + } + + #[inline] + pub(crate) + fn past_end_of_stream(&self) -> bool { + self.stream_inst.0.borrow_mut().0 + } + + #[inline] + pub(crate) + fn set_past_end_of_stream(&mut self) { + self.stream_inst.0.borrow_mut().0 = true; + } + + #[inline] + pub(crate) + fn position_relative_to_end(&mut self) -> AtEndOfStream { + if self.past_end_of_stream() { + return AtEndOfStream::Past; + } + + match self.stream_inst.0.borrow_mut().deref_mut() { + (past_end_of_stream, StreamInstance::InputFile(_, ref mut file)) => { + match file.metadata() { + Ok(metadata) => { + if let Ok(position) = file.seek(SeekFrom::Current(0)) { + return match position.cmp(&metadata.len()) { + Ordering::Equal => { + AtEndOfStream::End + } + Ordering::Less => { + AtEndOfStream::Not + } + Ordering::Greater => { + *past_end_of_stream = true; //self.set_past_end_of_stream(); + AtEndOfStream::Past + } + }; + } else { + *past_end_of_stream = true; //self.set_past_end_of_stream(); + AtEndOfStream::Past + } + } + _ => { + *past_end_of_stream = true; //self.set_past_end_of_stream(); + AtEndOfStream::Past + } + } + } + _ => { + AtEndOfStream::Not + } + } + } + + #[inline] + pub(crate) + fn file_name(&self) -> Option { + match self.stream_inst.0.borrow().1 { + StreamInstance::InputFile(ref name, _) => { + Some(name.clone()) + } + StreamInstance::OutputFile(ref name, ..) => { + Some(name.clone()) + } + StreamInstance::TcpStream(ref name, _) => { + Some(name.clone()) + } + _ => { + None + } + } + } + + #[inline] + pub(crate) + fn mode(&self) -> &'static str { + match self.stream_inst.0.borrow().1 { + StreamInstance::Bytes(_) | + StreamInstance::ReadlineStream(_) | + StreamInstance::DynReadSource(_) | + StreamInstance::InputFile(..) => { + "read" + } + StreamInstance::TcpStream(..) => { + "read_append" + } + StreamInstance::OutputFile(_, _, true) => { + "append" + } + StreamInstance::Stdout | + StreamInstance::OutputFile(_, _, false) => { + "write" + } + StreamInstance::Null => { + "" + } + } + } + + #[inline] + fn from_inst(stream_inst: StreamInstance) -> Self { + Stream { + options: StreamOptions::default(), + stream_inst: WrappedStreamInstance::new(stream_inst) + } } #[inline] pub(crate) fn stdout() -> Self { - Stream { - options: StreamOptions::default(), - stream_inst: WrappedStreamInstance::new( - StreamInstance::Stdout - ), - } + Stream::from_inst(StreamInstance::Stdout) } #[inline] pub(crate) - fn stdin() -> Self { - Stream { - options: StreamOptions::default(), - stream_inst: WrappedStreamInstance::new( - StreamInstance::Stdin - ), - } + fn from_tcp_stream(address: ClauseName, tcp_stream: TcpStream) -> Self { + tcp_stream.set_read_timeout(None).unwrap(); + tcp_stream.set_write_timeout(None).unwrap(); + + Stream::from_inst(StreamInstance::TcpStream(address, tcp_stream)) } #[inline] pub(crate) - fn null_stream() -> Self { - Stream { - options: StreamOptions::default(), // TODO: null_options? - stream_inst: WrappedStreamInstance::new( - StreamInstance::Null - ), - } + fn from_file_as_output(name: ClauseName, file: File, in_append_mode: bool) -> Self { + Stream::from_inst(StreamInstance::OutputFile(name, file, in_append_mode)) + } + + #[inline] + pub(crate) + fn from_file_as_input(name: ClauseName, file: File) -> Self { + Stream::from_inst(StreamInstance::InputFile(name, file)) } #[inline] pub(crate) fn is_stdout(&self) -> bool { - match *self.stream_inst.0.borrow() { + match self.stream_inst.0.borrow().1 { StreamInstance::Stdout => { true } @@ -248,8 +470,9 @@ impl Stream { #[inline] pub(crate) fn is_stdin(&self) -> bool { - match *self.stream_inst.0.borrow() { - StreamInstance::Stdin | StreamInstance::ReadlineStream(_) => { + match self.stream_inst.0.borrow().1 { + //StreamInstance::Stdin | + StreamInstance::ReadlineStream(_) => { true } _ => { @@ -258,18 +481,34 @@ impl Stream { } } + #[inline] + pub(crate) + fn close(&mut self) { + self.stream_inst.0.borrow_mut().1 = StreamInstance::Null; + } + + #[inline] + pub(crate) + fn is_null_stream(&self) -> bool { + if let StreamInstance::Null = self.stream_inst.0.borrow().1 { + true + } else { + false + } + } + #[inline] pub(crate) fn is_input_stream(&self) -> bool { - match *self.stream_inst.0.borrow() { - StreamInstance::Stdin - | StreamInstance::TcpStream(_) - | StreamInstance::Bytes(_) - | StreamInstance::ReadlineStream(_) - | StreamInstance::DynReadSource(_) - | StreamInstance::File(_) => { + match self.stream_inst.0.borrow().1 { + // StreamInstance::Stdin | + StreamInstance::TcpStream(..) | + StreamInstance::Bytes(_) | + StreamInstance::ReadlineStream(_) | + StreamInstance::DynReadSource(_) | + StreamInstance::InputFile(..) => { true - } + } _ => { false } @@ -279,11 +518,11 @@ impl Stream { #[inline] pub(crate) fn is_output_stream(&self) -> bool { - match *self.stream_inst.0.borrow() { + match self.stream_inst.0.borrow().1 { StreamInstance::Stdout - | StreamInstance::TcpStream(_) + | StreamInstance::TcpStream(..) | StreamInstance::Bytes(_) - | StreamInstance::File(_) => { + | StreamInstance::OutputFile(..) => { true } _ => { @@ -291,15 +530,496 @@ impl Stream { } } } + + // returns true on success. + #[inline] + fn reset(&mut self) -> bool { + match self.stream_inst.0.borrow_mut().1 { + StreamInstance::Bytes(ref mut cursor) => { + cursor.set_position(0); + true + } + StreamInstance::InputFile(_, ref mut file) => { + file.seek(SeekFrom::Start(0)).unwrap(); + true + } + StreamInstance::ReadlineStream(ref mut stream) => { + *stream = ReadlineStream::new(String::new()); + true + } + _ => { + false + } + } + } + + #[inline] + pub(crate) + fn peek_byte(&mut self) -> std::io::Result { + match self.stream_inst.0.borrow_mut().1 { + StreamInstance::Bytes(ref mut cursor) => { + let mut b = [0u8; 1]; + let pos = cursor.position(); + + match cursor.read(&mut b)? { + 1 => { + cursor.set_position(pos); + Ok(b[0]) + } + _ => { + Err(std::io::Error::new( + ErrorKind::UnexpectedEof, + "end of file", + )) + } + } + } + StreamInstance::InputFile(_, ref mut file) => { + let mut b = [0u8; 1]; + + match file.read(&mut b)? { + 1 => { + file.seek(SeekFrom::Current(-1))?; + Ok(b[0]) + } + _ => { + Err(std::io::Error::new( + ErrorKind::UnexpectedEof, + StreamError::PeekByteFailed, + )) + } + } + } + StreamInstance::ReadlineStream(ref mut stream) => { + stream.peek_byte() + } + StreamInstance::TcpStream(_, ref mut tcp_stream) => { + let mut b = [0u8; 1]; + tcp_stream.peek(&mut b)?; + Ok(b[0]) + } + _ => { + Err(std::io::Error::new( + ErrorKind::PermissionDenied, + StreamError::PeekByteFromNonPeekableStream, + )) + } + } + } + + #[inline] + pub(crate) + fn peek_char(&mut self) -> std::io::Result { + use unicode_reader::CodePoints; + + match self.stream_inst.0.borrow_mut().1 { + StreamInstance::InputFile(_, ref mut file) => { + let c = { + let mut iter = CodePoints::from(&*file); + + if let Some(Ok(c)) = iter.next() { + c + } else { + return Err(std::io::Error::new( + ErrorKind::UnexpectedEof, + StreamError::PeekCharFailed + )); + } + }; + + file.seek(SeekFrom::Current(- (c.len_utf8() as i64)))?; + + Ok(c) + } + StreamInstance::ReadlineStream(ref mut stream) => { + stream.peek_char() + } + StreamInstance::TcpStream(_, ref tcp_stream) => { + let c = { + let mut buf = [0u8; 8]; + tcp_stream.peek(&mut buf)?; + + let mut iter = CodePoints::from(buf.bytes()); + + if let Some(Ok(c)) = iter.next() { + c + } else { + return Err(std::io::Error::new( + ErrorKind::UnexpectedEof, + StreamError::PeekCharFailed + )); + } + }; + + Ok(c) + } + _ => { + Err(std::io::Error::new( + ErrorKind::PermissionDenied, + StreamError::PeekCharFromNonPeekableStream, + )) + } + } + } +} + +impl MachineState { + #[inline] + pub(crate) + fn eof_action( + &mut self, + result: Addr, + stream: &mut Stream, + caller: ClauseName, + arity: usize, + ) -> CallResult { + match stream.options.eof_action { + EOFAction::Error => { + stream.set_past_end_of_stream(); + return Err(self.open_past_eos_error(stream.clone(), caller, arity)); + } + EOFAction::EOFCode => { + let end_of_stream = self.heap.to_unifiable( + HeapCellValue::Atom(clause_name!("end_of_file"), None) + ); + + stream.set_past_end_of_stream(); + Ok(self.unify(result, end_of_stream)) + } + EOFAction::Reset => { + if !stream.reset() { + stream.set_past_end_of_stream(); + } + + Ok(self.fail = stream.past_end_of_stream()) + } + } + } + + pub(crate) + fn to_stream_options( + &self, + alias: Addr, + eof_action: Addr, + reposition: Addr, + stream_type: Addr, + ) -> StreamOptions { + let alias = + match self.store(self.deref(alias)) { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref name, _) = &self.heap[h] { + Some(name.clone()) + } else { + unreachable!() + } + } + _ => { + None + } + }; + + let eof_action = + match self.store(self.deref(eof_action)) { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref name, _) = &self.heap[h] { + match name.as_str() { + "eof_code" => EOFAction::EOFCode, + "error" => EOFAction::Error, + "reset" => EOFAction::Reset, + _ => unreachable!(), + } + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + }; + + let reposition = + match self.store(self.deref(reposition)) { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref name, _) = &self.heap[h] { + name.as_str() == "true" + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + }; + + let stream_type = + match self.store(self.deref(stream_type)) { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref name, _) = &self.heap[h] { + match name.as_str() { + "text" => StreamType::Text, + "binary" => StreamType::Binary, + _ => unreachable!() + } + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + }; + + let mut options = StreamOptions::default(); + + options.stream_type = stream_type; + options.reposition = reposition; + options.alias = alias; + options.eof_action = eof_action; + + options + } + + pub(crate) + fn get_stream_or_alias( + &mut self, + addr: Addr, + indices: &IndexStore, + caller: &'static str, + arity: usize, + ) -> Result + { + Ok(match self.store(self.deref(addr)) { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref atom, ref spec) = self.heap.clone(h) { + match indices.stream_aliases.get(atom) { + Some(stream) if !stream.is_null_stream() => { + stream.clone() + } + _ => { + let stub = MachineError::functor_stub(clause_name!(caller), arity); + + let addr = self.heap.to_unifiable( + HeapCellValue::Atom(atom.clone(), spec.clone()) + ); + + return Err(self.error_form( + MachineError::existence_error(self.heap.h(), ExistenceError::Stream(addr)), + stub, + )); + } + } + } else { + unreachable!() + } + } + Addr::Stream(h) => { + if let HeapCellValue::Stream(ref stream) = &self.heap[h] { + if stream.is_null_stream() { + return Err(self.open_permission_error( + Addr::Stream(h), + caller, + arity, + )); + } else { + stream.clone() + } + } else { + unreachable!() + } + } + addr => { + let stub = MachineError::functor_stub(clause_name!(caller), arity); + + if addr.is_ref() { + return Err(self.error_form( + MachineError::instantiation_error(), + stub, + )); + } else { + return Err(self.error_form( + MachineError::domain_error(DomainErrorType::StreamOrAlias, addr), + stub, + )); + } + } + }) + } + + pub(crate) + fn open_parsing_stream( + &self, + stream: Stream, + stub_name: &'static str, + stub_arity: usize, + ) -> Result { + match parsing_stream(stream.clone()) { + Ok(parsing_stream) => { + Ok(parsing_stream) + } + Err(e) => { + let stub = MachineError::functor_stub(clause_name!(stub_name), stub_arity); + let err = MachineError::session_error( + self.heap.h(), + SessionError::from(e), + ); + + Err(self.error_form(err, stub)) + } + } + } + + pub(crate) + fn stream_permission_error( + &self, + perm: Permission, + err_string: &'static str, + stream: Stream, + caller: ClauseName, + arity: usize, + ) -> MachineStub { + let stub = MachineError::functor_stub(caller, arity); + let payload = vec![ + HeapCellValue::Stream(stream) + ]; + + let err = MachineError::permission_error( + self.heap.h(), + perm, + err_string, + payload, + ); + + return self.error_form(err, stub); + } + + #[inline] + pub(crate) + fn open_past_eos_error( + &self, + stream: Stream, + caller: ClauseName, + arity: usize, + ) -> MachineStub { + self.stream_permission_error( + Permission::InputStream, + "past_end_of_stream", + stream, + caller, + arity, + ) + } + + pub(crate) + fn open_permission_error( + &self, + culprit: T, + stub_name: &'static str, + stub_arity: usize, + ) -> MachineStub { + let stub = MachineError::functor_stub(clause_name!(stub_name), stub_arity); + let err = MachineError::permission_error( + self.heap.h(), + Permission::Open, + "source_sink", + culprit, + ); + + return self.error_form(err, stub); + } + + pub(crate) + fn occupied_alias_permission_error( + &self, + alias: ClauseName, + stub_name: &'static str, + stub_arity: usize, + ) -> MachineStub { + let stub = MachineError::functor_stub(clause_name!(stub_name), stub_arity); + let err = MachineError::permission_error( + self.heap.h(), + Permission::Open, + "source_sink", + functor!("alias", [clause_name(alias)]), + ); + + return self.error_form(err, stub); + } + + pub(crate) + fn reposition_error( + &self, + stub_name: &'static str, + stub_arity: usize, + ) -> MachineStub { + let stub = MachineError::functor_stub(clause_name!(stub_name), stub_arity); + let rep_stub = functor!("reposition", [atom("true")]); + + let err = MachineError::permission_error( + self.heap.h(), + Permission::Open, + "source_sink", + rep_stub, + ); + + return self.error_form(err, stub); + } + + + pub(crate) + fn check_stream_properties( + &mut self, + stream: &mut Stream, + expected_type: StreamType, + input: Option, + caller: ClauseName, + arity: usize, + ) -> CallResult { + let opt_err = + if input.is_some() && !stream.is_input_stream() { + Some("stream") // 8.14.2.3 g) + } else if input.is_none() && !stream.is_output_stream() { + Some("stream") // 8.14.2.3 g) + } else if stream.options.stream_type != expected_type { + Some(expected_type.other().as_str()) // 8.14.2.3 h) + } else { + None + }; + + let permission = + if input.is_some() { Permission::InputStream } else { Permission::OutputStream }; + + if let Some(err_string) = opt_err { + return Err(self.stream_permission_error( + permission, + err_string, + stream.clone(), + caller, + arity, + )); + } + + if let Some(input) = input { + if stream.past_end_of_stream() { + self.eof_action( + input, + stream, + caller, + arity, + )?; + } + } + + Ok(()) + } } impl Read for Stream { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { - match *self.stream_inst.0.borrow_mut() { - StreamInstance::File(ref mut file) => { + match self.stream_inst.0.borrow_mut().1 { + StreamInstance::InputFile(_, ref mut file) => { file.read(buf) } - StreamInstance::TcpStream(ref mut tcp_stream) => { + StreamInstance::TcpStream(_, ref mut tcp_stream) => { tcp_stream.read(buf) } StreamInstance::ReadlineStream(ref mut rl_stream) => { @@ -311,10 +1031,12 @@ impl Read for Stream { StreamInstance::Bytes(ref mut cursor) => { cursor.read(buf) } +/* StreamInstance::Stdin => { stdin().read(buf) } - StreamInstance::Stdout | StreamInstance::Null => { +*/ + StreamInstance::OutputFile(..) | StreamInstance::Stdout | StreamInstance::Null => { Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::ReadFromOutputStream, @@ -326,11 +1048,11 @@ impl Read for Stream { impl Write for Stream { fn write(&mut self, buf: &[u8]) -> std::io::Result { - match *self.stream_inst.0.borrow_mut() { - StreamInstance::File(ref mut file) => { + match self.stream_inst.0.borrow_mut().1 { + StreamInstance::OutputFile(_, ref mut file, _) => { file.write(buf) } - StreamInstance::TcpStream(ref mut tcp_stream) => { + StreamInstance::TcpStream(_, ref mut tcp_stream) => { tcp_stream.write(buf) } StreamInstance::Bytes(ref mut cursor) => { @@ -339,7 +1061,8 @@ impl Write for Stream { StreamInstance::Stdout => { stdout().write(buf) } - _ => { + StreamInstance::DynReadSource(_) | StreamInstance::ReadlineStream(_) | + StreamInstance::InputFile(..) | StreamInstance::Null => { Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::WriteToInputStream, @@ -349,11 +1072,11 @@ impl Write for Stream { } fn flush(&mut self) -> std::io::Result<()> { - match *self.stream_inst.0.borrow_mut() { - StreamInstance::File(ref mut file) => { + match self.stream_inst.0.borrow_mut().1 { + StreamInstance::OutputFile(_, ref mut file, _) => { file.flush() } - StreamInstance::TcpStream(ref mut tcp_stream) => { + StreamInstance::TcpStream(_, ref mut tcp_stream) => { tcp_stream.flush() } StreamInstance::Bytes(ref mut cursor) => { @@ -362,7 +1085,8 @@ impl Write for Stream { StreamInstance::Stdout => { stdout().flush() } - _ => { + StreamInstance::DynReadSource(_) | StreamInstance::ReadlineStream(_) | + StreamInstance::InputFile(..) | StreamInstance::Null => { Err(std::io::Error::new( ErrorKind::PermissionDenied, StreamError::FlushToInputStream, @@ -371,5 +1095,3 @@ impl Write for Stream { } } } - -//TODO: write a Seek instance. diff --git a/src/prolog/machine/system_calls.rs b/src/prolog/machine/system_calls.rs index 4d3bfef6..ce7c7a67 100644 --- a/src/prolog/machine/system_calls.rs +++ b/src/prolog/machine/system_calls.rs @@ -21,10 +21,13 @@ use crate::prolog::rug::Integer; use crate::ref_thread_local::RefThreadLocal; use std::cmp; +use std::collections::BTreeSet; use std::convert::TryFrom; -use std::io::{stdout, Read, Write}; +use std::io::{ErrorKind, Read, Write}; use std::iter::{once, FromIterator}; -use std::fs::File; +use std::fs::{File, OpenOptions}; +use std::net::{TcpListener, TcpStream}; +use std::ops::Sub; use std::rc::Rc; use std::time::Duration; @@ -359,56 +362,6 @@ impl MachineState { Ok(()) } - fn get_stream_or_alias( - &mut self, - addr: Addr, - indices: &IndexStore, - caller: &'static str, - ) -> Result - { - Ok(match addr { - Addr::Con(h) if self.heap.atom_at(h) => { - if let HeapCellValue::Atom(ref atom, ref spec) = self.heap.clone(h) { - match indices.stream_aliases.get(atom) { - Some(stream) => { - stream.clone() - } - None => { - let stub = MachineError::functor_stub(clause_name!(caller), 1); - let h = self.heap.h(); - - let addr = self.heap.to_unifiable( - HeapCellValue::Atom(atom.clone(), spec.clone()) - ); - - return Err(self.error_form( - MachineError::existence_error(h + 1, ExistenceError::Stream(addr)), - stub, - )); - } - } - } else { - unreachable!() - } - } - Addr::Stream(h) => { - if let HeapCellValue::Stream(ref stream) = &self.heap[h] { - stream.clone() - } else { - unreachable!() - } - } - _ => { - let stub = MachineError::functor_stub(clause_name!(caller), 1); - - return Err(self.error_form( - MachineError::domain_error(DomainErrorType::StreamOrAlias, addr), - stub, - )); - } - }) - } - #[inline] fn install_new_block(&mut self, r: RegType) -> usize { self.block = self.b; @@ -774,6 +727,29 @@ impl MachineState { self.p = CodePtr::DynamicTransaction(trans_type, p); return Ok(()); } + &SystemClauseType::CurrentHostname => { + match hostname::get().ok() { + Some(host) => { + match host.into_string().ok() { + Some(host) => { + let hostname = self.heap.to_unifiable( + HeapCellValue::Atom(clause_name!(host, indices.atom_tbl), None) + ); + + self.unify(self[temp_v!(1)], hostname); + return return_from_clause!(self.last_call, self); + } + None => { + } + } + } + None => { + } + } + + self.fail = true; + return Ok(()); + } &SystemClauseType::CurrentInput => { let addr = self.store(self.deref(self[temp_v!(1)])); @@ -1190,6 +1166,308 @@ impl MachineState { } } } + &SystemClauseType::PeekByte => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "peek_byte", 2)?; + + self.check_stream_properties( + &mut stream, + StreamType::Binary, + Some(self[temp_v!(2)]), + clause_name!("peek_byte"), + 2, + )?; + + if stream.past_end_of_stream() { + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + } + + let addr = + match self.store(self.deref(self[temp_v!(2)])) { + addr if addr.is_ref() => { + addr + } + addr => { + match Number::try_from((addr, &self.heap)) { + Ok(Number::Integer(n)) => { + if let Some(nb) = n.to_u8() { + Addr::Usize(nb as usize) + } else { + return Err(self.type_error( + ValidType::InByte, + addr, + clause_name!("peek_byte"), + 2, + )); + } + } + Ok(Number::Fixnum(n)) => { + if let Ok(nb) = u8::try_from(n) { + Addr::Usize(nb as usize) + } else { + return Err(self.type_error( + ValidType::InByte, + addr, + clause_name!("peek_byte"), + 2, + )); + } + } + _ => { + return Err(self.type_error( + ValidType::InByte, + addr, + clause_name!("peek_byte"), + 2, + )); + } + } + } + }; + + loop { + match stream.peek_byte().map_err(|e| e.kind()) { + Ok(b) => { + if let Some(var) = addr.as_var() { + self.bind(var, Addr::Usize(b as usize)); + break; + } else if addr == Addr::Usize(b as usize) { + break; + } else { + self.fail = true; + return Ok(()); + } + } + Err(ErrorKind::PermissionDenied) => { + self.fail = true; + break; + } + _ => { + self.eof_action( + self[temp_v!(2)], + &mut stream, + clause_name!("peek_byte"), + 2, + )?; + + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + } + } + } + } + &SystemClauseType::PeekChar => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "peek_char", 2)?; + + self.check_stream_properties( + &mut stream, + StreamType::Text, + Some(self[temp_v!(2)]), + clause_name!("peek_char"), + 2, + )?; + + if stream.past_end_of_stream() { + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + } + + let addr = + match self.store(self.deref(self[temp_v!(2)])) { + addr if addr.is_ref() => { + addr + } + Addr::Con(h) if self.heap.atom_at(h) => { + match &self.heap[h] { + HeapCellValue::Atom(ref atom, _) if atom.is_char() => { + if let Some(c) = atom.as_str().chars().next() { + Addr::Char(c) + } else { + unreachable!() + } + } + culprit => { + return Err(self.type_error( + ValidType::InCharacter, + culprit.as_addr(h), + clause_name!("peek_char"), + 2, + )); + } + } + } + Addr::Char(d) => { + Addr::Char(d) + } + culprit => { + return Err(self.type_error( + ValidType::InCharacter, + culprit, + clause_name!("peek_char"), + 2, + )); + } + }; + + loop { + match stream.peek_char().map_err(|e| e.kind()) { + Ok(d) => { + if let Some(var) = addr.as_var() { + self.bind(var, Addr::Char(d)); + break; + } else if addr == Addr::Char(d) { + break; + } else { + self.fail = true; + return Ok(()); + } + } + Err(ErrorKind::PermissionDenied) => { + self.fail = true; + break; + } + _ => { + self.eof_action( + self[temp_v!(2)], + &mut stream, + clause_name!("peek_char"), + 2, + )?; + + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + }/* + _ => { + let stub = MachineError::functor_stub(clause_name!("peek_char"), 2); + let err = MachineError::representation_error(RepFlag::Character); + let err = self.error_form(err, stub); + + return Err(err); + }*/ + } + } + } + &SystemClauseType::PeekCode => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "peek_code", 2)?; + + self.check_stream_properties( + &mut stream, + StreamType::Text, + Some(self[temp_v!(2)]), + clause_name!("peek_code"), + 2, + )?; + + if stream.past_end_of_stream() { + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + } + + let addr = + match self.store(self.deref(self[temp_v!(2)])) { + addr if addr.is_ref() => { + addr + } + Addr::CharCode(d) => { + Addr::CharCode(d) + } + addr => { + match Number::try_from((addr, &self.heap)) { + Ok(Number::Integer(n)) => { + if let Some(c) = n.to_u32().and_then(|c| char::try_from(c).ok()) { + Addr::CharCode(c as u32) + } else { + return Err(self.representation_error( + RepFlag::InCharacterCode, + clause_name!("peek_code"), + 2, + )); + } + } + Ok(Number::Fixnum(n)) => { + if let Some(c) = u32::try_from(n).ok().and_then(|c| char::try_from(c).ok()) { + Addr::CharCode(c as u32) + } else { + return Err(self.representation_error( + RepFlag::InCharacterCode, + clause_name!("peek_code"), + 2, + )); + } + } + _ => { + return Err(self.type_error( + ValidType::Integer, + self[temp_v!(2)], + clause_name!("peek_code"), + 2, + )); + } + } + } + }; + + loop { + let result = stream.peek_char(); + + match result.map_err(|e| e.kind()) { + Ok(c) => { + if let Some(var) = addr.as_var() { + self.bind(var, Addr::CharCode(c as u32)); + break; + } else if addr == Addr::CharCode(c as u32) { + break; + } else { + self.fail = true; + return Ok(()); + } + } + Err(ErrorKind::PermissionDenied) => { + self.fail = true; + break; + } + _ => { + self.eof_action( + self[temp_v!(2)], + &mut stream, + clause_name!("peek_code"), + 2, + )?; + + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + }/* + _ => { + let stub = MachineError::functor_stub(clause_name!("get_char"), 2); + let err = MachineError::representation_error(RepFlag::Character); + let err = self.error_form(err, stub); + + return Err(err); + }*/ + } + } + } &SystemClauseType::NumberToChars => { let n = self[temp_v!(1)]; let chs = self[temp_v!(2)]; @@ -1534,8 +1812,6 @@ impl MachineState { } &SystemClauseType::FileToChars => { // TODO: Replace this with stream. - use std::io; - let a1 = self.store(self.deref(self[temp_v!(1)])); let a2 = self.store(self.deref(self[temp_v!(2)])); @@ -1565,15 +1841,15 @@ impl MachineState { let h = self.heap.h(); let err = match e.kind() { - io::ErrorKind::NotFound => { + ErrorKind::NotFound => { MachineError::existence_error( h, - ExistenceError::SourceSink( + ExistenceError::ModuleSource( ModuleSource::File(file_name) ), ) } - io::ErrorKind::PermissionDenied => { + ErrorKind::PermissionDenied => { let source_sink = self.store(self.deref(a1)); MachineError::permission_error( @@ -1615,37 +1891,560 @@ impl MachineState { self.unify(complete_string, a2); } - &SystemClauseType::GetChar => { - let mut iter = self.open_parsing_stream( - current_input_stream.clone(), - "get_char", - 1, + &SystemClauseType::PutCode => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "put_code", 2)?; + + self.check_stream_properties( + &mut stream, + StreamType::Text, + None, + clause_name!("put_code"), + 2, )?; - let result = iter.next(); - let a1 = self[temp_v!(1)]; + match self.store(self.deref(self[temp_v!(2)])) { + addr if addr.is_ref() => { + let stub = MachineError::functor_stub(clause_name!("put_code"), 2); + let err = MachineError::instantiation_error(); - match result { - Some(Ok(b)) => { - self.unify(Addr::Char(b as char), a1); + return Err(self.error_form(err, stub)); } - Some(Err(_)) => { - let end_of_file = self.heap.to_unifiable(HeapCellValue::Atom( - clause_name!("end_of_file"), - None, - )); - - self.unify(a1, end_of_file); + Addr::CharCode(c) => { + let c = char::try_from(c).unwrap(); + write!(&mut stream, "{}", c).unwrap(); } - None => { - let stub = MachineError::functor_stub(clause_name!("get_char"), 1); - let err = MachineError::representation_error(RepFlag::Character); - let err = self.error_form(err, stub); + addr => { + match Number::try_from((addr, &self.heap)) { + Ok(Number::Integer(n)) => { + if let Some(c) = n.to_u32().and_then(|c| char::try_from(c).ok()) { + write!(&mut stream, "{}", c).unwrap(); + return return_from_clause!(self.last_call, self); + } + } + Ok(Number::Fixnum(n)) => { + if let Some(c) = u32::try_from(n).ok().and_then(|c| char::try_from(c).ok()) { + write!(&mut stream, "{}", c).unwrap(); + return return_from_clause!(self.last_call, self); + } + } + _ => { + let stub = MachineError::functor_stub(clause_name!("put_code"), 2); + let err = MachineError::type_error( + self.heap.h(), + ValidType::Integer, + self[temp_v!(2)], + ); - return Err(err); + return Err(self.error_form(err, stub)); + } + } + + let stub = MachineError::functor_stub(clause_name!("put_code"), 2); + let err = MachineError::representation_error( + RepFlag::CharacterCode, + ); + + return Err(self.error_form(err, stub)); } } } + &SystemClauseType::PutChar => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "put_char", 2)?; + + self.check_stream_properties( + &mut stream, + StreamType::Text, + None, + clause_name!("put_char"), + 2, + )?; + + match self.store(self.deref(self[temp_v!(2)])) { + addr if addr.is_ref() => { + let stub = MachineError::functor_stub(clause_name!("put_char"), 2); + let err = MachineError::instantiation_error(); + + return Err(self.error_form(err, stub)); + } + addr => { + match self.store(self.deref(self[temp_v!(2)])) { + Addr::Con(h) if self.heap.atom_at(h) => { + match &self.heap[h] { + HeapCellValue::Atom(ref atom, _) if atom.is_char() => { + if let Some(c) = atom.as_str().chars().next() { + write!(&mut stream, "{}", c).unwrap(); + return return_from_clause!(self.last_call, self); + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + } + } + Addr::Char(c) => { + write!(&mut stream, "{}", c).unwrap(); + return return_from_clause!(self.last_call, self); + } + _ => { + } + } + + let stub = MachineError::functor_stub(clause_name!("put_char"), 2); + let err = MachineError::type_error( + self.heap.h(), + ValidType::Character, + addr, + ); + + return Err(self.error_form(err, stub)); + } + } + } + &SystemClauseType::PutByte => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "put_byte", 2)?; + + self.check_stream_properties( + &mut stream, + StreamType::Binary, + None, + clause_name!("put_byte"), + 2, + )?; + + match self.store(self.deref(self[temp_v!(2)])) { + addr if addr.is_ref() => { + let stub = MachineError::functor_stub(clause_name!("put_byte"), 2); + let err = MachineError::instantiation_error(); + + return Err(self.error_form(err, stub)); + } + addr => { + match Number::try_from((addr, &self.heap)) { + Ok(Number::Integer(n)) => { + if let Some(nb) = n.to_u8() { + stream.write(&mut [nb]).unwrap(); + return return_from_clause!(self.last_call, self); + } + } + Ok(Number::Fixnum(n)) => { + if let Ok(nb) = u8::try_from(n) { + stream.write(&mut [nb]).unwrap(); + return return_from_clause!(self.last_call, self); + } + } + _ => { + } + } + + let stub = MachineError::functor_stub(clause_name!("put_byte"), 2); + let err = MachineError::type_error( + self.heap.h(), + ValidType::Byte, + self[temp_v!(2)], + ); + + return Err(self.error_form(err, stub)); + } + } + } + &SystemClauseType::GetByte => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "get_byte", 2)?; + + self.check_stream_properties( + &mut stream, + StreamType::Binary, + Some(self[temp_v!(2)]), + clause_name!("get_byte"), + 2, + )?; + + if stream.past_end_of_stream() { + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + } + + let addr = + match self.store(self.deref(self[temp_v!(2)])) { + addr if addr.is_ref() => { + addr + } + addr => { + match Number::try_from((addr, &self.heap)) { + Ok(Number::Integer(n)) => { + if let Some(nb) = n.to_u8() { + Addr::Usize(nb as usize) + } else { + return Err(self.type_error( + ValidType::InByte, + addr, + clause_name!("get_byte"), + 2, + )); + } + } + Ok(Number::Fixnum(n)) => { + if let Ok(nb) = u8::try_from(n) { + Addr::Usize(nb as usize) + } else { + return Err(self.type_error( + ValidType::InByte, + addr, + clause_name!("get_byte"), + 2, + )); + } + } + _ => { + return Err(self.type_error( + ValidType::InByte, + addr, + clause_name!("get_byte"), + 2, + )); + } + } + } + }; + + loop { + let mut b = [0u8; 1]; + + match stream.read(&mut b) { + Ok(1) => { + if let Some(var) = addr.as_var() { + self.bind(var, Addr::Usize(b[0] as usize)); + break; + } else if addr == Addr::Usize(b[0] as usize) { + break; + } else { + self.fail = true; + return Ok(()); + } + } + _ => { + self.eof_action( + self[temp_v!(2)], + &mut stream, + clause_name!("get_byte"), + 2, + )?; + + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + } + } + } + } + &SystemClauseType::GetChar => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "get_char", 2)?; + + self.check_stream_properties( + &mut stream, + StreamType::Text, + Some(self[temp_v!(2)]), + clause_name!("get_char"), + 2, + )?; + + if stream.past_end_of_stream() { + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + } + + let mut iter = self.open_parsing_stream( + stream.clone(), + "get_char", + 2, + )?; + + let addr = + match self.store(self.deref(self[temp_v!(2)])) { + addr if addr.is_ref() => { + addr + } + Addr::Con(h) if self.heap.atom_at(h) => { + match &self.heap[h] { + HeapCellValue::Atom(ref atom, _) if atom.is_char() => { + if let Some(c) = atom.as_str().chars().next() { + Addr::Char(c) + } else { + unreachable!() + } + } + culprit => { + return Err(self.type_error( + ValidType::InCharacter, + culprit.as_addr(h), + clause_name!("get_char"), + 2, + )); + } + } + } + Addr::Char(d) => { + Addr::Char(d) + } + culprit => { + return Err(self.type_error( + ValidType::InCharacter, + culprit, + clause_name!("get_char"), + 2, + )); + } + }; + + loop { + let result = iter.next(); + + match result { + Some(Ok(d)) => { + if let Some(var) = addr.as_var() { + self.bind(var, Addr::Char(d)); + break; + } else if addr == Addr::Char(d) { + break; + } else { + self.fail = true; + return Ok(()); + } + } + _ => { + self.eof_action( + self[temp_v!(2)], + &mut stream, + clause_name!("get_char"), + 2, + )?; + + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + }/* + _ => { + let stub = MachineError::functor_stub(clause_name!("get_char"), 2); + let err = MachineError::representation_error(RepFlag::Character); + let err = self.error_form(err, stub); + + return Err(err); + }*/ + } + } + } + &SystemClauseType::GetCode => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "get_code", 2)?; + + self.check_stream_properties( + &mut stream, + StreamType::Text, + Some(self[temp_v!(2)]), + clause_name!("get_code"), + 2, + )?; + + if stream.past_end_of_stream() { + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + } + + let addr = + match self.store(self.deref(self[temp_v!(2)])) { + addr if addr.is_ref() => { + addr + } + Addr::CharCode(d) => { + Addr::CharCode(d) + } + addr => { + match Number::try_from((addr, &self.heap)) { + Ok(Number::Integer(n)) => { + if let Some(c) = n.to_u32().and_then(|c| char::try_from(c).ok()) { + Addr::CharCode(c as u32) + } else { + return Err(self.representation_error( + RepFlag::InCharacterCode, + clause_name!("get_code"), + 2, + )); + } + } + Ok(Number::Fixnum(n)) => { + if let Some(c) = u32::try_from(n).ok().and_then(|c| char::try_from(c).ok()) { + Addr::CharCode(c as u32) + } else { + return Err(self.representation_error( + RepFlag::InCharacterCode, + clause_name!("get_code"), + 2, + )); + } + } + _ => { + return Err(self.type_error( + ValidType::Integer, + self[temp_v!(2)], + clause_name!("get_code"), + 2, + )); + } + } + } + }; + + let mut iter = self.open_parsing_stream( + stream.clone(), + "get_code", + 2, + )?; + + loop { + let result = iter.next(); + + match result { + Some(Ok(c)) => { + if let Some(var) = addr.as_var() { + self.bind(var, Addr::CharCode(c as u32)); + break; + } else if addr == Addr::CharCode(c as u32) { + break; + } else { + self.fail = true; + return Ok(()); + } + } + _ => { + self.eof_action( + self[temp_v!(2)], + &mut stream, + clause_name!("get_code"), + 2, + )?; + + if EOFAction::Reset != stream.options.eof_action { + return return_from_clause!(self.last_call, self); + } else if self.fail { + return Ok(()); + } + }/* + _ => { + let stub = MachineError::functor_stub(clause_name!("get_char"), 2); + let err = MachineError::representation_error(RepFlag::Character); + let err = self.error_form(err, stub); + + return Err(err); + }*/ + } + } + } + &SystemClauseType::FirstStream => { + let mut first_stream = None; + let mut null_streams = BTreeSet::new(); + + for stream in indices.streams.iter().cloned() { + if !stream.is_null_stream() { + first_stream = Some(stream); + break; + } else { + null_streams.insert(stream); + } + } + + indices.streams = indices.streams.sub(&null_streams); + + if let Some(first_stream) = first_stream { + let stream = self.heap.to_unifiable(HeapCellValue::Stream(first_stream)); + + let var = self.store(self.deref(self[temp_v!(1)])).as_var().unwrap(); + self.bind(var, stream); + } else { + self.fail = true; + return Ok(()); + } + } + &SystemClauseType::NextStream => { + let prev_stream = + match self.store(self.deref(self[temp_v!(1)])) { + Addr::Stream(h) => { + if let HeapCellValue::Stream(ref stream) = &self.heap[h] { + stream.clone() + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + }; + + let mut next_stream = None; + let mut null_streams = BTreeSet::new(); + + for stream in indices.streams.range(prev_stream.clone() ..).skip(1).cloned() { + if !stream.is_null_stream() { + next_stream = Some(stream); + break; + } else { + null_streams.insert(stream); + } + } + + indices.streams = indices.streams.sub(&null_streams); + + if let Some(next_stream) = next_stream { + let var = self.store(self.deref(self[temp_v!(2)])).as_var().unwrap(); + let next_stream = self.heap.to_unifiable(HeapCellValue::Stream(next_stream)); + + self.bind(var, next_stream); + } else { + self.fail = true; + return Ok(()); + } + } + &SystemClauseType::FlushOutput => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "flush_output", 1)?; + + if !stream.is_output_stream() { + let stub = MachineError::functor_stub(clause_name!("flush_output"), 1); + + let addr = vec![ + HeapCellValue::Stream(stream) + ]; + + let err = MachineError::permission_error( + self.heap.h(), + Permission::OutputStream, + "stream", + addr, + ); + + return Err(self.error_form(err, stub)); + } + + stream.flush().unwrap(); + } &SystemClauseType::GetSingleChar => { let ctrl_c = KeyEvent { code: KeyCode::Char('c'), @@ -1786,6 +2585,38 @@ impl MachineState { } }; } + &SystemClauseType::Close => { + let mut stream = + self.get_stream_or_alias(self[temp_v!(1)], indices, "close", 2)?; + + if !stream.is_input_stream() { + stream.flush().unwrap(); // 8.11.6.1b) + } + + indices.streams.remove(&stream); + + if stream == *current_input_stream { + *current_input_stream = indices.stream_aliases.get( + &clause_name!("user_input") + ).cloned().unwrap(); + + indices.streams.insert(current_input_stream.clone()); + } else if stream == *current_output_stream { + *current_output_stream = indices.stream_aliases.get( + &clause_name!("user_output") + ).cloned().unwrap(); + + indices.streams.insert(current_output_stream.clone()); + } + + if !stream.is_stdin() && !stream.is_stdout() { + stream.close(); + + if let Some(alias) = stream.options.alias { + indices.stream_aliases.remove(&alias); + } + } + } &SystemClauseType::CopyToLiftedHeap => { match self.store(self.deref(self[temp_v!(1)])) { Addr::Usize(lh_offset) => { @@ -2249,6 +3080,109 @@ impl MachineState { } }; } + &SystemClauseType::Open => { + let alias = self[temp_v!(4)]; + let eof_action = self[temp_v!(5)]; + let reposition = self[temp_v!(6)]; + let stream_type = self[temp_v!(7)]; + + let options = + self.to_stream_options(alias, eof_action, reposition, stream_type); + + let file_spec = + atom_from!(self, indices, self.store(self.deref(self[temp_v!(1)]))); + + // 8.11.5.3l) + if let Some(ref alias) = &options.alias { + if indices.stream_aliases.contains_key(alias) { + return Err(self.occupied_alias_permission_error( + alias.clone(), + "open", + 4, + )); + } + } + + let mode = + atom_from!(self, indices, self.store(self.deref(self[temp_v!(2)]))); + + let mut open_options = OpenOptions::new(); + + let (is_input_file, in_append_mode) = + match mode.as_str() { + "read" => { + open_options.read(true).write(false).create(false); + (true, false) + } + "write" => { + open_options.read(false).write(true).truncate(true).create(true); + (false, false) + } + "append" => { + open_options.read(false).write(true).create(true).append(true); + (false, true) + } + _ => { + let stub = MachineError::functor_stub(clause_name!("open"), 4); + let err = MachineError::domain_error( + DomainErrorType::IOMode, + self[temp_v!(2)], + ); + + // 8.11.5.3h) + return Err(self.error_form(err, stub)); + } + }; + + let file = + match open_options.open(file_spec.as_str()).map_err(|e| e.kind()) { + Ok(file) => { + file + } + Err(ErrorKind::NotFound) => { + // 8.11.5.3j) + let stub = MachineError::functor_stub( + clause_name!("open"), + 4, + ); + + let err = MachineError::existence_error( + self.heap.h(), + ExistenceError::SourceSink(self[temp_v!(1)]), + ); + + return Err(self.error_form(err, stub)); + } + Err(ErrorKind::PermissionDenied) => { + // 8.11.5.3k) + return Err(self.open_permission_error(self[temp_v!(1)], "open", 4)); + } + Err(_) => { + // for now, just fail. expand to meaningful error messages later. + self.fail = true; + return Ok(()); + } + }; + + let mut stream = if is_input_file { + Stream::from_file_as_input(file_spec, file) + } else { + Stream::from_file_as_output(file_spec, file, in_append_mode) + }; + + stream.options = options; + + indices.streams.insert(stream.clone()); + + if let Some(ref alias) = &stream.options.alias { + indices.stream_aliases.insert(alias.clone(), stream.clone()); + } + + let stream = self.heap.to_unifiable(HeapCellValue::Stream(stream)); + let stream_var = self.store(self.deref(self[temp_v!(3)])); + + self.bind(stream_var.as_var().unwrap(), stream); + } &SystemClauseType::TruncateIfNoLiftedHeapGrowthDiff => { self.truncate_if_no_lifted_heap_diff(|h| Addr::HeapCell(h)) } @@ -2560,17 +3494,14 @@ impl MachineState { 3, ); - let type_error = self.error_form( + return Err(self.error_form( MachineError::type_error( self.heap.h(), ValidType::Integer, a2, ), stub, - ); - - self.throw_exception(type_error); - return Ok(()); + )); } }; @@ -2883,9 +3814,9 @@ impl MachineState { } &SystemClauseType::SetInput => { let addr = self.store(self.deref(self[temp_v!(1)])); - let stream = self.get_stream_or_alias(addr, indices, "set_input")?; + let stream = self.get_stream_or_alias(addr, indices, "set_input", 1)?; - if stream.is_output_stream() { + if !stream.is_input_stream() { let stub = MachineError::functor_stub( clause_name!("set_input"), 1, @@ -2909,9 +3840,9 @@ impl MachineState { } &SystemClauseType::SetOutput => { let addr = self.store(self.deref(self[temp_v!(1)])); - let stream = self.get_stream_or_alias(addr, indices, "set_output")?; + let stream = self.get_stream_or_alias(addr, indices, "set_output", 1)?; - if stream.is_input_stream() { + if !stream.is_output_stream() { let stub = MachineError::functor_stub( clause_name!("set_input"), 1, @@ -3197,14 +4128,29 @@ impl MachineState { } &SystemClauseType::ReadQueryTerm => { readline::set_prompt(true); - let result = self.read_term(current_input_stream, indices); + let result = self.read_term(current_input_stream.clone(), indices); readline::set_prompt(false); - let _ = result?; + match result { + Ok(()) => { + } + Err(e) => { + *current_input_stream = readline::input_stream(); + return Err(e); + } + } } &SystemClauseType::ReadTerm => { readline::set_prompt(false); - self.read_term(current_input_stream, indices)?; + + let stream = self.get_stream_or_alias( + self[temp_v!(1)], + indices, + "read_term", + 3, + )?; + + self.read_term(stream, indices)?; } &SystemClauseType::ReadTermFromChars => { let mut heap_pstr_iter = self.heap_pstr_iter(self[temp_v!(1)]); @@ -3317,6 +4263,471 @@ impl MachineState { let duration = duration.mul_f64(time); ::std::thread::sleep(duration); } + &SystemClauseType::SocketClientOpen => { + let addr = self.store(self.deref(self[temp_v!(1)])); + let port = self.store(self.deref(self[temp_v!(2)])); + + let socket_atom = + match addr { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref name, _) = &self.heap[h] { + name.clone() + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + }; + + let port = + match port { + Addr::Fixnum(n) => { + n.to_string() + } + Addr::Usize(n) => { + n.to_string() + } + Addr::Con(h) => { + match &self.heap[h] { + HeapCellValue::Atom(ref name, _) => { + name.as_str().to_string() + } + HeapCellValue::Integer(ref n) => { + n.to_string() + } + _ => { + unreachable!() + } + } + } + _ => { + unreachable!() + } + }; + + let socket_addr = + format!( + "{}:{}", + if socket_atom.as_str() == "" { + "127.0.0.1" + } else { + socket_atom.as_str() + }, + port, + ); + + let alias = self[temp_v!(4)]; + let eof_action = self[temp_v!(5)]; + let reposition = self[temp_v!(6)]; + let stream_type = self[temp_v!(7)]; + + let options = + self.to_stream_options(alias, eof_action, reposition, stream_type); + + if options.reposition { + return Err(self.reposition_error("socket_client_open", 3)); + } + + if let Some(ref alias) = &options.alias { + if indices.stream_aliases.contains_key(alias) { + return Err(self.occupied_alias_permission_error( + alias.clone(), + "socket_client_open", + 3, + )); + } + } + + let stream = + match TcpStream::connect(&socket_addr).map_err(|e| e.kind()) { + Ok(tcp_stream) => { + let socket_addr = clause_name!(socket_addr, indices.atom_tbl.clone()); + + let mut stream = Stream::from_tcp_stream(socket_addr, tcp_stream); + stream.options = options; + + if let Some(ref alias) = &stream.options.alias { + indices.stream_aliases.insert(alias.clone(), stream.clone()); + } + + indices.streams.insert(stream.clone()); + + self.heap.to_unifiable(HeapCellValue::Stream(stream)) + } + Err(ErrorKind::PermissionDenied) => { + return Err(self.open_permission_error(addr, "socket_client_open", 3)); + } + Err(ErrorKind::NotFound) => { + let stub = MachineError::functor_stub( + clause_name!("socket_client_open"), + 3, + ); + + let err = MachineError::existence_error( + self.heap.h(), + ExistenceError::SourceSink(addr), + ); + + return Err(self.error_form(err, stub)); + } + Err(_) => { + // for now, just fail. expand to meaningful error messages later. + self.fail = true; + return Ok(()); + } + }; + + let stream_addr = self.store(self.deref(self[temp_v!(3)])); + self.bind(stream_addr.as_var().unwrap(), stream); + } + &SystemClauseType::SocketServerOpen => { + let addr = self.store(self.deref(self[temp_v!(1)])); + let socket_atom = + match addr { + Addr::EmptyList => { + "127.0.0.1".to_string() + } + Addr::Con(h) if self.heap.atom_at(h) => { + match &self.heap[h] { + HeapCellValue::Atom(ref name, _) => { + name.as_str().to_string() + } + _ => { + unreachable!() + } + } + } + _ => { + unreachable!() + } + }; + + let port = + match self.store(self.deref(self[temp_v!(2)])) { + Addr::Fixnum(n) => { + n.to_string() + } + Addr::Usize(n) => { + n.to_string() + } + Addr::Con(h) => { + match &self.heap[h] { + HeapCellValue::Integer(ref n) => { + n.to_string() + } + _ => { + unreachable!() + } + } + } + addr if addr.is_ref() => { + "0".to_string() + } + _ => { + unreachable!() + } + }; + + let had_zero_port = &port == "0"; + + let server_addr = if socket_atom.is_empty() { + port + } else { + format!("{}:{}", socket_atom, port) + }; + + let (tcp_listener, port) = + match TcpListener::bind(server_addr).map_err(|e| e.kind()) { + Ok(tcp_listener) => { + let port = tcp_listener.local_addr().map(|addr| addr.port()).ok(); + + if let Some(port) = port { + ( + self.heap.to_unifiable(HeapCellValue::TcpListener(tcp_listener)), + port as usize, + ) + } else { + self.fail = true; + return Ok(()); + } + } + Err(ErrorKind::PermissionDenied) => { + return Err(self.open_permission_error(addr, "socket_server_open", 2)); + } + _ => { + self.fail = true; + return Ok(()); + } + }; + + let addr = self.store(self.deref(self[temp_v!(3)])); + self.bind(addr.as_var().unwrap(), tcp_listener); + + if had_zero_port { + self.unify(self[temp_v!(2)], Addr::Usize(port)); + } + } + &SystemClauseType::SocketServerAccept => { + let alias = self[temp_v!(4)]; + let eof_action = self[temp_v!(5)]; + let reposition = self[temp_v!(6)]; + let stream_type = self[temp_v!(7)]; + + let options = + self.to_stream_options(alias, eof_action, reposition, stream_type); + + if options.reposition { + return Err(self.reposition_error("socket_server_accept", 4)); + } + + if let Some(ref alias) = &options.alias { + if indices.stream_aliases.contains_key(alias) { + return Err(self.occupied_alias_permission_error( + alias.clone(), + "socket_server_accept", + 4, + )); + } + } + + match self.store(self.deref(self[temp_v!(1)])) { + Addr::TcpListener(h) => { + match &mut self.heap[h] { + HeapCellValue::TcpListener(ref mut tcp_listener) => { + match tcp_listener.accept().ok() { + Some((tcp_stream, socket_addr)) => { + let client = + clause_name!(format!("{}", socket_addr), indices.atom_tbl); + + let mut tcp_stream = + Stream::from_tcp_stream(client.clone(), tcp_stream); + + tcp_stream.options = options; + + if let Some(ref alias) = &tcp_stream.options.alias { + indices.stream_aliases.insert( + alias.clone(), + tcp_stream.clone(), + ); + } + + indices.streams.insert(tcp_stream.clone()); + + let tcp_stream = + self.heap.to_unifiable(HeapCellValue::Stream(tcp_stream)); + + let client = + self.heap.to_unifiable(HeapCellValue::Atom(client, None)); + + let client_addr = self.store(self.deref(self[temp_v!(2)])); + let stream_addr = self.store(self.deref(self[temp_v!(3)])); + + self.bind(client_addr.as_var().unwrap(), client); + self.bind(stream_addr.as_var().unwrap(), tcp_stream); + } + None => { + self.fail = true; + return Ok(()); + } + } + } + culprit => { + let culprit = culprit.as_addr(h); + + return Err(self.type_error( + ValidType::TcpListener, + culprit, + clause_name!("socket_server_accept"), + 4, + )); + } + } + } + culprit => { + return Err(self.type_error( + ValidType::TcpListener, + culprit, + clause_name!("socket_server_accept"), + 4, + )); + } + } + } + &SystemClauseType::SocketServerClose => { + match self.store(self.deref(self[temp_v!(1)])) { + Addr::TcpListener(h) => { + let closed_tcp_listener = clause_name!("$closed_tcp_listener"); + self.heap[h] = HeapCellValue::Atom(closed_tcp_listener, None); + } + culprit => { + return Err(self.type_error( + ValidType::TcpListener, + culprit, + clause_name!("socket_server_close"), + 1, + )); + } + } + } + &SystemClauseType::SetStreamPosition => { + let mut stream = self.get_stream_or_alias( + self[temp_v!(1)], + indices, + "set_stream_position", + 2, + )?; + + if !stream.options.reposition { + let stub = MachineError::functor_stub(clause_name!("set_stream_position"), 2); + + let err = MachineError::permission_error( + self.heap.h(), + Permission::Reposition, + "stream", + vec![HeapCellValue::Stream(stream)], + ); + + return Err(self.error_form(err, stub)); + } + + let position = self.store(self.deref(self[temp_v!(2)])); + + let position = + match Number::try_from((position, &self.heap)) { + Ok(Number::Fixnum(n)) => { + n as u64 + } + Ok(Number::Integer(n)) => { + if let Some(n) = n.to_u64() { + n + } else { + self.fail = true; + return Ok(()); + } + } + _ => { + unreachable!() + } + }; + + stream.set_position(position); + } + &SystemClauseType::StreamProperty => { + let mut stream = self.get_stream_or_alias( + self[temp_v!(1)], + indices, + "stream_property", + 2, + )?; + + let property = + match self.store(self.deref(self[temp_v!(2)])) { + Addr::Con(h) if self.heap.atom_at(h) => { + match &self.heap[h] { + HeapCellValue::Atom(ref name, _) => { + match name.as_str() { + "file_name" => { + if let Some(file_name) = stream.file_name() { + HeapCellValue::Atom( + file_name, + None, + ) + } else { + self.fail = true; + return Ok(()); + } + } + "mode" => { + HeapCellValue::Atom( + clause_name!(stream.mode()), + None, + ) + } + "direction" => { + HeapCellValue::Atom( + if stream.is_input_stream() && stream.is_output_stream() { + clause_name!("input_output") + } else if stream.is_input_stream() { + clause_name!("input") + } else { + clause_name!("output") + }, + None, + ) + } + "alias" => { + if let Some(alias) = &stream.options.alias { + HeapCellValue::Atom( + alias.clone(), + None, + ) + } else { + self.fail = true; + return Ok(()); + } + } + "position" => { + if stream.options.reposition { + if let Some(position) = stream.position() { + HeapCellValue::Addr(Addr::Usize(position as usize)) + } else { + unreachable!() + } + } else { + self.fail = true; + return Ok(()); + } + } + "end_of_stream" => { + let end_of_stream_pos = stream.position_relative_to_end(); + + HeapCellValue::Atom( + clause_name!(end_of_stream_pos.as_str()), + None, + ) + } + "eof_action" => { + HeapCellValue::Atom( + clause_name!(stream.options.eof_action.as_str()), + None, + ) + } + "reposition" => { + HeapCellValue::Atom( + clause_name!(if stream.options.reposition { + "true" + } else { + "false" + }), + None, + ) + } + "type" => { + HeapCellValue::Atom( + clause_name!(stream.options.stream_type.as_property_str()), + None, + ) + } + _ => { + unreachable!() + } + } + } + _ => { + unreachable!() + } + } + } + _ => { + unreachable!() + } + }; + + let property = self.heap.to_unifiable(property); + self.unify(self[temp_v!(3)], property); + } &SystemClauseType::StoreGlobalVar => { let key = self[temp_v!(1)]; @@ -3519,7 +4930,41 @@ impl MachineState { self.unify(listing, listing_var); } &SystemClauseType::WriteTerm => { - let addr = self[temp_v!(1)]; + let mut stream = self.get_stream_or_alias( + self[temp_v!(1)], + indices, + "write_term", + 3, + )?; + + self.check_stream_properties( + &mut stream, + StreamType::Text, + None, // input + clause_name!("write_term"), + 3, + )?; + + let opt_err = + if !stream.is_output_stream() { + Some("stream") // 8.14.2.3 g) + } else if stream.options.stream_type == StreamType::Binary { + Some("binary_stream") // 8.14.2.3 h) + } else { + None + }; + + if let Some(err_string) = opt_err { + return Err(self.stream_permission_error( + Permission::OutputStream, + err_string, + stream, + clause_name!("write_term"), + 3, + )); + } + + let addr = self[temp_v!(2)]; let printer = match self.write_term(&indices.op_dir)? { @@ -3534,11 +4979,24 @@ impl MachineState { let output = printer.print(addr); - print!("{}", output.result()); - stdout().flush().unwrap(); + match write!(&mut stream, "{}", output.result()) { + Ok(_) => { + } + Err(_) => { + let stub = MachineError::functor_stub(clause_name!("open"), 4); + let err = MachineError::existence_error( + self.heap.h(), + ExistenceError::Stream(self[temp_v!(1)]), + ); + + return Err(self.error_form(err, stub)); + } + } + + stream.flush().unwrap(); } &SystemClauseType::WriteTermToChars => { - let addr = self[temp_v!(1)]; + let addr = self[temp_v!(2)]; let printer = match self.write_term(&indices.op_dir)? { @@ -3554,7 +5012,7 @@ impl MachineState { let result = printer.print(addr).result(); let chars = self.heap.put_complete_string(&result); - let result_addr = self.store(self.deref(self[temp_v!(7)])); + let result_addr = self.store(self.deref(self[temp_v!(1)])); if let Some(var) = result_addr.as_var() { self.bind(var, chars); diff --git a/src/prolog/macros.rs b/src/prolog/macros.rs index 834ad9f8..5ac0f450 100644 --- a/src/prolog/macros.rs +++ b/src/prolog/macros.rs @@ -358,6 +358,7 @@ macro_rules! index_store { op_dir: $op_dir, modules: $modules, stream_aliases: StreamAliasDir::new(), + streams: StreamDir::new(), } }; } @@ -405,3 +406,26 @@ macro_rules! ar_reg { ArithmeticTerm::Reg($r) }; } + +macro_rules! atom_from { + ($self:expr, $indices:expr, $e:expr) => { + match $e { + Addr::Con(h) if $self.heap.atom_at(h) => { + match &$self.heap[h] { + HeapCellValue::Atom(ref atom, _) => { + atom.clone() + } + _ => { + unreachable!() + } + } + } + Addr::Char(c) => { + clause_name!(c.to_string(), $indices.atom_tbl.clone()) + } + _ => { + unreachable!() + } + } + } +} diff --git a/src/prolog/read.rs b/src/prolog/read.rs index 28d35358..29b37fcb 100644 --- a/src/prolog/read.rs +++ b/src/prolog/read.rs @@ -18,7 +18,7 @@ pub mod readline { use crate::prolog::machine::streams::Stream; use crate::prolog::rustyline::error::ReadlineError; use crate::prolog::rustyline::{Cmd, Editor, KeyPress}; - use std::io::{Cursor, Read}; + use std::io::{Cursor, Error, ErrorKind, Read}; static mut PROMPT: bool = false; @@ -42,6 +42,11 @@ pub mod readline { } impl ReadlineStream { + pub fn new(pending_input: String) -> Self { + let rl = Editor::<()>::new(); + ReadlineStream { rl, pending_input: Cursor::new(pending_input) } + } + pub fn input_stream(pending_input: String) -> Stream { let mut rl = Editor::<()>::new(); rl.bind_sequence(KeyPress::Tab, Cmd::Insert(1, "\t".to_string())); @@ -68,7 +73,61 @@ pub mod readline { Ok(0) } Err(e) => { - Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) + Err(Error::new(ErrorKind::InvalidInput, e)) + } + } + } + + pub fn peek_byte(&mut self) -> std::io::Result { + set_prompt(false); + + loop { + match self.pending_input.get_ref().bytes().next() { + Some(b) => { + return Ok(b); + } + None => { + match self.call_readline(&mut []) { + Err(e) => { + return Err(e); + } + Ok(0) => { + return Err(Error::new( + ErrorKind::UnexpectedEof, + "end of file", + )); + } + _ => { + } + } + } + } + } + } + + pub fn peek_char(&mut self) -> std::io::Result { + set_prompt(false); + + loop { + match self.pending_input.get_ref().chars().next() { + Some(c) => { + return Ok(c); + } + None => { + match self.call_readline(&mut []) { + Err(e) => { + return Err(e); + } + Ok(0) => { + return Err(Error::new( + ErrorKind::UnexpectedEof, + "end of file", + )); + } + _ => { + } + } + } } } } diff --git a/src/prolog/toplevel.pl b/src/prolog/toplevel.pl index 9e457395..6bece860 100644 --- a/src/prolog/toplevel.pl +++ b/src/prolog/toplevel.pl @@ -105,7 +105,7 @@ repl :- repl. read_and_match :- - '$read_query_term'(Term, VarList), + '$read_query_term'(_, Term, _, _, VarList), instruction_match(Term, VarList). % make compile_batch, a system routine, callable. @@ -235,8 +235,8 @@ write_eqs_and_read_input(B, VarList) :- append([Vars0, AttrVars, AttrGoalVars], Vars), charsio:extend_var_list(Vars, VarList, NewVarList, fabricated), '$get_b_value'(B0), - gather_query_vars(VarList, QueryVars), - gather_equations(NewVarList, QueryVars, Goals, AttrGoals), + gather_query_vars(VarList, OrigVars), + gather_equations(NewVarList, OrigVars, Goals, AttrGoals), ( bb_get('$first_answer', true) -> write(' '), bb_put('$first_answer', false) @@ -287,8 +287,8 @@ help_message :- gather_query_vars([_ = Var | Vars], QueryVars) :- ( var(Var) -> - QueryVars = [Var | QueryVars1], - gather_query_vars(Vars, QueryVars1) + QueryVars = [Var | QueryVars0], + gather_query_vars(Vars, QueryVars0) ; gather_query_vars(Vars, QueryVars) ). gather_query_vars([], []). @@ -298,7 +298,7 @@ is_a_different_variable([_ = Binding | Pairs], Value) :- ; is_a_different_variable(Pairs, Value) ). -eq_member(X, [Y|_]) :- X == Y, !. +eq_member(X, [Y|_]) :- X == Y, !. eq_member(X, [_|Ys]) :- eq_member(X, Ys). gather_equations([], _, Goals, Goals). @@ -321,9 +321,9 @@ gather_equations([Var = Value | Pairs], OrigVarList, Goals, Goals1) :- /* gather_equations([], MasterList, Goals, Goals). gather_equations([Var = Value | Pairs], MasterList, Goals, Goals1) :- - select((Var = _), MasterList, MasterPairs), ( ( nonvar(Value) - ; is_a_different_variable(MasterPairs, Value) + ; select((Var = _), MasterList, MasterPairs), + is_a_different_variable(MasterPairs, Value) ) -> Goals = [Var = Value | Goals0], gather_equations(Pairs, MasterList, Goals0, Goals1) diff --git a/src/prolog/write.rs b/src/prolog/write.rs index 9c2267ae..4c94d955 100644 --- a/src/prolog/write.rs +++ b/src/prolog/write.rs @@ -181,6 +181,9 @@ impl fmt::Display for HeapCellValue { &HeapCellValue::Stream(ref stream) => { write!(f, "$stream({})", stream.as_ptr() as usize) } + &HeapCellValue::TcpListener(ref tcp_listener) => { + write!(f, "$tcp_listener({})", tcp_listener.local_addr().unwrap()) + } } } } @@ -213,6 +216,7 @@ impl fmt::Display for Addr { &Addr::Str(s) => write!(f, "Addr::Str({})", s), &Addr::PStrLocation(h, n) => write!(f, "Addr::PStrLocation({}, {})", h, n), &Addr::Stream(stream) => write!(f, "Addr::Stream({})", stream), + &Addr::TcpListener(tcp_listener) => write!(f, "Addr::TcpListener({})", tcp_listener), &Addr::Usize(cp) => write!(f, "Addr::Usize({})", cp), } } @@ -332,11 +336,14 @@ impl fmt::Display for ExistenceError { &ExistenceError::Module(ref module_name) => { write!(f, "the module {} does not exist", module_name) } + &ExistenceError::ModuleSource(ref module_source) => { + write!(f, "the source/sink {} does not exist", module_source) + } &ExistenceError::Procedure(ref name, arity) => { write!(f, "the procedure {}/{} does not exist", name, arity) } - &ExistenceError::SourceSink(ref module_source) => { - write!(f, "the source/sink {} does not exist", module_source) + &ExistenceError::SourceSink(ref addr) => { + write!(f, "the source/sink {} does not exist", addr) } &ExistenceError::Stream(ref addr) => { write!(f, "the stream at {} does not exist", addr)