Skip to content

Commit dd5a8f9

Browse files
authored
Merge pull request #1606 from harshsingh-24/str-funcs
New string Method `endswith()` support
2 parents 1a93c68 + 269190e commit dd5a8f9

File tree

74 files changed

+227
-73
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+227
-73
lines changed

integration_tests/test_str_attributes.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,55 @@ def startswith():
7878
assert s.startswith("sdd") == False
7979
assert "".startswith("ok") == False
8080

81+
def endswith():
82+
83+
# The following test suite fulfils the control flow graph coverage
84+
# in terms of Statement Coverage and Branch Coverage associated with endwith() functionality.
85+
86+
# Case 1: When string is constant and suffix is also constant
87+
assert "".endswith("") == True
88+
assert "".endswith(" ") == False
89+
assert "".endswith("%") == False
90+
assert "".endswith("a1234PT#$") == False
91+
assert "".endswith("blah blah") == False
92+
assert " rendezvous 5:30 ".endswith("") == True
93+
assert " rendezvous 5:30 ".endswith(" ") == True
94+
assert " rendezvous 5:30 ".endswith(" 5:30 ") == True
95+
assert " rendezvous 5:30 ".endswith("apple") == False
96+
assert "two plus".endswith("longer than string") == False
97+
98+
99+
# Case 2: When string is constant and suffix is variable
100+
suffix: str
101+
suffix = ""
102+
assert "".endswith(suffix) == True
103+
suffix = " "
104+
assert "".endswith(suffix) == False
105+
suffix = "5:30 "
106+
assert " rendezvous 5:30 ".endswith(suffix) == True
107+
suffix = ""
108+
assert " rendezvous 5:30 ".endswith(suffix) == True
109+
suffix = "apple"
110+
assert " rendezvous 5:30 ".endswith(suffix) == False
111+
suffix = "longer than string"
112+
assert "two plus".endswith(suffix) == False
113+
114+
# Case 3: When string is variable and suffix is either constant or variable
115+
s: str
116+
s = ""
117+
assert s.endswith("") == True
118+
assert s.endswith("apple") == False
119+
assert s.endswith(" ") == False
120+
assert s.endswith(suffix) == False
121+
122+
s = " rendezvous 5 "
123+
assert s.endswith(" $3324") == False
124+
assert s.endswith("5 ") == True
125+
assert s.endswith(s) == True
126+
suffix = "vous 5 "
127+
assert s.endswith(suffix) == True
128+
suffix = "apple"
129+
assert s.endswith(suffix) == False
81130

82131
def check():
83132
capitalize()
@@ -86,5 +135,6 @@ def check():
86135
swapcase()
87136
find()
88137
startswith()
138+
endswith()
89139

90140
check()

