feat: add unsound module

This commit is contained in:
Shunsuke Shibayama 2023-10-15 02:30:05 +09:00
parent fc75151f2a
commit 08770e84fc
9 changed files with 130 additions and 8 deletions

View file

@ -502,6 +502,8 @@ impl Input {
.canonicalize()
{
Some(normalize_path(path))
} else if path == Path::new("unsound") {
Some(PathBuf::from("unsound"))
} else {
None
}

View file

@ -259,6 +259,16 @@ impl<Parser: ASTBuildable, Builder: Buildable> PackageBuilder<Parser, Builder> {
let ValueObj::Str(__name__) = &mod_name.value else {
return Ok(());
};
if call
.additional_operation()
.is_some_and(|op| op.is_erg_import())
&& __name__ == "unsound"
{
if let Some(mod_ctx) = self.get_context() {
mod_ctx.context.build_module_unsound();
}
return Ok(());
}
let import_path = match cfg.input.resolve_path(Path::new(&__name__[..])) {
Some(path) => path,
None => {

View file

@ -1153,6 +1153,69 @@ impl Context {
.register(PathBuf::from("<builtins>"), None, None, module);
}
pub(crate) fn build_module_unsound(&self) {
use erg_common::pathutil::NormalizedPathBuf;
use std::path::Path;
use crate::hir::{
Accessor, Args, Block, Def, DefBody, Expr, Identifier, Module, Params, Signature,
SubrSignature, VarSignature, HIR,
};
use erg_parser::ast::{DefId, NonDefaultParamSignature, TypeBoundSpecs};
use erg_parser::token::Token;
let path = NormalizedPathBuf::from(Path::new("unsound"));
let mut ctx = Context::new_module(
"unsound",
self.cfg.inherit(path.to_path_buf()),
self.shared().clone(),
);
let eval_t = func1(Type::Str, Type::Obj);
ctx.register_builtin_erg_impl(
"pyeval",
eval_t,
Mutability::Immutable,
Visibility::BUILTIN_PUBLIC,
);
let pyeval = Identifier::public("pyeval");
let sig = VarSignature::new(pyeval.clone(), None);
let sig = Signature::Var(sig);
let eval = Expr::Accessor(Accessor::Ident(Identifier::public("eval")));
let block = Block::new(vec![eval]);
let body = DefBody::new(Token::DUMMY, block, DefId(0));
let eval = Def::new(sig, body);
let T = type_q("T");
let perform_t = func1(proc0(T.clone()), T).quantify();
ctx.register_builtin_erg_impl(
"perform",
perform_t,
Mutability::Immutable,
Visibility::BUILTIN_PUBLIC,
);
let perform = Identifier::public("perform");
let params = Params::single(crate::hir::NonDefaultParamSignature::new(
NonDefaultParamSignature::simple("p!".into()),
VarInfo::const_default_public(),
None,
));
let sig = SubrSignature::new(
set! {},
perform.clone(),
TypeBoundSpecs::empty(),
params,
None,
);
let sig = Signature::Subr(sig);
let call = Identifier::private("p!").call(Args::empty());
let block = Block::new(vec![Expr::Call(call)]);
let body = DefBody::new(Token::DUMMY, block, DefId(0));
let perform = Def::new(sig, body);
let module = Module::new(vec![Expr::Def(eval), Expr::Def(perform)]);
let hir = HIR::new("unsound".into(), module);
let ctx = ModuleContext::new(ctx, dict! {});
self.mod_cache().register(path, None, Some(hir), ctx);
}
pub fn new_module<S: Into<Str>>(
name: S,
cfg: ErgConfig,

View file

@ -497,6 +497,10 @@ impl Identifier {
}
}
pub fn call(self, args: Args) -> Call {
Call::new(Expr::Accessor(Accessor::Ident(self)), None, args)
}
pub fn is_py_api(&self) -> bool {
self.vi.py_name.is_some()
}

View file

@ -0,0 +1,9 @@
'''
Evaluate a string as Python code.
'''
.pyeval: (code: Str, globals := {Str: Obj}, locals := {Str: Obj}) -> Obj
'''
Execute the procedure circumventing effect analysis.
If you want to use side effects locally in a function, use the `isolate` function instead.
'''
.perform: |T|(p!: () => T) -> T

View file

@ -6,10 +6,10 @@ use std::rc::Rc;
use erg_common::config::ErgConfig;
use erg_common::dict::Dict as Dic;
use erg_common::fresh::SharedFreshNameGenerator;
use erg_common::log;
use erg_common::pathutil::squash;
use erg_common::traits::{Locational, Stream};
use erg_common::Str;
use erg_common::{enum_unwrap, log};
use erg_parser::ast::{DefId, OperationKind};
use erg_parser::token::{Token, TokenKind, DOT, EQUAL};
@ -402,10 +402,14 @@ impl<'a> HIRLinker<'a> {
.remove(path.as_path())
.and_then(|entry| entry.hir.map(|hir| (hir, entry.module.context.cfg.clone())))
};
let mod_name = enum_unwrap!(expr, Expr::Call)
.args
.get_left_or_key("Path")
.unwrap();
let Expr::Call(call) = expr else {
log!(err "{expr}");
return;
};
let Some(mod_name) = call.args.get_left_or_key("Path") else {
log!(err "{call}");
return;
};
// let sig = option_enum_unwrap!(&def.sig, Signature::Var)
// .unwrap_or_else(|| todo!("module subroutines are not allowed"));
if let Some((hir, cfg)) = hir_cfg {
@ -464,9 +468,20 @@ impl<'a> HIRLinker<'a> {
/// x = __import__("a.x").x
/// ```
fn replace_py_import(&self, expr: &mut Expr) {
let args = &mut enum_unwrap!(expr, Expr::Call).args;
let mod_name_lit = enum_unwrap!(args.remove_left_or_key("Path").unwrap(), Expr::Literal);
let mod_name_str = enum_unwrap!(mod_name_lit.value.clone(), ValueObj::Str);
let args = if let Expr::Call(call) = expr {
&mut call.args
} else {
log!(err "{expr}");
return;
};
let Some(Expr::Literal(mod_name_lit)) = args.remove_left_or_key("Path") else {
log!(err "{args}");
return;
};
let ValueObj::Str(mod_name_str) = mod_name_lit.value.clone() else {
log!(err "{mod_name_lit}");
return;
};
let mut dir = self.cfg.input.dir();
let mod_path = self
.cfg

View file

@ -4076,6 +4076,10 @@ impl NonDefaultParamSignature {
Self { pat, t_spec }
}
pub fn simple(name: Str) -> Self {
Self::new(ParamPattern::VarName(VarName::from_str(name)), None)
}
pub const fn inspect(&self) -> Option<&Str> {
self.pat.inspect()
}

10
examples/magic.er Normal file
View file

@ -0,0 +1,10 @@
unsound = import "unsound"
i = unsound.pyeval "1 + 1"
assert i in Nat
assert i == 2
print x =
unsound.perform do! print! x
print "hello"

View file

@ -196,6 +196,11 @@ fn exec_long() -> Result<(), ()> {
expect_success("tests/should_ok/long.er", 257)
}
#[test]
fn exec_magic() -> Result<(), ()> {
expect_success("examples/magic.er", 0)
}
#[test]
fn exec_mangling() -> Result<(), ()> {
expect_success("tests/should_ok/mangling.er", 0)