Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
12 changes: 11 additions & 1 deletion codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 {
Expand Down
16 changes: 16 additions & 0 deletions codegen/e2e_concurrency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 20 additions & 5 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down