From 6c659a2b0c89b5ed7f1cb0ea8c60fc1568abf15c Mon Sep 17 00:00:00 2001
From: Eduard Urbach <eduard@urbach.dev>
Date: Sun, 9 Mar 2025 19:24:15 +0100
Subject: [PATCH] Added more tests

---
 docs/readme.md                         |  2 +-
 src/core/CompileAssignDivision.go      |  4 +--
 src/core/CompileDefinition.go          |  2 +-
 src/core/CompileFor.go                 | 43 ++++++++++++++++----------
 src/core/MultiDefine.go                |  2 +-
 src/core/{Define.go => NewVariable.go} |  4 +--
 tests/programs/factorial.q             | 23 ++++++++++++++
 tests/programs_test.go                 |  1 +
 8 files changed, 58 insertions(+), 23 deletions(-)
 rename src/core/{Define.go => NewVariable.go} (85%)
 create mode 100644 tests/programs/factorial.q

diff --git a/docs/readme.md b/docs/readme.md
index 1ef10e1..5873bd8 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -33,7 +33,7 @@ You can take a look at the [examples](../examples).
 go run gotest.tools/gotestsum@latest
 ```
 
-This will run over 350 [tests](../tests) in various categories.
+This will run over 400 [tests](../tests) in various categories.
 
 ## Platforms
 
diff --git a/src/core/CompileAssignDivision.go b/src/core/CompileAssignDivision.go
index c75026a..3d4ab0e 100644
--- a/src/core/CompileAssignDivision.go
+++ b/src/core/CompileAssignDivision.go
@@ -24,13 +24,13 @@ func (f *Function) CompileAssignDivision(expr *expression.Expression) error {
 	)
 
 	if expr.Token.Kind == token.Define {
-		quotientVariable, err = f.Define(variables.Children[0])
+		quotientVariable, err = f.NewVariable(variables.Children[0])
 
 		if err != nil {
 			return err
 		}
 
-		remainderVariable, err = f.Define(variables.Children[1])
+		remainderVariable, err = f.NewVariable(variables.Children[1])
 
 		if err != nil {
 			return err
diff --git a/src/core/CompileDefinition.go b/src/core/CompileDefinition.go
index 1ea9564..ddab01b 100644
--- a/src/core/CompileDefinition.go
+++ b/src/core/CompileDefinition.go
@@ -13,7 +13,7 @@ func (f *Function) CompileDefinition(node *ast.Define) error {
 	right := node.Expression.Children[1]
 
 	if left.IsLeaf() {
-		variable, err := f.Define(left)
+		variable, err := f.NewVariable(left)
 
 		if err != nil {
 			return err
diff --git a/src/core/CompileFor.go b/src/core/CompileFor.go
index 0d1530f..a820b9d 100644
--- a/src/core/CompileFor.go
+++ b/src/core/CompileFor.go
@@ -9,6 +9,7 @@ import (
 	"git.urbach.dev/cli/q/src/errors"
 	"git.urbach.dev/cli/q/src/eval"
 	"git.urbach.dev/cli/q/src/expression"
+	"git.urbach.dev/cli/q/src/scope"
 	"git.urbach.dev/cli/q/src/token"
 	"git.urbach.dev/cli/q/src/types"
 )
@@ -29,14 +30,15 @@ func (f *Function) CompileFor(loop *ast.For) error {
 		to       *expression.Expression
 	)
 
+	counter = f.NewRegister()
+	defer f.FreeRegister(counter)
+
 	switch loop.Head.Token.Kind {
 	case token.Define:
 		from = loop.Head.Children[1].Children[0]
 		to = loop.Head.Children[1].Children[1]
 
 	case token.Range:
-		counter = f.NewRegister()
-		defer f.FreeRegister(counter)
 		from = loop.Head.Children[0]
 		to = loop.Head.Children[1]
 
@@ -60,20 +62,6 @@ func (f *Function) CompileFor(loop *ast.For) error {
 		return errors.New(&errors.TypeMismatch{Encountered: value.Type().Name(), Expected: types.AnyInt.Name()}, f.File, to.Token.Position)
 	}
 
-	scope := f.PushScope(loop.Body, f.File.Bytes)
-	scope.InLoop = true
-
-	if loop.Head.Token.Kind == token.Define {
-		variable, err := f.Define(loop.Head.Children[0])
-
-		if err != nil {
-			return err
-		}
-
-		counter = variable.Value.Register
-		f.AddVariable(variable)
-	}
-
 	switch value := value.(type) {
 	case *eval.Number:
 		f.AddLabel(label)
@@ -95,6 +83,29 @@ func (f *Function) CompileFor(loop *ast.For) error {
 		panic(fmt.Errorf("%s: not implemented: %v", f.UniqueName, value))
 	}
 
+	loopScope := f.PushScope(loop.Body, f.File.Bytes)
+	loopScope.InLoop = true
+
+	if loop.Head.Token.Kind == token.Define {
+		leaf := loop.Head.Children[0]
+		name := leaf.Token.Text(f.File.Bytes)
+		variable := f.VariableByName(name)
+
+		if variable != nil {
+			return errors.New(&errors.VariableAlreadyExists{Name: name}, f.File, leaf.Token.Position)
+		}
+
+		variable = &scope.Variable{
+			Name: name,
+			Value: eval.Register{
+				Register: counter,
+				Alive:    ast.Count(loop.Body, f.File.Bytes, token.Identifier, name),
+			},
+		}
+
+		f.AddVariable(variable)
+	}
+
 	f.Jump(asm.JGE, labelEnd)
 	err = f.CompileAST(loop.Body)
 	f.RegisterNumber(asm.ADD, counter, 1)
diff --git a/src/core/MultiDefine.go b/src/core/MultiDefine.go
index 6240c69..c1ed73b 100644
--- a/src/core/MultiDefine.go
+++ b/src/core/MultiDefine.go
@@ -15,7 +15,7 @@ func (f *Function) MultiDefine(left *expression.Expression, right *expression.Ex
 	}
 
 	return left.EachLeaf(func(leaf *expression.Expression) error {
-		variable, err := f.Define(leaf)
+		variable, err := f.NewVariable(leaf)
 
 		if err != nil {
 			return err
diff --git a/src/core/Define.go b/src/core/NewVariable.go
similarity index 85%
rename from src/core/Define.go
rename to src/core/NewVariable.go
index c5c6cef..ecf405a 100644
--- a/src/core/Define.go
+++ b/src/core/NewVariable.go
@@ -8,8 +8,8 @@ import (
 	"git.urbach.dev/cli/q/src/token"
 )
 
-// Define defines a new variable.
-func (f *Function) Define(leaf *expression.Expression) (*scope.Variable, error) {
+// NewVariable creates a new variable.
+func (f *Function) NewVariable(leaf *expression.Expression) (*scope.Variable, error) {
 	name := leaf.Token.Text(f.File.Bytes)
 	variable := f.VariableByName(name)
 
diff --git a/tests/programs/factorial.q b/tests/programs/factorial.q
new file mode 100644
index 0000000..3e52582
--- /dev/null
+++ b/tests/programs/factorial.q
@@ -0,0 +1,23 @@
+main() {
+	for i := 0..10 {
+		assert fac1(i) == fac2(i)
+	}
+}
+
+fac1(n int) -> int {
+	x := 1
+
+	for i := 1..n+1 {
+		x = x * i
+	}
+
+	return x
+}
+
+fac2(n int) -> int {
+	if n <= 1 {
+		return 1
+	}
+
+	return n * fac2(n - 1)
+}
\ No newline at end of file
diff --git a/tests/programs_test.go b/tests/programs_test.go
index cfb6d2a..b4b18ed 100644
--- a/tests/programs_test.go
+++ b/tests/programs_test.go
@@ -62,6 +62,7 @@ var programs = []struct {
 	{"loop-lifetime", 0},
 	{"loop-in-loop", 0},
 	{"for", 0},
+	{"factorial", 0},
 	{"fibonacci", 0},
 	{"memory-free", 0},
 	{"out-of-memory", 0},