Skip to content
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
455 changes: 455 additions & 0 deletions examples/fusefmt.fuse

Large diffs are not rendered by default.

15 changes: 0 additions & 15 deletions examples/lambda_calc.fuse
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,6 @@ type List[T]:
Cons(h: T, t: List[T])
Nil

type Option[T]:
None
Some(T)

impl Option[T]:
fun is_some(self) -> bool
match self:
Some(v) => true
_ => false

fun is_none(self) -> bool
match self:
Some(v) => false
_ => true

type Tuple[A, B](A, B)

type Type:
Expand Down
12 changes: 3 additions & 9 deletions examples/list.fuse
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
type List[A]:
Cons(h: A, t: List[A])
Nil

trait Functor[A]:
fun map[B](self, f: A -> B) -> Self[B];

Expand Down Expand Up @@ -39,15 +35,13 @@ impl Functor[A] for List[A]:
fun fmap[A, B, T: Functor](f: A -> B, c: T[A]) -> T[B]
c.map(f)

fun main() -> i32
fun main() -> IO[Unit]
let l = Cons(2, Cons(3, Nil))
let l1 = fmap(v => v + 1, l)
let l2 = Cons(7, Nil)
let l3 = List::append(l1, l2)
let l4 = l3.filter(e => e > 3)
let s = List::sum(l4)
let p = List::product(l4)
let io = print(int_to_str(s + p))
io.exec()
0

print(int_to_str(s + p))

12 changes: 1 addition & 11 deletions examples/option.fuse
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
type Option[A]:
None
Some(A)

impl Option[A]:
fun map[B](self, f: A -> B) -> Option[B]
match self:
Some(v) => Some(f(v))
_ => None

fun main() -> i32
let o = Some(5)
let o1 = o.map(a => a + 1)
match o1:
Some(v) => {
print(int_to_str(v))
print(int_to_str(v)).exec()
0
}
None => 1
7 changes: 1 addition & 6 deletions examples/untyped_lambda.fuse
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
type Option[T]:
None
Some(T)

type Term:
Var(index: i32, ctxlen: i32)
Abs(hint: str, body: Term)
Expand Down Expand Up @@ -71,8 +67,7 @@ fun term_to_str(t: Term) -> str
App(t1, t2) => "(" + term_to_str(t1) + " " + term_to_str(t2) + ")"

fun println(s: str) -> Unit
print(s + "\n")
()
print(s + "\n").exec()

fun main() -> i32
let id = Abs("x", Var(0, 1))
Expand Down
39 changes: 38 additions & 1 deletion grin/prim_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ int64_t _prim_int_print(int64_t p1) {
return 0;
}

