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

Add Hare #7130

Merged
merged 1 commit into from
Nov 25, 2024
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
8 changes: 8 additions & 0 deletions lib/linguist/languages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2945,6 +2945,14 @@ Harbour:
tm_scope: source.harbour
ace_mode: text
language_id: 156
Hare:
type: programming
color: "#9d7424"
extensions:
- ".ha"
ace_mode: text
language_id: 463518941
tm_scope: none
Haskell:
type: programming
color: "#5e5086"
Expand Down
47 changes: 47 additions & 0 deletions samples/Hare/contains.ha
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

// Returns true if a byte slice contains a byte or a sequence of bytes.
export fn contains(haystack: []u8, needles: (u8 | []u8)...) bool = {
for (let i = 0z; i < len(needles); i += 1) {
const matched = match (needles[i]) {
case let b: u8 =>
yield index_byte(haystack, b) is size;
case let b: []u8 =>
yield index_slice(haystack, b) is size;
};
if (matched) {
return true;
};
};
return false;
};

// Returns true if "in" has the given prefix, false otherwise
export fn hasprefix(in: []u8, prefix: []u8) bool = {
return len(in) >= len(prefix) && equal(in[..len(prefix)], prefix);
};

@test fn hasprefix() void = {
assert(hasprefix([], []));
assert(hasprefix([0], []));
assert(!hasprefix([], [0]));
assert(hasprefix([1, 2, 3], [1, 2]));
assert(!hasprefix([1, 2, 3], [1, 1]));
assert(!hasprefix([1, 2, 3], [1, 2, 3, 4]));
};

// Returns true if "in" has the given suffix, false otherwise
export fn hassuffix(in: []u8, suffix: []u8) bool = {
return len(in) >= len(suffix)
&& equal(in[len(in) - len(suffix)..], suffix);
};

@test fn hassuffix() void = {
assert(hassuffix([], []));
assert(hassuffix([0], []));
assert(!hassuffix([], [0]));
assert(hassuffix([1, 2, 3], [2, 3]));
assert(!hassuffix([1, 2, 3], [2, 2]));
assert(hassuffix([1, 2, 3, 4], [2, 3, 4]));
};
194 changes: 194 additions & 0 deletions samples/Hare/iter.ha
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// SPDX-License-Identifier: MPL-2.0
// (c) Hare authors <https://harelang.org>

use ascii;
use strings;
use strconv;
use types;

// Tagged union of the [[formattable]] types and [[mods]]. Used for
// functions which accept format strings.
export type field = (...formattable | *mods);

// Tagged union of all types which are formattable.
export type formattable = (...types::numeric | uintptr | str | rune | bool |
nullable *opaque | void);

// Negative modifier. Specifies for numerical arguments when to prepend a plus
// or minus sign or a blank space.
export type neg = enum {
NONE,
SPACE,
PLUS,
};

// Alignment modifier. Specifies how to align an argument within a given width.
export type alignment = enum {
RIGHT,
CENTER,
LEFT,
};

// Specifies how to format an argument.
export type mods = struct {
alignment: alignment,
pad: rune,
neg: neg,
width: size,
prec: size,
base: strconv::base,
ffmt: strconv::ffmt,
fflags: strconv::fflags,
};

type iterator = struct {
iter: strings::iterator,
args: []field,
idx: size,
checkunused: bool,
};

fn iter(fmt: str, args: []field) iterator = iterator {
iter = strings::iter(fmt),
args = args,
idx = 0,
checkunused = true,
};

fn next(it: *iterator) (str | (formattable, mods) | done) = {
let r = match (strings::next(&it.iter)) {
case done =>
return done;
case let r: rune =>
yield r;
};
switch (r) {
case '{' => void; // handled below
case '}' =>
match (strings::next(&it.iter)) {
case done =>
abort("Invalid format string (hanging '}')");
case let r: rune =>
assert(r == '}', "Invalid format string (hanging '}')");
};
return "}";
case =>
strings::prev(&it.iter);
let start = it.iter;
for (let r => strings::next(&it.iter)) {
if (r == '{' || r == '}') {
strings::prev(&it.iter);
break;
};
};
return strings::slice(&start, &it.iter);
};

r = getrune(it);
if (r == '{') {
return "{";
};

let idx = if (ascii::isdigit(r)) {
strings::prev(&it.iter);
it.checkunused = false;
defer r = getrune(it);
yield scan_sz(it);
} else {
defer it.idx += 1;
yield it.idx;
};
assert(idx < len(it.args), "Not enough parameters given");
let arg = it.args[idx] as formattable;
let mod = mods { ... };

switch (r) {
case ':' =>
scan_modifiers(it, &mod);
case '%' =>
r = getrune(it);
let idx = if (ascii::isdigit(r)) {
strings::prev(&it.iter);
it.checkunused = false;
defer r = getrune(it);
yield scan_sz(it);
} else {
defer it.idx += 1;
yield it.idx;
};
assert(idx < len(it.args), "Not enough parameters given");
mod = *(it.args[idx] as *mods);
assert(r == '}', "Invalid format string (didn't find '}' after modifier index)");
case '}' => void;
case => abort("Invalid format string");
};

return (arg, mod);
};

fn scan_modifiers(it: *iterator, mod: *mods) void = {
mod.pad = ' ';
for (true) switch (getrune(it)) {
// alignment
case '-' => mod.alignment = alignment::LEFT;
case '=' => mod.alignment = alignment::CENTER;
// padding
case '_' => mod.pad = getrune(it);
// negation
case ' ' => mod.neg = neg::SPACE;
case '+' => mod.neg = neg::PLUS;
// base
case 'x' => mod.base = strconv::base::HEX_LOWER;
case 'X' => mod.base = strconv::base::HEX_UPPER;
case 'o' => mod.base = strconv::base::OCT;
case 'b' => mod.base = strconv::base::BIN;
// ffmt
case 'e' => mod.ffmt = strconv::ffmt::E;
case 'f' => mod.ffmt = strconv::ffmt::F;
case 'g' => mod.ffmt = strconv::ffmt::G;
// fflags
case 'F' =>
switch (getrune(it)) {
case 's' => mod.fflags |= strconv::fflags::SHOW_POS;
case '.' => mod.fflags |= strconv::fflags::SHOW_POINT;
case 'U' => mod.fflags |= strconv::fflags::UPPERCASE;
case 'E' => mod.fflags |= strconv::fflags::UPPER_EXP;
case 'S' => mod.fflags |= strconv::fflags::SHOW_POS_EXP;
case '2' => mod.fflags |= strconv::fflags::SHOW_TWO_EXP_DIGITS;
case => abort("Invalid float flag");
};
// precision
case '.' => mod.prec = scan_sz(it);
// width
case '1', '2', '3', '4', '5', '6', '7', '8', '9' =>
strings::prev(it);
mod.width = scan_sz(it);
case =>
strings::prev(it);
break;
};
assert(getrune(it) == '}', "Invalid format string (unterminated '{')");
};

fn scan_sz(it: *iterator) size = {
let start = it.iter;
assert(ascii::isdigit(getrune(it)));
for (ascii::isdigit(getrune(it))) void;
strings::prev(&it.iter);

match (strconv::stoz(strings::slice(&start, &it.iter))) {
case strconv::invalid =>
abort("Invalid format string (invalid integer)");
case strconv::overflow =>
abort("Invalid format string (integer overflow)");
case let z: size =>
return z;
};
};

fn getrune(it: *iterator) rune = match (strings::next(&it.iter)) {
case done =>
abort("Invalid format string (unterminated '{')");
case let r: rune =>
return r;
};
Loading
Loading