Skip to content

Commit

Permalink
🚧 Start work on project support
Browse files Browse the repository at this point in the history
  • Loading branch information
MystPi committed Apr 28, 2024
1 parent febad89 commit 971574c
Show file tree
Hide file tree
Showing 23 changed files with 302 additions and 116 deletions.
1 change: 1 addition & 0 deletions examples/TestProject/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
1 change: 1 addition & 0 deletions examples/TestProject/spark.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
name = "TestProject"
22 changes: 22 additions & 0 deletions examples/TestProject/src/TestProject.spark
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Spark/IO
import Spark/List

external "import { add } from './testproject_ffi.mjs';"
external "let $id = 0;"

def pub fresh_id =
external "return $id++"

const squares = List.map([1, 2, 3], \x -> x * x)

def pub main =
IO.println("Hello, world!") ;
IO.println("Goodbye, now.") ;
IO.println(squares) ;
IO.println(@(fresh_id(), fresh_id(), fresh_id()))

# Atoms don't need a name! Unnamed atoms are basically tuples.
const me = @("Joe", 30)

def pub first\@(a, _) = a
def pub second\@(_, b) = b
3 changes: 3 additions & 0 deletions examples/TestProject/src/testproject_ffi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function add(a, b) {
return a + b;
}
Empty file removed examples/blah/build/Blah.mjs
Empty file.
Empty file removed examples/blah/build/Spark/IO.mjs
Empty file.
Empty file.
1 change: 0 additions & 1 deletion examples/blah/spark.toml

This file was deleted.

6 changes: 0 additions & 6 deletions examples/blah/src/Blah.spark

This file was deleted.

2 changes: 2 additions & 0 deletions gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ gleam_community_ansi = ">= 1.4.0 and < 2.0.0"
hug = "~> 1.0"
glam = ">= 2.0.0 and < 3.0.0"
dedent = ">= 1.0.0 and < 2.0.0"
filepath = ">= 1.0.0 and < 2.0.0"
simplifile = ">= 1.7.0 and < 2.0.0"