struct string* _prim_read_string() {
struct string* _prim_read_string(int64_t unit) {
char *buffer = NULL;
size_t len = 0;
size_t read;
Expand Down Expand Up @@ -305,3 +305,40 @@ int64_t _prim_bool_or(int64_t p1, int64_t p2) {
int64_t _prim_string_ne(struct string* p1, struct string* p2) {
return !_prim_string_eq(p1, p2);
}

extern int g_argc;
extern char** g_argv;

int64_t _prim_args_count(int64_t unit) {
(void)unit;
return (int64_t)(g_argc > 0 ? g_argc - 1 : 0);
}

struct string* _prim_args_get(int64_t idx) {
int64_t total = (int64_t)(g_argc > 0 ? g_argc - 1 : 0);
if (idx < 0 || idx >= total) {
struct string* msg = create_string_copy("args index out of bounds");
_prim_error(msg);
return create_string_len(0);
}
return create_string_copy(g_argv[idx + 1]);
}

int64_t _prim_string_char_at(struct string* s, int64_t idx) {
if (idx < 0 || idx >= s->length) {
struct string* msg = create_string_copy("string index out of bounds");
_prim_error(msg);
return 0;
}
return (int64_t)(unsigned char)s->data[idx];
}

struct string* _prim_string_substring(struct string* s, int64_t start, int64_t end) {
if (start < 0) start = 0;
if (end > s->length) end = s->length;
if (start >= end) return create_string_len(0);
int64_t len = end - start;
struct string* r = create_string_len(len);
memcpy(r->data, s->data + start, len);
return r;
}
8 changes: 7 additions & 1 deletion grin/prim_ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ void cstring(char* buffer, struct string* s);

int64_t _prim_string_print(struct string* p1);
int64_t _prim_int_print(int64_t p1);
struct string* _prim_read_string();
struct string* _prim_read_string(int64_t unit);
int64_t _prim_usleep(int64_t p1);
int64_t _prim_error(struct string* p1);
int64_t _prim_ffi_file_eof(int64_t p1);
Expand Down Expand Up @@ -45,3 +45,9 @@ float _prim_float_mod(float p1, float p2);
int64_t _prim_bool_and(int64_t p1, int64_t p2);
int64_t _prim_bool_or(int64_t p1, int64_t p2);
int64_t _prim_string_ne(struct string* p1, struct string* p2);

int64_t _prim_args_count(int64_t unit);
struct string* _prim_args_get(int64_t idx);

int64_t _prim_string_char_at(struct string* s, int64_t idx);
struct string* _prim_string_substring(struct string* s, int64_t start, int64_t end);
8 changes: 7 additions & 1 deletion grin/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@
extern int64_t _heap_ptr_;
#endif

int g_argc = 0;
char** g_argv = NULL;

int64_t grinMain();

void __runtime_error(int64_t c){
exit(c);
}

int main() {
int main(int argc, char** argv) {
g_argc = argc;
g_argv = argv;

#ifdef USE_BOEHM_GC
GC_INIT();
#else
Expand Down
52 changes: 44 additions & 8 deletions src/main/scala/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.parboiled2.*
import parser.FuseParser
import parser.FuseParser.*
import parser.ParserErrorFormatter
import parser.Types.*

import java.io.*
import java.nio.file.{Files, Path, Paths}
Expand Down Expand Up @@ -66,11 +67,11 @@ object Compiler {
*/
def readStdlibFiles(dir: Path): IO[List[(Path, String)]] = IO.blocking {
import scala.jdk.CollectionConverters.*
val stream = Files.newDirectoryStream(dir, "*.fuse")
val stream = Files.newDirectoryStream(dir)
try
stream.asScala.toList.map(p =>
p -> new String(Files.readAllBytes(p)).trim
)
stream.asScala.toList
.filter(p => p.getFileName.toString.endsWith(".fuse"))
.map(p => p -> new String(Files.readAllBytes(p)).trim)
finally stream.close()
}

Expand Down Expand Up @@ -107,13 +108,48 @@ object Compiler {
case FTupleTypeDecl(_, i, _, _) => i.value
case FTypeAlias(_, i, _, _) => i.value
}.toSet
val requires = decls.collect {
case FTraitInstance(_, traitId, _, _, _, _) =>
traitId.value
}.toSet
val requires = decls.flatMap(declTypeRefs).toSet -- provides
StdlibFileDeps(provides, requires)
}

/** Collect every type-name referenced by a declaration's signature, body type
* annotations, and trait/instance heads. Used by `extractStdlibFileDeps` to
* drive topological ordering: a file referencing `List` in any signature
* must load after the file that provides `List`.
*/
def declTypeRefs(decl: FDecl): Set[String] = decl match {
case FTraitInstance(_, traitId, _, typeId, _, methods) =>
Set(traitId.value, typeId.value) ++ methods.flatMap(declTypeRefs)
case FFuncDecl(sig, _) => funcSigTypeRefs(sig)
case FMethodDecl(sig, _) => methodSigTypeRefs(sig)
case FTypeFuncDecls(_, _, _, methods) => methods.flatMap(declTypeRefs).toSet
case FTraitDecl(_, _, _, members) =>
members.flatMap {
case Left(FMethodDecl(sig, _)) => methodSigTypeRefs(sig)
case Right(sig) => methodSigTypeRefs(sig)
}.toSet
case _ => Set.empty[String]
}

def funcSigTypeRefs(sig: FFuncSig): Set[String] = {
val paramTypes = sig.p.toList.flatten.map(_.t)
(paramTypes :+ sig.r).flatMap(typeRefs).toSet
}

def methodSigTypeRefs(sig: FMethodSig): Set[String] = {
val paramTypes =
sig.p.toList.flatMap(_.params.toList.flatten).map(_.t)
(paramTypes :+ sig.r).flatMap(typeRefs).toSet
}

def typeRefs(t: FType): Set[String] = t match {
case FSimpleType(_, id, args) =>
Set(id.value) ++ args.toList.flatten.flatMap(typeRefs)
case FTupleType(_, ts) => ts.flatMap(typeRefs).toSet
case FFuncType(_, ins, out) => (ins :+ out).flatMap(typeRefs).toSet
case FUnitType(_) => Set.empty
}

/** Kahn-style topological sort. Each pass emits every file whose `requires`
* is satisfied by files already emitted; if no file is ready and the
* frontier is non-empty, the graph has a cycle (error). A `requires` entry
Expand Down
63 changes: 62 additions & 1 deletion src/main/scala/Fuse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ case class BuildFile(file: String, includeStdlib: Boolean = true)
extends Command
case class CheckFile(file: String, includeStdlib: Boolean = true)
extends Command
case class RunFile(
file: String,
args: List[String] = Nil,
includeStdlib: Boolean = true
) extends Command

/** Build pipeline errors. */
sealed trait BuildError
Expand Down Expand Up @@ -72,12 +77,22 @@ object Fuse
fileOpts.map(f => CheckFile(f))
}

val compilerCommand: Opts[Command] = buildCommand `orElse` checkCommand
val runCommand: Opts[RunFile] =
Opts.subcommand("run", "Compile and run a fuse source code file.") {
(
fileOpts,
Opts.arguments[String](metavar = "args").orEmpty.map(_.toList)
).mapN((f, as) => RunFile(f, as))
}

val compilerCommand: Opts[Command] =
buildCommand `orElse` checkCommand `orElse` runCommand

override def main: Opts[IO[ExitCode]] =
compilerCommand.map {
case c: BuildFile => build(c)
case c: CheckFile => check(c)
case c: RunFile => run(c)
}

/** Build pipeline using EitherT for short-circuit error handling. */
Expand All @@ -102,6 +117,52 @@ object Fuse
}
}

/** Compile and run a Fuse source file, forwarding stdio and exit code. */
def run(command: RunFile): IO[ExitCode] =
runFile(command.file, command.args, command.includeStdlib, executeInherited)
.flatMap {
case Right(code) => IO.pure(code)
case Left(err) =>
IO.println(formatBuildError(err)).as(ExitCode.Error)
}

/** Shared compile-then-execute pipeline. Builds the Fuse source to a native
* binary, runs the binary via the supplied executor, and removes the `.grin`
* and `.out` intermediates regardless of executor outcome.
*/
def runFile[A](
file: String,
args: List[String],
includeStdlib: Boolean,
executor: (Path, List[String]) => IO[A]
): IO[Either[BuildError, A]] = {
val paths = BuildPaths.fromSource(file)
val pipeline: EitherT[IO, BuildError, A] = for {
_ <- compileFuseToGrin(BuildFile(file, includeStdlib), paths)
_ <- compileGrinWithGC(paths)
_ <- EitherT.right[BuildError](
cleanupIntermediateFiles(List(paths.grin))
)
result <- EitherT.right[BuildError](
executor(paths.output, args)
.guarantee(cleanupIntermediateFiles(List(paths.output)))
)
} yield result
pipeline.value
}

/** Executor for `run` that forwards stdin/stdout/stderr to the child and
* returns the child's exit code as the CLI exit code.
*/
def executeInherited(exe: Path, args: List[String]): IO[ExitCode] =
IO.blocking {
val code = new ProcessBuilder((exe.toString +: args)*)
.inheritIO()
.start()
.waitFor()
ExitCode(code)
}

/** Format build error for display. */
def formatBuildError(error: BuildError): String = error match {
case FuseCompileError(e) => e.toString
Expand Down
Loading
Loading