Skip to content

Commit a43d65b

Browse files
committed
goroutines
Signed-off-by: Tyler Laprade <[email protected]>
1 parent bf5d132 commit a43d65b

File tree

27 files changed

+467
-160
lines changed

27 files changed

+467
-160
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ For unimplemented features, the transpiler generates TODO comments:
124124
| └ Method definitions ||
125125
| └ Method calls ||
126126
| └ Function literals/closures ||
127-
| **`go` - Goroutines** | |
127+
| **`go` - Goroutines** | |
128128
| **`goto` - Goto statements** ||
129129
| **`if` - If statements** | |
130130
| └ Basic if ||

go/imports.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ func (it *ImportTracker) GenerateImports() string {
8080
imports = append(imports, fmt.Sprintf("use std::fmt::{%s};", strings.Join(fmtImports, ", ")))
8181
}
8282

83+
// Thread and time imports
84+
if it.needs["thread"] {
85+
imports = append(imports, "use std::thread;")
86+
}
87+
if it.needs["time::Duration"] {
88+
imports = append(imports, "use std::time::Duration;")
89+
}
90+
8391
// Other imports
8492
if it.needs["Error"] {
8593
imports = append(imports, "use std::error::Error;")

go/stdlib.go

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func init() {
4848
"sort.Strings": transpileSortStrings,
4949
"sort.Ints": transpileSortInts,
5050
"slices.Sort": transpileSlicesSort,
51+
"time.Sleep": transpileTimeSleep,
5152
}
5253

5354
builtinMappings = map[string]StdlibHandler{
@@ -776,9 +777,78 @@ func transpilePanic(out *strings.Builder, call *ast.CallExpr) {
776777
func transpileRecover(out *strings.Builder, call *ast.CallExpr) {
777778
// In Rust, we can use std::panic::catch_unwind for similar functionality
778779
// For now, we'll generate a placeholder that returns None
779-
// TODO: A proper implementation would need to track defer context and use catch_unwind
780+
// A proper implementation would need to track defer context and use catch_unwind
780781
// This is a simplified version that always returns None
781-
WriteWrappedNone(out)
782-
out.WriteString("::<String>))")
782+
out.WriteString("Arc::new(Mutex::new(None::<String>))")
783+
}
784+
785+
// transpileTimeSleep handles the time.Sleep function
786+
func transpileTimeSleep(out *strings.Builder, call *ast.CallExpr) {
787+
// Track that we need time and thread imports
788+
TrackImport("thread")
789+
TrackImport("time::Duration")
790+
791+
if len(call.Args) > 0 {
792+
// time.Sleep takes a Duration in nanoseconds in Go
793+
// We need to convert to milliseconds for Rust's Duration::from_millis
794+
// Handle different cases of duration arguments
795+
796+
// Check if it's a simple multiplication like 500 * time.Millisecond
797+
if binOp, ok := call.Args[0].(*ast.BinaryExpr); ok && binOp.Op == token.MUL {
798+
// Check if one side is time.Millisecond, time.Second, etc.
799+
var multiplier ast.Expr
800+
var unit string
801+
802+
if sel, ok := binOp.Y.(*ast.SelectorExpr); ok {
803+
if ident, ok := sel.X.(*ast.Ident); ok && ident.Name == "time" {
804+
unit = sel.Sel.Name
805+
multiplier = binOp.X
806+
}
807+
} else if sel, ok := binOp.X.(*ast.SelectorExpr); ok {
808+
if ident, ok := sel.X.(*ast.Ident); ok && ident.Name == "time" {
809+
unit = sel.Sel.Name
810+
multiplier = binOp.Y
811+
}
812+
}
813+
814+
if unit != "" {
815+
out.WriteString("std::thread::sleep(std::time::Duration::")
816+
switch unit {
817+
case "Millisecond":
818+
out.WriteString("from_millis(")
819+
TranspileExpression(out, multiplier)
820+
case "Second":
821+
out.WriteString("from_secs(")
822+
TranspileExpression(out, multiplier)
823+
case "Microsecond":
824+
out.WriteString("from_micros(")
825+
TranspileExpression(out, multiplier)
826+
case "Nanosecond":
827+
out.WriteString("from_nanos(")
828+
TranspileExpression(out, multiplier)
829+
case "Minute":
830+
out.WriteString("from_secs(60 * ")
831+
TranspileExpression(out, multiplier)
832+
case "Hour":
833+
out.WriteString("from_secs(3600 * ")
834+
TranspileExpression(out, multiplier)
835+
default:
836+
// Unknown unit, default to milliseconds
837+
out.WriteString("from_millis(")
838+
TranspileExpression(out, multiplier)
839+
}
783840

841+
if unit == "Minute" || unit == "Hour" {
842+
out.WriteString(")")
843+
}
844+
out.WriteString("))")
845+
return
846+
}
847+
}
848+
849+
// Fallback: assume it's a duration in nanoseconds
850+
out.WriteString("std::thread::sleep(std::time::Duration::from_nanos(")
851+
TranspileExpression(out, call.Args[0])
852+
out.WriteString(" as u64))")
853+
}
784854
}

go/stmt.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,6 +1584,131 @@ func TranspileStatement(out *strings.Builder, stmt ast.Stmt, fnType *ast.FuncTyp
15841584
// Restore previous capture renames
15851585
currentCaptureRenames = oldCaptureRenames
15861586

1587+
case *ast.GoStmt:
1588+
// Track that we need thread import
1589+
TrackImport("thread")
1590+
1591+
// Check if the go statement contains a closure that captures variables
1592+
captured := findCapturedInCall(s.Call)
1593+
1594+
// Generate clones for captured variables
1595+
// Sort variable names for deterministic output
1596+
var capturedVars []string
1597+
for varName := range captured {
1598+
capturedVars = append(capturedVars, varName)
1599+
}
1600+
slices.Sort(capturedVars)
1601+
1602+
for _, varName := range capturedVars {
1603+
out.WriteString("let ")
1604+
out.WriteString(varName)
1605+
out.WriteString("_thread = ")
1606+
out.WriteString(varName)
1607+
out.WriteString(".clone(); ")
1608+
}
1609+
1610+
// Store current capture renames for nested transpilation
1611+
captureRenames := make(map[string]string)
1612+
for _, varName := range capturedVars {
1613+
captureRenames[varName] = varName + "_thread"
1614+
}
1615+
oldCaptureRenames := currentCaptureRenames
1616+
currentCaptureRenames = captureRenames
1617+
1618+
// Generate the thread::spawn call
1619+
out.WriteString("std::thread::spawn(move || {\n")
1620+
out.WriteString(" ")
1621+
1622+
// Check if it's an immediately invoked function literal
1623+
if funcLit, ok := s.Call.Fun.(*ast.FuncLit); ok {
1624+
// Generate the closure body inline
1625+
if len(s.Call.Args) > 0 {
1626+
// Has arguments - need to create parameter bindings
1627+
out.WriteString("let __closure = move |")
1628+
// Parameters
1629+
if funcLit.Type.Params != nil {
1630+
var params []string
1631+
for _, field := range funcLit.Type.Params.List {
1632+
paramType := GoTypeToRust(field.Type)
1633+
for _, name := range field.Names {
1634+
params = append(params, name.Name+": "+paramType)
1635+
}
1636+
}
1637+
out.WriteString(strings.Join(params, ", "))
1638+
}
1639+
out.WriteString("| {\n ")
1640+
1641+
// Body
1642+
for i, stmt := range funcLit.Body.List {
1643+
if i > 0 {
1644+
out.WriteString("\n ")
1645+
}
1646+
TranspileStatementSimple(out, stmt, funcLit.Type, fileSet)
1647+
out.WriteString(";")
1648+
}
1649+
1650+
out.WriteString("\n };\n")
1651+
out.WriteString(" __closure(")
1652+
1653+
// Arguments
1654+
for i, arg := range s.Call.Args {
1655+
if i > 0 {
1656+
out.WriteString(", ")
1657+
}
1658+
// Wrap arguments appropriately
1659+
if ident, ok := arg.(*ast.Ident); ok && ident.Name != "nil" && ident.Name != "_" {
1660+
// Check if this is a variable (not a constant)
1661+
if _, isRangeVar := rangeLoopVars[ident.Name]; !isRangeVar {
1662+
if _, isLocalConst := localConstants[ident.Name]; !isLocalConst {
1663+
// It's a variable, clone it
1664+
if captureRenames[ident.Name] != "" {
1665+
out.WriteString(captureRenames[ident.Name])
1666+
} else {
1667+
out.WriteString(ident.Name)
1668+
out.WriteString(".clone()")
1669+
}
1670+
} else {
1671+
// It's a constant, wrap it
1672+
out.WriteString("Arc::new(Mutex::new(Some(")
1673+
TranspileExpression(out, arg)
1674+
out.WriteString(")))")
1675+
}
1676+
} else {
1677+
// Range variable, wrap it
1678+
out.WriteString("Arc::new(Mutex::new(Some(")
1679+
TranspileExpression(out, arg)
1680+
out.WriteString(")))")
1681+
}
1682+
} else {
1683+
// Complex expression or literal, wrap it
1684+
out.WriteString("Arc::new(Mutex::new(Some(")
1685+
TranspileExpression(out, arg)
1686+
out.WriteString(")))")
1687+
}
1688+
}
1689+
out.WriteString(")")
1690+
} else {
1691+
// No arguments - just inline the body
1692+
for i, stmt := range funcLit.Body.List {
1693+
if i > 0 {
1694+
out.WriteString("\n ")
1695+
}
1696+
TranspileStatementSimple(out, stmt, funcLit.Type, fileSet)
1697+
out.WriteString(";")
1698+
}
1699+
}
1700+
} else {
1701+
// Regular function call
1702+
TranspileCall(out, s.Call)
1703+
}
1704+
1705+
out.WriteString(";\n")
1706+
out.WriteString(" })")
1707+
out.WriteString(";")
1708+
1709+
// Restore previous capture renames
1710+
currentCaptureRenames = oldCaptureRenames
1711+
15871712
default:
15881713
out.WriteString("// TODO: Unhandled statement type: " + strings.TrimPrefix(fmt.Sprintf("%T", s), "*ast."))
15891714
}

tests.bats

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,14 @@ run_xfail_test() {
319319
run_test "tests/functions_multiple_return"
320320
}
321321

322+
@test "goroutines_basic" {
323+
run_test "tests/goroutines_basic"
324+
}
325+
326+
@test "goroutines_simple" {
327+
run_test "tests/goroutines_simple"
328+
}
329+
322330
@test "hello_world" {
323331
run_test "tests/hello_world"
324332
}
@@ -559,14 +567,6 @@ run_xfail_test() {
559567
run_xfail_test "tests/XFAIL/generics_basic"
560568
}
561569

562-
@test "XFAIL: goroutines_basic" {
563-
run_xfail_test "tests/XFAIL/goroutines_basic"
564-
}
565-
566-
@test "XFAIL: goroutines_simple" {
567-
run_xfail_test "tests/XFAIL/goroutines_simple"
568-
}
569-
570570
@test "XFAIL: goto_labels" {
571571
run_xfail_test "tests/XFAIL/goto_labels"
572572
}

tests/XFAIL/channel_sync/main.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
use std::sync::{Arc, Mutex};
2+
use std::thread;
3+
use std::time::Duration;
24

35
pub fn worker(done: Arc<Mutex<Option</* TODO: Unhandled type *ast.ChanType */ Arc<Mutex<Option<()>>>>>>) {
46
(*fmt.lock().unwrap().as_mut().unwrap()).print(Arc::new(Mutex::new(Some("working...".to_string()))));
5-
(*time.lock().unwrap().as_mut().unwrap()).sleep(Arc::new(Mutex::new(Some(500 * (*(*time.lock().unwrap().as_mut().unwrap())::millisecond.lock().unwrap().as_ref().unwrap())))));
7+
std::thread::sleep(std::time::Duration::from_millis(500));
68
println!("{}", "done".to_string());
79

810
// TODO: Unhandled statement type: SendStmt
911
}
1012

1113
fn main() {
1214
let mut done = ;
13-
// TODO: Unhandled statement type: GoStmt
15+
std::thread::spawn(move || {
16+
worker(done.clone());
17+
});
1418

1519
<-(*done.lock().unwrap().as_mut().unwrap());
1620
}

tests/XFAIL/channels_basic/main.rs

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use std::sync::{Arc, Mutex};
2+
use std::thread;
3+
use std::time::Duration;
24

35
pub fn sender(ch: Arc<Mutex<Option</* TODO: Unhandled type *ast.ChanType */ Arc<Mutex<Option<()>>>>>>) {
46
let mut i = Arc::new(Mutex::new(Some(1)));
57
while (*i.lock().unwrap().as_mut().unwrap()) <= 5 {
68
print!("Sending: {}\n", (*i.lock().unwrap().as_mut().unwrap()));
79
// TODO: Unhandled statement type: SendStmt
8-
(*time.lock().unwrap().as_mut().unwrap()).sleep(Arc::new(Mutex::new(Some(100 * (*(*time.lock().unwrap().as_mut().unwrap())::millisecond.lock().unwrap().as_ref().unwrap())))));
10+
std::thread::sleep(std::time::Duration::from_millis(100));
911
{ let mut guard = i.lock().unwrap(); *guard = Some(guard.as_ref().unwrap() + 1); }
1012
}
1113
(close.lock().unwrap().as_ref().unwrap())(ch.clone());
@@ -26,10 +28,14 @@ fn main() {
2628
// Unbuffered channel
2729
let mut ch = ;
2830

29-
// TODO: Unhandled statement type: GoStmt
30-
// TODO: Unhandled statement type: GoStmt
31+
std::thread::spawn(move || {
32+
sender(ch.clone());
33+
});
34+
std::thread::spawn(move || {
35+
receiver(ch.clone());
36+
});
3137

32-
(*time.lock().unwrap().as_mut().unwrap()).sleep(Arc::new(Mutex::new(Some(500 * (*(*time.lock().unwrap().as_mut().unwrap())::millisecond.lock().unwrap().as_ref().unwrap())))));
38+
std::thread::sleep(std::time::Duration::from_millis(500));
3339

3440
// Buffered channel
3541
let mut buffered = ;
@@ -47,7 +53,14 @@ fn main() {
4753

4854
// Channel range
4955
let mut numbers = ;
50-
// TODO: Unhandled statement type: GoStmt
56+
let numbers_thread = numbers.clone(); std::thread::spawn(move || {
57+
let mut i = Arc::new(Mutex::new(Some(10)));
58+
while (*i.lock().unwrap().as_mut().unwrap()) < 15 {
59+
// TODO: Unhandled statement type: SendStmt
60+
{ let mut guard = i.lock().unwrap(); *guard = Some(guard.as_ref().unwrap() + 1); }
61+
};
62+
(close.lock().unwrap().as_ref().unwrap())(numbers.clone());;;
63+
});
5164

5265
println!("{}", "Range over channel:".to_string());
5366
for num in 0..(*numbers.lock().unwrap().as_mut().unwrap()).len() {

tests/XFAIL/channels_simple/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use std::sync::{Arc, Mutex};
2+
use std::thread;
23

34
fn main() {
45
let mut messages = ;
56

6-
// TODO: Unhandled statement type: GoStmt
7+
let messages_thread = messages.clone(); std::thread::spawn(move || {
8+
// TODO: Unhandled statement type: SendStmt;;
9+
});
710

811
let mut msg = Arc::new(Mutex::new(Some(<-(*messages.lock().unwrap().as_mut().unwrap()))));
912
println!("{}", (*msg.lock().unwrap().as_mut().unwrap()));

tests/XFAIL/closing_channels/main.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
11
use std::sync::{Arc, Mutex};
2+
use std::thread;
23

34
fn main() {
45
let mut jobs = ;
56
let mut done = ;
67

7-
// TODO: Unhandled statement type: GoStmt
8+
let done_thread = done.clone(); let jobs_thread = jobs.clone(); std::thread::spawn(move || {
9+
while true {
10+
let (mut j, mut more) = <-(*jobs_thread.lock().unwrap().as_mut().unwrap());
11+
if (*more.lock().unwrap().as_mut().unwrap()) {
12+
println!("{} {}", "received job".to_string(), (*j.lock().unwrap().as_mut().unwrap()));
13+
} else {
14+
println!("{}", "received all jobs".to_string());
15+
// TODO: Unhandled statement type: SendStmt
16+
return;
17+
}
18+
};;
19+
});
820

921
let mut j = Arc::new(Mutex::new(Some(1)));
1022
while (*j.lock().unwrap().as_mut().unwrap()) <= 3 {

0 commit comments

Comments
 (0)