From 1e67f131e678e3a976034e06e6a0b16c7fac80ac Mon Sep 17 00:00:00 2001 From: salano_ym <53254905+salano-ym@users.noreply.github.com> Date: Thu, 9 May 2024 07:40:22 +0000 Subject: [PATCH 1/3] =?UTF-8?q?str.starts=5Fwith=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + docs/primitive-props.md | 7 +++++ src/interpreter/primitive-props.ts | 15 +++++++++++ test/index.ts | 42 ++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ef6934a..f004b87c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ # 未リリース分 - `Date:year`系の関数に0を渡すと現在時刻になる問題を修正 - シンタックスエラーなどの位置情報を修正 +- `str.starts_with`を追加 # 0.18.0 - `Core:abort`でプログラムを緊急停止できるように diff --git a/docs/primitive-props.md b/docs/primitive-props.md index 609cc6ee..233b155c 100644 --- a/docs/primitive-props.md +++ b/docs/primitive-props.md @@ -62,6 +62,13 @@ 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).slice(_begin_: num, _end_: num): str 文字列の _begin_ 番目から _end_ 番目の直前までの部分を取得します。 diff --git a/src/interpreter/primitive-props.ts b/src/interpreter/primitive-props.ts index c154c45a..2fa34826 100644 --- a/src/interpreter/primitive-props.ts +++ b/src/interpreter/primitive-props.ts @@ -119,6 +119,21 @@ const PRIMITIVE_PROPS: { const res = target.value.codePointAt(i.value) ?? target.value.charCodeAt(i.value); 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; + }), }, arr: { diff --git a/test/index.ts b/test/index.ts index fed26c01..cb5f6250 100644 --- a/test/index.ts +++ b/test/index.ts @@ -2626,6 +2626,48 @@ describe('primitive props', () => { ARR([NUM(97), NUM(98), NUM(99), NUM(240), NUM(169), NUM(184), NUM(189), NUM(240), NUM(159), NUM(145), NUM(137), NUM(240), NUM(159), NUM(143), NUM(191), NUM(240), NUM(159), NUM(145), NUM(168), NUM(226), NUM(128), NUM(141), NUM(240), NUM(159), NUM(145), NUM(166), NUM(100), NUM(101), NUM(102)]) ); }); + + 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, + ])); + }); }); describe('arr', () => { From 32c6a4000d44539a45add9e1e5fbde5cac1f1239 Mon Sep 17 00:00:00 2001 From: salano_ym <53254905+salano-ym@users.noreply.github.com> Date: Thu, 9 May 2024 07:40:53 +0000 Subject: [PATCH 2/3] =?UTF-8?q?str.ends=5Fwith=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- docs/primitive-props.md | 7 +++++ src/interpreter/primitive-props.ts | 16 ++++++++++++ test/index.ts | 42 ++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f004b87c..bbdb8a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ # 未リリース分 - `Date:year`系の関数に0を渡すと現在時刻になる問題を修正 - シンタックスエラーなどの位置情報を修正 -- `str.starts_with`を追加 +- `str.starts_with`,`str.ends_with`を追加 # 0.18.0 - `Core:abort`でプログラムを緊急停止できるように diff --git a/docs/primitive-props.md b/docs/primitive-props.md index 233b155c..eaa27c63 100644 --- a/docs/primitive-props.md +++ b/docs/primitive-props.md @@ -69,6 +69,13 @@ _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_ が指定されている場合、そのインデックスの直前を末尾とします。\ +_end\_index_ が`v_.len`より大きいか`-v.len`より小さい場合は`false`を返します。\ +_end\_index_ が負の場合は末尾から数えます。 + ### @(_v_: str).slice(_begin_: num, _end_: num): str 文字列の _begin_ 番目から _end_ 番目の直前までの部分を取得します。 diff --git a/src/interpreter/primitive-props.ts b/src/interpreter/primitive-props.ts index 2fa34826..a5881f14 100644 --- a/src/interpreter/primitive-props.ts +++ b/src/interpreter/primitive-props.ts @@ -134,6 +134,22 @@ const PRIMITIVE_PROPS: { 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; + }), }, arr: { diff --git a/test/index.ts b/test/index.ts index cb5f6250..7a21ee59 100644 --- a/test/index.ts +++ b/test/index.ts @@ -2668,6 +2668,48 @@ describe('primitive props', () => { 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, + ])); + }); }); describe('arr', () => { From 11e5f013104a2003b71ae8bc1eedc0d3f701780e Mon Sep 17 00:00:00 2001 From: salano_ym <53254905+salano-ym@users.noreply.github.com> Date: Thu, 9 May 2024 13:07:05 +0000 Subject: [PATCH 3/3] fix docs/primitive-props.md - str.starts_with - str.ends_with --- docs/primitive-props.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/primitive-props.md b/docs/primitive-props.md index eaa27c63..2e797c79 100644 --- a/docs/primitive-props.md +++ b/docs/primitive-props.md @@ -66,14 +66,14 @@ Core:range(0,2).push(4) //[0,1,2,4] 文字列が _prefix_ で始まっていれば`true`、そうでなければ`false`を返します。\ _prefix_ が空文字列の場合は常に`true`を返します。\ _start\_index_ が指定されている場合、そのインデックスから始めます。\ -_start\_index_ が`v_.len`より大きいか`-v.len`より小さい場合は`false`を返します。\ +_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_ が指定されている場合、そのインデックスの直前を末尾とします。\ -_end\_index_ が`v_.len`より大きいか`-v.len`より小さい場合は`false`を返します。\ +_end\_index_ が指定されている場合、そのインデックスの直前を末尾とします。(省略時は`v.len`)\ +_end\_index_ が`v.len`より大きいか`-v.len`より小さい場合は`false`を返します。\ _end\_index_ が負の場合は末尾から数えます。 ### @(_v_: str).slice(_begin_: num, _end_: num): str