<commit_before>package compiler

import (
	"fmt"
	"io"
	"os/exec"
	"strings"

	"github.com/mibk/hawk/value"
)

type Status int

const (
	StatusNone Status = iota
	StatusBreak
	StatusContinue
	StatusReturn
)

type Stmt interface {
	Exec(io.Writer) Status
}

type ExprStmt struct {
	expr Expr
}

func (e *ExprStmt) Exec(w io.Writer) Status {
	e.expr.Eval(w)
	return StatusNone
}

type BlockStmt struct {
	stmts []Stmt
}

func (b *BlockStmt) Exec(w io.Writer) Status {
	for _, stmt := range b.stmts {
		switch s := stmt.Exec(w); s {
		case StatusBreak, StatusReturn:
			return s
		case StatusContinue:
			return StatusNone
		}
	}
	return StatusNone
}

type PipeStmt struct {
	stmt Stmt
	cmd  string
}

func (p *PipeStmt) Exec(w io.Writer) Status {
	// TODO: better method for argument parsing. (Arguments could be in quotes.)
	args := strings.Fields(p.cmd)
	if len(args) == 0 {
		panic("pipe statement: no command specified")
	}
	name := args[0]
	cmd := exec.Command(name, args[1:]...)
	cmd.Stdout, cmd.Stderr = w, w
	wc, err := cmd.StdinPipe()
	if err != nil {
		panic(fmt.Sprintf("%s: %v", name, err))
	}
	if err := cmd.Start(); err != nil {
		panic(fmt.Sprintf("%s: %v", name, err))
	}
	st := p.stmt.Exec(wc)
	if err := wc.Close(); err != nil {
		panic(fmt.Sprintf("%s: %v", name, err))
	}
	if err := cmd.Wait(); err != nil {
		panic(fmt.Sprintf("%s: %v", name, err))
	}
	return st
}

type AssignStmt struct {
	scope Scope
	left  Expr
	right Expr
}

func (a *AssignStmt) Exec(w io.Writer) Status {
	v := a.right.Eval(w)
	switch e := a.left.(type) {
	case *Ident:
		a.scope.SetVar(e.name, v)
	case *IndexExpr:
		a, ok := e.expr.Eval(w).Array()
		if !ok {
			panic("assigning to a scalar value using index expression")
		}
		var index *value.Scalar
		if e.index != nil {
			index, ok = e.index.Eval(w).Scalar()
			if !ok {
				panic("indexing an array using a non-scalar value")
			}
		}
		a.Put(index, v)
	default:
		panic("unsupported assignment type")
	}
	return StatusNone
}

type IfStmt struct {
	expr     Expr
	stmt     Stmt
	elseStmt Stmt
}

func (i *IfStmt) Exec(w io.Writer) Status {
	v, ok := i.expr.Eval(w).Scalar()
	if !ok {
		panic("non-scalar value used as a condition")
	}
	if v.Bool() {
		return i.stmt.Exec(w)
	} else if i.elseStmt != nil {
		return i.elseStmt.Exec(w)
	}
	return StatusNone
}

type ForStmt struct {
	init Stmt
	cond Expr
	step Stmt
	body Stmt
}

func (f *ForStmt) Exec(w io.Writer) Status {
	if f.init != nil {
		f.init.Exec(w)
	}
	for {
		if f.cond != nil {
			v, ok := f.cond.Eval(w).Scalar()
			if !ok {
				panic("non-scalar value used as a condition")
			}
			if !v.Bool() {
				break
			}
		}
		switch f.body.Exec(w) {
		case StatusBreak:
			break
		case StatusReturn:
			return StatusReturn
		}<commit_msg>compiler: Make {IfStmt,ForStmt}'s body explicitly *BlockStmt<commit_after>package compiler

import (
	"fmt"
	"io"
	"os/exec"
	"strings"

	"github.com/mibk/hawk/value"
)

type Status int

const (
	StatusNone Status = iota
	StatusBreak
	StatusContinue
	StatusReturn
)

type Stmt interface {
	Exec(io.Writer) Status
}

type ExprStmt struct {
	expr Expr
}

func (e *ExprStmt) Exec(w io.Writer) Status {
	e.expr.Eval(w)
	return StatusNone
}

type BlockStmt struct {
	stmts []Stmt
}

func (b *BlockStmt) Exec(w io.Writer) Status {
	for _, stmt := range b.stmts {
		switch s := stmt.Exec(w); s {
		case StatusBreak, StatusReturn:
			return s
		case StatusContinue:
			return StatusNone
		}
	}
	return StatusNone
}

type PipeStmt struct {
	stmt Stmt
	cmd  string
}

func (p *PipeStmt) Exec(w io.Writer) Status {
	// TODO: better method for argument parsing. (Arguments could be in quotes.)
	args := strings.Fields(p.cmd)
	if len(args) == 0 {
		panic("pipe statement: no command specified")
	}
	name := args[0]
	cmd := exec.Command(name, args[1:]...)
	cmd.Stdout, cmd.Stderr = w, w
	wc, err := cmd.StdinPipe()
	if err != nil {
		panic(fmt.Sprintf("%s: %v", name, err))
	}
	if err := cmd.Start(); err != nil {
		panic(fmt.Sprintf("%s: %v", name, err))
	}
	st := p.stmt.Exec(wc)
	if err := wc.Close(); err != nil {
		panic(fmt.Sprintf("%s: %v", name, err))
	}
	if err := cmd.Wait(); err != nil {
		panic(fmt.Sprintf("%s: %v", name, err))
	}
	return st
}

type AssignStmt struct {
	scope Scope
	left  Expr
	right Expr
}

func (a *AssignStmt) Exec(w io.Writer) Status {
	v := a.right.Eval(w)
	switch e := a.left.(type) {
	case *Ident:
		a.scope.SetVar(e.name, v)
	case *IndexExpr:
		a, ok := e.expr.Eval(w).Array()
		if !ok {
			panic("assigning to a scalar value using index expression")
		}
		var index *value.Scalar
		if e.index != nil {
			index, ok = e.index.Eval(w).Scalar()
			if !ok {
				panic("indexing an array using a non-scalar value")
			}
		}
		a.Put(index, v)
	default:
		panic("unsupported assignment type")
	}
	return StatusNone
}

type IfStmt struct {
	expr     Expr
	stmt     *BlockStmt
	elseStmt Stmt
}

func (i *IfStmt) Exec(w io.Writer) Status {
	v, ok := i.expr.Eval(w).Scalar()
	if !ok {
		panic("non-scalar value used as a condition")
	}
	if v.Bool() {
		return i.stmt.Exec(w)
	} else if i.elseStmt != nil {
		return i.elseStmt.Exec(w)
	}
	return StatusNone
}

type ForStmt struct {
	init Stmt
	cond Expr
	step Stmt
	body *BlockStmt
}

func (f *ForStmt) Exec(w io.Writer) Status {
	if f.init != nil {
		f.init.Exec(w)
	}
	for {
		if f.cond != nil {
			v, ok := f.cond.Eval(w).Scalar()
			if !ok {
				panic("non-scalar value used as a condition")
			}
			if !v.Bool() {
				break
			}
		}
		switch f.body.Exec(w) {
		case StatusBreak:
			break
		case StatusReturn:
			return StatusReturn
		}
{'fraction_non_alphanumeric': 0.14777437376103802, 'fraction_numerical': 0.001081275905568571, 'mean_word_length': 3.111111111111111, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6295378', 'n_tokens_mistral': 2324, 'n_tokens_neox': 2205, 'n_words': 609}