Skip to content

Commit

Permalink
Merge pull request #828 from DaddyWesker/issue_814
Browse files Browse the repository at this point in the history
python's repr fixed
  • Loading branch information
vsbogd authored Jan 14, 2025
2 parents 8e9b081 + e51cef9 commit d121d35
Show file tree
Hide file tree
Showing 14 changed files with 99 additions and 109 deletions.
1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ rand = "0.8.5"
bitset = "0.1.2"
dyn-fmt = "0.4.0"
itertools = "0.13.0"
unescaper = "0.1.5"

# pkg_mgmt deps
xxhash-rust = {version="0.8.7", features=["xxh3"], optional=true }
Expand Down
4 changes: 0 additions & 4 deletions lib/src/metta/runner/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,6 @@ fn git_catalog_from_cfg_atom(atom: &ExpressionAtom, env: &Environment) -> Result
let refresh_time = refresh_time.ok_or_else(|| format!("Error in environment.metta. \"refreshTime\" property required for #gitCatalog"))?
.parse::<u64>().map_err(|e| format!("Error in environment.metta. Error parsing \"refreshTime\": {e}"))?;

let catalog_name = crate::metta::runner::str::strip_quotes(catalog_name);
let catalog_url = crate::metta::runner::str::strip_quotes(catalog_url);

let mut managed_remote_catalog = LocalCatalog::new(caches_dir, catalog_name).unwrap();
let remote_catalog = GitCatalog::new(caches_dir, env.fs_mod_formats.clone(), catalog_name, catalog_url, refresh_time).unwrap();
managed_remote_catalog.push_upstream_catalog(Box::new(remote_catalog));
Expand All @@ -474,7 +471,6 @@ fn include_path_from_cfg_atom(atom: &ExpressionAtom, env: &Environment) -> Resul
None => return Err(format!("Error in environment.metta. #includePath missing path value"))
};
let path = <&crate::SymbolAtom>::try_from(path_atom)?.name();
let path = crate::metta::runner::str::strip_quotes(path);

//TODO-FUTURE: In the future we may want to replace dyn-fmt with strfmt, and do something a
// little bit nicer than this
Expand Down
4 changes: 3 additions & 1 deletion lib/src/metta/runner/stdlib/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use crate::space::*;
use crate::common::collections::{VecDisplay, Equality, DefaultEquality};
use crate::common::assert::compare_vec_no_order;
use crate::atom::matcher::atoms_are_equivalent;
use crate::metta::runner::stdlib::{grounded_op, atom_to_string, regex, interpret_no_error, unit_result};
use crate::metta::runner::stdlib::{grounded_op, regex, interpret_no_error, unit_result};
use crate::metta::runner::bool::*;

use crate::metta::runner::str::atom_to_string;

use std::convert::TryInto;