src/lpython/semantics/python_ast_to_asr.cpp

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5665,13 +5665,13 @@ class BodyVisitor : public CommonVisitor<BodyVisitor> {
56655665
fn_args.push_back(al, arg);
56665666
} else if (attr_name == "startswith") {
56675667
if(args.size() != 1) {
5668-
throw SemanticError("str.startwith() takes one argument",
5668+
throw SemanticError("str.startswith() takes one argument",
56695669
loc);
56705670
}
56715671
ASR::expr_t *arg_sub = args[0].m_value;
56725672
ASR::ttype_t *arg_sub_type = ASRUtils::expr_type(arg_sub);
56735673
if (!ASRUtils::is_character(*arg_sub_type)) {
5674-
throw SemanticError("str.startwith() takes one argument of type: str",
5674+
throw SemanticError("str.startswith() takes one argument of type: str",
56755675
loc);
56765676
}
56775677
fn_call_name = "_lpython_str_startswith";
@@ -5683,6 +5683,38 @@ class BodyVisitor : public CommonVisitor<BodyVisitor> {
56835683
sub.m_value = args[0].m_value;
56845684
fn_args.push_back(al, str);
56855685
fn_args.push_back(al, sub);
5686+
} else if (attr_name == "endswith") {
5687+
/*
5688+
str.endswith(suffix) ---->
5689+
Return True if the string ends with the specified suffix, otherwise return False.
5690+
5691+
arg_sub: Substring argument provided inside endswith() function
5692+
arg_sub_type: Type of Substring argument
5693+
fn_call_name: Name of the Function that has logic/implementation of endswith() function
5694+
str: Associates with string on which endswith() function will act on
5695+
suffix: Associates with the suffix string which is provided as an argument to endswith() function
5696+
*/
5697+
if(args.size() != 1) {
5698+
throw SemanticError("str.endswith() takes only one argument", loc);
5699+
}
5700+
ASR::expr_t *arg_suffix = args[0].m_value;
5701+
ASR::ttype_t *arg_suffix_type = ASRUtils::expr_type(arg_suffix);
5702+
if (!ASRUtils::is_character(*arg_suffix_type)) {
5703+
throw SemanticError("str.endswith() takes one argument of type: str", loc);
5704+
}
5705+
5706+
fn_call_name = "_lpython_str_endswith";
5707+
ASR::call_arg_t str;
5708+
str.loc = loc;
5709+
str.m_value = s_var;
5710+
5711+
ASR::call_arg_t suffix;
5712+
suffix.loc = loc;
5713+
suffix.m_value = args[0].m_value;
5714+
5715+
// Push string and substring argument on top of Vector (or Function Arguments Stack basically)
5716+
fn_args.push_back(al, str);
5717+
fn_args.push_back(al, suffix);
56865718
} else {
56875719
throw SemanticError("String method not implemented: " + attr_name,
56885720
loc);
@@ -5874,6 +5906,62 @@ class BodyVisitor : public CommonVisitor<BodyVisitor> {
58745906
"_lpython_str_startswith", loc);
58755907
}
58765908
return;
5909+
} else if (attr_name == "endswith") {
5910+
/*
5911+
str.endswith(suffix) ---->
5912+
Return True if the string ends with the specified suffix, otherwise return False.
5913+
*/
5914+
5915+
if (args.size() != 1) {
5916+
throw SemanticError("str.endswith() takes one arguments", loc);
5917+
}
5918+
5919+
ASR::expr_t *arg_suffix = args[0].m_value;
5920+
ASR::ttype_t *arg_suffix_type = ASRUtils::expr_type(arg_suffix);
5921+
if (!ASRUtils::is_character(*arg_suffix_type)) {
5922+
throw SemanticError("str.endswith() takes one arguments of type: str", arg_suffix->base.loc);
5923+
}
5924+
5925+
if (ASRUtils::expr_value(arg_suffix) != nullptr) {
5926+
/*
5927+
Invoked when Suffix argument is provided as a constant string
5928+
*/
5929+
ASR::StringConstant_t* suffix_constant = ASR::down_cast<ASR::StringConstant_t>(arg_suffix);
5930+
std::string suffix = suffix_constant->m_s;
5931+
5932+
bool res = true;
5933+
if (suffix.size() > s_var.size())
5934+
res = false;
5935+
else
5936+
res = std::equal(suffix.rbegin(), suffix.rend(), s_var.rbegin());
5937+
5938+
tmp = ASR::make_LogicalConstant_t(al, loc, res,
5939+
ASRUtils::TYPE(ASR::make_Logical_t(al, loc, 4, nullptr, 0)));
5940+
5941+
} else {
5942+
/*
5943+
Invoked when Suffix argument is provided as a variable
5944+
b: str = "ple"
5945+
Eg: "apple".endswith(b)
5946+
*/
5947+
ASR::symbol_t *fn_div = resolve_intrinsic_function(loc, "_lpython_str_endswith");
5948+
Vec<ASR::call_arg_t> args;
5949+
args.reserve(al, 1);
5950+
ASR::call_arg_t str_arg;
5951+
str_arg.loc = loc;
5952+
ASR::ttype_t *str_type = ASRUtils::TYPE(ASR::make_Character_t(al, loc,
5953+
1, s_var.size(), nullptr, nullptr, 0));
5954+
str_arg.m_value = ASRUtils::EXPR(
5955+
ASR::make_StringConstant_t(al, loc, s2c(al, s_var), str_type));
5956+
ASR::call_arg_t sub_arg;
5957+
sub_arg.loc = loc;
5958+
sub_arg.m_value = arg_suffix;
5959+
args.push_back(al, str_arg);
5960+
args.push_back(al, sub_arg);
5961+
5962+
tmp = make_call_helper(al, fn_div, current_scope, args, "_lpython_str_endswith", loc);
5963+
}
5964+
return;
58775965
} else {
58785966
throw SemanticError("'str' object has no attribute '" + attr_name + "'",
58795967
loc);

src/lpython/semantics/python_comptime_eval.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ struct PythonIntrinsicProcedures {
7777
{"_lpython_str_lstrip", {m_builtin, &not_implemented}},
7878
{"_lpython_str_strip", {m_builtin, &not_implemented}},
7979
{"_lpython_str_swapcase", {m_builtin, &not_implemented}},
80-
{"_lpython_str_startswith", {m_builtin, &not_implemented}}
80+
{"_lpython_str_startswith", {m_builtin, &not_implemented}},
81+
{"_lpython_str_endswith", {m_builtin, &not_implemented}}
8182
};
8283
}
8384

src/runtime/lpython_builtin.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,21 @@ def _lpython_str_startswith(s: str ,sub: str) -> bool:
775775
res = res and (j == len(sub))
776776
return res
777777

778+
@overload
779+
def _lpython_str_endswith(s: str, suffix: str) -> bool:
780+
781+
if(len(suffix) > len(s)):
782+
return False
783+
784+
i : i32
785+
i = 0
786+
while(i < len(suffix)):
787+
if(suffix[len(suffix) - i - 1] != s[len(s) - i - 1]):
788+
return False
789+
i += 1
790+
791+
return True
792+
778793

779794
def list(s: str) -> list[str]:
780795
l: list[str] = []

tests/reference/asr-array_01_decl-39cf894.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"outfile": null,
77
"outfile_hash": null,
88
"stdout": "asr-array_01_decl-39cf894.stdout",
9-
"stdout_hash": "5ecd298c183e6b4027f8f601a2b5d3963033ebf54bca582c982b5929",
9+
"stdout_hash": "4377072f8ac3cfc1bd619f53d07542c11dd648fa100d0f3104e1a4c7",
1010
"stderr": null,
1111
"stderr_hash": null,
1212
"returncode": 0

tests/reference/asr-array_01_decl-39cf894.stdout

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/reference/asr-array_02_decl-e8f6874.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"outfile": null,
77
"outfile_hash": null,
88
"stdout": "asr-array_02_decl-e8f6874.stdout",
9-
"stdout_hash": "9fcfaf5cb7d6dfd6798902f102d41743c0c6c89180572345d886154f",
9+
"stdout_hash": "dd1434de42dcd718f283abd72d736f032846819a9a31585b32a528c0",
1010
"stderr": null,
1111
"stderr_hash": null,
1212
"returncode": 0

tests/reference/asr-array_02_decl-e8f6874.stdout

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/reference/asr-bindc_02-bc1a7ea.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"outfile": null,
77
"outfile_hash": null,
88
"stdout": "asr-bindc_02-bc1a7ea.stdout",
9-
"stdout_hash": "00799f9ef4a45d4c3e93d043b31f73f8038a4b82a4ee191f29b0467f",
9+
"stdout_hash": "2e6dbece0d145af97d42a7780c4dd0701c3456381a9761bfb95da91c",
1010
"stderr": null,
1111
"stderr_hash": null,
1212
"returncode": 0

tests/reference/asr-bindc_02-bc1a7ea.stdout

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/reference/asr-cast-435c233.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"outfile": null,
77
"outfile_hash": null,
88
"stdout": "asr-cast-435c233.stdout",
9-
"stdout_hash": "17cc5e55a18b44793f2e153ef08fd85e279fb740ad6991a3d47665b8",
9+
"stdout_hash": "d5f977b7e0bb753288b2cec19cfa7187dbdf5158216d1564d8322fd9",
1010
"stderr": null,
1111
"stderr_hash": null,
1212
"returncode": 0
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
(TranslationUnit (SymbolTable 1 {_global_symbols: (Module (SymbolTable 105 {_lpython_main_program: (Function (SymbolTable 104 {}) _lpython_main_program (FunctionType [] () Source Implementation () .false. .false. .false. .false. .false. [] [] .false.) [f] [] [(SubroutineCall 105 f () [] ())] () Public .false. .false.), f: (Function (SymbolTable 2 {list: (ExternalSymbol 2 list 4 list lpython_builtin [] list Private), s: (Variable 2 s [] Local () () Default (Character 1 -2 () []) Source Public Required .false.), x: (Variable 2 x [] Local () () Default (List (Character 1 -2 () [])) Source Public Required .false.), y: (Variable 2 y [] Local () () Default (List (Character 1 -2 () [])) Source Public Required .false.)}) f (FunctionType [] () Source Implementation () .false. .false. .false. .false. .false. [] [] .false.) [list list list] [] [(= (Var 2 s) (StringConstant "lpython" (Character 1 7 () [])) ()) (= (Var 2 x) (FunctionCall 2 list () [((Var 2 s))] (List (Character 1 -2 () [])) () ()) ()) (= (Var 2 y) (ListConstant [(StringConstant "a" (Character 1 1 () [])) (StringConstant "b" (Character 1 1 () [])) (StringConstant "c" (Character 1 1 () []))] (List (Character 1 1 () []))) ()) (= (Var 2 x) (FunctionCall 2 list () [((Var 2 y))] (List (Character 1 -2 () [])) () ()) ()) (= (Var 2 x) (FunctionCall 2 list () [((StringConstant "lpython" (Character 1 7 () [])))] (List (Character 1 -2 () [])) (ListConstant [(StringConstant "l" (Character 1 1 () [])) (StringConstant "p" (Character 1 1 () [])) (StringConstant "y" (Character 1 1 () [])) (StringConstant "t" (Character 1 1 () [])) (StringConstant "h" (Character 1 1 () [])) (StringConstant "o" (Character 1 1 () [])) (StringConstant "n" (Character 1 1 () []))] (List (Character 1 1 () []))) ()) ())] () Public .false. .false.)}) _global_symbols [lpython_builtin] .false. .false.), lpython_builtin: (IntrinsicModule lpython_builtin), main_program: (Program (SymbolTable 103 {_lpython_main_program: (ExternalSymbol 103 _lpython_main_program 105 _lpython_main_program _global_symbols [] _lpython_main_program Public)}) main_program [_global_symbols] [(SubroutineCall 103 _lpython_main_program () [] ())])}) [])
1+
(TranslationUnit (SymbolTable 1 {_global_symbols: (Module (SymbolTable 106 {_lpython_main_program: (Function (SymbolTable 105 {}) _lpython_main_program (FunctionType [] () Source Implementation () .false. .false. .false. .false. .false. [] [] .false.) [f] [] [(SubroutineCall 106 f () [] ())] () Public .false. .false.), f: (Function (SymbolTable 2 {list: (ExternalSymbol 2 list 4 list lpython_builtin [] list Private), s: (Variable 2 s [] Local () () Default (Character 1 -2 () []) Source Public Required .false.), x: (Variable 2 x [] Local () () Default (List (Character 1 -2 () [])) Source Public Required .false.), y: (Variable 2 y [] Local () () Default (List (Character 1 -2 () [])) Source Public Required .false.)}) f (FunctionType [] () Source Implementation () .false. .false. .false. .false. .false. [] [] .false.) [list list list] [] [(= (Var 2 s) (StringConstant "lpython" (Character 1 7 () [])) ()) (= (Var 2 x) (FunctionCall 2 list () [((Var 2 s))] (List (Character 1 -2 () [])) () ()) ()) (= (Var 2 y) (ListConstant [(StringConstant "a" (Character 1 1 () [])) (StringConstant "b" (Character 1 1 () [])) (StringConstant "c" (Character 1 1 () []))] (List (Character 1 1 () []))) ()) (= (Var 2 x) (FunctionCall 2 list () [((Var 2 y))] (List (Character 1 -2 () [])) () ()) ()) (= (Var 2 x) (FunctionCall 2 list () [((StringConstant "lpython" (Character 1 7 () [])))] (List (Character 1 -2 () [])) (ListConstant [(StringConstant "l" (Character 1 1 () [])) (StringConstant "p" (Character 1 1 () [])) (StringConstant "y" (Character 1 1 () [])) (StringConstant "t" (Character 1 1 () [])) (StringConstant "h" (Character 1 1 () [])) (StringConstant "o" (Character 1 1 () [])) (StringConstant "n" (Character 1 1 () []))] (List (Character 1 1 () []))) ()) ())] () Public .false. .false.)}) _global_symbols [lpython_builtin] .false. .false.), lpython_builtin: (IntrinsicModule lpython_builtin), main_program: (Program (SymbolTable 104 {_lpython_main_program: (ExternalSymbol 104 _lpython_main_program 106 _lpython_main_program _global_symbols [] _lpython_main_program Public)}) main_program [_global_symbols] [(SubroutineCall 104 _lpython_main_program () [] ())])}) [])

tests/reference/asr-complex1-f26c460.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"outfile": null,
77
"outfile_hash": null,
88
"stdout": "asr-complex1-f26c460.stdout",
9-
"stdout_hash": "76dbd92fc200e51a52c957bfafdc5847c25ffcf9e81d8e5f15378073",
9+
"stdout_hash": "550c9766480cf97a621465ee015419264ed3d1350db6ae794d979069",
1010
"stderr": null,
1111
"stderr_hash": null,
1212
"returncode": 0

0 commit comments

Comments
 (0)