# High-level abstraction for all text representations
abstract class Text
super Comparable
- super StringCapable
redef type OTHER: Text
# assert "\na\nb\tc\t".trim == "a\nb\tc"
fun trim: SELFTYPE do return (self.l_trim).r_trim
+ # Returns `self` removed from its last line terminator (if any).
+ #
+ # assert "Hello\n".chomp == "Hello"
+ # assert "Hello".chomp == "Hello"
+ #
+ # assert "\n".chomp == ""
+ # assert "".chomp == ""
+ #
+ # Line terminators are `"\n"`, `"\r\n"` and `"\r"`.
+ # A single line terminator, the last one, is removed.
+ #
+ # assert "\r\n".chomp == ""
+ # assert "\r\n\n".chomp == "\r\n"
+ # assert "\r\n\r\n".chomp == "\r\n"
+ # assert "\r\n\r".chomp == "\r\n"
+ #
+ # Note: unlike with most IO methods like `IStream::read_line`,
+ # a single `\r` is considered here to be a line terminator and will be removed.
+ fun chomp: SELFTYPE
+ do
+ var len = length
+ if len == 0 then return self
+ var l = self.chars.last
+ if l == '\r' then
+ return substring(0, len-1)
+ else if l != '\n' then
+ return self
+ else if len > 1 and self.chars[len-2] == '\r' then
+ return substring(0, len-2)
+ else
+ return substring(0, len-1)
+ end
+ end
+
# Justify a self in a space of `length`
#
# `left` is the space ratio on the left side.
# * 1.0 for right-justified (all spaces at the left)
# * 0.5 for centered (half the spaces at the left)
#
+ # Examples
+ #
# assert "hello".justify(10, 0.0) == "hello "
# assert "hello".justify(10, 1.0) == " hello"
# assert "hello".justify(10, 0.5) == " hello "
#
# assert "hello".justify(2, 0.0) == "hello"
#
- # REQUIRE: left >= 0.0 and left <= 1.0
+ # REQUIRE: `left >= 0.0 and left <= 1.0`
# ENSURE: `self.length <= length implies result.length == length`
- # ENSURE: `self.length >= length implies result == self
+ # ENSURE: `self.length >= length implies result == self`
fun justify(length: Int, left: Float): SELFTYPE
do
var diff = length - self.length
return " " * before + self + " " * (diff-before)
end
- # Mangle a string to be a unique string only made of alphanumeric characters
+ # Mangle a string to be a unique string only made of alphanumeric characters and underscores.
+ #
+ # This method is injective (two different inputs never produce the same
+ # output) and the returned string always respect the following rules:
+ #
+ # * Contains only US-ASCII letters, digits and underscores.
+ # * Never starts with a digit.
+ # * Never ends with an underscore.
+ # * Never contains two contiguous underscores.
+ #
+ # assert "42_is/The answer!".to_cmangle == "_52d2_is_47dThe_32danswer_33d"
+ # assert "__".to_cmangle == "_95d_95d"
+ # assert "__d".to_cmangle == "_95d_d"
+ # assert "_d_".to_cmangle == "_d_95d"
+ # assert "_42".to_cmangle == "_95d42"
+ # assert "foo".to_cmangle == "foo"
+ # assert "".to_cmangle == ""
fun to_cmangle: String
do
+ if is_empty then return ""
var res = new FlatBuffer
var underscore = false
- for i in [0..length[ do
- var c = chars[i]
+ var start = 0
+ var c = chars[0]
+
+ if c >= '0' and c <= '9' then
+ res.add('_')
+ res.append(c.ascii.to_s)
+ res.add('d')
+ start = 1
+ end
+ for i in [start..length[ do
+ c = chars[i]
if (c >= 'a' and c <= 'z') or (c >='A' and c <= 'Z') then
res.add(c)
underscore = false
underscore = false
end
end
+ if underscore then
+ res.append('_'.ascii.to_s)
+ res.add('d')
+ end
return res.to_s
end
return buf.to_s
end
- # Escape the four characters `<`, `>`, `&`, and `"` with their html counterpart
+ # Escape the characters `<`, `>`, `&`, `"`, `'` and `/` as HTML/XML entity references.
+ #
+ # assert "a&b-<>\"x\"/'".html_escape == "a&b-<>"x"/'"
#
- # assert "a&b->\"x\"".html_escape == "a&b->"x""
+ # SEE: <https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet#RULE_.231_-_HTML_Escape_Before_Inserting_Untrusted_Data_into_HTML_Element_Content>
fun html_escape: SELFTYPE
do
var buf = new FlatBuffer
else if c == '>' then
buf.append ">"
else if c == '"' then
- buf.append """
+ buf.append """
+ else if c == '\'' then
+ buf.append "'"
+ else if c == '/' then
+ buf.append "/"
else buf.add c
end
end
+# A `String` holds and manipulates an arbitrary sequence of characters.
+#
+# String objects may be created using literals.
+#
+# assert "Hello World!" isa String
abstract class String
super Text
# assert "abc" * 0 == ""
fun *(i: Int): SELFTYPE is abstract
+ # Insert `s` at `pos`.
+ #
+ # assert "helloworld".insert_at(" ", 5) == "hello world"
fun insert_at(s: String, pos: Int): SELFTYPE is abstract
redef fun substrings: Iterator[String] is abstract
#
# SEE : `Char::is_letter` for the definition of letter.
#
- # assert "jAVASCRIPT".capitalized == "Javascript"
- # assert "i am root".capitalized == "I Am Root"
- # assert "ab_c -ab0c ab\nc".capitalized == "Ab_C -Ab0C Ab\nC"
+ # assert "jAVASCRIPT".capitalized == "Javascript"
+ # assert "i am root".capitalized == "I Am Root"
+ # assert "ab_c -ab0c ab\nc".capitalized == "Ab_C -Ab0C Ab\nC"
fun capitalized: SELFTYPE do
if length == 0 then return self
redef fun reversed
do
- var native = calloc_string(self.length + 1)
+ var native = new NativeString(self.length + 1)
var length = self.length
var items = self.items
var pos = 0
redef fun to_upper
do
- var outstr = calloc_string(self.length + 1)
+ var outstr = new NativeString(self.length + 1)
var out_index = 0
var myitems = self.items
redef fun to_lower
do
- var outstr = calloc_string(self.length + 1)
+ var outstr = new NativeString(self.length + 1)
var out_index = 0
var myitems = self.items
if real_items != null then
return real_items.as(not null)
else
- var newItems = calloc_string(length + 1)
+ var newItems = new NativeString(length + 1)
self.items.copy_to(newItems, length, index_from, 0)
newItems[length] = '\0'
self.real_items = newItems
var total_length = my_length + its_length
- var target_string = calloc_string(my_length + its_length + 1)
+ var target_string = new NativeString(my_length + its_length + 1)
self.items.copy_to(target_string, my_length, index_from, 0)
if s isa FlatString then
var my_items = self.items
- var target_string = calloc_string((final_length) + 1)
+ var target_string = new NativeString(final_length + 1)
target_string[final_length] = '\0'
end
+# A mutable sequence of characters.
abstract class Buffer
super Text
#
# SEE: `Char::is_letter` for the definition of a letter.
#
- # var b = new FlatBuffer.from("jAVAsCriPt")"
- # b.capitalize
- # assert b == "Javascript"
- # b = new FlatBuffer.from("i am root")
- # b.capitalize
- # assert b == "I Am Root"
- # b = new FlatBuffer.from("ab_c -ab0c ab\nc")
- # b.capitalize
- # assert b == "Ab_C -Ab0C Ab\nC"
+ # var b = new FlatBuffer.from("jAVAsCriPt")
+ # b.capitalize
+ # assert b == "Javascript"
+ # b = new FlatBuffer.from("i am root")
+ # b.capitalize
+ # assert b == "I Am Root"
+ # b = new FlatBuffer.from("ab_c -ab0c ab\nc")
+ # b.capitalize
+ # assert b == "Ab_C -Ab0C Ab\nC"
fun capitalize do
if length == 0 then return
var c = self[0].to_upper
self[0] = c
- var prev: Char = c
+ var prev = c
for i in [1 .. length[ do
prev = c
c = self[i]
# The COW flag can be set at false here, since
# it does a copy of the current `Buffer`
written = false
- var a = calloc_string(c+1)
+ var a = new NativeString(c+1)
if length > 0 then items.copy_to(a, length, 0, 0)
items = a
capacity = c
redef fun to_cstring
do
if is_dirty then
- var new_native = calloc_string(length + 1)
+ var new_native = new NativeString(length + 1)
new_native[length] = '\0'
if length > 0 then items.copy_to(new_native, length, 0, 0)
real_items = new_native
# Create a new empty string.
init do end
+ # Create a new string copied from `s`.
init from(s: Text)
do
capacity = s.length + 1
length = s.length
- items = calloc_string(capacity)
+ items = new NativeString(capacity)
if s isa FlatString then
s.items.copy_to(items, length, s.index_from, 0)
else if s isa FlatBuffer then
do
assert cap >= 0
# _items = new NativeString.calloc(cap)
- items = calloc_string(cap+1)
+ items = new NativeString(cap+1)
capacity = cap
length = 0
end
redef fun reverse
do
written = false
- var ns = calloc_string(capacity)
+ var ns = new NativeString(capacity)
var si = length - 1
var ni = 0
var it = items
private class FlatBufferCharView
super BufferCharView
- super StringCapable
redef type SELFTYPE: FlatBuffer
# The class name of the object.
#
- # assert 5.class_name == "Int"
+ # assert 5.class_name == "Int"
fun class_name: String do return native_class_name.to_s
# Developer readable representation of `self`.
# Returns true if the char is a numerical digit
#
- # assert '0'.is_numeric
- # assert '9'.is_numeric
- # assert not 'a'.is_numeric
- # assert not '?'.is_numeric
+ # assert '0'.is_numeric
+ # assert '9'.is_numeric
+ # assert not 'a'.is_numeric
+ # assert not '?'.is_numeric
fun is_numeric: Bool
do
return self >= '0' and self <= '9'
# Returns true if the char is an alpha digit
#
- # assert 'a'.is_alpha
- # assert 'Z'.is_alpha
- # assert not '0'.is_alpha
- # assert not '?'.is_alpha
+ # assert 'a'.is_alpha
+ # assert 'Z'.is_alpha
+ # assert not '0'.is_alpha
+ # assert not '?'.is_alpha
fun is_alpha: Bool
do
return (self >= 'a' and self <= 'z') or (self >= 'A' and self <= 'Z')
# Returns true if the char is an alpha or a numeric digit
#
- # assert 'a'.is_alphanumeric
- # assert 'Z'.is_alphanumeric
- # assert '0'.is_alphanumeric
- # assert '9'.is_alphanumeric
- # assert not '?'.is_alphanumeric
+ # assert 'a'.is_alphanumeric
+ # assert 'Z'.is_alphanumeric
+ # assert '0'.is_alphanumeric
+ # assert '9'.is_alphanumeric
+ # assert not '?'.is_alphanumeric
fun is_alphanumeric: Bool
do
return self.is_numeric or self.is_alpha
# Native strings are simple C char *
extern class NativeString `{ char* `}
- super StringCapable
# Creates a new NativeString with a capacity of `length`
new(length: Int) is intern
+
+ # Get char at `index`.
fun [](index: Int): Char is intern
+
+ # Set char `item` at index.
fun []=(index: Int, item: Char) is intern
+
+ # Copy `self` to `dest`.
fun copy_to(dest: NativeString, length: Int, from: Int, to: Int) is intern
# Position of the first nul character.
while self[l] != '\0' do l += 1
return l
end
+
+ # Parse `self` as an Int.
fun atoi: Int is intern
+
+ # Parse `self` as a Float.
fun atof: Float is extern "atof"
redef fun to_s
return to_s_with_length(cstring_length)
end
+ # Returns `self` as a String of `length`.
fun to_s_with_length(length: Int): FlatString
do
assert length >= 0
return str
end
+ # Returns `self` as a new String.
fun to_s_with_copy: FlatString
do
var length = cstring_length
- var new_self = calloc_string(length + 1)
+ var new_self = new NativeString(length + 1)
copy_to(new_self, length, 0, 0)
var str = new FlatString.with_infos(new_self, length, 0, length - 1)
new_self[length] = '\0'
end
end
-# StringCapable objects can create native strings
-interface StringCapable
- protected fun calloc_string(size: Int): NativeString is intern
-end
-
redef class Sys
private var args_cache: nullable Sequence[String]