fn assert_results_equal(actual: &Vec<Atom>, expected: &Vec<Atom>) -> Result<Vec<Atom>, ExecError> {
Expand Down
13 changes: 0 additions & 13 deletions lib/src/metta/runner/stdlib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ use crate::metta::*;
use crate::metta::text::{Tokenizer, SExprParser};
use crate::common::shared::Shared;
use crate::metta::runner::{Metta, RunContext, ModuleLoader};
use super::str::*;

use regex::Regex;

Expand Down Expand Up @@ -47,18 +46,6 @@ pub(crate) fn regex(regex: &str) -> Regex {
Regex::new(regex).unwrap()
}

pub fn atom_to_string(atom: &Atom) -> String {
match atom {
Atom::Grounded(gnd) if gnd.type_() == ATOM_TYPE_STRING => {
let mut s = gnd.to_string();
s.remove(0);
s.pop();
s
},
_ => atom.to_string(),
}
}

// TODO: remove hiding errors completely after making it possible passing
// them to the user
pub fn interpret_no_error(space: DynSpace, expr: &Atom) -> Result<Vec<Atom>, String> {
Expand Down
35 changes: 7 additions & 28 deletions lib/src/metta/runner/stdlib/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ use crate::metta::*;
use crate::metta::text::Tokenizer;
use crate::common::shared::Shared;
use crate::metta::runner::{Metta, RunContext, ResourceKey};
use crate::metta::runner::str::*;
use super::{grounded_op, regex, unit_result};
use crate::metta::runner::str::expect_string_like_atom;

use regex::Regex;

Expand Down Expand Up @@ -68,20 +68,13 @@ impl CustomExecute for ImportOp {

let arg_error = || ExecError::from("import! expects a destination &space and a module name argument");
let dest_arg = args.get(0).ok_or_else(arg_error)?;
let mod_name_atom = args.get(1).ok_or_else(arg_error)?;

// TODO: replace Symbol by grounded String?
let mod_name = match mod_name_atom {
Atom::Symbol(mod_name) => mod_name.name(),
_ => return Err("import! expects a module name as the first argument".into())
};
let mod_name = strip_quotes(mod_name);
let mod_name = args.get(1).and_then(expect_string_like_atom).ok_or_else(arg_error)?;

// Load the module into the runner, or get the ModId if it's already loaded
//TODO: Remove this hack to access the RunContext, when it's part of the arguments to `execute`
let ctx_ref = self.context.lock().unwrap().last().unwrap().clone();
let mut context = ctx_ref.lock().unwrap();
let mod_id = context.load_module(mod_name)?;
let mod_id = context.load_module(&mod_name)?;

// Import the module, as per the behavior described above
match dest_arg {
Expand Down Expand Up @@ -136,19 +129,12 @@ impl Grounded for IncludeOp {
impl CustomExecute for IncludeOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = || ExecError::from("include expects a module name argument");
let mod_name_atom = args.get(0).ok_or_else(arg_error)?;

// TODO: replace Symbol by grounded String?
let mod_name = match mod_name_atom {
Atom::Symbol(mod_name) => mod_name.name(),
_ => return Err(arg_error())
};
let mod_name = strip_quotes(mod_name);
let mod_name = args.get(0).and_then(expect_string_like_atom).ok_or_else(arg_error)?;

//TODO: Remove this hack to access the RunContext, when it's part of the arguments to `execute`
let ctx_ref = self.context.lock().unwrap().last().unwrap().clone();
let mut context = ctx_ref.lock().unwrap();
let program_buf = context.load_resource_from_module(mod_name, ResourceKey::MainMettaSrc)?;
let program_buf = context.load_resource_from_module(&mod_name, ResourceKey::MainMettaSrc)?;

// Interpret the loaded MeTTa S-Expression text
let program_text = String::from_utf8(program_buf)
Expand Down Expand Up @@ -199,20 +185,13 @@ impl Grounded for ModSpaceOp {
impl CustomExecute for ModSpaceOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = "mod-space! expects a module name argument";
let mod_name_atom = args.get(0).ok_or_else(|| ExecError::from(arg_error))?;

// TODO: replace Symbol by grounded String?
let mod_name = match mod_name_atom {
Atom::Symbol(mod_name) => mod_name.name(),
_ => {return Err(ExecError::from(arg_error))}
};
let mod_name = strip_quotes(mod_name);
let mod_name = args.get(0).and_then(expect_string_like_atom).ok_or_else(|| ExecError::from(arg_error))?;

// Load the module into the runner, or get the ModId if it's already loaded
//TODO: Remove this hack to access the RunContext, when it's part of the arguments to `execute`
let ctx_ref = self.context.lock().unwrap().last().unwrap().clone();
let mut context = ctx_ref.lock().unwrap();
let mod_id = context.load_module(mod_name)?;
let mod_id = context.load_module(&mod_name)?;

let space = Atom::gnd(context.metta().module_space(mod_id));
Ok(vec![space])
Expand Down
21 changes: 4 additions & 17 deletions lib/src/metta/runner/stdlib/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::metta::runner::{Metta, RunContext,
git_catalog::ModuleGitLocation,
mod_name_from_url,
pkg_mgmt::UpdateMode};
use crate::metta::runner::str::*;
use crate::metta::runner::str::expect_string_like_atom;

/// Provides a way to access [Metta::load_module_at_path] from within MeTTa code
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -35,14 +35,8 @@ impl Grounded for RegisterModuleOp {
impl CustomExecute for RegisterModuleOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = "register-module! expects a file system path; use quotes if needed";
let path_arg_atom = args.get(0).ok_or_else(|| ExecError::from(arg_error))?;
let path = args.get(0).and_then(expect_string_like_atom).ok_or_else(|| ExecError::from(arg_error))?;

let path = match path_arg_atom {
Atom::Symbol(path_arg) => path_arg.name(),
Atom::Grounded(g) => g.downcast_ref::<Str>().ok_or_else(|| ExecError::from(arg_error))?.as_str(),
_ => return Err(arg_error.into()),
};
let path = strip_quotes(path);
let path = std::path::PathBuf::from(path);

// Load the module from the path
Expand Down Expand Up @@ -90,19 +84,12 @@ impl Grounded for GitModuleOp {
impl CustomExecute for GitModuleOp {
fn execute(&self, args: &[Atom]) -> Result<Vec<Atom>, ExecError> {
let arg_error = "git-module! expects a URL; use quotes if needed";
let url_arg_atom = args.get(0).ok_or_else(|| ExecError::from(arg_error))?;
let url = args.get(0).and_then(expect_string_like_atom).ok_or_else(|| ExecError::from(arg_error))?;
// TODO: When we figure out how to address varargs, it will be nice to take an optional branch name

let url = match url_arg_atom {
Atom::Symbol(url_arg) => url_arg.name(),
Atom::Grounded(g) => g.downcast_ref::<Str>().ok_or_else(|| ExecError::from(arg_error))?.as_str(),
_ => return Err(arg_error.into()),
};
let url = strip_quotes(url);

// TODO: Depending on what we do with `register-module!`, we might want to let the
// caller provide an optional mod_name here too, rather than extracting it from the url
let mod_name = match mod_name_from_url(url) {
let mod_name = match mod_name_from_url(&url) {
Some(mod_name) => mod_name,
None => return Err(ExecError::from("git-module! error extracting module name from URL"))
};
Expand Down
2 changes: 1 addition & 1 deletion lib/src/metta/runner/stdlib/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::*;
use crate::metta::*;
use crate::metta::text::Tokenizer;
use crate::metta::runner::str::*;
use super::{grounded_op, atom_to_string, unit_result, regex};
use super::{grounded_op, unit_result, regex};

use std::convert::TryInto;

Expand Down
69 changes: 52 additions & 17 deletions lib/src/metta/runner/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::*;
use crate::common::collections::ImmutableString;
use crate::serial;
use crate::atom::serial::ConvertingSerializer;
use unescaper;

/// String type
pub const ATOM_TYPE_STRING : Atom = sym!("String");
Expand Down Expand Up @@ -47,26 +48,10 @@ impl Grounded for Str {

impl std::fmt::Display for Str {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "\"{}\"", self.0)
write!(f, "{:?}", self.0.as_str())
}
}

/// A utility function to return the part of a string in between starting and ending quotes
pub fn strip_quotes(src: &str) -> &str {
if let Some(first) = src.chars().next() {
if first == '"' {
if let Some(last) = src.chars().last() {
if last == '"' {
if src.len() > 1 {
return &src[1..src.len()-1]
}
}
}
}
}
src
}

#[derive(Default)]
struct StrSerializer {
value: Option<Str>,
Expand All @@ -84,3 +69,53 @@ impl serial::ConvertingSerializer<Str> for StrSerializer {
self.value
}
}

pub fn atom_to_string(atom: &Atom) -> String {
match atom {
Atom::Grounded(gnd) if gnd.type_() == ATOM_TYPE_STRING =>
// TODO: get string from internal representation using
// serialization like we do for Number
unescape(&atom.to_string()).unwrap(),
_ => atom.to_string(),
}
}

pub fn unescape(str: &str) -> unescaper::Result<String> {
unescaper::unescape(str).map(|mut s| {
s.remove(0);
s.pop();
s
})
}

pub(crate) fn expect_string_like_atom(atom: &Atom) -> Option<String> {
match atom {
Atom::Symbol(_) | Atom::Grounded(_) => Some(atom_to_string(atom)),
_ => None,
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn str_display_escape() {
let s = Str::from_str("\\ \" \' \n \r \t \x1b abc");
assert_eq!(r#""\\ \" ' \n \r \t \u{1b} abc""#, s.to_string());
}

#[test]
fn str_unescape() {
let s = unescape(r#""\\ \" ' \n \r \t \u{1b} abc""#);
assert_eq!("\\ \" \' \n \r \t \x1b abc", s.unwrap());
}

#[test]
fn test_atom_to_string() {
let atom = Atom::gnd(Str::from_str("A\nB"));
assert_eq!("A\nB", atom_to_string(&atom));
let atom = Atom::sym(r#""AB""#);
assert_eq!(r#""AB""#, atom_to_string(&atom));
}
}
3 changes: 2 additions & 1 deletion python/hyperon/atoms.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ def __repr__(self):
# Overwrite Python default representation of a string to use
# double quotes instead of single quotes.
if isinstance(self.content, str):
return f'"{self.content}"'
newstr = repr(self.content)[1:-1].translate(str.maketrans({'"' : r'\"'}))
return f'"{newstr}"'

# Use default representation for everything else
return repr(self.content) if self.id is None else self.id
Expand Down
15 changes: 12 additions & 3 deletions python/hyperon/stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ def match_(self, atom):
return [{"matched_pattern": S(pattern)}]
return []

def parseImpl(atom, run_context):
try:
s = atom.get_object().content
if type(s) != str:
raise IncorrectArgumentError()
return [SExprParser(repr(s)[1:-1]).parse(run_context.tokenizer())]
except Exception as e:
raise IncorrectArgumentError()


@register_atoms(pass_metta=True)
def text_ops(run_context):
"""Add text operators
Expand All @@ -61,10 +71,9 @@ def text_ops(run_context):
"""

reprAtom = OperationAtom('repr', lambda a: [ValueAtom(repr(a))],
reprAtom = OperationAtom('repr', lambda a: [ValueAtom(repr(a), 'String')],
['Atom', 'String'], unwrap=False)
parseAtom = OperationAtom('parse', lambda s: [SExprParser(str(s)[1:-1]).parse(run_context.tokenizer())],
['String', 'Atom'], unwrap=False)
parseAtom = OperationAtom('parse', lambda s: parseImpl(s, run_context), ['String', 'Atom'], unwrap=False)
stringToCharsAtom = OperationAtom('stringToChars', lambda s: [E(*[ValueAtom(Char(c)) for c in str(s)[1:-1]])],
['String', 'Atom'], unwrap=False)
charsToStringAtom = OperationAtom('charsToString', lambda a: [ValueAtom("".join([str(c)[1:-1] for c in a.get_children()]))],
Expand Down
1 change: 0 additions & 1 deletion python/tests/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,6 @@ def test_char_vs_string(self):
self.assertEqualMettaRunnerResults(metta.run("!(get-type 'A')"), [[S('Char')]])
self.assertEqualMettaRunnerResults(metta.run('!(get-type "A")'), [[S('String')]])


class SomeObject():

def __init__(self):
Expand Down
4 changes: 4 additions & 0 deletions python/tests/test_grounded_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ def test_higher_func(self):
self.assertEqual(metta.run("!((curry_num plus 1) 2)"),
metta.run("! 3"))

def test_string_repr(self):
metta = MeTTa(env_builder=Environment.test_env())
self.assertEqual(metta.run('!(repr "A")')[0][0].get_object(), ValueObject("\"A\""))

def test_meta_types(self):
metta = MeTTa(env_builder=Environment.test_env())
### Basic functional types
Expand Down
2 changes: 1 addition & 1 deletion python/tests/test_stdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_text_ops(self):
# [[(V("X"))]])

self.assertEqualMettaRunnerResults(metta.run('!(parse "\\"A\\"")'),
[[(ValueAtom("A"))]])
[[(ValueAtom("A"))]])

#self.assertEqualMettaRunnerResults(metta.run('!(parse "(func (Cons $x (Cons $xs $xss))) ")'),
# [[E(S("func"), E(S("Cons"), V("x"), E(S("Cons"), V("xs"), V("xss"))))]])
Expand Down
Loading

0 comments on commit d121d35

Please sign in to comment.