Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- `arr.sort`の処理を非同期的にして高速化
- `arr.flat`,`arr.flat_map`を追加
- `Uri:encode_full`, `Uri:encode_component`, `Uri:decode_full`, `Uri:decode_component`を追加
- `str.starts_with`,`str.ends_with`を追加

# 0.18.0
- `Core:abort`でプログラムを緊急停止できるように
Expand Down
14 changes: 14 additions & 0 deletions docs/primitive-props.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ Core:range(0,2).push(4) //[0,1,2,4]
### @(_v_: str).incl(_keyword_: str): bool
文字列中に _keyword_ が含まれていれば`true`、なければ`false`を返します。

### @(_v_: str).starts_with(_prefix_: str, _start\_index_?: num): bool
文字列が _prefix_ で始まっていれば`true`、そうでなければ`false`を返します。\
_prefix_ が空文字列の場合は常に`true`を返します。\
_start\_index_ が指定されている場合、そのインデックスから始めます。\
_start\_index_ が`v.len`より大きいか`-v.len`より小さい場合は`false`を返します。\
_start\_index_ が負の場合は末尾から数えます。

### @(_v_: str).ends_with(_suffix_: str, _end\_index_?: num): bool
文字列が _suffix_ で終わっていれば`true`、そうでなければ`false`を返します。\
_suffix_ が空文字列の場合は常に`true`を返します。\
_end\_index_ が指定されている場合、そのインデックスの直前を末尾とします。(省略時は`v.len`)\
_end\_index_ が`v.len`より大きいか`-v.len`より小さい場合は`false`を返します。\
_end\_index_ が負の場合は末尾から数えます。

### @(_v_: str).slice(_begin_: num, _end_: num): str
文字列の _begin_ 番目から _end_ 番目の直前までの部分を取得します。

Expand Down
31 changes: 31 additions & 0 deletions src/interpreter/primitive-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,37 @@ const PRIMITIVE_PROPS: {
return Number.isNaN(res) ? NULL : NUM(res);
}),

starts_with: (target: VStr): VFn => FN_NATIVE(async ([prefix, start_index], _opts) => {
assertString(prefix);
if (!prefix.value) {
return TRUE;
}

if (start_index) assertNumber(start_index);
const raw_index = start_index?.value ?? 0;
if (raw_index < -target.value.length || raw_index > target.value.length) {
return FALSE;
}
const index = (raw_index >= 0) ? raw_index : target.value.length + raw_index;
return target.value.startsWith(prefix.value, index) ? TRUE : FALSE;
}),

ends_with: (target: VStr): VFn => FN_NATIVE(async ([suffix, end_index], _opts) => {
assertString(suffix);
if (!suffix.value) {
return TRUE;
}

if (end_index) assertNumber(end_index);
const raw_index = end_index?.value ?? target.value.length;
if (raw_index < -target.value.length || raw_index > target.value.length) {
return FALSE;
}
const index = (raw_index >= 0) ? raw_index : target.value.length + raw_index;

return target.value.endsWith(suffix.value, index) ? TRUE : FALSE;
}),

pad_start: (target: VStr): VFn => FN_NATIVE(([width, pad], _) => {
assertNumber(width);
const s = (pad) ? (assertString(pad), pad.value) : ' ';
Expand Down
86 changes: 85 additions & 1 deletion test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2628,7 +2628,91 @@ describe('primitive props', () => {
);
});

test.concurrent("pad_start", async () => {
test.concurrent('starts_with (no index)', async () => {
const res = await exe(`
let str = "hello"
let empty = ""
<: [
str.starts_with(""), str.starts_with("hello"),
str.starts_with("he"), str.starts_with("ell"),
empty.starts_with(""), empty.starts_with("he"),
]
`);
eq(res, ARR([
TRUE, TRUE,
TRUE, FALSE,
TRUE, FALSE,
]));
});

test.concurrent('starts_with (with index)', async () => {
const res = await exe(`
let str = "hello"
let empty = ""
<: [
str.starts_with("", 4), str.starts_with("he", 0),
str.starts_with("ll", 2), str.starts_with("lo", 3),
str.starts_with("lo", -2), str.starts_with("hel", -5),
str.starts_with("he", 2), str.starts_with("loa", 3),
str.starts_with("lo", -6), str.starts_with("", -7),
str.starts_with("lo", 6), str.starts_with("", 7),
empty.starts_with("", 2), empty.starts_with("ll", 2),
]
`);
eq(res, ARR([
TRUE, TRUE,
TRUE, TRUE,
TRUE, TRUE,
FALSE, FALSE,
FALSE, TRUE,
FALSE, TRUE,
TRUE, FALSE,
]));
});

test.concurrent('ends_with (no index)', async () => {
const res = await exe(`
let str = "hello"
let empty = ""
<: [
str.ends_with(""), str.ends_with("hello"),
str.ends_with("lo"), str.ends_with("ell"),
empty.ends_with(""), empty.ends_with("he"),
]
`);
eq(res, ARR([
TRUE, TRUE,
TRUE, FALSE,
TRUE, FALSE,
]));
});

test.concurrent('ends_with (with index)', async () => {
const res = await exe(`
let str = "hello"
let empty = ""
<: [
str.ends_with("", 3), str.ends_with("lo", 5),
str.ends_with("ll", 4), str.ends_with("he", 2),
str.ends_with("ll", -1), str.ends_with("he", -3),
str.ends_with("he", 5), str.ends_with("lo", 3),
str.ends_with("lo", -6), str.ends_with("", -7),
str.ends_with("lo", 6), str.ends_with("", 7),
empty.ends_with("", 2), empty.ends_with("ll", 2),
]
`);
eq(res, ARR([
TRUE, TRUE,
TRUE, TRUE,
TRUE, TRUE,
FALSE, FALSE,
FALSE, TRUE,
FALSE, TRUE,
TRUE, FALSE,
]));
});

test.concurrent("pad_start", async () => {
const res = await exe(`
let str = "abc"
<: [
Expand Down