feat: add File and Process effects for real I/O
Adds two essential effects that enable Lux to interact with the system:
File effect:
- read(path) - Read file contents as string
- write(path, content) - Write string to file
- append(path, content) - Append to file
- exists(path) - Check if file/directory exists
- delete(path) - Delete a file
- readDir(path) - List directory contents
- isDir(path) - Check if path is directory
- mkdir(path) - Create directory (including parents)
Process effect:
- exec(cmd) - Run shell command, return stdout
- execStatus(cmd) - Run command, return exit code
- env(name) - Get environment variable (returns Option)
- args() - Get command line arguments
- exit(code) - Exit program with code
- cwd() - Get current working directory
- setCwd(path) - Change working directory
Also fixes formatter bug with empty handler blocks in `run ... with {}`.
These effects make Lux capable of writing real CLI tools and scripts.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
50
examples/file_io.lux
Normal file
50
examples/file_io.lux
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// File I/O example - demonstrates the File effect
|
||||||
|
//
|
||||||
|
// This script reads a file, counts lines/words, and writes a report
|
||||||
|
|
||||||
|
fn countLines(content: String): Int = {
|
||||||
|
let lines = String.split(content, "\n")
|
||||||
|
List.length(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn countWords(content: String): Int = {
|
||||||
|
let words = String.split(content, " ")
|
||||||
|
List.length(List.filter(words, fn(w: String): Bool => String.length(w) > 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn analyzeFile(path: String): Unit with {File, Console} = {
|
||||||
|
Console.print("Analyzing file: " + path)
|
||||||
|
|
||||||
|
if File.exists(path) then {
|
||||||
|
let content = File.read(path)
|
||||||
|
let lines = countLines(content)
|
||||||
|
let words = countWords(content)
|
||||||
|
let chars = String.length(content)
|
||||||
|
|
||||||
|
Console.print(" Lines: " + toString(lines))
|
||||||
|
Console.print(" Words: " + toString(words))
|
||||||
|
Console.print(" Chars: " + toString(chars))
|
||||||
|
} else {
|
||||||
|
Console.print(" Error: File not found!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(): Unit with {File, Console} = {
|
||||||
|
Console.print("=== Lux File Analyzer ===")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Analyze this file itself
|
||||||
|
analyzeFile("examples/file_io.lux")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Analyze hello.lux
|
||||||
|
analyzeFile("examples/hello.lux")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Write a report
|
||||||
|
let report = "File analysis complete.\nAnalyzed 2 files."
|
||||||
|
File.write("/tmp/lux_report.txt", report)
|
||||||
|
Console.print("Report written to /tmp/lux_report.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = run main() with {}
|
||||||
58
examples/shell.lux
Normal file
58
examples/shell.lux
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// Shell/Process example - demonstrates the Process effect
|
||||||
|
//
|
||||||
|
// This script runs shell commands and uses environment variables
|
||||||
|
|
||||||
|
fn main(): Unit with {Process, Console} = {
|
||||||
|
Console.print("=== Lux Shell Example ===")
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Get current working directory
|
||||||
|
let cwd = Process.cwd()
|
||||||
|
Console.print("Current directory: " + cwd)
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Get environment variables
|
||||||
|
Console.print("Environment variables:")
|
||||||
|
match Process.env("USER") {
|
||||||
|
Some(user) => Console.print(" USER: " + user),
|
||||||
|
None => Console.print(" USER: (not set)")
|
||||||
|
}
|
||||||
|
match Process.env("HOME") {
|
||||||
|
Some(home) => Console.print(" HOME: " + home),
|
||||||
|
None => Console.print(" HOME: (not set)")
|
||||||
|
}
|
||||||
|
match Process.env("SHELL") {
|
||||||
|
Some(shell) => Console.print(" SHELL: " + shell),
|
||||||
|
None => Console.print(" SHELL: (not set)")
|
||||||
|
}
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Run shell commands
|
||||||
|
Console.print("Running shell commands:")
|
||||||
|
|
||||||
|
let date = Process.exec("date")
|
||||||
|
Console.print(" date: " + String.trim(date))
|
||||||
|
|
||||||
|
let kernel = Process.exec("uname -r")
|
||||||
|
Console.print(" kernel: " + String.trim(kernel))
|
||||||
|
|
||||||
|
let files = Process.exec("ls examples/*.lux | wc -l")
|
||||||
|
Console.print(" .lux files in examples/: " + String.trim(files))
|
||||||
|
Console.print("")
|
||||||
|
|
||||||
|
// Command line arguments
|
||||||
|
Console.print("Command line arguments:")
|
||||||
|
let args = Process.args()
|
||||||
|
let argCount = List.length(args)
|
||||||
|
if argCount == 0 then {
|
||||||
|
Console.print(" (no arguments)")
|
||||||
|
} else {
|
||||||
|
Console.print(" Count: " + toString(argCount))
|
||||||
|
match List.head(args) {
|
||||||
|
Some(first) => Console.print(" First: " + first),
|
||||||
|
None => Console.print(" First: (empty)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = run main() with {}
|
||||||
@@ -167,19 +167,51 @@ impl Formatter {
|
|||||||
|
|
||||||
self.write(" =");
|
self.write(" =");
|
||||||
|
|
||||||
// Body
|
// Body - handle blocks specially to keep `= {` on same line
|
||||||
let body_str = self.format_expr(&func.body);
|
if let Expr::Block { statements, result, .. } = &func.body {
|
||||||
if self.is_block_expr(&func.body) || body_str.contains('\n') {
|
self.write(" {");
|
||||||
self.newline();
|
self.newline();
|
||||||
self.indent_level += 1;
|
self.indent_level += 1;
|
||||||
|
for stmt in statements {
|
||||||
|
let indent = self.indent();
|
||||||
|
match stmt {
|
||||||
|
Statement::Let { name, typ, value, .. } => {
|
||||||
|
let type_str = typ.as_ref()
|
||||||
|
.map(|t| format!(": {}", self.format_type_expr(t)))
|
||||||
|
.unwrap_or_default();
|
||||||
|
self.write(&indent);
|
||||||
|
self.writeln(&format!(
|
||||||
|
"let {}{} = {}",
|
||||||
|
name.name,
|
||||||
|
type_str,
|
||||||
|
self.format_expr(value)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Statement::Expr(e) => {
|
||||||
|
self.write(&indent);
|
||||||
|
self.writeln(&self.format_expr(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.write(&self.indent());
|
self.write(&self.indent());
|
||||||
self.write(&body_str);
|
self.writeln(&self.format_expr(result));
|
||||||
self.indent_level -= 1;
|
self.indent_level -= 1;
|
||||||
|
self.write(&self.indent());
|
||||||
|
self.writeln("}");
|
||||||
} else {
|
} else {
|
||||||
self.write(" ");
|
let body_str = self.format_expr(&func.body);
|
||||||
self.write(&body_str);
|
if body_str.contains('\n') {
|
||||||
|
self.newline();
|
||||||
|
self.indent_level += 1;
|
||||||
|
self.write(&self.indent());
|
||||||
|
self.write(&body_str);
|
||||||
|
self.indent_level -= 1;
|
||||||
|
self.newline();
|
||||||
|
} else {
|
||||||
|
self.write(" ");
|
||||||
|
self.writeln(&body_str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.newline();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_let(&mut self, let_decl: &LetDecl) {
|
fn format_let(&mut self, let_decl: &LetDecl) {
|
||||||
@@ -674,12 +706,16 @@ impl Formatter {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Expr::Run { expr, handlers, .. } => {
|
Expr::Run { expr, handlers, .. } => {
|
||||||
let mut s = format!("run {} with {{\n", self.format_expr(expr));
|
if handlers.is_empty() {
|
||||||
for (effect, handler) in handlers {
|
format!("run {} with {{}}", self.format_expr(expr))
|
||||||
s.push_str(&format!(" {} = {},\n", effect.name, self.format_expr(handler)));
|
} else {
|
||||||
|
let mut s = format!("run {} with {{\n", self.format_expr(expr));
|
||||||
|
for (effect, handler) in handlers {
|
||||||
|
s.push_str(&format!(" {} = {},\n", effect.name, self.format_expr(handler)));
|
||||||
|
}
|
||||||
|
s.push('}');
|
||||||
|
s
|
||||||
}
|
}
|
||||||
s.push('}');
|
|
||||||
s
|
|
||||||
}
|
}
|
||||||
Expr::Resume { value, .. } => {
|
Expr::Resume { value, .. } => {
|
||||||
format!("resume({})", self.format_expr(value))
|
format!("resume({})", self.format_expr(value))
|
||||||
|
|||||||
@@ -2638,6 +2638,253 @@ impl Interpreter {
|
|||||||
thread::sleep(Duration::from_millis(ms));
|
thread::sleep(Duration::from_millis(ms));
|
||||||
Ok(Value::Unit)
|
Ok(Value::Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== File Effect =====
|
||||||
|
("File", "read") => {
|
||||||
|
let path = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.read requires a string path".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match std::fs::read_to_string(&path) {
|
||||||
|
Ok(content) => Ok(Value::String(content)),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Failed to read file '{}': {}", path, e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("File", "write") => {
|
||||||
|
let path = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.write requires a string path".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let content = match request.args.get(1) {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.write requires string content".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match std::fs::write(&path, &content) {
|
||||||
|
Ok(()) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Failed to write file '{}': {}", path, e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("File", "append") => {
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::Write;
|
||||||
|
let path = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.append requires a string path".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let content = match request.args.get(1) {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.append requires string content".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match OpenOptions::new().create(true).append(true).open(&path) {
|
||||||
|
Ok(mut file) => {
|
||||||
|
file.write_all(content.as_bytes()).map_err(|e| RuntimeError {
|
||||||
|
message: format!("Failed to append to file '{}': {}", path, e),
|
||||||
|
span: None,
|
||||||
|
})?;
|
||||||
|
Ok(Value::Unit)
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Failed to open file '{}': {}", path, e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("File", "exists") => {
|
||||||
|
let path = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.exists requires a string path".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
Ok(Value::Bool(std::path::Path::new(&path).exists()))
|
||||||
|
}
|
||||||
|
("File", "delete") => {
|
||||||
|
let path = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.delete requires a string path".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match std::fs::remove_file(&path) {
|
||||||
|
Ok(()) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Failed to delete file '{}': {}", path, e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("File", "readDir") => {
|
||||||
|
let path = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.readDir requires a string path".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match std::fs::read_dir(&path) {
|
||||||
|
Ok(entries) => {
|
||||||
|
let files: Vec<Value> = entries
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
.map(|e| Value::String(e.file_name().to_string_lossy().to_string()))
|
||||||
|
.collect();
|
||||||
|
Ok(Value::List(files))
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Failed to read directory '{}': {}", path, e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("File", "isDir") => {
|
||||||
|
let path = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.isDir requires a string path".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
Ok(Value::Bool(std::path::Path::new(&path).is_dir()))
|
||||||
|
}
|
||||||
|
("File", "mkdir") => {
|
||||||
|
let path = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "File.mkdir requires a string path".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match std::fs::create_dir_all(&path) {
|
||||||
|
Ok(()) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Failed to create directory '{}': {}", path, e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Process Effect =====
|
||||||
|
("Process", "exec") => {
|
||||||
|
use std::process::Command;
|
||||||
|
let cmd = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Process.exec requires a string command".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match Command::new("sh").arg("-c").arg(&cmd).output() {
|
||||||
|
Ok(output) => {
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
|
||||||
|
Ok(Value::String(stdout))
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Failed to execute command: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Process", "execStatus") => {
|
||||||
|
use std::process::Command;
|
||||||
|
let cmd = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Process.execStatus requires a string command".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match Command::new("sh").arg("-c").arg(&cmd).status() {
|
||||||
|
Ok(status) => {
|
||||||
|
let code = status.code().unwrap_or(-1) as i64;
|
||||||
|
Ok(Value::Int(code))
|
||||||
|
}
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Failed to execute command: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Process", "env") => {
|
||||||
|
let var_name = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Process.env requires a string name".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match std::env::var(&var_name) {
|
||||||
|
Ok(value) => Ok(Value::Constructor {
|
||||||
|
name: "Some".to_string(),
|
||||||
|
fields: vec![Value::String(value)],
|
||||||
|
}),
|
||||||
|
Err(_) => Ok(Value::Constructor {
|
||||||
|
name: "None".to_string(),
|
||||||
|
fields: vec![],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Process", "args") => {
|
||||||
|
let args: Vec<Value> = std::env::args()
|
||||||
|
.skip(1) // Skip the program name
|
||||||
|
.map(Value::String)
|
||||||
|
.collect();
|
||||||
|
Ok(Value::List(args))
|
||||||
|
}
|
||||||
|
("Process", "exit") => {
|
||||||
|
let code = match request.args.first() {
|
||||||
|
Some(Value::Int(n)) => *n as i32,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
std::process::exit(code);
|
||||||
|
}
|
||||||
|
("Process", "cwd") => {
|
||||||
|
match std::env::current_dir() {
|
||||||
|
Ok(path) => Ok(Value::String(path.to_string_lossy().to_string())),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Failed to get current directory: {}", e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
("Process", "setCwd") => {
|
||||||
|
let path = match request.args.first() {
|
||||||
|
Some(Value::String(s)) => s.clone(),
|
||||||
|
_ => return Err(RuntimeError {
|
||||||
|
message: "Process.setCwd requires a string path".to_string(),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
match std::env::set_current_dir(&path) {
|
||||||
|
Ok(()) => Ok(Value::Unit),
|
||||||
|
Err(e) => Err(RuntimeError {
|
||||||
|
message: format!("Failed to change directory to '{}': {}", path, e),
|
||||||
|
span: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => Err(RuntimeError {
|
_ => Err(RuntimeError {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Unhandled effect operation: {}.{}",
|
"Unhandled effect operation: {}.{}",
|
||||||
|
|||||||
@@ -935,7 +935,7 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Built-in effects are always available
|
// Built-in effects are always available
|
||||||
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time"];
|
let builtin_effects = ["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process"];
|
||||||
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
let is_builtin = builtin_effects.contains(&effect.name.as_str());
|
||||||
|
|
||||||
// Track this effect for inference
|
// Track this effect for inference
|
||||||
@@ -1440,7 +1440,7 @@ impl TypeChecker {
|
|||||||
|
|
||||||
// Built-in effects are always available in run blocks (they have runtime implementations)
|
// Built-in effects are always available in run blocks (they have runtime implementations)
|
||||||
let builtin_effects: EffectSet =
|
let builtin_effects: EffectSet =
|
||||||
EffectSet::from_iter(["Console", "Fail", "State", "Reader", "Random", "Time"].iter().map(|s| s.to_string()));
|
EffectSet::from_iter(["Console", "Fail", "State", "Reader", "Random", "Time", "File", "Process"].iter().map(|s| s.to_string()));
|
||||||
|
|
||||||
// Extend current effects with handled ones and built-in effects
|
// Extend current effects with handled ones and built-in effects
|
||||||
let combined = self.current_effects.union(&handled_effects).union(&builtin_effects);
|
let combined = self.current_effects.union(&handled_effects).union(&builtin_effects);
|
||||||
|
|||||||
103
src/types.rs
103
src/types.rs
@@ -852,6 +852,109 @@ impl TypeEnv {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add File effect
|
||||||
|
env.effects.insert(
|
||||||
|
"File".to_string(),
|
||||||
|
EffectDef {
|
||||||
|
name: "File".to_string(),
|
||||||
|
type_params: Vec::new(),
|
||||||
|
operations: vec![
|
||||||
|
EffectOpDef {
|
||||||
|
name: "read".to_string(),
|
||||||
|
params: vec![("path".to_string(), Type::String)],
|
||||||
|
return_type: Type::String,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "write".to_string(),
|
||||||
|
params: vec![
|
||||||
|
("path".to_string(), Type::String),
|
||||||
|
("content".to_string(), Type::String),
|
||||||
|
],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "append".to_string(),
|
||||||
|
params: vec![
|
||||||
|
("path".to_string(), Type::String),
|
||||||
|
("content".to_string(), Type::String),
|
||||||
|
],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "exists".to_string(),
|
||||||
|
params: vec![("path".to_string(), Type::String)],
|
||||||
|
return_type: Type::Bool,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "delete".to_string(),
|
||||||
|
params: vec![("path".to_string(), Type::String)],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "readDir".to_string(),
|
||||||
|
params: vec![("path".to_string(), Type::String)],
|
||||||
|
return_type: Type::List(Box::new(Type::String)),
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "isDir".to_string(),
|
||||||
|
params: vec![("path".to_string(), Type::String)],
|
||||||
|
return_type: Type::Bool,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "mkdir".to_string(),
|
||||||
|
params: vec![("path".to_string(), Type::String)],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add Process effect
|
||||||
|
env.effects.insert(
|
||||||
|
"Process".to_string(),
|
||||||
|
EffectDef {
|
||||||
|
name: "Process".to_string(),
|
||||||
|
type_params: Vec::new(),
|
||||||
|
operations: vec![
|
||||||
|
EffectOpDef {
|
||||||
|
name: "exec".to_string(),
|
||||||
|
params: vec![("cmd".to_string(), Type::String)],
|
||||||
|
return_type: Type::String,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "execStatus".to_string(),
|
||||||
|
params: vec![("cmd".to_string(), Type::String)],
|
||||||
|
return_type: Type::Int,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "env".to_string(),
|
||||||
|
params: vec![("name".to_string(), Type::String)],
|
||||||
|
return_type: Type::Option(Box::new(Type::String)),
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "args".to_string(),
|
||||||
|
params: Vec::new(),
|
||||||
|
return_type: Type::List(Box::new(Type::String)),
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "exit".to_string(),
|
||||||
|
params: vec![("code".to_string(), Type::Int)],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "cwd".to_string(),
|
||||||
|
params: Vec::new(),
|
||||||
|
return_type: Type::String,
|
||||||
|
},
|
||||||
|
EffectOpDef {
|
||||||
|
name: "setCwd".to_string(),
|
||||||
|
params: vec![("path".to_string(), Type::String)],
|
||||||
|
return_type: Type::Unit,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Add Some and Ok, Err constructors
|
// Add Some and Ok, Err constructors
|
||||||
// Some : fn(a) -> Option<a>
|
// Some : fn(a) -> Option<a>
|
||||||
let a = Type::var();
|
let a = Type::var();
|
||||||
|
|||||||
Reference in New Issue
Block a user