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
26 changes: 26 additions & 0 deletions docs/lexicon.html
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,32 @@ <h3 class="title-3">
- Replaces named parameters in the string.
</span>
</li>
<li>
<span>
<code class="code"
><span class="fn-name">ljust</span><span class="fn-p">(</span
><span class="fn-arg">width</span>[,
<span class="fn-arg">fillchar</span>]<span class="fn-p">)</span></code
>
- returns a copy of this string left-padded with the character given
in <code class="code">fillchar</code> (or the default <code class="code"
>' '</code> if no value is given) until it is <code class="code"
>width</code> characters long.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is it worth declaring here that this is done based on how Go counts characters(/runes)? Or are we happy to leave the "how long is a (piece of) string" nonsense unsaid?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

IMO it's best left unsaid for now, because I'm pretty sure string length operations are inconsistent in ASP and I'd rather people not draw inferences based on statements made in one part of the documentation that are omitted in others. I don't think it's the highest-priority task, but we should probably go through the language and make sure length operations on strings are all character-wise, not byte-wise, as they are in Python.

</span>
</li>
<li>
<span>
<code class="code"
><span class="fn-name">rjust</span><span class="fn-p">(</span
><span class="fn-arg">width</span>[,
<span class="fn-arg">fillchar</span>]<span class="fn-p">)</span></code
>
- returns a copy of this string right-padded with the character given
in <code class="code">fillchar</code> (or the default <code class="code"
>' '</code> if no value is given) until it is <code class="code"
>width</code> characters long.
</span>
</li>
<li>
<span>
<code class="code"
Expand Down
4 changes: 4 additions & 0 deletions rules/builtins.build_defs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ def endswith(self:str, s:str) -> bool:
pass
def format(self:str) -> str:
pass
def ljust(self:str, width:int, fillchar:str=' ') -> str:
pass
def rjust(self:str, width:int, fillchar:str=' ') -> str:
pass
def lstrip(self:str, cutset:str=' \n') -> str:
pass
def rstrip(self:str, cutset:str=' \n') -> str:
Expand Down
32 changes: 32 additions & 0 deletions src/parse/asp/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"strings"
"sync"
"unicode"
"unicode/utf8"

"github.com/Masterminds/semver/v3"
"github.com/manifoldco/promptui"
Expand Down Expand Up @@ -88,6 +89,8 @@ func registerBuiltins(s *scope) {
"rpartition": setNativeCode(s, "rpartition", strRPartition),
"startswith": setNativeCode(s, "startswith", strStartsWith),
"endswith": setNativeCode(s, "endswith", strEndsWith),
"ljust": setNativeCode(s, "ljust", strLJust),
"rjust": setNativeCode(s, "rjust", strRJust),
"lstrip": setNativeCode(s, "lstrip", strLStrip),
"rstrip": setNativeCode(s, "rstrip", strRStrip),
"removeprefix": setNativeCode(s, "removeprefix", strRemovePrefix),
Expand Down Expand Up @@ -542,6 +545,35 @@ func strEndsWith(s *scope, args []pyObject) pyObject {
return newPyBool(strings.HasSuffix(string(self), string(x)))
}

func strLJust(s *scope, args []pyObject) pyObject {
return strJust(s, args, true)
}

func strRJust(s *scope, args []pyObject) pyObject {
return strJust(s, args, false)
}

func strJust(s *scope, args []pyObject, left bool) pyObject {
self := string(args[0].(pyString))
width := int(args[1].(pyInt))
fillchar := string(args[2].(pyString))

// utf8.RuneCountInString rather than objLen, because objLen returns the length of a string in
// bytes rather than characters:
s.Assert(utf8.RuneCountInString(fillchar) == 1, "fillchar must be exactly one character long")

count := width - utf8.RuneCountInString(self)
// strings.Repeat's second operand must be non-negative, otherwise it panics. In that case, no
// changes need to be made to the input string anyway, so just return a copy of it.
if count <= 0 {
return pyString(self)
}
if left {
return pyString(strings.Repeat(fillchar, count) + self)
}
return pyString(self + strings.Repeat(fillchar, count))
}

func strLStrip(s *scope, args []pyObject) pyObject {
self := args[0].(pyString)
cutset := args[1].(pyString)
Expand Down
40 changes: 40 additions & 0 deletions src/parse/asp/interpreter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -730,3 +730,43 @@ func TestBreakLoop(t *testing.T) {
assert.NoError(t, err)
assert.EqualValues(t, pyString("ok"), s.Lookup("x"))
}

func TestStrLjust(t *testing.T) {
s, err := parseFile("src/parse/asp/test_data/interpreter/str/ljust.build")
assert.NoError(t, err)
assert.EqualValues(t, pyString(" kittens"), s.Lookup("s1"))
assert.EqualValues(t, pyString("+++kittens"), s.Lookup("s2"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s3"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s4"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s5"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s6"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s7"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s8"))
assert.EqualValues(t, pyString("£££kittens"), s.Lookup("s9"))
assert.EqualValues(t, pyString("++++££kittens££"), s.Lookup("s10"))
// Make sure the original string wasn't modified:
assert.EqualValues(t, pyString("kittens"), s.Lookup("orig"))

_, err = parseFile("src/parse/asp/test_data/interpreter/str/ljust_multiple_fillchars.build")
assert.Error(t, err, "fillchar must be exactly one character long")
}

func TestStrRjust(t *testing.T) {
s, err := parseFile("src/parse/asp/test_data/interpreter/str/rjust.build")
assert.NoError(t, err)
assert.EqualValues(t, pyString("kittens "), s.Lookup("s1"))
assert.EqualValues(t, pyString("kittens+++"), s.Lookup("s2"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s3"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s4"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s5"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s6"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s7"))
assert.EqualValues(t, pyString("kittens"), s.Lookup("s8"))
assert.EqualValues(t, pyString("kittens£££"), s.Lookup("s9"))
assert.EqualValues(t, pyString("££kittens££++++"), s.Lookup("s10"))
// Make sure the original string wasn't modified:
assert.EqualValues(t, pyString("kittens"), s.Lookup("orig"))

_, err = parseFile("src/parse/asp/test_data/interpreter/str/rjust_multiple_fillchars.build")
assert.Error(t, err, "fillchar must be exactly one character long")
}
16 changes: 16 additions & 0 deletions src/parse/asp/test_data/interpreter/str/ljust.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
orig = "kittens"

s1 = orig.ljust(10)
s2 = orig.ljust(10, "+")
s3 = orig.ljust(len(orig))
s4 = orig.ljust(len(orig), "+")
s5 = orig.ljust(0)
s6 = orig.ljust(0, "+")
s7 = orig.ljust(-5)
s8 = orig.ljust(-5, "+")

# 1-character, 2-byte fillchar:
s9 = orig.ljust(10, "£")

# Input string containing a multi-byte character:
s10 = "££kittens££".ljust(15, "+")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s = "kittens".ljust(10, "!!")
16 changes: 16 additions & 0 deletions src/parse/asp/test_data/interpreter/str/rjust.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
orig = "kittens"

s1 = orig.rjust(10)
s2 = orig.rjust(10, "+")
s3 = orig.rjust(len(orig))
s4 = orig.rjust(len(orig), "+")
s5 = orig.rjust(0)
s6 = orig.rjust(0, "+")
s7 = orig.rjust(-5)
s8 = orig.rjust(-5, "+")

# 1-character, 2-byte fillchar:
s9 = orig.rjust(10, "£")

# Input string containing a multi-byte character:
s10 = "££kittens££".rjust(15, "+")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
s = "kittens".rjust(10, "!!")
Loading