feat: simple connection to a php repl
This commit is contained in:
parent
f3f6e9fd44
commit
73d31ea046
12
CHANGELOG.md
12
CHANGELOG.md
@ -21,23 +21,25 @@
|
||||
|
||||
## v0.1.4
|
||||
|
||||
- [ ] Synchronize the THP & PHP repls outputs
|
||||
- [ ] Test semantic analysis
|
||||
- [ ] Generate php code from current AST
|
||||
- [ ] Parse obj/map/dict syntax
|
||||
- [ ] Parse tuple syntax
|
||||
- [ ] Parse class instantiation syntax
|
||||
- [ ] Parse logic operators `&& ||`
|
||||
- [ ] Parse namespace operator `::`
|
||||
- [ ] Implement subtyping for numbers
|
||||
|
||||
|
||||
## v0.1.3
|
||||
|
||||
- [ ] Test semantic analysis
|
||||
- [ ] Generate php code from current AST
|
||||
- [x] Typecheck and semantic check simple assignment
|
||||
- [x] Test correct operator precedence
|
||||
- [x] Parse assignments
|
||||
- [x] Parse dot `.` operator
|
||||
- [ ] Parse logic operators `&& ||`
|
||||
- [x] Parse Array access `arr[pos]`
|
||||
- [ ] Parse namespace operator `::`
|
||||
- [ ] Implement subtyping for numbers
|
||||
- [x] Spawn a PHP repl, and connect the THP repl to it
|
||||
|
||||
|
||||
## v0.1.2
|
||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -152,7 +152,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thp"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"ariadne",
|
||||
"colored",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "thp"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
|
@ -12,6 +12,16 @@ impl Transpilable for PFile<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl PFile<'_> {
|
||||
pub fn transpile_without_header(&self) -> String {
|
||||
let mut fragments = vec![];
|
||||
for statement in self.statements.iter() {
|
||||
fragments.push(statement.transpile());
|
||||
}
|
||||
fragments.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
|
133
src/repl/mod.rs
133
src/repl/mod.rs
@ -1,4 +1,7 @@
|
||||
use ::std::io::{self, Write};
|
||||
use ::std::io::{self, BufRead, BufReader, Error, Read, Write};
|
||||
use ::std::process::{Command, Stdio};
|
||||
use ::std::thread;
|
||||
use ::std::time::Duration;
|
||||
|
||||
use crate::codegen::Transpilable;
|
||||
use crate::error_handling::PrintableError;
|
||||
@ -12,13 +15,95 @@ use crate::php_ast::transformers::PHPTransformable;
|
||||
|
||||
/// Executes the REPL, reading from stdin, compiling and emitting PHP to stdout
|
||||
pub fn run() -> io::Result<()> {
|
||||
// attempt to spawn a php repl
|
||||
|
||||
let php_repl = Command::new("php")
|
||||
.arg("-a")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn();
|
||||
let mut php_repl = match php_repl {
|
||||
Ok(c) => c,
|
||||
Err(error) => {
|
||||
eprintln!("Couldn't open a PHP REPL session: {:?}", error);
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
let mut php_stdin = match php_repl.stdin.take() {
|
||||
Some(handle) => handle,
|
||||
None => {
|
||||
eprintln!("Error: couldn't get stdin handle from PHP REPL");
|
||||
return Err(Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Can't get PHP REPL stdin handle",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let mut php_stdout = match php_repl.stdout.take() {
|
||||
Some(h) => h,
|
||||
None => {
|
||||
eprintln!("Error: couldn't get stdout handle from PHP REPL");
|
||||
return Err(Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"Can't get PHP REPL stdout handle",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let stdin = io::stdin();
|
||||
let mut buffer = String::new();
|
||||
let mut repl_symbol_table = SymbolTable::new();
|
||||
std::populate(&mut repl_symbol_table);
|
||||
|
||||
println!("REPL: Enter expressions to evaluate. Type Ctrl-D to exit.");
|
||||
// start a thread that prints whatever php sends back
|
||||
let php_stdout_handle = thread::spawn(move || {
|
||||
let mut reader = BufReader::new(php_stdout);
|
||||
|
||||
loop {
|
||||
// sleep for 50ms
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
|
||||
// read a line from php
|
||||
let mut line = String::new();
|
||||
match reader.read_line(&mut line) {
|
||||
Ok(n) => {
|
||||
if n == 0 {
|
||||
// EOF
|
||||
break;
|
||||
}
|
||||
if n == 1 {
|
||||
// just a newline
|
||||
continue;
|
||||
}
|
||||
|
||||
// Suppress some php outputs
|
||||
if line == "Interactive shell\n" {
|
||||
continue;
|
||||
}
|
||||
// Ignore anything that starts with `php > `
|
||||
if line.starts_with("php > ") {
|
||||
continue;
|
||||
}
|
||||
|
||||
print!("php output: `{line}`")
|
||||
}
|
||||
Err(error) => {
|
||||
// log error and exit
|
||||
eprint!("Error while reading from PHP STDOUT: {:?}", error);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
println!("php stdout thread finished");
|
||||
});
|
||||
|
||||
println!("REPL: Enter expressions to evaluate. Type Ctrl-D to exit.");
|
||||
let result = loop {
|
||||
// TODO: syncronize the writes to thp stdout
|
||||
// such that the php output doesnt overlap with this
|
||||
print!("> ");
|
||||
io::stdout().flush()?;
|
||||
buffer.clear();
|
||||
@ -30,18 +115,46 @@ pub fn run() -> io::Result<()> {
|
||||
break Ok(());
|
||||
}
|
||||
Ok(_) => {
|
||||
compile(&buffer, &mut repl_symbol_table);
|
||||
match compile(&buffer, &mut repl_symbol_table) {
|
||||
Some(php_code) => {
|
||||
// TODO: this cant be efficient, fix
|
||||
let php_code = format!("{php_code}\n");
|
||||
|
||||
// send php code
|
||||
//println!("{php_code}");
|
||||
match php_stdin.write_all(php_code.as_bytes()) {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
eprintln!("Error writing the generated code to the PHP process.");
|
||||
break Err(error);
|
||||
}
|
||||
};
|
||||
|
||||
// the php repl should respond with its output, and that
|
||||
// will be printed by another thread
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!("Error reading stdin.");
|
||||
break Err(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// kill the php process
|
||||
php_repl.kill().expect("Couldnt KILL child php process...");
|
||||
|
||||
php_stdout_handle
|
||||
.join()
|
||||
.expect("STDOUT thread failed to join...");
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Full pipeline from THP source code to PHP output
|
||||
fn compile(input: &String, symbol_table: &mut SymbolTable) {
|
||||
/// Compiles THP code and returns the generated PHP code as a String
|
||||
fn compile(input: &String, symbol_table: &mut SymbolTable) -> Option<String> {
|
||||
//
|
||||
// Lexical analysis
|
||||
//
|
||||
@ -49,7 +162,7 @@ fn compile(input: &String, symbol_table: &mut SymbolTable) {
|
||||
Ok(t) => t,
|
||||
Err(error) => {
|
||||
error.print_ariadne(input);
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
@ -60,7 +173,7 @@ fn compile(input: &String, symbol_table: &mut SymbolTable) {
|
||||
Ok(ast) => ast,
|
||||
Err(error) => {
|
||||
error.print_ariadne(input);
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
@ -72,7 +185,7 @@ fn compile(input: &String, symbol_table: &mut SymbolTable) {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
error.print_ariadne(input);
|
||||
return;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,5 +197,5 @@ fn compile(input: &String, symbol_table: &mut SymbolTable) {
|
||||
//
|
||||
// Codegen
|
||||
//
|
||||
println!("{}", php_ast.transpile());
|
||||
Some(php_ast.transpile_without_header())
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user