diff --git a/ast/ast.go b/ast/ast.go index b826599..90d5e51 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -383,6 +383,16 @@ type Receive struct { func (r *Receive) statementNode() {} func (r *Receive) TokenLiteral() string { return r.Token.Literal } +// TimerAfterWait represents a standalone timer AFTER wait: tim ? AFTER expr +type TimerAfterWait struct { + Token lexer.Token // the ? token + Timer string // timer variable name + Deadline Expression // the deadline expression +} + +func (t *TimerAfterWait) statementNode() {} +func (t *TimerAfterWait) TokenLiteral() string { return t.Token.Literal } + // AltBlock represents an ALT block (alternation/select) // If Replicator is non-nil, this is a replicated ALT (ALT i = 0 FOR n) type AltBlock struct { diff --git a/codegen/codegen.go b/codegen/codegen.go index 0c63b4f..fabcf7b 100644 --- a/codegen/codegen.go +++ b/codegen/codegen.go @@ -745,7 +745,7 @@ func (g *Generator) containsPrint(stmt ast.Statement) bool { func (g *Generator) containsTimer(stmt ast.Statement) bool { switch s := stmt.(type) { - case *ast.TimerDecl, *ast.TimerRead: + case *ast.TimerDecl, *ast.TimerRead, *ast.TimerAfterWait: return true case *ast.AltBlock: for _, c := range s.Cases { @@ -1135,6 +1135,8 @@ func (g *Generator) generateStatement(stmt ast.Statement) { g.generateSend(s) case *ast.Receive: g.generateReceive(s) + case *ast.TimerAfterWait: + g.generateTimerAfterWait(s) case *ast.SeqBlock: g.generateSeqBlock(s) case *ast.ParBlock: @@ -1431,6 +1433,14 @@ func (g *Generator) generateSend(send *ast.Send) { g.write("\n") } +func (g *Generator) generateTimerAfterWait(s *ast.TimerAfterWait) { + // tim ? AFTER deadline → time.Sleep(time.Duration(deadline - time.Now().UnixMicro()) * time.Microsecond) + g.builder.WriteString(strings.Repeat("\t", g.indent)) + g.write("time.Sleep(time.Duration(") + g.generateExpression(s.Deadline) + g.write(" - int(time.Now().UnixMicro())) * time.Microsecond)\n") +} + func (g *Generator) generateReceive(recv *ast.Receive) { chanRef := goIdent(recv.Channel) if len(recv.ChannelIndices) > 0 { diff --git a/codegen/e2e_concurrency_test.go b/codegen/e2e_concurrency_test.go index ccb8317..2523ac5 100644 --- a/codegen/e2e_concurrency_test.go +++ b/codegen/e2e_concurrency_test.go @@ -181,6 +181,22 @@ func TestE2E_TimerAltTimeout(t *testing.T) { } } +func TestE2E_TimerAfterWait(t *testing.T) { + // Test standalone tim ? AFTER expr (non-ALT timer wait) + occam := `SEQ + TIMER tim: + INT t: + tim ? t + tim ? AFTER (t + 1000) + print.int(42) +` + output := transpileCompileRun(t, occam) + expected := "42\n" + if output != expected { + t.Errorf("expected %q, got %q", expected, output) + } +} + func TestE2E_ChanParam(t *testing.T) { occam := `PROC sender(CHAN OF INT output) output ! 42 diff --git a/parser/parser.go b/parser/parser.go index 020862b..b2ee3a6 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1207,13 +1207,28 @@ func (p *Parser) parseTimerDecl() *ast.TimerDecl { return decl } -func (p *Parser) parseTimerRead() *ast.TimerRead { - stmt := &ast.TimerRead{ - Timer: p.curToken.Literal, - } +func (p *Parser) parseTimerRead() ast.Statement { + timerName := p.curToken.Literal p.nextToken() // move to ? - stmt.Token = p.curToken + recvToken := p.curToken + + // Check for timer AFTER wait: tim ? AFTER expr + if p.peekTokenIs(lexer.AFTER) { + p.nextToken() // move to AFTER + p.nextToken() // move past AFTER to deadline expression + deadline := p.parseExpression(LOWEST) + return &ast.TimerAfterWait{ + Token: recvToken, + Timer: timerName, + Deadline: deadline, + } + } + + stmt := &ast.TimerRead{ + Timer: timerName, + } + stmt.Token = recvToken if !p.expectPeek(lexer.IDENT) { return nil