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
50 changes: 50 additions & 0 deletions integration_tests/test_str_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,55 @@ def startswith():
assert s.startswith("sdd") == False
assert "".startswith("ok") == False

def endswith():

# The following test suite fulfils the control flow graph coverage
# in terms of Statement Coverage and Branch Coverage associated with endwith() functionality.

# Case 1: When string is constant and suffix is also constant
assert "".endswith("") == True
assert "".endswith(" ") == False
assert "".endswith("%") == False
assert "".endswith("a1234PT#$") == False
assert "".endswith("blah blah") == False
assert " rendezvous 5:30 ".endswith("") == True
assert " rendezvous 5:30 ".endswith(" ") == True
assert " rendezvous 5:30 ".endswith(" 5:30 ") == True
assert " rendezvous 5:30 ".endswith("apple") == False
assert "two plus".endswith("longer than string") == False


# Case 2: When string is constant and suffix is variable
suffix: str
suffix = ""
assert "".endswith(suffix) == True
suffix = " "
assert "".endswith(suffix) == False
suffix = "5:30 "
assert " rendezvous 5:30 ".endswith(suffix) == True
suffix = ""
assert " rendezvous 5:30 ".endswith(suffix) == True
suffix = "apple"
assert " rendezvous 5:30 ".endswith(suffix) == False
suffix = "longer than string"
assert "two plus".endswith(suffix) == False

# Case 3: When string is variable and suffix is either constant or variable
s: str
s = ""
assert s.endswith("") == True
assert s.endswith("apple") == False
assert s.endswith(" ") == False
assert s.endswith(suffix) == False

s = " rendezvous 5 "
assert s.endswith(" $3324") == False
assert s.endswith("5 ") == True
assert s.endswith(s) == True
suffix = "vous 5 "
assert s.endswith(suffix) == True
suffix = "apple"
assert s.endswith(suffix) == False

def check():
capitalize()
Expand All @@ -86,5 +135,6 @@ def check():
swapcase()
find()
startswith()
endswith()