[dev-dependencies]
gleeunit = "~> 1.0"
Expand Down
4 changes: 4 additions & 0 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
packages = [
{ name = "chomp", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], source = "local", path = "../chomp-nibble" },
{ name = "dedent", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "dedent", source = "hex", outer_checksum = "591C78F019CFE8B4F138BDCA5DB57EB429D95F90CD12B51262A3FC6455EC1AEF" },
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
{ name = "glam", version = "2.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glam", source = "hex", outer_checksum = "66EC3BCD632E51EED029678F8DF419659C1E57B1A93D874C5131FE220DFAD2B2" },
{ name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" },
{ name = "gleam_community_colour", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "795964217EBEDB3DA656F5EB8F67D7AD22872EB95182042D3E7AFEF32D3FD2FE" },
Expand All @@ -12,15 +13,18 @@ packages = [
{ name = "gleeunit", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "72CDC3D3F719478F26C4E2C5FED3E657AC81EC14A47D2D2DEBB8693CA3220C3B" },
{ name = "hug", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib"], otp_app = "hug", source = "hex", outer_checksum = "D7D8C4F5A3093FB0B6A0228288D94E95966AADC5500133F9E983BA4634E92CC8" },
{ name = "pprint", version = "1.0.2", build_tools = ["gleam"], requirements = ["glam", "gleam_stdlib"], otp_app = "pprint", source = "hex", outer_checksum = "3EB2A7A8F72322F73EEF342374D0354AE39E7F9C64678B960BE8B2DC1B564AE1" },
{ name = "simplifile", version = "1.7.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "1D5DFA3A2F9319EC85825F6ED88B8E449F381B0D55A62F5E61424E748E7DDEB0" },
{ name = "thoas", version = "1.2.0", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "540C8CB7D9257F2AD0A14145DC23560F91ACDCA995F0CCBA779EB33AF5D859D1" },
]

[requirements]
chomp = { path = "../chomp-nibble" }
dedent = { version = ">= 1.0.0 and < 2.0.0" }
filepath = { version = ">= 1.0.0 and < 2.0.0" }
glam = { version = ">= 2.0.0 and < 3.0.0" }
gleam_community_ansi = { version = ">= 1.4.0 and < 2.0.0" }
gleam_stdlib = { version = "~> 0.34 or ~> 1.0" }
gleeunit = { version = "~> 1.0" }
hug = { version = "~> 1.0" }
pprint = { version = "~> 1.0" }
simplifile = { version = ">= 1.7.0 and < 2.0.0" }
111 changes: 35 additions & 76 deletions src/spark.gleam
Original file line number Diff line number Diff line change
@@ -1,87 +1,46 @@
import filepath
import gleam/io
import gleam/result
import spark/compile
import spark/error
import spark/lex
import spark/parse
import gleam/result.{try}
import gleam/string
import spark/file
import spark/project
import gleam_community/ansi

pub fn main() {
let source =
"
import Spark/IO
external \"let $id = 0;\"
def pub fresh_id =
external \"return $id++\"
def pub map\\over, fn =
case over
| [] as list = list
| [x : xs] = fn(x) : map(xs, fn)
const squares = map([1, 2, 3], \\x -> x * x)
const foo =
\\sum <- add_with_cb(5, 6)
sum * (2 + 3 * external \"return 5\")
const bar =
[1, 2, 3]
|> baz
|> map(\\x -> x * x)
def pub main =
IO.println(\"Hello, world!\") ;
\"Isn't this language nice?\"
|> IO.println ;
2 * 3
const dsf =
case { x: 3, y: 5, z: 5 }
| { x, y, z } = x + y + z
| { x, y } = x + y
let result = {
use project <- try(project.from("TestProject", "examples/TestProject"))
build(project)
}

def println\\text =
@IO {
perform: \\ -> external \"
console.log(text);
return $.nil;
\"
}
case result {
Ok(_) -> Nil
Error(e) -> io.println_error(e)
}
}

def then\\@IO { perform }, f =
@IO {
perform: \\ -> f(perform()).perform()
}
/// Build a project, with the stdlib and prelude included, to the /build directory.
///
fn build(project: project.Project) -> Result(Nil, String) {
let build_dir = filepath.join(project.dir, "build")

def test =
\\_ <- println(\"Hello!\") |> then
println(\"Goodbye, now.\")
report("Compiling", "Spark")
use template <- try(project.from("Spark", "./templates"))
use _ <- try(project.compile(template, to: build_dir))

# Atoms don't need a name! Unnamed atoms are basically tuples.
const me = @(\"Joe\", 30)
report("Compiling", project.name)
use _ <- try(project.compile(project, to: build_dir))

def pub first\\@(a, _) = a
def pub second\\@(_, b) = b
"
report("Creating", "index file")
let path = filepath.join(project.dir, "build/index.mjs")
let contents = "import { main } from './" <> project.name <> ".mjs';\nmain();"
use _ <- try(file.write_all(path, contents))

let result = {
use tokens <- result.try(lex.lex(source))
use ast <- result.try(parse.parse(tokens))
let compiled = compile.compile(ast)
Ok(compiled)
}
report("Compiled", "successfully")
report("Entry", path)
Ok(Nil)
}

case result {
Ok(js) -> {
io.println(js)
Nil
}
Error(e) ->
error.to_string(e, source, "file.spark")
|> io.println_error
}
fn report(status: String, message: String) {
let padding = string.repeat(" ", 9 - string.length(status))
io.println(padding <> ansi.bold(ansi.blue(status)) <> " " <> message)
}
11 changes: 5 additions & 6 deletions src/spark/ast.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,15 @@ pub type Module {

/// An import consists of the following:
///
/// import foo/bar/baz as blah
/// import Foo/Bar/Baz as Blah
///
/// - The base module name: "foo"
/// - Any paths: ["bar", "baz"]
/// - An optional rename: Some("blah")
/// - import segments: ["Foo", "Bar", "Baz"]
/// - An optional rename: Some("Blah")
///
/// The path list is guaranteed to have at least one element by the parser.
/// The segments list is guaranteed to have at least one element by the parser.
///
pub type Import {
Import(path: List(String), rename: Option(String))
Import(segments: List(String), rename: Option(String))
}

/// A declaration is something at the top-level of a module.
Expand Down
31 changes: 18 additions & 13 deletions src/spark/compile.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,34 @@ import gleam/option.{type Option, None, Some}
import gleam/string
import spark/ast
import spark/compile/pattern
import spark/compile/util
import spark/module
import spark/util

const max_width = 80

// ---- COMPILATION ------------------------------------------------------------

pub fn compile(module: ast.Module) -> String {
module
|> compile_module
pub fn compile(module_ast: ast.Module, module: module.Module) -> String {
module_ast
|> compile_module(module)
|> doc.to_string(max_width)
}

// -- Modules --

fn compile_module(module: ast.Module) -> Document {
// TODO: actually resolve imports
let ast.Module(imports, declarations) = module
fn compile_module(module_ast: ast.Module, module: module.Module) -> Document {
let ast.Module(imports, declarations) = module_ast

let prelude = doc.from_string("import * as $ from './spark.prelude.mjs';")
let prelude =
doc.from_string(
"import * as $ from '"
<> module.resolve_import(module, ["spark.prelude"])
<> "';",
)

let imports =
imports
|> list.map(compile_import)
|> list.map(compile_import(_, module))
|> list.prepend(prelude)
|> doc.join(doc.line)

Expand All @@ -53,16 +58,16 @@ fn compile_module(module: ast.Module) -> Document {
doc.concat([imports, doc.lines(2), declarations])
}

fn compile_import(i: ast.Import) -> Document {
let ast.Import(import_path, rename) = i
fn compile_import(i: ast.Import, module: module.Module) -> Document {
let ast.Import(import_segments, rename) = i

let path = "./" <> string.join(import_path, "/") <> ".mjs"
let path = module.resolve_import(module, import_segments)

let name =
case rename {
Some(rename) -> rename
None -> {
let assert Ok(last) = list.last(import_path)
let assert Ok(last) = list.last(import_segments)
last
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/spark/compile/pattern.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import gleam/option.{None, Some}
import gleam/pair
import gleam/string
import spark/ast
import spark/compile/util
import spark/util

// ---- TYPES ------------------------------------------------------------------

Expand Down
41 changes: 41 additions & 0 deletions src/spark/file.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import filepath
import gleam/result.{try}
import gleam_community/ansi
import simplifile

/// Write a file, creating directories as needed.
///
pub fn write_all(path: String, contents: String) -> Result(Nil, String) {
let dir = filepath.directory_name(path)
use _ <- try(
simplifile.create_directory_all(dir)
|> result.replace_error(file_error("create this directory", dir)),
)
simplifile.write(path, contents)
|> result.replace_error(file_error("write to this file", path))
}

/// Read a file.
///
pub fn read(path: String) -> Result(String, String) {
simplifile.read(path)
|> result.replace_error(file_error("read this file", path))
}

/// Copy a file at the first path to the second path.
///
pub fn copy(from: String, to: String) -> Result(Nil, String) {
simplifile.copy_file(from, to)
|> result.replace_error(file_error("copy this file", from))
}

/// List files in a directory.
///
pub fn get_files(dir: String) -> Result(List(String), String) {
simplifile.get_files(dir)
|> result.replace_error(file_error("read this directory", dir))
}

fn file_error(message, path) -> String {
ansi.red("I was unable to " <> message <> ": ") <> path
}
56 changes: 56 additions & 0 deletions src/spark/module.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import filepath
import gleam/list
import gleam/string
import spark/util

// ---- TYPES ------------------------------------------------------------------

pub type Module {
Module(segments: List(String), path: String)
}

// ---- CONSTRUCTOR ------------------------------------------------------------

pub fn from_path(path: String, inside_dir: String) -> Result(Module, Nil) {
let stripped_path =
path
|> util.remove_prefix(inside_dir <> "/")

let segments = filepath.split(stripped_path)

case
segments
|> list.all(fn(segment) {
util.capitalise(segment) == segment
&& {
filepath.extension(segment) == Ok("spark")
|| filepath.strip_extension(segment) == segment
}
})
{
False -> Error(Nil)
True ->
segments
|> list.map(filepath.strip_extension)
|> Module(path)
|> Ok
}
}

// ---- FUNCTIONS --------------------------------------------------------------

/// Resolves an import from the given module to an import path, from the root of
/// the project.
///
pub fn resolve_import(module: Module, import_segments: List(String)) -> String {
let prefix = case module.segments {
[_] | [] -> "./"
[_, ..rest] ->
rest
|> list.map(fn(_) { ".." })
|> string.join("/")
<> "/"
}

prefix <> string.join(import_segments, with: "/") <> ".mjs"
}
Loading

0 comments on commit 971574c

Please sign in to comment.