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
|
## v0.1.4
|
||||||
|
|
||||||
|
- [ ] Synchronize the THP & PHP repls outputs
|
||||||
|
- [ ] Test semantic analysis
|
||||||
|
- [ ] Generate php code from current AST
|
||||||
- [ ] Parse obj/map/dict syntax
|
- [ ] Parse obj/map/dict syntax
|
||||||
- [ ] Parse tuple syntax
|
- [ ] Parse tuple syntax
|
||||||
- [ ] Parse class instantiation syntax
|
- [ ] Parse class instantiation syntax
|
||||||
|
- [ ] Parse logic operators `&& ||`
|
||||||
|
- [ ] Parse namespace operator `::`
|
||||||
|
- [ ] Implement subtyping for numbers
|
||||||
|
|
||||||
|
|
||||||
## v0.1.3
|
## v0.1.3
|
||||||
|
|
||||||
- [ ] Test semantic analysis
|
|
||||||
- [ ] Generate php code from current AST
|
|
||||||
- [x] Typecheck and semantic check simple assignment
|
- [x] Typecheck and semantic check simple assignment
|
||||||
- [x] Test correct operator precedence
|
- [x] Test correct operator precedence
|
||||||
- [x] Parse assignments
|
- [x] Parse assignments
|
||||||
- [x] Parse dot `.` operator
|
- [x] Parse dot `.` operator
|
||||||
- [ ] Parse logic operators `&& ||`
|
|
||||||
- [x] Parse Array access `arr[pos]`
|
- [x] Parse Array access `arr[pos]`
|
||||||
- [ ] Parse namespace operator `::`
|
- [x] Spawn a PHP repl, and connect the THP repl to it
|
||||||
- [ ] Implement subtyping for numbers
|
|
||||||
|
|
||||||
|
|
||||||
## v0.1.2
|
## v0.1.2
|
||||||
|
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -152,7 +152,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thp"
|
name = "thp"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ariadne",
|
"ariadne",
|
||||||
"colored",
|
"colored",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "thp"
|
name = "thp"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
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::codegen::Transpilable;
|
||||||
use crate::error_handling::PrintableError;
|
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
|
/// Executes the REPL, reading from stdin, compiling and emitting PHP to stdout
|
||||||
pub fn run() -> io::Result<()> {
|
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 stdin = io::stdin();
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
let mut repl_symbol_table = SymbolTable::new();
|
let mut repl_symbol_table = SymbolTable::new();
|
||||||
std::populate(&mut repl_symbol_table);
|
std::populate(&mut repl_symbol_table);
|
||||||
|
|
||||||
|
// 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.");
|
println!("REPL: Enter expressions to evaluate. Type Ctrl-D to exit.");
|
||||||
loop {
|
let result = loop {
|
||||||
|
// TODO: syncronize the writes to thp stdout
|
||||||
|
// such that the php output doesnt overlap with this
|
||||||
print!("> ");
|
print!("> ");
|
||||||
io::stdout().flush()?;
|
io::stdout().flush()?;
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
@ -30,18 +115,46 @@ pub fn run() -> io::Result<()> {
|
|||||||
break Ok(());
|
break Ok(());
|
||||||
}
|
}
|
||||||
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) => {
|
Err(error) => {
|
||||||
eprintln!("Error reading stdin.");
|
eprintln!("Error reading stdin.");
|
||||||
break Err(error);
|
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
|
/// Compiles THP code and returns the generated PHP code as a String
|
||||||
fn compile(input: &String, symbol_table: &mut SymbolTable) {
|
fn compile(input: &String, symbol_table: &mut SymbolTable) -> Option<String> {
|
||||||
//
|
//
|
||||||
// Lexical analysis
|
// Lexical analysis
|
||||||
//
|
//
|
||||||
@ -49,7 +162,7 @@ fn compile(input: &String, symbol_table: &mut SymbolTable) {
|
|||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error.print_ariadne(input);
|
error.print_ariadne(input);
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -60,7 +173,7 @@ fn compile(input: &String, symbol_table: &mut SymbolTable) {
|
|||||||
Ok(ast) => ast,
|
Ok(ast) => ast,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error.print_ariadne(input);
|
error.print_ariadne(input);
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -72,7 +185,7 @@ fn compile(input: &String, symbol_table: &mut SymbolTable) {
|
|||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error.print_ariadne(input);
|
error.print_ariadne(input);
|
||||||
return;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,5 +197,5 @@ fn compile(input: &String, symbol_table: &mut SymbolTable) {
|
|||||||
//
|
//
|
||||||
// Codegen
|
// Codegen
|
||||||
//
|
//
|
||||||
println!("{}", php_ast.transpile());
|
Some(php_ast.transpile_without_header())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user