From 4c510001ce7a41352527437318fcdb2ce2ba4874 Mon Sep 17 00:00:00 2001 From: Markus Triska Date: Wed, 22 Jul 2020 20:17:46 +0200 Subject: [PATCH] ADDED: chars_base64/3 for efficient bidirectional Base64 conversion. --- Cargo.toml | 1 + README.md | 7 +++-- src/clause_types.rs | 3 ++ src/lib/charsio.pl | 42 ++++++++++++++++++++++++- src/machine/system_calls.rs | 63 +++++++++++++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc13c81f..6ac8ebe4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,3 +46,4 @@ native-tls = "0.2.4" chrono = "0.4.11" select = "0.4.3" roxmltree = "0.11.0" +base64 = "0.12.3" diff --git a/README.md b/README.md index d31d1739..0cb1a268 100644 --- a/README.md +++ b/README.md @@ -387,9 +387,10 @@ The modules that ship with Scryer Prolog are also called file, reading lazily only as much as is needed. Due to the compact internal string representation, also extremely large files can be efficiently processed with Scryer Prolog in this way. -* [`charsio`](src/lib/charsio.pl) Various predicates that are - useful for parsing and reasoning about characters, notably - `char_type/2` to classify characters according to their type. +* [`charsio`](src/lib/charsio.pl) Various predicates that are useful + for parsing and reasoning about characters, notably `char_type/2` to + classify characters according to their type, and conversion + predicates for different encodings of strings. * [`error`](src/lib/error.pl) `must_be/2` and `can_be/2` complement the type checks provided by [`library(si)`](src/lib/si.pl), and are especially useful for diff --git a/src/clause_types.rs b/src/clause_types.rs index ab555630..ee6ad24c 100644 --- a/src/clause_types.rs +++ b/src/clause_types.rs @@ -314,6 +314,7 @@ pub enum SystemClauseType { GetEnv, SetEnv, UnsetEnv, + CharsBase64, } impl SystemClauseType { @@ -526,6 +527,7 @@ impl SystemClauseType { &SystemClauseType::GetEnv => clause_name!("$getenv"), &SystemClauseType::SetEnv => clause_name!("$setenv"), &SystemClauseType::UnsetEnv => clause_name!("$unsetenv"), + &SystemClauseType::CharsBase64 => clause_name!("$chars_base64"), } } @@ -718,6 +720,7 @@ impl SystemClauseType { ("$getenv", 2) => Some(SystemClauseType::GetEnv), ("$setenv", 2) => Some(SystemClauseType::SetEnv), ("$unsetenv", 1) => Some(SystemClauseType::UnsetEnv), + ("$chars_base64", 4) => Some(SystemClauseType::CharsBase64), _ => None, } } diff --git a/src/lib/charsio.pl b/src/lib/charsio.pl index 4e82f23b..dd41a597 100644 --- a/src/lib/charsio.pl +++ b/src/lib/charsio.pl @@ -3,7 +3,8 @@ get_single_char/1, read_line_to_chars/3, read_term_from_chars/2, - write_term_to_chars/3]). + write_term_to_chars/3, + chars_base64/3]). :- use_module(library(dcgs)). :- use_module(library(iso_ext)). @@ -194,3 +195,42 @@ read_line_to_chars(Stream, Cs0, Cs) :- ; read_line_to_chars(Stream, Rest, Cs) ) ). + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Relation between a list of characters Cs and its Base64 encoding Bs, + also a list of characters. + + Options are: + + - padding(Boolean) + Whether to use padding: true (the default) or false. + - charset(C) + Either 'standard' (RFC 4648 §4, the default) or 'url' (RFC 4648 §5). + + Example: + + ?- chars_base64("hello", Bs, []). + Bs = "aGVsbG8=" + ; false. +- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +chars_base64(Cs, Bs, Options) :- + must_be(list, Options), + ( member(O, Options), var(O) -> + instantiation_error(chars_base64/3) + ; ( member(padding(Padding), Options) -> true + ; Padding = true + ), + ( member(charset(Charset), Options) -> true + ; Charset = standard + ) + ), + must_be(boolean, Padding), + ( var(Cs) -> + must_be(list, Bs), + maplist(must_be(character), Bs), + '$chars_base64'(Cs, Bs, Padding, Charset) + ; must_be(list, Cs), + maplist(must_be(character), Cs), + '$chars_base64'(Cs, Bs, Padding, Charset) + ). diff --git a/src/machine/system_calls.rs b/src/machine/system_calls.rs index 31024167..7971583f 100644 --- a/src/machine/system_calls.rs +++ b/src/machine/system_calls.rs @@ -55,6 +55,7 @@ use crate::native_tls::TlsConnector; extern crate select; use roxmltree; +use base64; pub fn get_key() -> KeyEvent { let key; @@ -5745,6 +5746,68 @@ impl MachineState { let key = self.heap_pstr_iter(self[temp_v!(1)]).to_string(); env::remove_var(key); } + &SystemClauseType::CharsBase64 => { + let mut options = vec![]; + + for i in 3..5 { + match self.store(self.deref(self[temp_v!(i)])) { + Addr::Con(h) if self.heap.atom_at(h) => { + if let HeapCellValue::Atom(ref atom, _) = &self.heap[h] { + options.push(atom.as_str()); + } else { + unreachable!() + } + } + _ => { + unreachable!() + } + }; + } + + let config = + if options[0] == "true" { + if options[1] == "standard" { + base64::STANDARD + } else { + base64::URL_SAFE + } + } else { + if options[1] == "standard" { + base64::STANDARD_NO_PAD + } else { + base64::URL_SAFE_NO_PAD + } + }; + + if self.store(self.deref(self[temp_v!(1)])).is_ref() { + let b64 = self.heap_pstr_iter(self[temp_v!(2)]).to_string(); + let bytes = base64::decode_config(b64, config); + + match bytes { + Ok(bs) => { + let mut string = String::new(); + for c in bs { + string.push(c as char); + } + let cstr = self.heap.put_complete_string(&string); + self.unify(self[temp_v!(1)], cstr); + } + _ => { + self.fail = true; + return Ok(()); + } + } + } else { + let mut bytes = vec![]; + for c in self.heap_pstr_iter(self[temp_v!(1)]).to_string().chars() { + bytes.push(c as u8); + } + let b64 = base64::encode_config(bytes, config); + + let cstr = self.heap.put_complete_string(&b64); + self.unify(self[temp_v!(2)], cstr); + } + } }; return_from_clause!(self.last_call, self) -- 2.54.0