Skip to content

Commit

Permalink
Add 'process' module (#491)
Browse files Browse the repository at this point in the history
Move the `Process` type out of the prelude and into its own module which
contains top-level exported functions rather than having them live as
static methods on a `Process` type (this was always weird).
  • Loading branch information
kengorab authored Nov 8, 2024
1 parent 904e681 commit 5b391ff
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 103 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ jobs:
wget https://github.com/kengorab/abra-lang/releases/latest/download/abra-${{ matrix.platform.suffix }}.tar.gz
tar -xzf abra-${{ matrix.platform.suffix }}.tar.gz -C abra-installation
echo "PATH=$(pwd)/abra-installation:$PATH" >> $GITHUB_ENV
echo "ABRA_HOME=`realpath $(pwd)/abra-installation/std`" >> $GITHUB_ENV
# echo "ABRA_HOME=`realpath $(pwd)/projects/std/src`" >> $GITHUB_ENV
# echo "ABRA_HOME=`realpath $(pwd)/abra-installation/std`" >> $GITHUB_ENV
echo "ABRA_HOME=`realpath $(pwd)/projects/std/src`" >> $GITHUB_ENV
- name: Build package
run: ./projects/compiler/scripts/generate-package.sh ${{ matrix.platform.suffix }} ${{ github.ref_name }}
- name: Upload to release
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/selfhost.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ jobs:
wget https://github.com/kengorab/abra-lang/releases/latest/download/abra-linux.tar.gz
tar -xzf abra-linux.tar.gz -C abra-linux
echo "PATH=$(pwd)/abra-linux:$PATH" >> $GITHUB_ENV
echo "ABRA_HOME=`realpath $(pwd)/abra-linux/std`" >> $GITHUB_ENV
# echo "ABRA_HOME=`realpath $(pwd)/projects/std/src`" >> $GITHUB_ENV
# echo "ABRA_HOME=`realpath $(pwd)/abra-linux/std`" >> $GITHUB_ENV
echo "ABRA_HOME=`realpath $(pwd)/projects/std/src`" >> $GITHUB_ENV
- name: Run tests
run: |
cd projects/compiler
Expand Down
3 changes: 2 additions & 1 deletion projects/compiler/src/compiler.abra
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "fs" as fs
import "process" as process
import Position from "./lexer"
import LiteralAstNode, UnaryOp, BinaryOp, AssignOp, BindingPattern, IndexingMode from "./parser"
import Project, TypedModule, Scope, TypedAstNode, TypedAstNodeKind, Type, TypeKind, Field, Struct, StructOrEnum, TypedInvokee, Function, FunctionKind, Decorator, AccessorPathSegment, TypedAssignmentMode, Enum, TypedEnumVariant, EnumVariantKind, TypedIndexingNode, VariableAlias, TypedMatchCase, TypedMatchCaseKind, BuiltinModule, Variable, Terminator from "./typechecker"
Expand Down Expand Up @@ -3098,7 +3099,7 @@ export type Compiler {
"errno" => {
self._currentFn.block.addComment("begin errno...")

val errnoFnName = match Process.uname().sysname {
val errnoFnName = match process.uname().sysname {
"Linux" => "__errno_location"
"Darwin" => "__error"
_ sysname => todo("[errno] unsupported system " + sysname)
Expand Down
5 changes: 3 additions & 2 deletions projects/compiler/src/compiler.test.abra
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@
// [^1] https://c9x.me/compile

import "fs" as fs
import "process" as process
import getAbsolutePath from "./utils"
import ModuleLoader, Project, Typechecker, TypedModule from "./typechecker"
import Compiler from "./compiler"

func main() {
val abraStdRoot = if Process.getEnvVar("ABRA_HOME") |v| v else {
val abraStdRoot = if process.getEnvVar("ABRA_HOME") |v| v else {
println("Could not find ABRA_HOME (make sure \$ABRA_HOME environment variable is set)")
return
}

val args = Process.args()
val args = process.args()

if args[1] |fileName| {
val absPathSegs = getAbsolutePath(fileName)
Expand Down
3 changes: 2 additions & 1 deletion projects/compiler/src/lexer.test.abra
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// testing.

import "fs" as fs
import "process" as process
import Token, Lexer from "./lexer"
import printTokenAsJson from "./test_utils"

Expand All @@ -22,7 +23,7 @@ export func printTokensAsJson(tokens: Token[]) {
println("]")
}

if Process.args()[1] |fileName| {
if process.args()[1] |fileName| {
match fs.readFile(fileName) {
Ok(contents) => {
match Lexer.tokenize(contents) {
Expand Down
3 changes: 2 additions & 1 deletion projects/compiler/src/main.abra
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import "fs" as fs
import "process" as process
import Lexer from "./lexer"

if Process.args()[1] |fileName| {
if process.args()[1] |fileName| {
match fs.readFile(fileName) {
Ok(contents) => {
val tokens = Lexer.tokenize(contents)
Expand Down
3 changes: 2 additions & 1 deletion projects/compiler/src/parser.test.abra
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
// testing.

import "fs" as fs
import "process" as process
import Lexer from "./lexer"
import Parser from "./parser"
import printParsedModuleAsJson from "./test_utils"

if Process.args()[1] |fileName| {
if process.args()[1] |fileName| {
match fs.readFile(fileName) {
Ok(contents) => {
match Lexer.tokenize(contents) {
Expand Down
5 changes: 3 additions & 2 deletions projects/compiler/src/typechecker.test.abra
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// the actual resulting binary; this results in a separate binary being compiled which is only used for
// testing.

import "process" as process
import getAbsolutePath from "./utils"
import ModuleLoader, Project, Typechecker, TypedModule from "./typechecker"
import Jsonifier from "./typechecker_test_utils"
Expand All @@ -25,12 +26,12 @@ func verifyStdModule(modulesSortedById: TypedModule[], expectedId: Int, name: St
}

func main() {
val abraStdRoot = if Process.getEnvVar("ABRA_HOME") |v| v else {
val abraStdRoot = if process.getEnvVar("ABRA_HOME") |v| v else {
println("Could not find ABRA_HOME (make sure \$ABRA_HOME environment variable is set)")
return
}

if Process.args()[1] |fileName| {
if process.args()[1] |fileName| {
val absPathSegs = getAbsolutePath(fileName)
val filePathAbs = "/" + absPathSegs.join("/")

Expand Down
7 changes: 4 additions & 3 deletions projects/compiler/test/compiler/process.abra
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
// This file is run with arguments passed to it, and environment variables present
import "process" as process

val args = Process.args()
val args = process.args()

/// Expect: [-f, bar, --baz, qux]
println(args[1:])

val bogusEnvVar = Process.getEnvVar("BOGUS")
val bogusEnvVar = process.getEnvVar("BOGUS")
/// Expect: Option.None
println(bogusEnvVar)

val fooEnvVar = Process.getEnvVar("FOO")
val fooEnvVar = process.getEnvVar("FOO")
/// Expect: Option.Some(value: "bar")
println(fooEnvVar)
3 changes: 2 additions & 1 deletion projects/compiler/test/compiler/process_linux.abra
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// This file is run on linux only
// These tests are difficult by nature since there's not many assumptions which will hold 100% of the time.
// Verify the sysname and machine per-platform should be sufficient to prove that it's working correctly.
import "process" as process

val uname = Process.uname()
val uname = process.uname()

/// Expect: Linux
println(uname.sysname)
Expand Down
3 changes: 2 additions & 1 deletion projects/compiler/test/compiler/process_macos.abra
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// This file is run on macos only
// These tests are difficult by nature since there's not many assumptions which will hold 100% of the time.
// Verify the sysname and machine per-platform should be sufficient to prove that it's working correctly.
import "process" as process

val uname = Process.uname()
val uname = process.uname()

/// Expect: Darwin
println(uname.sysname)
Expand Down
87 changes: 1 addition & 86 deletions projects/std/src/prelude.abra
Original file line number Diff line number Diff line change
Expand Up @@ -859,85 +859,6 @@ type Array<T> {
@Stub func reversed(self): T[]
}

type Uname {
sysname: String
nodename: String
release: String
version: String
machine: String
}

type Process {
func args(): String[] {
val argc = intrinsics.argc()
val argv = intrinsics.argv()

val args: String[] = Array.withCapacity(argc)
for i in range(0, argc) {
val str = argv.offset(i).load()
val len = libc.strlen(str)
args.push(String(length: len, _buffer: str))
}

args
}

func getEnvVar(name: String): String? {
val str = libc.getenv(name._buffer)
if str.isNullPtr() return None

val len = libc.strlen(str)
Some(String(length: len, _buffer: str))
}

func uname(): Uname {
// On macOS, each field in the utsname struct is a char[256]; on other platforms it's different, so
// this can be wasteful of memory.
var utsnameBuf = Pointer.malloc<Byte>(256 * 5)
val res = libc.uname(utsnameBuf)
if res != 0 { /* todo: handle error code */ }

val sysnameLen = libc.strlen(utsnameBuf)
val sysname = String.withLength(sysnameLen)
sysname._buffer.copyFrom(utsnameBuf, sysnameLen)

// The utsname struct has 5 fields (6 on some platforms), of constant and equal sizes, but the size of
// each field differs per platform. After extracting the first string, skip over the \0 bytes until we
// reach the start of the next string. From that offset, we can determine the size of each field, and
// extract the remaining fields more efficiently.
var offset = sysnameLen
utsnameBuf = utsnameBuf.offset(offset)
while utsnameBuf.load().asInt() == 0 {
utsnameBuf = utsnameBuf.offset(1)
offset += 1
}

val nodenameLen = libc.strlen(utsnameBuf)
val nodename = String.withLength(nodenameLen)
nodename._buffer.copyFrom(utsnameBuf, nodenameLen)
utsnameBuf = utsnameBuf.offset(offset)

val releaseLen = libc.strlen(utsnameBuf)
val release = String.withLength(releaseLen)
release._buffer.copyFrom(utsnameBuf, releaseLen)
utsnameBuf = utsnameBuf.offset(offset)

val versionLen = libc.strlen(utsnameBuf)
val version = String.withLength(versionLen)
version._buffer.copyFrom(utsnameBuf, versionLen)
utsnameBuf = utsnameBuf.offset(offset)

val machineLen = libc.strlen(utsnameBuf)
val machine = String.withLength(machineLen)
machine._buffer.copyFrom(utsnameBuf, machineLen)

Uname(sysname: sysname, nodename: nodename, release: release, version: version, machine: machine)
}

@noreturn
func exit(status = 1) = libc.exit(status)
}

@noreturn
export func unreachable(message = "") {
println("Encountered unreachable code:", message)
Expand Down Expand Up @@ -1138,13 +1059,7 @@ type Map<K, V> {
if self._entries[i] |bucket| {
var cursor: MapEntry<K, V>? = bucket
while cursor |cur| {
// TODO: there was a bug here somehow - previously it was `"${cur.key}: ${cur.value}"`, which meant
// that each item in the string interpolation was being cast to an instance of Any, which would
// then have `toString` called on it after the fact. I don't know what the issue was but there
// was some problem with llvm generation (likely generic resolution) which led to a bug. Explicitly
// calling `toString()` here seems to fix it.
val item = "${cur.key}: ${cur.value.toString()}"
reprs.push(item)
reprs.push("${cur.key}: ${cur.value}")
cursor = cur.next
}
}
Expand Down
89 changes: 89 additions & 0 deletions projects/std/src/process.abra
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import "./_intrinsics" as intrinsics
import Pointer, Byte from "./_intrinsics"
import "libc" as libc

export type Uname {
sysname: String
nodename: String
release: String
version: String
machine: String
}

var _args: String[]? = None
export func args(): String[] {
if _args |args| return args

val argc = intrinsics.argc()
val argv = intrinsics.argv()

val args: String[] = Array.withCapacity(argc)
for i in range(0, argc) {
val str = argv.offset(i).load()
val len = libc.strlen(str)
args.push(String(length: len, _buffer: str))
}

_args = Some(args)
args
}

export func getEnvVar(name: String): String? {
val str = libc.getenv(name._buffer)
if str.isNullPtr() return None

val len = libc.strlen(str)
Some(String(length: len, _buffer: str))
}

var _uname: Uname? = None
export func uname(): Uname {
if _uname |uname| return uname

// On macOS, each field in the utsname struct is a char[256]; on other platforms it's different, so
// this can be wasteful of memory.
var utsnameBuf = Pointer.malloc<Byte>(256 * 5)
val res = libc.uname(utsnameBuf)
if res != 0 { /* todo: handle error code */ }

val sysnameLen = libc.strlen(utsnameBuf)
val sysname = String.withLength(sysnameLen)
sysname._buffer.copyFrom(utsnameBuf, sysnameLen)

// The utsname struct has 5 fields (6 on some platforms), of constant and equal sizes, but the size of
// each field differs per platform. After extracting the first string, skip over the \0 bytes until we
// reach the start of the next string. From that offset, we can determine the size of each field, and
// extract the remaining fields more efficiently.
var offset = sysnameLen
utsnameBuf = utsnameBuf.offset(offset)
while utsnameBuf.load().asInt() == 0 {
utsnameBuf = utsnameBuf.offset(1)
offset += 1
}

val nodenameLen = libc.strlen(utsnameBuf)
val nodename = String.withLength(nodenameLen)
nodename._buffer.copyFrom(utsnameBuf, nodenameLen)
utsnameBuf = utsnameBuf.offset(offset)

val releaseLen = libc.strlen(utsnameBuf)
val release = String.withLength(releaseLen)
release._buffer.copyFrom(utsnameBuf, releaseLen)
utsnameBuf = utsnameBuf.offset(offset)

val versionLen = libc.strlen(utsnameBuf)
val version = String.withLength(versionLen)
version._buffer.copyFrom(utsnameBuf, versionLen)
utsnameBuf = utsnameBuf.offset(offset)

val machineLen = libc.strlen(utsnameBuf)
val machine = String.withLength(machineLen)
machine._buffer.copyFrom(utsnameBuf, machineLen)

val uname = Uname(sysname: sysname, nodename: nodename, release: release, version: version, machine: machine)
_uname = Some(uname)
uname
}

@noreturn
export func exit(status = 1) = libc.exit(status)

0 comments on commit 5b391ff

Please sign in to comment.