Merge remote-tracking branch 'mozilla/master'
This commit is contained in:
commit
a831e7ce13
32 changed files with 254 additions and 100 deletions
|
@ -5,6 +5,7 @@ Graydon Hoare <graydon@mozilla.com>
|
|||
Other authors:
|
||||
|
||||
Adam Bozanich <adam.boz@gmail.com>
|
||||
Aleksander Balicki <balicki.aleksander@gmail.com>
|
||||
Andreas Gal <gal@mozilla.com>
|
||||
Austin Seipp <mad.one@gmail.com>
|
||||
Ben Striegel <ben.striegel@gmail.com>
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
# Installation macro. Call with source directory as arg 1,
|
||||
# destination directory as arg 2, and filename/libname-glob as arg 3
|
||||
ifdef VERBOSE
|
||||
INSTALL = cp $(1)/$(3) $(2)/$(3)
|
||||
INSTALL_LIB = cp `ls -rt1 $(1)/$(3) | tail -1` $(2)/
|
||||
INSTALL = install -m755 -T $(1)/$(3) $(2)/$(3)
|
||||
INSTALL_LIB = install -m644 `ls -rt1 $(1)/$(3) | tail -1` $(2)/
|
||||
else
|
||||
INSTALL = $(Q)$(call E, install: $(2)/$(3)) && cp $(1)/$(3) $(2)/$(3)
|
||||
INSTALL = $(Q)$(call E, install: $(2)/$(3)) && install -m755 -T $(1)/$(3) $(2)/$(3)
|
||||
INSTALL_LIB = $(Q)$(call E, install_lib: $(2)/$(3)) && \
|
||||
cp `ls -rt1 $(1)/$(3) | tail -1` $(2)/
|
||||
install -m644 `ls -rt1 $(1)/$(3) | tail -1` $(2)/
|
||||
endif
|
||||
|
||||
# The stage we install from
|
||||
|
|
|
@ -83,7 +83,7 @@ fn item_family(item: ebml::doc) -> u8 {
|
|||
|
||||
fn item_symbol(item: ebml::doc) -> str {
|
||||
let sym = ebml::get_doc(item, tag_items_data_item_symbol);
|
||||
ret str::unsafe_from_bytes(ebml::doc_data(sym));
|
||||
ret str::from_bytes(ebml::doc_data(sym));
|
||||
}
|
||||
|
||||
fn variant_enum_id(d: ebml::doc) -> ast::def_id {
|
||||
|
@ -162,7 +162,7 @@ fn enum_variant_ids(item: ebml::doc, cdata: cmd) -> [ast::def_id] {
|
|||
// definition the path refers to.
|
||||
fn resolve_path(path: [ast::ident], data: @[u8]) -> [ast::def_id] {
|
||||
fn eq_item(data: [u8], s: str) -> bool {
|
||||
ret str::eq(str::unsafe_from_bytes(data), s);
|
||||
ret str::eq(str::from_bytes(data), s);
|
||||
}
|
||||
let s = str::connect(path, "::");
|
||||
let md = ebml::new_doc(data);
|
||||
|
@ -178,7 +178,7 @@ fn resolve_path(path: [ast::ident], data: @[u8]) -> [ast::def_id] {
|
|||
|
||||
fn item_name(item: ebml::doc) -> ast::ident {
|
||||
let name = ebml::get_doc(item, tag_paths_data_name);
|
||||
str::unsafe_from_bytes(ebml::doc_data(name))
|
||||
str::from_bytes(ebml::doc_data(name))
|
||||
}
|
||||
|
||||
fn lookup_item_name(data: @[u8], id: ast::node_id) -> ast::ident {
|
||||
|
@ -325,7 +325,7 @@ fn read_path(d: ebml::doc) -> {path: str, pos: uint} {
|
|||
let desc = ebml::doc_data(d);
|
||||
let pos = ebml::be_uint_from_bytes(@desc, 0u, 4u);
|
||||
let pathbytes = vec::slice::<u8>(desc, 4u, vec::len::<u8>(desc));
|
||||
let path = str::unsafe_from_bytes(pathbytes);
|
||||
let path = str::from_bytes(pathbytes);
|
||||
ret {path: path, pos: pos};
|
||||
}
|
||||
|
||||
|
@ -358,21 +358,21 @@ fn get_meta_items(md: ebml::doc) -> [@ast::meta_item] {
|
|||
let items: [@ast::meta_item] = [];
|
||||
ebml::tagged_docs(md, tag_meta_item_word) {|meta_item_doc|
|
||||
let nd = ebml::get_doc(meta_item_doc, tag_meta_item_name);
|
||||
let n = str::unsafe_from_bytes(ebml::doc_data(nd));
|
||||
let n = str::from_bytes(ebml::doc_data(nd));
|
||||
items += [attr::mk_word_item(n)];
|
||||
};
|
||||
ebml::tagged_docs(md, tag_meta_item_name_value) {|meta_item_doc|
|
||||
let nd = ebml::get_doc(meta_item_doc, tag_meta_item_name);
|
||||
let vd = ebml::get_doc(meta_item_doc, tag_meta_item_value);
|
||||
let n = str::unsafe_from_bytes(ebml::doc_data(nd));
|
||||
let v = str::unsafe_from_bytes(ebml::doc_data(vd));
|
||||
let n = str::from_bytes(ebml::doc_data(nd));
|
||||
let v = str::from_bytes(ebml::doc_data(vd));
|
||||
// FIXME (#611): Should be able to decode meta_name_value variants,
|
||||
// but currently they can't be encoded
|
||||
items += [attr::mk_name_value_item_str(n, v)];
|
||||
};
|
||||
ebml::tagged_docs(md, tag_meta_item_list) {|meta_item_doc|
|
||||
let nd = ebml::get_doc(meta_item_doc, tag_meta_item_name);
|
||||
let n = str::unsafe_from_bytes(ebml::doc_data(nd));
|
||||
let n = str::from_bytes(ebml::doc_data(nd));
|
||||
let subitems = get_meta_items(meta_item_doc);
|
||||
items += [attr::mk_list_item(n, subitems)];
|
||||
};
|
||||
|
@ -427,7 +427,7 @@ fn get_crate_deps(data: @[u8]) -> [crate_dep] {
|
|||
let depsdoc = ebml::get_doc(cratedoc, tag_crate_deps);
|
||||
let crate_num = 1;
|
||||
ebml::tagged_docs(depsdoc, tag_crate_dep) {|depdoc|
|
||||
let depname = str::unsafe_from_bytes(ebml::doc_data(depdoc));
|
||||
let depname = str::from_bytes(ebml::doc_data(depdoc));
|
||||
deps += [{cnum: crate_num, ident: depname}];
|
||||
crate_num += 1;
|
||||
};
|
||||
|
@ -447,7 +447,7 @@ fn list_crate_deps(data: @[u8], out: io::writer) {
|
|||
fn get_crate_hash(data: @[u8]) -> str {
|
||||
let cratedoc = ebml::new_doc(data);
|
||||
let hashdoc = ebml::get_doc(cratedoc, tag_crate_hash);
|
||||
ret str::unsafe_from_bytes(ebml::doc_data(hashdoc));
|
||||
ret str::from_bytes(ebml::doc_data(hashdoc));
|
||||
}
|
||||
|
||||
fn list_crate_items(bytes: @[u8], md: ebml::doc, out: io::writer) {
|
||||
|
|
|
@ -661,7 +661,7 @@ fn encode_hash(ebml_w: ebml::writer, hash: str) {
|
|||
ebml::end_tag(ebml_w);
|
||||
}
|
||||
|
||||
fn encode_metadata(cx: @crate_ctxt, crate: @crate) -> str {
|
||||
fn encode_metadata(cx: @crate_ctxt, crate: @crate) -> [u8] {
|
||||
|
||||
let abbrevs = ty::new_ty_hash();
|
||||
let ecx = @{ccx: cx, type_abbrevs: abbrevs};
|
||||
|
@ -694,7 +694,7 @@ fn encode_metadata(cx: @crate_ctxt, crate: @crate) -> str {
|
|||
// Pad this, since something (LLVM, presumably) is cutting off the
|
||||
// remaining % 4 bytes.
|
||||
buf_w.write([0u8, 0u8, 0u8, 0u8]);
|
||||
io::mem_buffer_str(buf)
|
||||
io::mem_buffer_buf(buf)
|
||||
}
|
||||
|
||||
// Get the encoded string for a type
|
||||
|
|
|
@ -39,7 +39,7 @@ fn parse_ident_(st: @pstate, is_last: fn@(char) -> bool) ->
|
|||
ast::ident {
|
||||
let rslt = "";
|
||||
while !is_last(peek(st) as char) {
|
||||
rslt += str::unsafe_from_byte(next(st));
|
||||
rslt += str::from_byte(next(st));
|
||||
}
|
||||
ret rslt;
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ fn parse_ty(st: @pstate, conv: conv_did) -> ty::t {
|
|||
while peek(st) as char != ']' {
|
||||
let name = "";
|
||||
while peek(st) as char != '=' {
|
||||
name += str::unsafe_from_byte(next(st));
|
||||
name += str::from_byte(next(st));
|
||||
}
|
||||
st.pos = st.pos + 1u;
|
||||
fields += [{ident: name, mt: parse_mt(st, conv)}];
|
||||
|
|
|
@ -5,7 +5,7 @@ import syntax::visit;
|
|||
import syntax::ast_util;
|
||||
import driver::session::session;
|
||||
|
||||
enum deref_t { unbox, field, index, }
|
||||
enum deref_t { unbox(bool), field, index, }
|
||||
|
||||
type deref = @{mut: bool, kind: deref_t, outer_t: ty::t};
|
||||
|
||||
|
@ -20,15 +20,15 @@ fn expr_root(tcx: ty::ctxt, ex: @expr, autoderef: bool) ->
|
|||
while true {
|
||||
alt ty::struct(tcx, t) {
|
||||
ty::ty_box(mt) {
|
||||
ds += [@{mut: mt.mut == mut, kind: unbox, outer_t: t}];
|
||||
ds += [@{mut: mt.mut == mut, kind: unbox(false), outer_t: t}];
|
||||
t = mt.ty;
|
||||
}
|
||||
ty::ty_uniq(mt) {
|
||||
ds += [@{mut: mt.mut == mut, kind: unbox, outer_t: t}];
|
||||
ds += [@{mut: mt.mut == mut, kind: unbox(false), outer_t: t}];
|
||||
t = mt.ty;
|
||||
}
|
||||
ty::ty_res(_, inner, tps) {
|
||||
ds += [@{mut: false, kind: unbox, outer_t: t}];
|
||||
ds += [@{mut: false, kind: unbox(false), outer_t: t}];
|
||||
t = ty::substitute_type_params(tcx, tps, inner);
|
||||
}
|
||||
ty::ty_enum(did, tps) {
|
||||
|
@ -37,7 +37,7 @@ fn expr_root(tcx: ty::ctxt, ex: @expr, autoderef: bool) ->
|
|||
vec::len(variants[0].args) != 1u {
|
||||
break;
|
||||
}
|
||||
ds += [@{mut: false, kind: unbox, outer_t: t}];
|
||||
ds += [@{mut: false, kind: unbox(false), outer_t: t}];
|
||||
t = ty::substitute_type_params(tcx, tps, variants[0].args[0]);
|
||||
}
|
||||
_ { break; }
|
||||
|
@ -85,15 +85,16 @@ fn expr_root(tcx: ty::ctxt, ex: @expr, autoderef: bool) ->
|
|||
expr_unary(op, base) {
|
||||
if op == deref {
|
||||
let base_t = ty::expr_ty(tcx, base);
|
||||
let is_mut = false;
|
||||
let is_mut = false, ptr = false;
|
||||
alt ty::struct(tcx, base_t) {
|
||||
ty::ty_box(mt) { is_mut = mt.mut == mut; }
|
||||
ty::ty_uniq(mt) { is_mut = mt.mut == mut; }
|
||||
ty::ty_res(_, _, _) { }
|
||||
ty::ty_enum(_, _) { }
|
||||
ty::ty_ptr(mt) { is_mut = mt.mut == mut; }
|
||||
ty::ty_ptr(mt) { is_mut = mt.mut == mut; ptr = true; }
|
||||
}
|
||||
ds += [@{mut: is_mut, kind: unbox, outer_t: base_t}];
|
||||
ds += [@{mut: is_mut, kind: unbox(ptr && is_mut),
|
||||
outer_t: base_t}];
|
||||
ex = base;
|
||||
} else { break; }
|
||||
}
|
||||
|
@ -187,7 +188,7 @@ fn check_lval(cx: @ctx, dest: @expr, msg: msg) {
|
|||
} else if !root.ds[0].mut {
|
||||
let name =
|
||||
alt root.ds[0].kind {
|
||||
mut::unbox { "immutable box" }
|
||||
mut::unbox(_) { "immutable box" }
|
||||
mut::field { "immutable field" }
|
||||
mut::index { "immutable vec content" }
|
||||
};
|
||||
|
@ -212,7 +213,8 @@ fn check_move_rhs(cx: @ctx, src: @expr) {
|
|||
let root = expr_root(cx.tcx, src, false);
|
||||
|
||||
// Not a path and no-derefs means this is a temporary.
|
||||
if vec::len(*root.ds) != 0u {
|
||||
if vec::len(*root.ds) != 0u &&
|
||||
root.ds[vec::len(*root.ds) - 1u].kind != unbox(true) {
|
||||
cx.tcx.sess.span_err(src.span, "moving out of a data structure");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -268,7 +268,7 @@ fn sanitize(s: str) -> str {
|
|||
c != ' ' as u8 && c != '\t' as u8 && c != ';' as u8
|
||||
{
|
||||
let v = [c];
|
||||
result += str::unsafe_from_bytes(v);
|
||||
result += str::from_bytes(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5412,7 +5412,7 @@ fn fill_crate_map(ccx: @crate_ctxt, map: ValueRef) {
|
|||
|
||||
fn write_metadata(cx: @crate_ctxt, crate: @ast::crate) {
|
||||
if !cx.sess.building_library { ret; }
|
||||
let llmeta = C_postr(metadata::encoder::encode_metadata(cx, crate));
|
||||
let llmeta = C_bytes(metadata::encoder::encode_metadata(cx, crate));
|
||||
let llconst = C_struct([llmeta]);
|
||||
let llglobal = str::as_buf("rust_metadata", {|buf|
|
||||
llvm::LLVMAddGlobal(cx.llmod, val_ty(llconst), buf)
|
||||
|
|
|
@ -172,6 +172,7 @@ export type_is_str;
|
|||
export type_is_unique;
|
||||
export type_is_enum;
|
||||
export type_is_c_like_enum;
|
||||
export type_structurally_contains;
|
||||
export type_structurally_contains_uniques;
|
||||
export type_autoderef;
|
||||
export type_param;
|
||||
|
@ -2420,7 +2421,8 @@ mod unify {
|
|||
fn fixup_vars(tcx: ty_ctxt, sp: option::t<span>, vb: @var_bindings,
|
||||
typ: t) -> fixup_result {
|
||||
fn subst_vars(tcx: ty_ctxt, sp: option::t<span>, vb: @var_bindings,
|
||||
unresolved: @mutable option::t<int>, vid: int) -> t {
|
||||
unresolved: @mutable option::t<int>,
|
||||
vars_seen: std::list::list<int>, vid: int) -> t {
|
||||
// Should really return a fixup_result instead of a t, but fold_ty
|
||||
// doesn't allow returning anything but a t.
|
||||
if vid as uint >= ufind::set_count(vb.sets) {
|
||||
|
@ -2431,21 +2433,28 @@ mod unify {
|
|||
alt smallintmap::find::<t>(vb.types, root_id) {
|
||||
none { *unresolved = some(vid); ret ty::mk_var(tcx, vid); }
|
||||
some(rt) {
|
||||
if occurs_check_fails(tcx, sp, vid, rt) {
|
||||
// Return the type unchanged, so we can error out
|
||||
// downstream
|
||||
ret rt;
|
||||
let give_up = false;
|
||||
std::list::iter(vars_seen) {|v|
|
||||
if v == vid {
|
||||
give_up = true;
|
||||
option::may(sp) {|sp|
|
||||
tcx.sess.span_fatal(
|
||||
sp, "can not instantiate infinite type");
|
||||
}
|
||||
}
|
||||
}
|
||||
ret fold_ty(tcx,
|
||||
fm_var(bind subst_vars(tcx, sp, vb, unresolved,
|
||||
_)), rt);
|
||||
// Return the type unchanged, so we can error out
|
||||
// downstream
|
||||
if give_up { ret rt; }
|
||||
ret fold_ty(tcx, fm_var(bind subst_vars(
|
||||
tcx, sp, vb, unresolved, std::list::cons(vid, @vars_seen),
|
||||
_)), rt);
|
||||
}
|
||||
}
|
||||
}
|
||||
let unresolved = @mutable none::<int>;
|
||||
let rty =
|
||||
fold_ty(tcx, fm_var(bind subst_vars(tcx, sp, vb, unresolved, _)),
|
||||
typ);
|
||||
let rty = fold_ty(tcx, fm_var(bind subst_vars(
|
||||
tcx, sp, vb, unresolved, std::list::nil, _)), typ);
|
||||
let ur = *unresolved;
|
||||
alt ur {
|
||||
none { ret fix_ok(rty); }
|
||||
|
|
|
@ -259,10 +259,9 @@ fn ast_ty_to_ty(tcx: ty::ctxt, mode: mode, &&ast_ty: @ast::ty) -> ty::t {
|
|||
alt tcx.ast_ty_to_ty_cache.find(ast_ty) {
|
||||
some(some(ty)) { ret ty; }
|
||||
some(none) {
|
||||
tcx.sess.span_fatal(ast_ty.span,
|
||||
"illegal recursive type \
|
||||
insert a enum in the cycle, \
|
||||
if this is desired)");
|
||||
tcx.sess.span_fatal(ast_ty.span, "illegal recursive type. \
|
||||
insert a enum in the cycle, \
|
||||
if this is desired)");
|
||||
}
|
||||
none { }
|
||||
} /* go on */
|
||||
|
@ -2298,7 +2297,9 @@ fn check_expr_with_unifier(fcx: @fn_ctxt, expr: @ast::expr, unify: unifier,
|
|||
let msg = #fmt["attempted access of field %s on type %s, but \
|
||||
no method implementation was found",
|
||||
field, ty_to_str(tcx, t_err)];
|
||||
tcx.sess.span_fatal(expr.span, msg);
|
||||
tcx.sess.span_err(expr.span, msg);
|
||||
// NB: Adding a bogus type to allow typechecking to continue
|
||||
write::ty_only_fixup(fcx, id, ty::mk_nil(tcx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2490,7 +2491,7 @@ fn check_const(ccx: @crate_ctxt, _sp: span, e: @ast::expr, id: ast::node_id) {
|
|||
demand::simple(fcx, e.span, declty, cty);
|
||||
}
|
||||
|
||||
fn check_enum_variants(ccx: @crate_ctxt, _sp: span, vs: [ast::variant],
|
||||
fn check_enum_variants(ccx: @crate_ctxt, sp: span, vs: [ast::variant],
|
||||
id: ast::node_id) {
|
||||
// FIXME: this is kinda a kludge; we manufacture a fake function context
|
||||
// and statement context for checking the initializer expression.
|
||||
|
@ -2512,7 +2513,7 @@ fn check_enum_variants(ccx: @crate_ctxt, _sp: span, vs: [ast::variant],
|
|||
some(e) {
|
||||
check_expr(fcx, e);
|
||||
let cty = expr_ty(fcx.ccx.tcx, e);
|
||||
let declty =ty::mk_int(fcx.ccx.tcx);
|
||||
let declty = ty::mk_int(fcx.ccx.tcx);
|
||||
demand::simple(fcx, e.span, declty, cty);
|
||||
// FIXME: issue #1417
|
||||
// Also, check_expr (from check_const pass) doesn't guarantee that
|
||||
|
@ -2537,6 +2538,20 @@ fn check_enum_variants(ccx: @crate_ctxt, _sp: span, vs: [ast::variant],
|
|||
disr_vals += [disr_val];
|
||||
disr_val += 1;
|
||||
}
|
||||
let outer = true, did = local_def(id);
|
||||
if ty::type_structurally_contains(ccx.tcx, rty, {|sty|
|
||||
alt sty {
|
||||
ty::ty_enum(id, _) if id == did {
|
||||
if outer { outer = false; false }
|
||||
else { true }
|
||||
}
|
||||
_ { false }
|
||||
}
|
||||
}) {
|
||||
ccx.tcx.sess.span_fatal(sp, "illegal recursive enum type. \
|
||||
wrap the inner value in a box to \
|
||||
make it represenable");
|
||||
}
|
||||
}
|
||||
|
||||
// A generic function for checking the pred in a check
|
||||
|
|
|
@ -672,7 +672,7 @@ fn gather_comments_and_literals(cm: codemap::codemap,
|
|||
path: str,
|
||||
srdr: io::reader) ->
|
||||
{cmnts: [cmnt], lits: [lit]} {
|
||||
let src = @str::unsafe_from_bytes(srdr.read_whole_stream());
|
||||
let src = @str::from_bytes(srdr.read_whole_stream());
|
||||
let itr = @interner::mk::<str>(str::hash, str::eq);
|
||||
let rdr = new_reader(cm, span_diagnostic,
|
||||
codemap::new_filemap(path, src, 0u, 0u), itr);
|
||||
|
|
|
@ -118,7 +118,7 @@ fn ty_to_str(cx: ctxt, typ: t) -> str {
|
|||
}
|
||||
ty_var(v) { "<T" + int::str(v) + ">" }
|
||||
ty_param(id, _) {
|
||||
"'" + str::unsafe_from_bytes([('a' as u8) + (id as u8)])
|
||||
"'" + str::from_bytes([('a' as u8) + (id as u8)])
|
||||
}
|
||||
_ { ty_to_short_str(cx, typ) }
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ fn readclose(fd: fd_t) -> str {
|
|||
let buf = "";
|
||||
while !reader.eof() {
|
||||
let bytes = reader.read_bytes(4096u);
|
||||
buf += str::unsafe_from_bytes(bytes);
|
||||
buf += str::from_bytes(bytes);
|
||||
}
|
||||
os::fclose(file);
|
||||
ret buf;
|
||||
|
@ -114,8 +114,8 @@ fn worker(p: port<request>) {
|
|||
// the alt discriminant are wrong.
|
||||
alt recv(p) {
|
||||
exec(lib_path, prog, args, respchan) {
|
||||
{lib_path: str::unsafe_from_bytes(lib_path),
|
||||
prog: str::unsafe_from_bytes(prog),
|
||||
{lib_path: str::from_bytes(lib_path),
|
||||
prog: str::from_bytes(prog),
|
||||
args: clone_vecu8str(args),
|
||||
respchan: respchan}
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ fn clone_vecstr(v: [str]) -> [[u8]] {
|
|||
fn clone_vecu8str(v: [[u8]]) -> [str] {
|
||||
let r = [];
|
||||
for t in vec::slice(v, 0u, vec::len(v)) {
|
||||
r += [str::unsafe_from_bytes(t)];
|
||||
r += [str::from_bytes(t)];
|
||||
}
|
||||
ret r;
|
||||
}
|
||||
|
|
|
@ -390,7 +390,7 @@ mod rt {
|
|||
fn str_init_elt(n_elts: uint, c: char) -> str {
|
||||
let svec = vec::init_elt::<u8>(n_elts, c as u8);
|
||||
|
||||
ret str::unsafe_from_bytes(svec);
|
||||
ret str::from_bytes(svec);
|
||||
}
|
||||
enum pad_mode { pad_signed, pad_unsigned, pad_nozero, }
|
||||
fn pad(cv: conv, s: str, mode: pad_mode) -> str {
|
||||
|
@ -439,7 +439,8 @@ mod rt {
|
|||
if signed && zero_padding && str::byte_len(s) > 0u {
|
||||
let head = s[0];
|
||||
if head == '+' as u8 || head == '-' as u8 || head == ' ' as u8 {
|
||||
let headstr = str::unsafe_from_bytes([head]);
|
||||
let headstr = str::from_bytes([head]);
|
||||
// FIXME: not UTF-8 safe
|
||||
let bytelen = str::byte_len(s);
|
||||
let numpart = str::substr(s, 1u, bytelen - 1u);
|
||||
ret headstr + padstr + numpart;
|
||||
|
|
|
@ -13,6 +13,7 @@ export
|
|||
// Creating a string
|
||||
from_bytes,
|
||||
unsafe_from_bytes,
|
||||
from_byte,
|
||||
unsafe_from_byte,
|
||||
//push_utf8_bytes,
|
||||
from_char,
|
||||
|
@ -117,14 +118,11 @@ Section: Creating a string
|
|||
/*
|
||||
Function: from_bytes
|
||||
|
||||
Safely convert a vector of bytes to a UTF-8 string, or error
|
||||
Convert a vector of bytes to a UTF-8 string. Fails if invalid UTF-8.
|
||||
*/
|
||||
fn from_bytes(vv: [u8]) -> result::t<str, str> {
|
||||
if is_utf8(vv) {
|
||||
ret result::ok(unsafe_from_bytes(vv));
|
||||
} else {
|
||||
ret result::err("vector doesn't contain valid UTF-8");
|
||||
}
|
||||
fn from_bytes(vv: [u8]) -> str {
|
||||
assert is_utf8(vv);
|
||||
ret unsafe_from_bytes(vv);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -133,7 +131,7 @@ Function: unsafe_from_bytes
|
|||
Converts a vector of bytes to a string. Does not verify that the
|
||||
vector contains valid UTF-8.
|
||||
|
||||
// FIXME: remove?
|
||||
FIXME: stop exporting
|
||||
*/
|
||||
fn unsafe_from_bytes(v: [const u8]) -> str unsafe {
|
||||
let vcopy: [u8] = v + [0u8];
|
||||
|
@ -148,10 +146,20 @@ Function: unsafe_from_byte
|
|||
Converts a byte to a string. Does not verify that the byte is
|
||||
valid UTF-8.
|
||||
|
||||
FIXME: rename to 'from_byte'
|
||||
FIXME: stop exporting
|
||||
*/
|
||||
fn unsafe_from_byte(u: u8) -> str { unsafe_from_bytes([u]) }
|
||||
|
||||
|
||||
/*
|
||||
Function: from_byte
|
||||
|
||||
Convert a byte to a UTF-8 string. Fails if invalid UTF-8.
|
||||
*/
|
||||
fn from_byte(uu: u8) -> str {
|
||||
from_bytes([uu])
|
||||
}
|
||||
|
||||
fn push_utf8_bytes(&s: str, ch: char) {
|
||||
let code = ch as uint;
|
||||
let bytes =
|
||||
|
@ -209,16 +217,16 @@ Function: from_cstr
|
|||
Create a Rust string from a null-terminated C string
|
||||
*/
|
||||
unsafe fn from_cstr(cstr: sbuf) -> str {
|
||||
let res = "";
|
||||
let res = [];
|
||||
let start = cstr;
|
||||
let curr = start;
|
||||
let i = 0u;
|
||||
while *curr != 0u8 {
|
||||
push_byte(res, *curr);
|
||||
vec::push(res, *curr);
|
||||
i += 1u;
|
||||
curr = ptr::offset(start, i);
|
||||
}
|
||||
ret res;
|
||||
ret from_bytes(res);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -526,7 +534,7 @@ fn split(s: str, sep: u8) -> [str] {
|
|||
v += [accum];
|
||||
accum = "";
|
||||
ends_with_sep = true;
|
||||
} else { accum += unsafe_from_byte(c); ends_with_sep = false; }
|
||||
} else { accum += from_byte(c); ends_with_sep = false; }
|
||||
}
|
||||
if byte_len(accum) != 0u || ends_with_sep { v += [accum]; }
|
||||
ret v;
|
||||
|
@ -554,7 +562,7 @@ fn splitn(s: str, sep: u8, count: uint) -> [str] {
|
|||
v += [accum];
|
||||
accum = "";
|
||||
ends_with_sep = true;
|
||||
} else { accum += unsafe_from_byte(c); ends_with_sep = false; }
|
||||
} else { accum += from_byte(c); ends_with_sep = false; }
|
||||
}
|
||||
if byte_len(accum) != 0u || ends_with_sep { v += [accum]; }
|
||||
ret v;
|
||||
|
@ -575,12 +583,12 @@ FIXME: should behave like split and split_char:
|
|||
*/
|
||||
fn split_str(s: str, sep: str) -> [str] {
|
||||
assert byte_len(sep) > 0u;
|
||||
let v: [str] = [], accum = "", sep_match = 0u, leading = true;
|
||||
let v: [str] = [], accum = [], sep_match = 0u, leading = true;
|
||||
for c: u8 in s {
|
||||
// Did we match the entire separator?
|
||||
if sep_match == byte_len(sep) {
|
||||
if !leading { v += [accum]; }
|
||||
accum = "";
|
||||
if !leading { vec::push(v, from_bytes(accum)); }
|
||||
accum = [];
|
||||
sep_match = 0u;
|
||||
}
|
||||
|
||||
|
@ -588,13 +596,13 @@ fn split_str(s: str, sep: str) -> [str] {
|
|||
sep_match += 1u;
|
||||
} else {
|
||||
sep_match = 0u;
|
||||
accum += unsafe_from_byte(c);
|
||||
vec::push(accum, c);
|
||||
leading = false;
|
||||
}
|
||||
}
|
||||
|
||||
if byte_len(accum) > 0u { v += [accum]; }
|
||||
if sep_match == byte_len(sep) { v += [""]; }
|
||||
if vec::len(accum) > 0u { vec::push(v, from_bytes(accum)); }
|
||||
if sep_match == byte_len(sep) { vec::push(v, ""); }
|
||||
|
||||
ret v;
|
||||
}
|
||||
|
@ -1783,7 +1791,24 @@ mod tests {
|
|||
0x20_u8, 0x4e_u8, 0x61_u8,
|
||||
0x6d_u8];
|
||||
|
||||
assert ss == result::get(from_bytes(bb));
|
||||
assert ss == from_bytes(bb);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_fail]
|
||||
fn test_from_bytes_fail() {
|
||||
let bb = [0xff_u8, 0xb8_u8, 0xa8_u8,
|
||||
0xe0_u8, 0xb9_u8, 0x84_u8,
|
||||
0xe0_u8, 0xb8_u8, 0x97_u8,
|
||||
0xe0_u8, 0xb8_u8, 0xa2_u8,
|
||||
0xe4_u8, 0xb8_u8, 0xad_u8,
|
||||
0xe5_u8, 0x8d_u8, 0x8e_u8,
|
||||
0x56_u8, 0x69_u8, 0xe1_u8,
|
||||
0xbb_u8, 0x87_u8, 0x74_u8,
|
||||
0x20_u8, 0x4e_u8, 0x61_u8,
|
||||
0x6d_u8];
|
||||
|
||||
let _x = from_bytes(bb);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1821,7 +1846,7 @@ mod tests {
|
|||
let s1: str = "All mimsy were the borogoves";
|
||||
|
||||
let v: [u8] = bytes(s1);
|
||||
let s2: str = unsafe_from_bytes(v);
|
||||
let s2: str = from_bytes(v);
|
||||
let i: uint = 0u;
|
||||
let n1: uint = byte_len(s1);
|
||||
let n2: uint = vec::len::<u8>(v);
|
||||
|
|
|
@ -236,12 +236,12 @@ fn to_str(num: uint, radix: uint) -> str {
|
|||
if n == 0u { ret "0"; }
|
||||
let s: str = "";
|
||||
while n != 0u {
|
||||
s += str::unsafe_from_byte(digit(n % radix) as u8);
|
||||
s += str::from_byte(digit(n % radix) as u8);
|
||||
n /= radix;
|
||||
}
|
||||
let s1: str = "";
|
||||
let len: uint = str::byte_len(s);
|
||||
while len != 0u { len -= 1u; s1 += str::unsafe_from_byte(s[len]); }
|
||||
while len != 0u { len -= 1u; s1 += str::from_byte(s[len]); }
|
||||
ret s1;
|
||||
}
|
||||
|
||||
|
|
|
@ -307,6 +307,14 @@ fn pop<T: copy>(&v: [const T]) -> T {
|
|||
let e = v[ln];
|
||||
v = slice(v, 0u, ln);
|
||||
ret e;
|
||||
// FIXME use this implementation after the next snapshot (27.01.2012)
|
||||
/* let new_ln = len(v) - 1u;
|
||||
assert (new_ln > 0u);
|
||||
let valptr = ptr::mut_addr_of(v[new_ln]);
|
||||
let val <- *valptr;
|
||||
unsafe::set_len(v, new_ln);
|
||||
val
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -129,7 +129,8 @@ fn dylib_filename(base: str) -> str { ret "lib" + base + ".so"; }
|
|||
/// followed by a path separator
|
||||
fn get_exe_path() -> option::t<fs::path> unsafe {
|
||||
let bufsize = 1023u;
|
||||
let path = str::unsafe_from_bytes(vec::init_elt(bufsize, 0u8));
|
||||
// FIXME: path "strings" will likely need fixing...
|
||||
let path = str::from_bytes(vec::init_elt(bufsize, 0u8));
|
||||
let mib = [libc_constants::CTL_KERN,
|
||||
libc_constants::KERN_PROC,
|
||||
libc_constants::KERN_PROC_PATHNAME, -1i32];
|
||||
|
|
|
@ -75,7 +75,7 @@ fn getenv(n: str) -> option::t<str> {
|
|||
unsafe {
|
||||
vec::unsafe::set_len(v, res);
|
||||
}
|
||||
ret option::some(str::unsafe_from_bytes(v));
|
||||
ret option::some(str::from_bytes(v)); // UTF-8 or fail
|
||||
} else { nsize = res; }
|
||||
}
|
||||
fail;
|
||||
|
|
|
@ -109,7 +109,7 @@ impl reader_util for reader {
|
|||
if ch == -1 || ch == 10 { break; }
|
||||
buf += [ch as u8];
|
||||
}
|
||||
str::unsafe_from_bytes(buf)
|
||||
str::from_bytes(buf)
|
||||
}
|
||||
|
||||
fn read_c_str() -> str {
|
||||
|
@ -118,7 +118,7 @@ impl reader_util for reader {
|
|||
let ch = self.read_byte();
|
||||
if ch < 1 { break; } else { buf += [ch as u8]; }
|
||||
}
|
||||
str::unsafe_from_bytes(buf)
|
||||
str::from_bytes(buf)
|
||||
}
|
||||
|
||||
// FIXME deal with eof?
|
||||
|
@ -461,7 +461,10 @@ fn mk_mem_buffer() -> mem_buffer {
|
|||
}
|
||||
fn mem_buffer_writer(b: mem_buffer) -> writer { b as writer }
|
||||
fn mem_buffer_buf(b: mem_buffer) -> [u8] { vec::from_mut(b.buf) }
|
||||
fn mem_buffer_str(b: mem_buffer) -> str { str::unsafe_from_bytes(b.buf) }
|
||||
fn mem_buffer_str(b: mem_buffer) -> str {
|
||||
let b_ = vec::from_mut(b.buf);
|
||||
str::from_bytes(b_)
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
fn seek_in_buf(offset: int, pos: uint, len: uint, whence: seek_style) ->
|
||||
|
@ -479,7 +482,7 @@ fn seek_in_buf(offset: int, pos: uint, len: uint, whence: seek_style) ->
|
|||
|
||||
fn read_whole_file_str(file: str) -> result::t<str, str> {
|
||||
result::chain(read_whole_file(file), { |bytes|
|
||||
result::ok(str::unsafe_from_bytes(bytes))
|
||||
result::ok(str::from_bytes(bytes))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,8 @@ fn dylib_filename(base: str) -> str { ret "lib" + base + ".so"; }
|
|||
/// followed by a path separator
|
||||
fn get_exe_path() -> option::t<fs::path> {
|
||||
let bufsize = 1023u;
|
||||
let path = str::unsafe_from_bytes(vec::init_elt(bufsize, 0u8));
|
||||
// FIXME: path "strings" will likely need fixing...
|
||||
let path = str::from_bytes(vec::init_elt(bufsize, 0u8));
|
||||
ret str::as_buf("/proc/self/exe", { |proc_self_buf|
|
||||
str::as_buf(path, { |path_buf|
|
||||
if libc::readlink(proc_self_buf, path_buf, bufsize) != -1 {
|
||||
|
|
|
@ -128,7 +128,7 @@ Function: tail
|
|||
|
||||
Returns all but the first element of a list
|
||||
*/
|
||||
pure fn tail<T: copy>(ls: list<T>) : is_not_empty(ls) -> list<T> {
|
||||
pure fn tail<T: copy>(ls: list<T>) -> list<T> {
|
||||
alt ls {
|
||||
cons(_, tl) { ret *tl; }
|
||||
nil { fail "list empty" }
|
||||
|
|
|
@ -133,8 +133,9 @@ fn dylib_filename(base: str) -> str { ret "lib" + base + ".dylib"; }
|
|||
|
||||
fn get_exe_path() -> option::t<fs::path> {
|
||||
// FIXME: This doesn't handle the case where the buffer is too small
|
||||
// FIXME: path "strings" will likely need fixing...
|
||||
let bufsize = 1023u32;
|
||||
let path = str::unsafe_from_bytes(vec::init_elt(bufsize as uint, 0u8));
|
||||
let path = str::from_bytes(vec::init_elt(bufsize as uint, 0u8));
|
||||
ret str::as_buf(path, { |path_buf|
|
||||
if mac_libc::_NSGetExecutablePath(path_buf,
|
||||
ptr::mut_addr_of(bufsize)) == 0i32 {
|
||||
|
|
|
@ -216,7 +216,7 @@ fn read_all(rd: io::reader) -> str {
|
|||
let buf = "";
|
||||
while !rd.eof() {
|
||||
let bytes = rd.read_bytes(4096u);
|
||||
buf += str::unsafe_from_bytes(bytes);
|
||||
buf += str::from_bytes(bytes);
|
||||
}
|
||||
ret buf;
|
||||
}
|
||||
|
@ -347,7 +347,7 @@ mod tests {
|
|||
let buf = "";
|
||||
while !reader.eof() {
|
||||
let bytes = reader.read_bytes(4096u);
|
||||
buf += str::unsafe_from_bytes(bytes);
|
||||
buf += str::from_bytes(bytes);
|
||||
}
|
||||
os::fclose(file);
|
||||
ret buf;
|
||||
|
|
|
@ -27,6 +27,11 @@ native mod rustrt {
|
|||
thread: thread,
|
||||
req_id: u32,
|
||||
chan: comm::chan<iomsg>);
|
||||
fn rust_uvtmp_timer(
|
||||
thread: thread,
|
||||
timeout: u32,
|
||||
req_id: u32,
|
||||
chan: comm::chan<iomsg>);
|
||||
fn rust_uvtmp_delete_buf(buf: *u8);
|
||||
fn rust_uvtmp_get_req_id(cd: connect_data) -> u32;
|
||||
}
|
||||
|
@ -39,7 +44,9 @@ enum iomsg {
|
|||
whatever,
|
||||
connected(connect_data),
|
||||
wrote(connect_data),
|
||||
read(connect_data, *u8, ctypes::ssize_t)
|
||||
read(connect_data, *u8, ctypes::ssize_t),
|
||||
timer(u32),
|
||||
exit
|
||||
}
|
||||
|
||||
fn create_thread() -> thread {
|
||||
|
@ -80,6 +87,11 @@ fn read_start(thread: thread, req_id: u32,
|
|||
rustrt::rust_uvtmp_read_start(thread, req_id, chan);
|
||||
}
|
||||
|
||||
fn timer_start(thread: thread, timeout: u32, req_id: u32,
|
||||
chan: comm::chan<iomsg>) {
|
||||
rustrt::rust_uvtmp_timer(thread, timeout, req_id, chan);
|
||||
}
|
||||
|
||||
fn delete_buf(buf: *u8) {
|
||||
rustrt::rust_uvtmp_delete_buf(buf);
|
||||
}
|
||||
|
@ -138,7 +150,7 @@ fn test_http() {
|
|||
unsafe {
|
||||
log(error, len);
|
||||
let buf = vec::unsafe::from_buf(buf, len as uint);
|
||||
let str = str::unsafe_from_bytes(buf);
|
||||
let str = str::from_bytes(buf);
|
||||
#error("read something");
|
||||
io::println(str);
|
||||
}
|
||||
|
@ -153,4 +165,4 @@ fn test_http() {
|
|||
}
|
||||
join_thread(thread);
|
||||
delete_thread(thread);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,8 +113,9 @@ fn getcwd() -> str { ret rustrt::rust_getcwd(); }
|
|||
|
||||
fn get_exe_path() -> option::t<fs::path> {
|
||||
// FIXME: This doesn't handle the case where the buffer is too small
|
||||
// FIXME: path "strings" will likely need fixing...
|
||||
let bufsize = 1023u;
|
||||
let path = str::unsafe_from_bytes(vec::init_elt(bufsize, 0u8));
|
||||
let path = str::from_bytes(vec::init_elt(bufsize, 0u8));
|
||||
ret str::as_buf(path, { |path_buf|
|
||||
if kernel32::GetModuleFileNameA(0u, path_buf,
|
||||
bufsize as u32) != 0u32 {
|
||||
|
|
|
@ -15,9 +15,12 @@ struct connect_data {
|
|||
chan_handle chan;
|
||||
};
|
||||
|
||||
const intptr_t whatever_tag = 0;
|
||||
const intptr_t connected_tag = 1;
|
||||
const intptr_t wrote_tag = 2;
|
||||
const intptr_t read_tag = 3;
|
||||
const intptr_t timer_tag = 4;
|
||||
const intptr_t exit_tag = 5;
|
||||
|
||||
struct iomsg {
|
||||
intptr_t tag;
|
||||
|
@ -29,6 +32,7 @@ struct iomsg {
|
|||
uint8_t *buf;
|
||||
ssize_t nread;
|
||||
} read_val;
|
||||
uint32_t timer_req_id;
|
||||
} val;
|
||||
};
|
||||
|
||||
|
@ -44,6 +48,13 @@ struct read_start_data {
|
|||
chan_handle chan;
|
||||
};
|
||||
|
||||
struct timer_start_data {
|
||||
rust_uvtmp_thread *thread;
|
||||
uint32_t timeout;
|
||||
uint32_t req_id;
|
||||
chan_handle chan;
|
||||
};
|
||||
|
||||
// FIXME: Copied from rust_builtins.cpp. Could bitrot easily
|
||||
static void
|
||||
send(rust_task *task, chan_handle chan, void *data) {
|
||||
|
@ -72,7 +83,7 @@ private:
|
|||
std::queue<connect_data*> close_connection_queue;
|
||||
std::queue<write_data*> write_queue;
|
||||
std::queue<read_start_data*> read_start_queue;
|
||||
|
||||
std::queue<timer_start_data*> timer_start_queue;
|
||||
public:
|
||||
|
||||
rust_uvtmp_thread() {
|
||||
|
@ -139,6 +150,17 @@ public:
|
|||
read_start_queue.push(rd);
|
||||
}
|
||||
|
||||
void
|
||||
timer(uint32_t timeout, uint32_t req_id, chan_handle chan) {
|
||||
scoped_lock with(lock);
|
||||
|
||||
timer_start_data *td = new timer_start_data();
|
||||
td->timeout = timeout;
|
||||
td->req_id = req_id;
|
||||
td->chan = chan;
|
||||
timer_start_queue.push(td);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
virtual void
|
||||
|
@ -159,6 +181,7 @@ private:
|
|||
close_connections();
|
||||
write_buffers();
|
||||
start_reads();
|
||||
start_timers();
|
||||
close_idle_if_stop();
|
||||
}
|
||||
|
||||
|
@ -246,7 +269,7 @@ private:
|
|||
void
|
||||
on_write(uv_write_t *handle, write_data *wd) {
|
||||
iomsg msg;
|
||||
msg.tag = wrote_tag;
|
||||
msg.tag = timer_tag;
|
||||
msg.val.wrote_val = wd->cd;
|
||||
|
||||
send(task, wd->chan, &msg);
|
||||
|
@ -299,6 +322,40 @@ private:
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
start_timers() {
|
||||
assert (lock.lock_held_by_current_thread());
|
||||
while (!timer_start_queue.empty()) {
|
||||
timer_start_data *td = timer_start_queue.front();
|
||||
timer_start_queue.pop();
|
||||
|
||||
td->thread = this;
|
||||
|
||||
uv_timer_t *timer = (uv_timer_t *)malloc(sizeof(uv_timer_t));
|
||||
timer->data = td;
|
||||
uv_timer_init(loop, timer);
|
||||
uv_timer_start(timer, timer_cb, td->timeout, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
timer_cb(uv_timer_t *handle, int what) {
|
||||
timer_start_data *td = (timer_start_data*)handle->data;
|
||||
rust_uvtmp_thread *self = td->thread;
|
||||
self->on_timer(td);
|
||||
free(handle);
|
||||
}
|
||||
|
||||
void
|
||||
on_timer(timer_start_data *rd) {
|
||||
iomsg msg;
|
||||
msg.tag = timer_tag;
|
||||
msg.val.timer_req_id = rd->req_id;
|
||||
|
||||
send(task, rd->chan, &msg);
|
||||
delete rd;
|
||||
}
|
||||
|
||||
void
|
||||
close_idle_if_stop() {
|
||||
assert(lock.lock_held_by_current_thread());
|
||||
|
@ -353,6 +410,11 @@ rust_uvtmp_read_start(rust_uvtmp_thread *thread, uint32_t req_id,
|
|||
thread->read_start(req_id, *chan);
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
rust_uvtmp_timer(rust_uvtmp_thread *thread, uint32_t timeout, uint32_t req_id, chan_handle *chan) {
|
||||
thread->timer(timeout, req_id, *chan);
|
||||
}
|
||||
|
||||
extern "C" void
|
||||
rust_uvtmp_delete_buf(uint8_t *buf) {
|
||||
delete [] buf;
|
||||
|
|
|
@ -96,6 +96,7 @@ rust_uvtmp_connect
|
|||
rust_uvtmp_close_connection
|
||||
rust_uvtmp_write
|
||||
rust_uvtmp_read_start
|
||||
rust_uvtmp_timer
|
||||
rust_uvtmp_delete_buf
|
||||
rust_uvtmp_get_req_id
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ import comm::recv;
|
|||
import comm::send;
|
||||
|
||||
fn map(&&filename: [u8], emit: map_reduce::putter<[u8], int>) {
|
||||
let f = io::file_reader(str::unsafe_from_bytes(filename));
|
||||
let f = io::file_reader(str::from_bytes(filename));
|
||||
|
||||
while true {
|
||||
alt read_word(f) {
|
||||
|
|
6
src/test/compile-fail/attempted-access-non-fatal.rs
Normal file
6
src/test/compile-fail/attempted-access-non-fatal.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
// Check that bogus field access is non-fatal
|
||||
fn main() {
|
||||
let x = 0;
|
||||
log(debug, x.foo); //! ERROR attempted access of field
|
||||
log(debug, x.bar); //! ERROR attempted access of field
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
// error-pattern: Type inference failed because I could not find
|
||||
// error-pattern: can not instantiate infinite type
|
||||
fn main() { let f; f = @f; }
|
||||
|
|
5
src/test/compile-fail/recursive-enum.rs
Normal file
5
src/test/compile-fail/recursive-enum.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
// error-pattern: illegal recursive enum type
|
||||
|
||||
enum list<T> { cons(T, list<T>), nil }
|
||||
|
||||
fn main() {}
|
|
@ -81,7 +81,7 @@ mod map_reduce {
|
|||
mapper_done { num_mappers -= 1; }
|
||||
find_reducer(k, cc) {
|
||||
let c;
|
||||
alt reducers.find(str::unsafe_from_bytes(k)) {
|
||||
alt reducers.find(str::from_bytes(k)) {
|
||||
some(_c) { c = _c; }
|
||||
none { c = 0; }
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue