Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stdlib)!: Add an Ascii submodule to Char and move isAscii, toUppercase, toLowercase #2178

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
92 changes: 70 additions & 22 deletions compiler/test/stdlib/char.test.gr
Original file line number Diff line number Diff line change
Expand Up @@ -87,25 +87,73 @@ assert !('a' >= 'b')
assert 'a' >= 'a'
assert 'B' >= 'B'

// isAsciiDigit
assert Char.isAsciiDigit('1')
assert !Char.isAsciiDigit('a')
assert !Char.isAsciiDigit('🌾')

// isAsciiAlpha
assert !Char.isAsciiAlpha('1')
assert Char.isAsciiAlpha('a')
assert Char.isAsciiAlpha('Z')
assert !Char.isAsciiAlpha('λ')

// toAsciiLowercase
assert Char.toAsciiLowercase('A') == 'a'
assert Char.toAsciiLowercase('a') == 'a'
assert Char.toAsciiLowercase('1') == '1'
assert Char.toAsciiLowercase('λ') == 'λ'

// toAsciiUppercase
assert Char.toAsciiUppercase('a') == 'A'
assert Char.toAsciiUppercase('A') == 'A'
assert Char.toAsciiUppercase('1') == '1'
assert Char.toAsciiUppercase('λ') == 'λ'
// Char.Ascii
module AsciiTest {
use Char.{ module Ascii }

// isValid
assert Ascii.isValid('1')
assert Ascii.isValid('a')
assert Ascii.isValid(';')
assert Ascii.isValid(' ')
assert Ascii.isValid('\n')
assert !Ascii.isValid('🌾')

// isDigit
assert Ascii.isDigit('1')
assert !Ascii.isDigit('a')
assert !Ascii.isDigit('🌾')

// isAlpha
assert !Ascii.isAlpha('1')
assert Ascii.isAlpha('a')
assert Ascii.isAlpha('Z')
assert !Ascii.isAlpha('λ')

// isControl
assert Ascii.isControl('\n')
assert Ascii.isControl('\t')
assert Ascii.isControl('\u{007F}')
assert !Ascii.isControl(' ')
assert !Ascii.isControl('a')
assert !Ascii.isControl('🌾')

// isWhitespace
assert Ascii.isWhitespace(' ')
assert Ascii.isWhitespace('\t')
assert Ascii.isWhitespace('\n')
assert Ascii.isWhitespace('\r')
assert Ascii.isWhitespace('\x0C')
assert !Ascii.isWhitespace('a')
assert !Ascii.isWhitespace('1')
assert !Ascii.isWhitespace('🌾')

// isPunctuation
assert Ascii.isPunctuation('!')
assert Ascii.isPunctuation('?')
assert Ascii.isPunctuation('.')
assert Ascii.isPunctuation(',')
assert !Ascii.isPunctuation('1')
assert !Ascii.isPunctuation('a')
assert !Ascii.isPunctuation('🌾')

// isGraphic
assert Ascii.isGraphic('1')
assert Ascii.isGraphic('a')
assert Ascii.isGraphic('!')
assert !Ascii.isGraphic('\n')
assert !Ascii.isGraphic('\t')
assert !Ascii.isGraphic('🌾')

// toLowercase
assert Ascii.toLowercase('A') == 'a'
assert Ascii.toLowercase('a') == 'a'
assert Ascii.toLowercase('1') == '1'
assert Ascii.toLowercase('λ') == 'λ'

// toUppercase
assert Ascii.toUppercase('a') == 'A'
assert Ascii.toUppercase('A') == 'A'
assert Ascii.toUppercase('1') == '1'
assert Ascii.toUppercase('λ') == 'λ'
}
210 changes: 158 additions & 52 deletions stdlib/char.gr
Original file line number Diff line number Diff line change
Expand Up @@ -315,64 +315,170 @@ provide let (>=) = (x: Char, y: Char) => {
}