check()
92 changes: 90 additions & 2 deletions src/lpython/semantics/python_ast_to_asr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5665,13 +5665,13 @@ class BodyVisitor : public CommonVisitor<BodyVisitor> {
fn_args.push_back(al, arg);
} else if (attr_name == "startswith") {
if(args.size() != 1) {
throw SemanticError("str.startwith() takes one argument",
throw SemanticError("str.startswith() takes one argument",
loc);
}
ASR::expr_t *arg_sub = args[0].m_value;
ASR::ttype_t *arg_sub_type = ASRUtils::expr_type(arg_sub);
if (!ASRUtils::is_character(*arg_sub_type)) {
throw SemanticError("str.startwith() takes one argument of type: str",
throw SemanticError("str.startswith() takes one argument of type: str",
loc);
}
fn_call_name = "_lpython_str_startswith";
Expand All @@ -5683,6 +5683,38 @@ class BodyVisitor : public CommonVisitor<BodyVisitor> {
sub.m_value = args[0].m_value;
fn_args.push_back(al, str);
fn_args.push_back(al, sub);
} else if (attr_name == "endswith") {
/*
str.endswith(suffix) ---->
Return True if the string ends with the specified suffix, otherwise return False.

arg_sub: Substring argument provided inside endswith() function
arg_sub_type: Type of Substring argument
fn_call_name: Name of the Function that has logic/implementation of endswith() function
str: Associates with string on which endswith() function will act on
suffix: Associates with the suffix string which is provided as an argument to endswith() function
*/
if(args.size() != 1) {
throw SemanticError("str.endswith() takes only one argument", loc);
}
ASR::expr_t *arg_suffix = args[0].m_value;
ASR::ttype_t *arg_suffix_type = ASRUtils::expr_type(arg_suffix);
if (!ASRUtils::is_character(*arg_suffix_type)) {
throw SemanticError("str.endswith() takes one argument of type: str", loc);
}

fn_call_name = "_lpython_str_endswith";
ASR::call_arg_t str;
str.loc = loc;
str.m_value = s_var;

ASR::call_arg_t suffix;
suffix.loc = loc;
suffix.m_value = args[0].m_value;

// Push string and substring argument on top of Vector (or Function Arguments Stack basically)
fn_args.push_back(al, str);
fn_args.push_back(al, suffix);
} else {
throw SemanticError("String method not implemented: " + attr_name,
loc);
Expand Down Expand Up @@ -5874,6 +5906,62 @@ class BodyVisitor : public CommonVisitor<BodyVisitor> {
"_lpython_str_startswith", loc);
}
return;
} else if (attr_name == "endswith") {
/*
str.endswith(suffix) ---->
Return True if the string ends with the specified suffix, otherwise return False.
*/

if (args.size() != 1) {
throw SemanticError("str.endswith() takes one arguments", loc);
}

ASR::expr_t *arg_suffix = args[0].m_value;
ASR::ttype_t *arg_suffix_type = ASRUtils::expr_type(arg_suffix);
if (!ASRUtils::is_character(*arg_suffix_type)) {
throw SemanticError("str.endswith() takes one arguments of type: str", arg_suffix->base.loc);
}

if (ASRUtils::expr_value(arg_suffix) != nullptr) {
/*
Invoked when Suffix argument is provided as a constant string
*/
ASR::StringConstant_t* suffix_constant = ASR::down_cast<ASR::StringConstant_t>(arg_suffix);
std::string suffix = suffix_constant->m_s;

bool res = true;
if (suffix.size() > s_var.size())
res = false;
else
res = std::equal(suffix.rbegin(), suffix.rend(), s_var.rbegin());

tmp = ASR::make_LogicalConstant_t(al, loc, res,
ASRUtils::TYPE(ASR::make_Logical_t(al, loc, 4, nullptr, 0)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is doing a compile time optimization. I think this should rather be implemented in the optimizer, but not done by the frontend, since ASR is then being lowered, and when we convert ASR back to Python, the original code is not recovered.

Copy link
Contributor Author

@harshsingh-24 harshsingh-24 Mar 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be refactored into IntrinsicFunction. Just add a new function. We will have thousands of such functions eventually.

@certik How can I go on with refactoring of code to Intrinsic Functions? Can you give a head start? Because what I understood from @Thirumalai-Shaktivel is that we have to sync between LPython and LFortran for that. Can you give some pointers here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we have to sync first. Until then, you can implement this in LFortran itself, in libasr.

Copy link
Contributor Author

@harshsingh-24 harshsingh-24 Mar 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where in libasr? Can you give an example of one such addition in LFortran? Maybe a previous PR or something. @certik

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in src/libasr.

Just look into merged PRs and search IntrinsicFunction. See also #1616 that is porting it to LPython.


} else {
/*
Invoked when Suffix argument is provided as a variable
b: str = "ple"
Eg: "apple".endswith(b)
*/
ASR::symbol_t *fn_div = resolve_intrinsic_function(loc, "_lpython_str_endswith");
Vec<ASR::call_arg_t> args;
args.reserve(al, 1);
ASR::call_arg_t str_arg;
str_arg.loc = loc;
ASR::ttype_t *str_type = ASRUtils::TYPE(ASR::make_Character_t(al, loc,
1, s_var.size(), nullptr, nullptr, 0));
str_arg.m_value = ASRUtils::EXPR(
ASR::make_StringConstant_t(al, loc, s2c(al, s_var), str_type));
ASR::call_arg_t sub_arg;
sub_arg.loc = loc;
sub_arg.m_value = arg_suffix;
args.push_back(al, str_arg);
args.push_back(al, sub_arg);

tmp = make_call_helper(al, fn_div, current_scope, args, "_lpython_str_endswith", loc);
}
return;
} else {
throw SemanticError("'str' object has no attribute '" + attr_name + "'",
loc);
Expand Down
3 changes: 2 additions & 1 deletion src/lpython/semantics/python_comptime_eval.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ struct PythonIntrinsicProcedures {
{"_lpython_str_lstrip", {m_builtin, &not_implemented}},
{"_lpython_str_strip", {m_builtin, &not_implemented}},
{"_lpython_str_swapcase", {m_builtin, &not_implemented}},
{"_lpython_str_startswith", {m_builtin, &not_implemented}}
{"_lpython_str_startswith", {m_builtin, &not_implemented}},
{"_lpython_str_endswith", {m_builtin, &not_implemented}}
};
}

Expand Down
15 changes: 15 additions & 0 deletions src/runtime/lpython_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,6 +775,21 @@ def _lpython_str_startswith(s: str ,sub: str) -> bool:
res = res and (j == len(sub))
return res

@overload
def _lpython_str_endswith(s: str, suffix: str) -> bool:

if(len(suffix) > len(s)):
return False

i : i32
i = 0
while(i < len(suffix)):
if(suffix[len(suffix) - i - 1] != s[len(s) - i - 1]):
return False
i += 1

return True


def list(s: str) -> list[str]:
l: list[str] = []
Expand Down
2 changes: 1 addition & 1 deletion tests/reference/asr-array_01_decl-39cf894.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"outfile": null,
"outfile_hash": null,
"stdout": "asr-array_01_decl-39cf894.stdout",
"stdout_hash": "5ecd298c183e6b4027f8f601a2b5d3963033ebf54bca582c982b5929",
"stdout_hash": "4377072f8ac3cfc1bd619f53d07542c11dd648fa100d0f3104e1a4c7",
"stderr": null,
"stderr_hash": null,
"returncode": 0
Expand Down
2 changes: 1 addition & 1 deletion tests/reference/asr-array_01_decl-39cf894.stdout

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/reference/asr-array_02_decl-e8f6874.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"outfile": null,
"outfile_hash": null,
"stdout": "asr-array_02_decl-e8f6874.stdout",
"stdout_hash": "9fcfaf5cb7d6dfd6798902f102d41743c0c6c89180572345d886154f",
"stdout_hash": "dd1434de42dcd718f283abd72d736f032846819a9a31585b32a528c0",
"stderr": null,
"stderr_hash": null,
"returncode": 0
Expand Down
2 changes: 1 addition & 1 deletion tests/reference/asr-array_02_decl-e8f6874.stdout

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/reference/asr-bindc_02-bc1a7ea.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"outfile": null,
"outfile_hash": null,
"stdout": "asr-bindc_02-bc1a7ea.stdout",
"stdout_hash": "00799f9ef4a45d4c3e93d043b31f73f8038a4b82a4ee191f29b0467f",
"stdout_hash": "2e6dbece0d145af97d42a7780c4dd0701c3456381a9761bfb95da91c",
"stderr": null,
"stderr_hash": null,
"returncode": 0
Expand Down
2 changes: 1 addition & 1 deletion tests/reference/asr-bindc_02-bc1a7ea.stdout

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/reference/asr-cast-435c233.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"outfile": null,
"outfile_hash": null,
"stdout": "asr-cast-435c233.stdout",
"stdout_hash": "17cc5e55a18b44793f2e153ef08fd85e279fb740ad6991a3d47665b8",
"stdout_hash": "d5f977b7e0bb753288b2cec19cfa7187dbdf5158216d1564d8322fd9",
"stderr": null,
"stderr_hash": null,
"returncode": 0
Expand Down
2 changes: 1 addition & 1 deletion tests/reference/asr-cast-435c233.stdout
Original file line number Diff line number Diff line change
@@ -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 () [] ())])}) [])
(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 () [] ())])}) [])
2 changes: 1 addition & 1 deletion tests/reference/asr-complex1-f26c460.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"outfile": null,
"outfile_hash": null,
"stdout": "asr-complex1-f26c460.stdout",
"stdout_hash": "76dbd92fc200e51a52c957bfafdc5847c25ffcf9e81d8e5f15378073",
"stdout_hash": "550c9766480cf97a621465ee015419264ed3d1350db6ae794d979069",
"stderr": null,
"stderr_hash": null,
"returncode": 0
Expand Down
Loading