Make range a type so that it can be used as parameters #452

Merged
sb10q merged 3 commits from enhance/range-type into master 2024-07-09 04:44:40 +08:00
7 changed files with 210 additions and 139 deletions

View File

@ -14,10 +14,7 @@ use strum::IntoEnumIterator;
use crate::{ use crate::{
codegen::{ codegen::{
builtin_fns, builtin_fns,
classes::{ classes::{ArrayLikeValue, NDArrayValue, ProxyValue, RangeValue, TypedArrayLikeAccessor},
ArrayLikeValue, NDArrayValue, ProxyType, ProxyValue, RangeType, RangeValue,
TypedArrayLikeAccessor,
},
expr::destructure_range, expr::destructure_range,
irrt::*, irrt::*,
numpy::*, numpy::*,
@ -460,9 +457,10 @@ impl<'a> BuiltinBuilder<'a> {
| PrimDef::Float | PrimDef::Float
| PrimDef::Bool | PrimDef::Bool
| PrimDef::Str | PrimDef::Str
| PrimDef::Range
| PrimDef::None => Self::build_simple_primitive_class(prim), | PrimDef::None => Self::build_simple_primitive_class(prim),
PrimDef::Range | PrimDef::FunRangeInit => self.build_range_class_related(prim),
PrimDef::Exception => self.build_exception_class_related(prim), PrimDef::Exception => self.build_exception_class_related(prim),
PrimDef::Option PrimDef::Option
@ -494,7 +492,6 @@ impl<'a> BuiltinBuilder<'a> {
| PrimDef::FunNpEye | PrimDef::FunNpEye
| PrimDef::FunNpIdentity => self.build_ndarray_other_factory_function(prim), | PrimDef::FunNpIdentity => self.build_ndarray_other_factory_function(prim),
PrimDef::FunRange => self.build_range_function(),
PrimDef::FunStr => self.build_str_function(), PrimDef::FunStr => self.build_str_function(),
PrimDef::FunFloor | PrimDef::FunFloor64 | PrimDef::FunCeil | PrimDef::FunCeil64 => { PrimDef::FunFloor | PrimDef::FunFloor64 | PrimDef::FunCeil | PrimDef::FunCeil64 => {
@ -599,7 +596,6 @@ impl<'a> BuiltinBuilder<'a> {
PrimDef::Float, PrimDef::Float,
PrimDef::Bool, PrimDef::Bool,
PrimDef::Str, PrimDef::Str,
PrimDef::Range,
PrimDef::None, PrimDef::None,
], ],
); );
@ -607,6 +603,165 @@ impl<'a> BuiltinBuilder<'a> {
TopLevelComposer::make_top_level_class_def(prim.id(), None, prim.name().into(), None, None) TopLevelComposer::make_top_level_class_def(prim.id(), None, prim.name().into(), None, None)
} }
fn build_range_class_related(&mut self, prim: PrimDef) -> TopLevelDef {
debug_assert_prim_is_allowed(prim, &[PrimDef::Range, PrimDef::FunRangeInit]);
let PrimitiveStore { int32, range, .. } = *self.primitives;
let make_ctor_signature = |unifier: &mut Unifier| {
unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![
FuncArg { name: "start".into(), ty: int32, default_value: None },
FuncArg {
name: "stop".into(),
ty: int32,
// placeholder
default_value: Some(SymbolValue::I32(0)),
},
FuncArg {
name: "step".into(),
ty: int32,
default_value: Some(SymbolValue::I32(1)),
},
],
ret: range,
vars: VarMap::default(),
}))
};
match prim {
PrimDef::Range => {
let fields = vec![
("start".into(), int32, true),
("stop".into(), int32, true),
("step".into(), int32, true),
];
let ctor_signature = make_ctor_signature(self.unifier);
TopLevelDef::Class {
name: prim.name().into(),
object_id: prim.id(),
type_vars: Vec::default(),
fields,
attributes: Vec::default(),
methods: vec![("__init__".into(), ctor_signature, PrimDef::FunRangeInit.id())],
ancestors: Vec::default(),
constructor: Some(ctor_signature),
resolver: None,
loc: None,
}
}
PrimDef::FunRangeInit => TopLevelDef::Function {
name: prim.name().into(),
simple_name: prim.simple_name().into(),
signature: make_ctor_signature(self.unifier),
var_id: Vec::default(),
instance_to_symbol: HashMap::default(),
instance_to_stmt: HashMap::default(),
resolver: None,
codegen_callback: Some(Arc::new(GenCall::new(Box::new(
|ctx, obj, _, args, generator| {
let (zelf_ty, zelf) = obj.unwrap();
let zelf =
zelf.to_basic_value_enum(ctx, generator, zelf_ty)?.into_pointer_value();
let zelf = RangeValue::from_ptr_val(zelf, Some("range"));
let mut start = None;
let mut stop = None;
let mut step = None;
let int32 = ctx.ctx.i32_type();
let ty_i32 = ctx.primitives.int32;
for (i, arg) in args.iter().enumerate() {
if arg.0 == Some("start".into()) {
start = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
} else if arg.0 == Some("stop".into()) {
stop = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
} else if arg.0 == Some("step".into()) {
step = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
} else if i == 0 {
start = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
} else if i == 1 {
stop = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
} else if i == 2 {
step = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
}
}
let step = match step {
Some(step) => {
// assert step != 0, throw exception if not
let not_zero = ctx
.builder
.build_int_compare(
IntPredicate::NE,
step,
step.get_type().const_zero(),
"range_step_ne",
)
.unwrap();
ctx.make_assert(
generator,
not_zero,
"0:ValueError",
"range() step must not be zero",
[None, None, None],
ctx.current_loc,
);
step
}
None => int32.const_int(1, false),
};
let stop = stop.unwrap_or_else(|| {
let v = start.unwrap();
start = None;
v
});
let start = start.unwrap_or_else(|| int32.const_zero());
zelf.store_start(ctx, start);
zelf.store_end(ctx, stop);
zelf.store_step(ctx, step);
Ok(Some(zelf.as_base_value().into()))
},
)))),
loc: None,
},
_ => unreachable!(),
}
}
/// Build the class `Exception` and its associated methods. /// Build the class `Exception` and its associated methods.
fn build_exception_class_related(&self, prim: PrimDef) -> TopLevelDef { fn build_exception_class_related(&self, prim: PrimDef) -> TopLevelDef {
// NOTE: currently only contains the class `Exception` // NOTE: currently only contains the class `Exception`
@ -1170,131 +1325,6 @@ impl<'a> BuiltinBuilder<'a> {
} }
} }
/// Build the `range()` function.
fn build_range_function(&mut self) -> TopLevelDef {
let prim = PrimDef::FunRange;
let PrimitiveStore { int32, range, .. } = *self.primitives;
TopLevelDef::Function {
name: prim.name().into(),
simple_name: prim.simple_name().into(),
signature: self.unifier.add_ty(TypeEnum::TFunc(FunSignature {
args: vec![
FuncArg { name: "start".into(), ty: int32, default_value: None },
FuncArg {
name: "stop".into(),
ty: int32,
// placeholder
default_value: Some(SymbolValue::I32(0)),
},
FuncArg {
name: "step".into(),
ty: int32,
default_value: Some(SymbolValue::I32(1)),
},
],
ret: range,
vars: VarMap::default(),
})),
var_id: Vec::default(),
instance_to_symbol: HashMap::default(),
instance_to_stmt: HashMap::default(),
resolver: None,
codegen_callback: Some(Arc::new(GenCall::new(Box::new(
|ctx, _, _, args, generator| {
let mut start = None;
let mut stop = None;
let mut step = None;
let int32 = ctx.ctx.i32_type();
let ty_i32 = ctx.primitives.int32;
for (i, arg) in args.iter().enumerate() {
if arg.0 == Some("start".into()) {
start = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
} else if arg.0 == Some("stop".into()) {
stop = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
} else if arg.0 == Some("step".into()) {
step = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
} else if i == 0 {
start = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
} else if i == 1 {
stop = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
} else if i == 2 {
step = Some(
arg.1
.clone()
.to_basic_value_enum(ctx, generator, ty_i32)?
.into_int_value(),
);
}
}
let step = match step {
Some(step) => {
// assert step != 0, throw exception if not
let not_zero = ctx
.builder
.build_int_compare(
IntPredicate::NE,
step,
step.get_type().const_zero(),
"range_step_ne",
)
.unwrap();
ctx.make_assert(
generator,
not_zero,
"0:ValueError",
"range() step must not be zero",
[None, None, None],
ctx.current_loc,
);
step
}
None => int32.const_int(1, false),
};
let stop = stop.unwrap_or_else(|| {
let v = start.unwrap();
start = None;
v
});
let start = start.unwrap_or_else(|| int32.const_zero());
let ptr = RangeType::new(ctx.ctx).new_value(generator, ctx, Some("range"));
ptr.store_start(ctx, start);
ptr.store_end(ctx, stop);
ptr.store_step(ctx, step);
Ok(Some(ptr.as_base_value().into()))
},
)))),
loc: None,
}
}
/// Build the `str()` function. /// Build the `str()` function.
fn build_str_function(&mut self) -> TopLevelDef { fn build_str_function(&mut self) -> TopLevelDef {
let prim = PrimDef::FunStr; let prim = PrimDef::FunStr;

View File

@ -49,7 +49,7 @@ pub enum PrimDef {
FunRound, FunRound,
FunRound64, FunRound64,
FunNpRound, FunNpRound,
FunRange, FunRangeInit,
FunStr, FunStr,
FunBool, FunBool,
FunFloor, FunFloor,
@ -203,7 +203,7 @@ impl PrimDef {
PrimDef::FunRound => fun("round", None), PrimDef::FunRound => fun("round", None),
PrimDef::FunRound64 => fun("round64", None), PrimDef::FunRound64 => fun("round64", None),
PrimDef::FunNpRound => fun("np_round", None), PrimDef::FunNpRound => fun("np_round", None),
PrimDef::FunRange => fun("range", None), PrimDef::FunRangeInit => fun("range.__init__", Some("__init__")),
PrimDef::FunStr => fun("str", None), PrimDef::FunStr => fun("str", None),
PrimDef::FunBool => fun("bool", None), PrimDef::FunBool => fun("bool", None),
PrimDef::FunFloor => fun("floor", None), PrimDef::FunFloor => fun("floor", None),

View File

@ -44,6 +44,18 @@ void output_float64(double x) {
} }
} }
void output_range(int32_t range[3]) {
printf("range(");
if (range[0] != 0) {
printf("%d, ", range[0]);
}
printf("%d", range[1]);
if (range[2] != 1) {
printf(", %d", range[2]);
}
puts(")");
}
void output_asciiart(int32_t x) { void output_asciiart(int32_t x) {
static const char *chars = " .,-:;i+hHM$*#@ "; static const char *chars = " .,-:;i+hHM$*#@ ";
if (x < 0) { if (x < 0) {
@ -79,6 +91,10 @@ void output_str(struct cslice *slice) {
for (usize i = 0; i < slice->len; ++i) { for (usize i = 0; i < slice->len; ++i) {
putchar(data[i]); putchar(data[i]);
} }
}
void output_strln(struct cslice *slice) {
output_str(slice);
putchar('\n'); putchar('\n');
} }

View File

@ -107,6 +107,9 @@ def patch(module):
def output_float(x): def output_float(x):
print("%f" % x) print("%f" % x)
def output_strln(x):
print(x, end='')
def dbg_stack_address(_): def dbg_stack_address(_):
return 0 return 0
@ -120,6 +123,8 @@ def patch(module):
return output_asciiart return output_asciiart
elif name == "output_float64": elif name == "output_float64":
return output_float return output_float
elif name == "output_str":
return output_strln
elif name in { elif name in {
"output_bool", "output_bool",
"output_int32", "output_int32",
@ -127,7 +132,7 @@ def patch(module):
"output_int32_list", "output_int32_list",
"output_uint32", "output_uint32",
"output_uint64", "output_uint64",
"output_str", "output_strln",
}: }:
return print return print
elif name == "dbg_stack_address": elif name == "dbg_stack_address":

View File

@ -7,7 +7,7 @@ def output_int64(x: int64):
... ...
@extern @extern
def output_str(x: str): def output_strln(x: str):
... ...
@ -33,7 +33,7 @@ class A:
class Initless: class Initless:
def foo(self): def foo(self):
output_str("hello") output_strln("hello")
def run() -> int32: def run() -> int32:
a = A(10) a = A(10)

View File

@ -22,6 +22,10 @@ def output_uint64(x: uint64):
def output_float64(x: float): def output_float64(x: float):
... ...
@extern
def output_range(x: range):
...
@extern @extern
def output_int32_list(x: list[int32]): def output_int32_list(x: list[int32]):
... ...
@ -34,6 +38,10 @@ def output_asciiart(x: int32):
def output_str(x: str): def output_str(x: str):
... ...
@extern
def output_strln(x: str):
...
def test_output_bool(): def test_output_bool():
output_bool(True) output_bool(True)
output_bool(False) output_bool(False)
@ -59,6 +67,15 @@ def test_output_float64():
output_float64(16.25) output_float64(16.25)
output_float64(-16.25) output_float64(-16.25)
def test_output_range():
r = range(1, 100, 5)
output_int32(r.start)
output_int32(r.stop)
output_int32(r.step)
output_range(range(10))
output_range(range(1, 10))
output_range(range(1, 10, 2))
def test_output_asciiart(): def test_output_asciiart():
for i in range(17): for i in range(17):
output_asciiart(i) output_asciiart(i)
@ -68,7 +85,8 @@ def test_output_int32_list():
output_int32_list([0, 1, 3, 5, 10]) output_int32_list([0, 1, 3, 5, 10])
def test_output_str_family(): def test_output_str_family():
output_str("hello world") output_str("hello")
output_strln(" world")
def run() -> int32: def run() -> int32:
test_output_bool() test_output_bool()
@ -77,6 +95,7 @@ def run() -> int32:
test_output_uint32() test_output_uint32()
test_output_uint64() test_output_uint64()
test_output_float64() test_output_float64()
test_output_range()
test_output_asciiart() test_output_asciiart()
test_output_int32_list() test_output_int32_list()
test_output_str_family() test_output_str_family()

View File

@ -23,11 +23,12 @@ def run() -> int32:
output_int32(x) output_int32(x)
output_str(" * ") output_str(" * ")
output_float64(n / x) output_float64(n / x)
output_str("\n")
except: # Assume this is intended to catch x == 0 except: # Assume this is intended to catch x == 0
break break
else: else:
# loop fell through without finding a factor # loop fell through without finding a factor
output_int32(n) output_int32(n)
output_str(" is a prime number") output_str(" is a prime number\n")
return 0 return 0