/**
* Checks if the character is an ASCII digit.
* Sub module for working with ASCII characters.
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @param char: The character to check
* @returns `true` if the character is an ASCII digit or `false` otherwise
*
* @example assert Char.isAsciiDigit('1')
* @example assert !Char.isAsciiDigit('a')
* @example Char.Ascii.isAscii('1')
Copy link
Member

Choose a reason for hiding this comment

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

I think it's fine to do the examples as just Ascii. without the leading Char..

Copy link
Member Author

@spotandjake spotandjake Oct 30, 2024

Choose a reason for hiding this comment

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

I think we should keep the Char.Ascii.isAscii, I feel like it's not very long and it makes the examples runnable, in other modules such as array I was using the pattern:

use Array.{ module Immutable }
assert Immutable.isEmpty(Immutable.empty) == true

but I felt that the module name ascii was small enough here to just directly inline it. I think there is real value in having our examples be as runnable as possible while still being small.

*
* @since v0.6.0
* @since v0.7.0
*/
provide let isAsciiDigit = char => char >= '0' && char <= '9'
provide module Ascii {
/**
* The minimum valid ASCII character code.
*
* @since v0.7.0
*/
provide let min = 0x00
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't these be chars? If someone really needs the code then they can do a Char.code on it.

Copy link
Member Author

@spotandjake spotandjake Oct 30, 2024

Choose a reason for hiding this comment

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

We currently use the code on the top level module Char.min. I think if we really want to change that we should do it in a seperate pr after this. Though there is the argument here that min and max are odd values for chars whereas they make more sense in relation to charCodes. maybe we have both minCode, maxCode and min, max?


/**
* Checks if the character is an ASCII alphabetical character.
*
* @param char: The character to check
* @returns `true` if the character is an ASCII alphabetical or `false` otherwise
*
* @example assert Char.isAsciiAlpha('a')
* @example assert !Char.isAsciiAlpha('1')
*
* @since v0.6.0
*/
provide let isAsciiAlpha = char =>
char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z'
/**
* The maximum valid ASCII character code.
*
* @since v0.7.0
*/
provide let max = 0x7F

/**
* Converts the character to ASCII lowercase if it is an ASCII uppercase character.
*
* @param char: The character to convert
* @returns The lowercased character
*
* @example assert Char.toAsciiLowercase('B') == 'b'
*
* @since v0.6.0
*/
provide let toAsciiLowercase = char => {
if (char >= 'A' && char <= 'Z') {
fromCode(code(char) + 0x20)
} else {
char
/**
* Checks if the character is a valid ASCII character.
*
* @param char: The character to check
* @returns `true` if the character is an ASCII character or `false` otherwise
*
* @example assert Char.Ascii.isValid('1')
* @example assert Char.Ascii.isValid('a')
* @example assert !Char.Ascii.isValid('🌾')
*
* @since v0.7.0
*/
provide let isValid = char => char <= '\u{007F}' // usv <= 0x7F
spotandjake marked this conversation as resolved.
Show resolved Hide resolved

/**
* Checks if the character is an ASCII digit.
*
* @param char: The character to check
* @returns `true` if the character is an ASCII digit or `false` otherwise
*
* @example assert Char.Ascii.isDigit('1')
* @example assert !Char.Ascii.isDigit('a')
*
* @since v0.7.0
* @history v0.6.0: Originally `Char.isAsciiDigit`
*/
provide let isDigit = char => char >= '0' && char <= '9'

/**
* Checks if the character is an ASCII alphabetical character.
*
* @param char: The character to check
* @returns `true` if the character is an ASCII alphabetical or `false` otherwise
*
* @example assert Char.Ascii.isAlpha('a')
* @example assert !Char.Ascii.isAlpha('1')
*
* @since v0.7.0
* @history v0.6.0: Originally `Char.isAsciiAlpha`
*/
provide let isAlpha = char =>
char >= 'a' && char <= 'z' || char >= 'A' && char <= 'Z'

/**
* Checks if the character is an ASCII control character.
*
* @param char: The character to check
* @returns `true` if the character is an ASCII control character or `false` otherwise
*
* @example assert Char.Ascii.isControl('\t')
* @example assert Char.Ascii.isControl('\n')
* @example assert !Char.Ascii.isControl('1')
* @example assert !Char.Ascii.isControl('a')
*
* @since v0.7.0
*/
provide let isControl = char => char <= '\u{001F}' || char == '\u{007F}'

/**
* Checks if the character is an ASCII whitespace character.
*
* @param char: The character to check
* @returns `true` if the character is an ASCII whitespace character or `false` otherwise
*
* @example assert Char.isWhitespace('\t')
* @example assert Char.isWhitespace('\n')
* @example assert !Char.isWhitespace('1')
* @example assert !Char.isWhitespace('a')
*
* @since v0.7.0
*/
provide let isWhitespace = char => {
match (char) {
'\t' | '\n' | '\x0C' | '\r' | ' ' => true,
_ => false,
}
}
}

/**
* Converts the character to ASCII uppercase if it is an ASCII lowercase character.
*
* @param char: The character to convert
* @returns The uppercased character
*
* @example assert Char.toAsciiUppercase('b') == 'B'
*
* @since v0.6.0
*/
provide let toAsciiUppercase = char => {
if (char >= 'a' && char <= 'z') {
fromCode(code(char) - 0x20)
} else {
char
/**
* Checks if the character is an ASCII punctuation character.
*
* @param char: The character to check
* @returns `true` if the character is an ASCII punctuation character or `false` otherwise
*
* @example assert Char.Ascii.isPunctuation('!')
* @example assert !Char.Ascii.isPunctuation('1')
*
* @since v0.7.0
*/
provide let isPunctuation = char =>
char >= '!' && char <= '/' ||
char >= ':' && char <= '@' ||
char >= '[' && char <= '`' ||
char >= '{' && char <= '~'

/**
* Checks if the character is an ASCII graphic character.
*
* @param char: The character to check
* @returns `true` if the character is an ASCII graphic character or `false` otherwise
*
* @example assert Char.Ascii.isGraphic('!')
* @example assert !Char.Ascii.isGraphic('\t')
*
* @since v0.7.0
*/
provide let isGraphic = char => char >= '!' && char <= '~'

/**
* Converts the character to ASCII lowercase if it is an ASCII uppercase character.
*
* @param char: The character to convert
* @returns The lowercased character
*
* @example assert Char.Ascii.toLowercase('B') == 'b'
*
* @since v0.7.0
* @history v0.6.0: Originally `Char.toAsciiLowercase`
*/
provide let toLowercase = char => {
if (char >= 'A' && char <= 'Z') {
fromCode(code(char) + 0x20)
} else {
char
}
}

/**
* Converts the character to ASCII uppercase if it is an ASCII lowercase character.
*
* @param char: The character to convert
* @returns The uppercased character
*
* @example assert Char.Ascii.toUppercase('b') == 'B'
*
* @since v0.7.0
* @history v0.6.0: Originally `Char.toAsciiUppercase`
*/
provide let toUppercase = char => {
if (char >= 'a' && char <= 'z') {
fromCode(code(char) - 0x20)
} else {
char
}
}
}
Loading
Loading