Skip to content

Instantly share code, notes, and snippets.

@taikedz
Last active October 29, 2024 18:11
Show Gist options
  • Save taikedz/42b05c4e46c4f98ec4500fab1eca16e0 to your computer and use it in GitHub Desktop.
Save taikedz/42b05c4e46c4f98ec4500fab1eca16e0 to your computer and use it in GitHub Desktop.
Zis idea

Zis : a Zig-Inspired Scripting Language (idea)

These are notes for a Zig-inspired strongly-typed GC language, taking some aspects from Python, and retaining some smiilar simplicity as promised by Go.

Zig brings so several great ideas to the table, but a variant of its apporoach for application-space would be very welcome, so it would provide a similar experience to Zig with many of its characteristics:

  • error unions
  • optionals
  • defer
  • block scopes
  • structs
  • struct methods with explicit self
  • strict typing
  • mandatory discard-assignment

It adds on top of this:

  • garbage collection
  • native variable-length lists and mash maps
  • private members on structs where name starts with underscore _
  • native strings and string-interpolation
    • supporting at least basic native types:
    • three primitives (int (u32), float (f64), bool)
    • three composites (string ([]u8), list, map (map(string, any)))
  • Error payloads
  • assert keyword on booleans and optionals (no "truthy" evaluation on other types)
  • simple enumerations (enum { a, b, c}, potentially assignment of primitives)
  • interface types

It would explicitly leave out comptime as the concept doesn't map, and specifially would choose to not implement:

  • pointers - they could be made to map (Go does this) but I am wary of having this alongside a GC
  • any attempt at object orientation - I'm not convinced this has really helped over the last few decades.
  • closures - they make tracing back during debugging a nightmare, and falsely complexify systems. They're also more complicated to implement. Struct interface types with methods can help instead.

To be clear I'm not expecting to implement this any time soon, if at all.... these are just musings in a stub note...

What would it look like?

See Zis_example.zig (should be .zis but changed the extension to use formatting

// No function calls at the top of the file
// Run from a main() function
// We can "import" as well as declare constants
// and define types
// No function calling is allowed. No global/module variables - only constants.
// All content is namespaced.
// There is no importing names into current space.
// Any asssigned names at top level are systematically constants.
import "std" as std;
// We can assign a constant if we want it in current space.
println = std.io.println;
strings = std.util.strings;
lists = std.util.lists;
// Local file imports - just point to the file
// Relative paths resolved from a ZISPATH env var
import "file_handling.zis" as files;
// Structs provide a basic type system
Date = struct {
// The basic numeric is int, which is actually a u32
// also exists long u64
const year:int,
const month:int,
const day:int,
pub fn toString(self) string {
// String interpolation deals directly with expressions.
return "${self.year}/${self.month}/${self.day}";
}
}
Person = struct {
var name:string,
var age:int,
}
// Define an error with the type of its payload (optional)
GeneralError = std.errors.ErrorType(null);
FileOpenError = std.errors.ErrorType(string);
// Unstated return type is always void (but not !void)
fn main() {
// Declaration without a type infers the type
// Declaration can only happen once
// Declaration is either var or const
var message = simpleGreet();
// println takes one argument and always appends LF
println(message);
// We have lists - content and length is mutable
const people:list(string) = ["Alex", "Jay", "Sam"];
// variables (var) can be re-assigned
message = greetPeople(people);
println(message);
{
// Some rudimentary memory control hints can be achieved via scoping
var namesmap = nameIndex(["Alix", "Bub"]);
const a_idx = namesmap["Alix"];
message = "Got Alix at ${a_idx}";
// namesmap and a_idx fall out of scope here
}
println(message); // Message about Alix
}
fn simpleGreet(phrase:?string) string {
// Strings are a basic type
var population:string = "Earthlings";
// We use optional-type syntax to set default values in-function
phrase = phrase orelse "Hello";
// Interpolation is available throguh "${}" notation
// Dereferencing an optional guarantees a value, or panics
var message:string = "${phrase.?} ${population}";
return message;
}
fn greetPeople(names:list(string)) string {
// Optional is available
var message:?string = null;
// Iterable loop
for(names) |name| {
if (message.? == null) {
message = name;
} else {
message = "${message.?}, ${name}";
}
}
return message.? orelse "y'all";
}
fn nameIndex(names:list(string) ) map(string, int) {
// Maps are denoted with curly braces
var namesmap:map(string, int) = {};
// Ranges are a lib, not a language construct
for(names, std.iter.range(i,names.len) ) |name| {
namesmap[name] = i;
}
return namesmap;
}
fn greetGuestList(list_file:str) !list(string) {
// Use the same try/catch mechanisms as Zig
var fh = files.open(list_file, files.MODE_READ) catch |err| {
// First arg is always the message, second is a payload
return FileOpenError("Could not open ${list_file}" , "${std.error.message(err)} : ${std.error.type(err)}");
};
// A file handler must be closed after usage
// Defer closure to end of block
defer fh.close();
const lines = fh.readLines();
var names:list(string) = [];
for(lines) |L| {
if(!strings.startsWith(L, "#") ) {
lists.append(names, strings.strip(L) );
}
}
return names;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment