···11+# These are supported funding model platforms
22+33+github: [vic] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
44+patreon: # Replace with a single Patreon username
55+open_collective: # Replace with a single Open Collective username
66+ko_fi: oeiuwq # Replace with a single Ko-fi username
77+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
88+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
99+liberapay: # Replace with a single Liberapay username
1010+issuehunt: # Replace with a single IssueHunt username
1111+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
1212+polar: # Replace with a single Polar username
1313+buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
1414+thanks_dev: # Replace with a single thanks.dev username
1515+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+39
.github/workflows/go.yml
···11+name: Build & Test
22+33+on:
44+ push:
55+ branches: [ "main" ]
66+ pull_request:
77+ branches: [ "main" ]
88+99+jobs:
1010+1111+ test:
1212+ runs-on: ubuntu-latest
1313+ steps:
1414+ - uses: actions/checkout@v4
1515+1616+ - name: Set up Go
1717+ uses: actions/setup-go@v5
1818+ with:
1919+ go-version: '1.25'
2020+2121+ - name: Test
2222+ run: go test -v ./...
2323+2424+ lint:
2525+ runs-on: ubuntu-latest
2626+ steps:
2727+ - uses: actions/checkout@v4
2828+2929+ - name: Set up Go
3030+ uses: actions/setup-go@v5
3131+ with:
3232+ go-version: '1.25'
3333+3434+ - name: Check if `go fmt` and `go mod tidy` make any changes
3535+ run: |
3636+ set -x
3737+ go fmt ./...
3838+ go mod tidy
3939+ git diff --exit-code
···11+ Apache License
22+ Version 2.0, January 2004
33+ http://www.apache.org/licenses/
44+55+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
66+77+ 1. Definitions.
88+99+ "License" shall mean the terms and conditions for use, reproduction,
1010+ and distribution as defined by Sections 1 through 9 of this document.
1111+1212+ "Licensor" shall mean the copyright owner or entity authorized by
1313+ the copyright owner that is granting the License.
1414+1515+ "Legal Entity" shall mean the union of the acting entity and all
1616+ other entities that control, are controlled by, or are under common
1717+ control with that entity. For the purposes of this definition,
1818+ "control" means (i) the power, direct or indirect, to cause the
1919+ direction or management of such entity, whether by contract or
2020+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
2121+ outstanding shares, or (iii) beneficial ownership of such entity.
2222+2323+ "You" (or "Your") shall mean an individual or Legal Entity
2424+ exercising permissions granted by this License.
2525+2626+ "Source" form shall mean the preferred form for making modifications,
2727+ including but not limited to software source code, documentation
2828+ source, and configuration files.
2929+3030+ "Object" form shall mean any form resulting from mechanical
3131+ transformation or translation of a Source form, including but
3232+ not limited to compiled object code, generated documentation,
3333+ and conversions to other media types.
3434+3535+ "Work" shall mean the work of authorship, whether in Source or
3636+ Object form, made available under the License, as indicated by a
3737+ copyright notice that is included in or attached to the work
3838+ (an example is provided in the Appendix below).
3939+4040+ "Derivative Works" shall mean any work, whether in Source or Object
4141+ form, that is based on (or derived from) the Work and for which the
4242+ editorial revisions, annotations, elaborations, or other modifications
4343+ represent, as a whole, an original work of authorship. For the purposes
4444+ of this License, Derivative Works shall not include works that remain
4545+ separable from, or merely link (or bind by name) to the interfaces of,
4646+ the Work and Derivative Works thereof.
4747+4848+ "Contribution" shall mean any work of authorship, including
4949+ the original version of the Work and any modifications or additions
5050+ to that Work or Derivative Works thereof, that is intentionally
5151+ submitted to Licensor for inclusion in the Work by the copyright owner
5252+ or by an individual or Legal Entity authorized to submit on behalf of
5353+ the copyright owner. For the purposes of this definition, "submitted"
5454+ means any form of electronic, verbal, or written communication sent
5555+ to the Licensor or its representatives, including but not limited to
5656+ communication on electronic mailing lists, source code control systems,
5757+ and issue tracking systems that are managed by, or on behalf of, the
5858+ Licensor for the purpose of discussing and improving the Work, but
5959+ excluding communication that is conspicuously marked or otherwise
6060+ designated in writing by the copyright owner as "Not a Contribution."
6161+6262+ "Contributor" shall mean Licensor and any individual or Legal Entity
6363+ on behalf of whom a Contribution has been received by Licensor and
6464+ subsequently incorporated within the Work.
6565+6666+ 2. Grant of Copyright License. Subject to the terms and conditions of
6767+ this License, each Contributor hereby grants to You a perpetual,
6868+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
6969+ copyright license to reproduce, prepare Derivative Works of,
7070+ publicly display, publicly perform, sublicense, and distribute the
7171+ Work and such Derivative Works in Source or Object form.
7272+7373+ 3. Grant of Patent License. Subject to the terms and conditions of
7474+ this License, each Contributor hereby grants to You a perpetual,
7575+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
7676+ (except as stated in this section) patent license to make, have made,
7777+ use, offer to sell, sell, import, and otherwise transfer the Work,
7878+ where such license applies only to those patent claims licensable
7979+ by such Contributor that are necessarily infringed by their
8080+ Contribution(s) alone or by combination of their Contribution(s)
8181+ with the Work to which such Contribution(s) was submitted. If You
8282+ institute patent litigation against any entity (including a
8383+ cross-claim or counterclaim in a lawsuit) alleging that the Work
8484+ or a Contribution incorporated within the Work constitutes direct
8585+ or contributory patent infringement, then any patent licenses
8686+ granted to You under this License for that Work shall terminate
8787+ as of the date such litigation is filed.
8888+8989+ 4. Redistribution. You may reproduce and distribute copies of the
9090+ Work or Derivative Works thereof in any medium, with or without
9191+ modifications, and in Source or Object form, provided that You
9292+ meet the following conditions:
9393+9494+ (a) You must give any other recipients of the Work or
9595+ Derivative Works a copy of this License; and
9696+9797+ (b) You must cause any modified files to carry prominent notices
9898+ stating that You changed the files; and
9999+100100+ (c) You must retain, in the Source form of any Derivative Works
101101+ that You distribute, all copyright, patent, trademark, and
102102+ attribution notices from the Source form of the Work,
103103+ excluding those notices that do not pertain to any part of
104104+ the Derivative Works; and
105105+106106+ (d) If the Work includes a "NOTICE" text file as part of its
107107+ distribution, then any Derivative Works that You distribute must
108108+ include a readable copy of the attribution notices contained
109109+ within such NOTICE file, excluding those notices that do not
110110+ pertain to any part of the Derivative Works, in at least one
111111+ of the following places: within a NOTICE text file distributed
112112+ as part of the Derivative Works; within the Source form or
113113+ documentation, if provided along with the Derivative Works; or,
114114+ within a display generated by the Derivative Works, if and
115115+ wherever such third-party notices normally appear. The contents
116116+ of the NOTICE file are for informational purposes only and
117117+ do not modify the License. You may add Your own attribution
118118+ notices within Derivative Works that You distribute, alongside
119119+ or as an addendum to the NOTICE text from the Work, provided
120120+ that such additional attribution notices cannot be construed
121121+ as modifying the License.
122122+123123+ You may add Your own copyright statement to Your modifications and
124124+ may provide additional or different license terms and conditions
125125+ for use, reproduction, or distribution of Your modifications, or
126126+ for any such Derivative Works as a whole, provided Your use,
127127+ reproduction, and distribution of the Work otherwise complies with
128128+ the conditions stated in this License.
129129+130130+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131131+ any Contribution intentionally submitted for inclusion in the Work
132132+ by You to the Licensor shall be under the terms and conditions of
133133+ this License, without any additional terms or conditions.
134134+ Notwithstanding the above, nothing herein shall supersede or modify
135135+ the terms of any separate license agreement you may have executed
136136+ with Licensor regarding such Contributions.
137137+138138+ 6. Trademarks. This License does not grant permission to use the trade
139139+ names, trademarks, service marks, or product names of the Licensor,
140140+ except as required for reasonable and customary use in describing the
141141+ origin of the Work and reproducing the content of the NOTICE file.
142142+143143+ 7. Disclaimer of Warranty. Unless required by applicable law or
144144+ agreed to in writing, Licensor provides the Work (and each
145145+ Contributor provides its Contributions) on an "AS IS" BASIS,
146146+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147147+ implied, including, without limitation, any warranties or conditions
148148+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149149+ PARTICULAR PURPOSE. You are solely responsible for determining the
150150+ appropriateness of using or redistributing the Work and assume any
151151+ risks associated with Your exercise of permissions under this License.
152152+153153+ 8. Limitation of Liability. In no event and under no legal theory,
154154+ whether in tort (including negligence), contract, or otherwise,
155155+ unless required by applicable law (such as deliberate and grossly
156156+ negligent acts) or agreed to in writing, shall any Contributor be
157157+ liable to You for damages, including any direct, indirect, special,
158158+ incidental, or consequential damages of any character arising as a
159159+ result of this License or out of the use or inability to use the
160160+ Work (including but not limited to damages for loss of goodwill,
161161+ work stoppage, computer failure or malfunction, or any and all
162162+ other commercial damages or losses), even if such Contributor
163163+ has been advised of the possibility of such damages.
164164+165165+ 9. Accepting Warranty or Additional Liability. While redistributing
166166+ the Work or Derivative Works thereof, You may choose to offer,
167167+ and charge a fee for, acceptance of support, warranty, indemnity,
168168+ or other liability obligations and/or rights consistent with this
169169+ License. However, in accepting such obligations, You may act only
170170+ on Your own behalf and on Your sole responsibility, not on behalf
171171+ of any other Contributor, and only if You agree to indemnify,
172172+ defend, and hold each Contributor harmless for any liability
173173+ incurred by, or claims asserted against, such Contributor by reason
174174+ of your accepting any such warranty or additional liability.
175175+176176+ END OF TERMS AND CONDITIONS
177177+178178+ APPENDIX: How to apply the Apache License to your work.
179179+180180+ To apply the Apache License to your work, attach the following
181181+ boilerplate notice, with the fields enclosed by brackets "[]"
182182+ replaced with your own identifying information. (Don't include
183183+ the brackets!) The text should be enclosed in the appropriate
184184+ comment syntax for the file format. We also recommend that a
185185+ file or class name and description of purpose be included on the
186186+ same "printed page" as the copyright notice for easier
187187+ identification within third-party archives.
188188+189189+ Copyright [yyyy] [name of copyright owner]
190190+191191+ Licensed under the Apache License, Version 2.0 (the "License");
192192+ you may not use this file except in compliance with the License.
193193+ You may obtain a copy of the License at
194194+195195+ http://www.apache.org/licenses/LICENSE-2.0
196196+197197+ Unless required by applicable law or agreed to in writing, software
198198+ distributed under the License is distributed on an "AS IS" BASIS,
199199+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200200+ See the License for the specific language governing permissions and
201201+ limitations under the License.
+177
README.md
···11+# Godnix - Nix to Lambda Calculus Compiler
22+33+> **_Go_** D-_Nix_ -- A Nix compiler into Delta Interaction Nets.
44+55+Godnix is a compiler that translates Nix expressions to Lambda calculus and evaluates them using the [godnet](https://github.com/vic/godnet) interaction net reduction engine.
66+77+## Features
88+99+This first version supports:
1010+- **Integers**: Church numeral encoding
1111+- **Anonymous functions**: `x: body` syntax
1212+- **Function application**: Direct application of functions to arguments
1313+- **Let bindings**: `let x = value; in body` expressions
1414+- **Addition operator**: Pure Church numeral addition (`+`)
1515+1616+## Building
1717+1818+```bash
1919+go build -o godnix ./cmd/godnix
2020+```
2121+2222+## Testing
2323+2424+Run the integration tests:
2525+2626+```bash
2727+go test ./cmd/godnix/
2828+```
2929+3030+For verbose output:
3131+3232+```bash
3333+go test -v ./cmd/godnix/
3434+```
3535+3636+The test suite includes:
3737+- Examples from `examples/` directory
3838+- Inline expression tests
3939+- Church numeral encoding/decoding
4040+- Error handling for unsupported features
4141+- Direct translator unit tests
4242+4343+## Usage
4444+4545+```bash
4646+./godnix <file.nix>
4747+```
4848+4949+## Examples
5050+5151+### Simple Integer
5252+```nix
5353+# examples/simple.nix
5454+42
5555+```
5656+5757+Output:
5858+```
5959+42
6060+```
6161+6262+### Addition
6363+```nix
6464+# examples/add.nix
6565+2 + 3
6666+```
6767+6868+Output:
6969+```
7070+5
7171+```
7272+7373+### Identity Function
7474+```nix
7575+# examples/identity.nix
7676+(x: x) 7
7777+```
7878+7979+Output:
8080+```
8181+7
8282+```
8383+8484+### Let Binding
8585+```nix
8686+# examples/let.nix
8787+let
8888+ x = 5;
8989+ y = 3;
9090+in
9191+ x + y
9292+```
9393+9494+Output:
9595+```
9696+8
9797+```
9898+9999+### Lambda Functions
100100+```nix
101101+# examples/lambda.nix
102102+let
103103+ add = x: y: x + y;
104104+in
105105+ add 4 6
106106+```
107107+108108+Output:
109109+```
110110+10
111111+```
112112+113113+## Architecture
114114+115115+### Components
116116+117117+1. **Parser** (`pkg/parser/`): Full-featured Nix parser (already present)
118118+2. **Translator** (`pkg/compiler/translator.go`): Converts Nix AST to Lambda calculus terms
119119+3. **Compiler** (`cmd/godnix/`): Main application that coordinates parsing, translation, and evaluation
120120+121121+### Translation Strategy
122122+123123+Godnix translates Nix expressions to pure Lambda calculus:
124124+125125+- **Integers** → Church numerals: `n = λf. λx. f^n x`
126126+- **Addition** → Church numeral addition: `λm. λn. λf. λx. m f (n f x)`
127127+- **Functions** → Lambda abstractions: `x: body` → `λx. body`
128128+- **Let bindings** → Function application: `let x = v in b` → `(λx. b) v`
129129+- **Application** → Standard application: `f x` → Lambda application
130130+131131+### Evaluation
132132+133133+The compiled Lambda terms are evaluated using the godnet deltanet reduction engine, which implements optimal reduction through interaction nets. This provides:
134134+135135+- Parallel reduction opportunities
136136+- Optimal sharing of computations
137137+- Efficient memory usage
138138+139139+## Performance
140140+141141+The evaluator provides statistics after each run:
142142+- Execution time
143143+- Total reductions performed
144144+- Reductions per second
145145+146146+Example output:
147147+```
148148+Stats:
149149+Time: 337.889µs
150150+Total Reductions: 60 (177573.11 ops/sec)
151151+```
152152+153153+## Limitations (v1)
154154+155155+This first version has intentional limitations:
156156+- Only integers (no floats, strings, lists, sets)
157157+- Only `+` operator (no other arithmetic or comparison)
158158+- No built-in functions beyond `+`
159159+- No attribute sets or recursive bindings
160160+- No imports or file system operations
161161+162162+These limitations allow focusing on the core compilation pipeline. The full Nix parser is available, so adding support for more features is straightforward.
163163+164164+## Future Directions
165165+166166+Potential additions for future versions:
167167+- More arithmetic operators (`-`, `*`, `/`)
168168+- Boolean operations and conditionals
169169+- Lists and list operations
170170+- Attribute sets
171171+- Built-in functions (map, filter, fold)
172172+- String operations
173173+- Native functions for performance-critical operations
174174+175175+## License
176176+177177+See LICENSE file.
+137
cmd/godnix/main.go
···11+package main
22+33+import (
44+ "fmt"
55+ "os"
66+ "time"
77+88+ "github.com/vic/godnet/pkg/deltanet"
99+ "github.com/vic/godnet/pkg/lambda"
1010+ "github.com/vic/godnix/pkg/compiler"
1111+ "github.com/vic/godnix/pkg/parser"
1212+)
1313+1414+func main() {
1515+ if len(os.Args) < 2 {
1616+ fmt.Fprintf(os.Stderr, "Usage: %s <file.nix> [output]\n", os.Args[0])
1717+ os.Exit(1)
1818+ }
1919+2020+ nixFile := os.Args[1]
2121+ outputName := ""
2222+ if len(os.Args) > 2 {
2323+ outputName = os.Args[2]
2424+ }
2525+2626+ if err := compileAndRun(nixFile, outputName); err != nil {
2727+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
2828+ os.Exit(1)
2929+ }
3030+}
3131+3232+func compileAndRun(nixFile, outputName string) error {
3333+ // Parse Nix file
3434+ fmt.Fprintf(os.Stderr, "Parsing %s...\n", nixFile)
3535+ p, err := parser.ParseFile(nixFile)
3636+ if err != nil {
3737+ return fmt.Errorf("parse error: %w", err)
3838+ }
3939+4040+ // Translate to Lambda calculus
4141+ fmt.Fprintf(os.Stderr, "Translating to Lambda calculus...\n")
4242+ translator := compiler.NewTranslator(p)
4343+ term, err := translator.Translate()
4444+ if err != nil {
4545+ return fmt.Errorf("translation error: %w", err)
4646+ }
4747+4848+ fmt.Fprintf(os.Stderr, "Lambda term: %s\n\n", term)
4949+5050+ // Evaluate directly using deltanet
5151+ fmt.Fprintf(os.Stderr, "Evaluating...\n")
5252+ fmt.Fprintf(os.Stderr, "=================================\n")
5353+ return evaluateDirect(term)
5454+}
5555+5656+func evaluateDirect(term lambda.Term) error {
5757+ // Create network
5858+ net := deltanet.NewNetwork()
5959+6060+ // Translate lambda term to deltanet
6161+ rootNode, rootPort, varNames := lambda.ToDeltaNet(term, net)
6262+6363+ // Connect to output
6464+ output := net.NewVar()
6565+ net.Link(rootNode, rootPort, output, 0)
6666+6767+ // Reduce
6868+ start := time.Now()
6969+ net.ReduceAll()
7070+ elapsed := time.Since(start)
7171+7272+ // Get result
7373+ resultNode, resultPort := net.GetLink(output, 0)
7474+ result := lambda.FromDeltaNet(net, resultNode, resultPort, varNames)
7575+7676+ // Try to decode as Church numeral
7777+ if num, ok := decodeChurchNumeral(result); ok {
7878+ fmt.Printf("%d\n", num)
7979+ } else {
8080+ fmt.Printf("%s\n", result)
8181+ }
8282+8383+ // Print stats
8484+ stats := net.GetStats()
8585+ seconds := elapsed.Seconds()
8686+ fmt.Fprintf(os.Stderr, "\nStats:\n")
8787+ fmt.Fprintf(os.Stderr, "Time: %v\n", elapsed)
8888+ fmt.Fprintf(os.Stderr, "Total Reductions: %d", stats.TotalReductions)
8989+ if seconds > 0 {
9090+ fmt.Fprintf(os.Stderr, " (%.2f ops/sec)", float64(stats.TotalReductions)/seconds)
9191+ }
9292+ fmt.Fprintf(os.Stderr, "\n")
9393+9494+ return nil
9595+}
9696+9797+// decodeChurchNumeral attempts to decode a lambda term as a Church numeral
9898+// Church numeral: λf. λx. f (f (... (f x) ...))
9999+func decodeChurchNumeral(term lambda.Term) (int, bool) {
100100+ // Must be: λf. λx. body
101101+ abs1, ok := term.(lambda.Abs)
102102+ if !ok {
103103+ return 0, false
104104+ }
105105+106106+ abs2, ok := abs1.Body.(lambda.Abs)
107107+ if !ok {
108108+ return 0, false
109109+ }
110110+111111+ // Count applications of f to x
112112+ count := 0
113113+ current := abs2.Body
114114+115115+ for {
116116+ app, ok := current.(lambda.App)
117117+ if !ok {
118118+ break
119119+ }
120120+121121+ // Check if function is the f variable
122122+ funVar, ok := app.Fun.(lambda.Var)
123123+ if !ok || funVar.Name != abs1.Arg {
124124+ return 0, false
125125+ }
126126+127127+ count++
128128+ current = app.Arg
129129+ }
130130+131131+ // Final term should be x
132132+ if varX, ok := current.(lambda.Var); ok && varX.Name == abs2.Arg {
133133+ return count, true
134134+ }
135135+136136+ return 0, false
137137+}
+352
cmd/godnix/main_test.go
···11+package main
22+33+import (
44+ "os"
55+ "testing"
66+77+ "github.com/vic/godnet/pkg/deltanet"
88+ "github.com/vic/godnet/pkg/lambda"
99+ "github.com/vic/godnix/pkg/compiler"
1010+ "github.com/vic/godnix/pkg/parser"
1111+)
1212+1313+func TestSimpleInteger(t *testing.T) {
1414+ result, err := compileAndEvaluate("../../examples/simple.nix")
1515+ if err != nil {
1616+ t.Fatalf("Failed to compile and evaluate: %v", err)
1717+ }
1818+ if result != 42 {
1919+ t.Errorf("Expected 42, got %d", result)
2020+ }
2121+}
2222+2323+func TestAddition(t *testing.T) {
2424+ result, err := compileAndEvaluate("../../examples/add.nix")
2525+ if err != nil {
2626+ t.Fatalf("Failed to compile and evaluate: %v", err)
2727+ }
2828+ if result != 5 {
2929+ t.Errorf("Expected 5, got %d", result)
3030+ }
3131+}
3232+3333+func TestIdentityFunction(t *testing.T) {
3434+ result, err := compileAndEvaluate("../../examples/identity.nix")
3535+ if err != nil {
3636+ t.Fatalf("Failed to compile and evaluate: %v", err)
3737+ }
3838+ if result != 7 {
3939+ t.Errorf("Expected 7, got %d", result)
4040+ }
4141+}
4242+4343+func TestLetBinding(t *testing.T) {
4444+ result, err := compileAndEvaluate("../../examples/let.nix")
4545+ if err != nil {
4646+ t.Fatalf("Failed to compile and evaluate: %v", err)
4747+ }
4848+ if result != 8 {
4949+ t.Errorf("Expected 8, got %d", result)
5050+ }
5151+}
5252+5353+func TestLambdaFunction(t *testing.T) {
5454+ result, err := compileAndEvaluate("../../examples/lambda.nix")
5555+ if err != nil {
5656+ t.Fatalf("Failed to compile and evaluate: %v", err)
5757+ }
5858+ if result != 10 {
5959+ t.Errorf("Expected 10, got %d", result)
6060+ }
6161+}
6262+6363+func TestInlineExpressions(t *testing.T) {
6464+ tests := []struct {
6565+ name string
6666+ expr string
6767+ expected int
6868+ }{
6969+ {"zero", "0", 0},
7070+ {"one", "1", 1},
7171+ {"simple_add", "1 + 2", 3},
7272+ {"multi_add", "1 + 2 + 3", 6},
7373+ {"identity_zero", "(x: x) 0", 0},
7474+ {"const_combinator", "(x: y: x) 5 3", 5},
7575+ {"nested_let", "let a = 1; b = 2; c = 3; in a + b + c", 6},
7676+ }
7777+7878+ for _, tt := range tests {
7979+ t.Run(tt.name, func(t *testing.T) {
8080+ result, err := compileAndEvaluateString(tt.expr)
8181+ if err != nil {
8282+ t.Fatalf("Failed to compile and evaluate: %v", err)
8383+ }
8484+ if result != tt.expected {
8585+ t.Errorf("Expected %d, got %d", tt.expected, result)
8686+ }
8787+ })
8888+ }
8989+}
9090+9191+func TestParseErrors(t *testing.T) {
9292+ tests := []struct {
9393+ name string
9494+ expr string
9595+ }{
9696+ {"unclosed_paren", "(x: x"},
9797+ {"invalid_syntax", "let in"},
9898+ }
9999+100100+ for _, tt := range tests {
101101+ t.Run(tt.name, func(t *testing.T) {
102102+ _, err := compileAndEvaluateString(tt.expr)
103103+ if err == nil {
104104+ t.Error("Expected error for invalid expression, got nil")
105105+ }
106106+ })
107107+ }
108108+}
109109+110110+func TestUnsupportedFeatures(t *testing.T) {
111111+ tests := []struct {
112112+ name string
113113+ expr string
114114+ }{
115115+ {"string", `"hello"`},
116116+ {"list", "[1 2 3]"},
117117+ {"set", "{a = 1;}"},
118118+ {"subtraction", "5 - 3"},
119119+ {"multiplication", "2 * 3"},
120120+ }
121121+122122+ for _, tt := range tests {
123123+ t.Run(tt.name, func(t *testing.T) {
124124+ _, err := compileAndEvaluateString(tt.expr)
125125+ if err == nil {
126126+ t.Errorf("Expected error for unsupported feature, got nil")
127127+ }
128128+ })
129129+ }
130130+}
131131+132132+// Helper functions
133133+134134+func compileAndEvaluate(nixFile string) (int, error) {
135135+ // Parse Nix file
136136+ p, err := parser.ParseFile(nixFile)
137137+ if err != nil {
138138+ return 0, err
139139+ }
140140+141141+ // Translate to Lambda calculus
142142+ translator := compiler.NewTranslator(p)
143143+ term, err := translator.Translate()
144144+ if err != nil {
145145+ return 0, err
146146+ }
147147+148148+ // Evaluate
149149+ return evaluate(term)
150150+}
151151+152152+func compileAndEvaluateString(nixExpr string) (int, error) {
153153+ // Parse Nix string
154154+ p, err := parser.ParseString(nixExpr)
155155+ if err != nil {
156156+ return 0, err
157157+ }
158158+159159+ // Translate to Lambda calculus
160160+ translator := compiler.NewTranslator(p)
161161+ term, err := translator.Translate()
162162+ if err != nil {
163163+ return 0, err
164164+ }
165165+166166+ // Evaluate
167167+ return evaluate(term)
168168+}
169169+170170+func evaluate(term lambda.Term) (int, error) {
171171+ // Create network
172172+ net := deltanet.NewNetwork()
173173+174174+ // Translate lambda term to deltanet
175175+ rootNode, rootPort, varNames := lambda.ToDeltaNet(term, net)
176176+177177+ // Connect to output
178178+ output := net.NewVar()
179179+ net.Link(rootNode, rootPort, output, 0)
180180+181181+ // Reduce
182182+ net.ReduceAll()
183183+184184+ // Get result
185185+ resultNode, resultPort := net.GetLink(output, 0)
186186+ result := lambda.FromDeltaNet(net, resultNode, resultPort, varNames)
187187+188188+ // Try to decode as Church numeral
189189+ if num, ok := decodeChurchNumeral(result); ok {
190190+ return num, nil
191191+ }
192192+193193+ return 0, nil
194194+}
195195+196196+// TestCompilerPackage tests the compiler package directly
197197+func TestTranslatorIntNode(t *testing.T) {
198198+ p, err := parser.ParseString("123")
199199+ if err != nil {
200200+ t.Fatalf("Parse error: %v", err)
201201+ }
202202+203203+ translator := compiler.NewTranslator(p)
204204+ term, err := translator.Translate()
205205+ if err != nil {
206206+ t.Fatalf("Translation error: %v", err)
207207+ }
208208+209209+ result, err := evaluate(term)
210210+ if err != nil {
211211+ t.Fatalf("Evaluation error: %v", err)
212212+ }
213213+214214+ if result != 123 {
215215+ t.Errorf("Expected 123, got %d", result)
216216+ }
217217+}
218218+219219+func TestTranslatorFunction(t *testing.T) {
220220+ p, err := parser.ParseString("x: x")
221221+ if err != nil {
222222+ t.Fatalf("Parse error: %v", err)
223223+ }
224224+225225+ translator := compiler.NewTranslator(p)
226226+ term, err := translator.Translate()
227227+ if err != nil {
228228+ t.Fatalf("Translation error: %v", err)
229229+ }
230230+231231+ // Check it's an abstraction
232232+ abs, ok := term.(lambda.Abs)
233233+ if !ok {
234234+ t.Fatalf("Expected Abs, got %T", term)
235235+ }
236236+237237+ if abs.Arg != "x" {
238238+ t.Errorf("Expected argument 'x', got '%s'", abs.Arg)
239239+ }
240240+}
241241+242242+func TestTranslatorNestedFunctions(t *testing.T) {
243243+ p, err := parser.ParseString("x: y: z: x")
244244+ if err != nil {
245245+ t.Fatalf("Parse error: %v", err)
246246+ }
247247+248248+ translator := compiler.NewTranslator(p)
249249+ term, err := translator.Translate()
250250+ if err != nil {
251251+ t.Fatalf("Translation error: %v", err)
252252+ }
253253+254254+ // Check it's nested abstractions
255255+ abs1, ok := term.(lambda.Abs)
256256+ if !ok {
257257+ t.Fatalf("Expected first Abs, got %T", term)
258258+ }
259259+260260+ abs2, ok := abs1.Body.(lambda.Abs)
261261+ if !ok {
262262+ t.Fatalf("Expected second Abs, got %T", abs1.Body)
263263+ }
264264+265265+ abs3, ok := abs2.Body.(lambda.Abs)
266266+ if !ok {
267267+ t.Fatalf("Expected third Abs, got %T", abs2.Body)
268268+ }
269269+270270+ // Body should be a variable reference to x
271271+ varX, ok := abs3.Body.(lambda.Var)
272272+ if !ok {
273273+ t.Fatalf("Expected Var, got %T", abs3.Body)
274274+ }
275275+276276+ if varX.Name != "x" {
277277+ t.Errorf("Expected variable 'x', got '%s'", varX.Name)
278278+ }
279279+}
280280+281281+func TestLambdaTermString(t *testing.T) {
282282+ tests := []struct {
283283+ name string
284284+ expr string
285285+ }{
286286+ {"simple_var", "x"},
287287+ {"simple_abs", "x: x"},
288288+ {"simple_app", "(x: x) y"},
289289+ {"nested", "x: y: x y"},
290290+ }
291291+292292+ for _, tt := range tests {
293293+ t.Run(tt.name, func(t *testing.T) {
294294+ p, err := parser.ParseString(tt.expr)
295295+ if err != nil {
296296+ t.Fatalf("Parse error: %v", err)
297297+ }
298298+299299+ translator := compiler.NewTranslator(p)
300300+ term, err := translator.Translate()
301301+ if err != nil {
302302+ t.Fatalf("Translation error: %v", err)
303303+ }
304304+305305+ str := term.String()
306306+ if str == "" {
307307+ t.Error("Expected non-empty string representation")
308308+ }
309309+ })
310310+ }
311311+}
312312+313313+func TestChurchNumeralEncoding(t *testing.T) {
314314+ // Test that integers are properly encoded and decoded
315315+ testCases := []struct {
316316+ value int
317317+ expr string
318318+ }{
319319+ {0, "0"},
320320+ {1, "1"},
321321+ {2, "2"},
322322+ {5, "5"},
323323+ {10, "10"},
324324+ {42, "42"},
325325+ {100, "100"},
326326+ }
327327+328328+ for _, tc := range testCases {
329329+ t.Run(tc.expr, func(t *testing.T) {
330330+ result, err := compileAndEvaluateString(tc.expr)
331331+ if err != nil {
332332+ t.Fatalf("Failed to evaluate %s: %v", tc.expr, err)
333333+ }
334334+335335+ if result != tc.value {
336336+ t.Errorf("Expected %d, got %d", tc.value, result)
337337+ }
338338+ })
339339+ }
340340+}
341341+342342+func TestMain(m *testing.M) {
343343+ // Ensure examples directory exists before running tests
344344+ if _, err := os.Stat("../../examples"); os.IsNotExist(err) {
345345+ // Running from different directory, try current
346346+ if _, err := os.Stat("examples"); os.IsNotExist(err) {
347347+ panic("examples directory not found")
348348+ }
349349+ }
350350+351351+ os.Exit(m.Run())
352352+}
+96
cmd/parse-check/main.go
···11+package main
22+33+import (
44+ "flag"
55+ "fmt"
66+ "os"
77+ "path/filepath"
88+ "strings"
99+ "sync"
1010+ "time"
1111+1212+ "github.com/vic/godnix/pkg/parser"
1313+)
1414+1515+func main() {
1616+ parseCmd := flag.NewFlagSet("parse", flag.ExitOnError)
1717+ printSexp := parseCmd.Bool("sexp", false, "Print Sexp")
1818+ filePath := parseCmd.String("file", "", "Path to the Nix file to parse")
1919+ dirPath := parseCmd.String("dir", "", "Path to the directory containing Nix files to parse")
2020+2121+ parseCmd.Parse(os.Args[1:])
2222+ if *filePath != "" {
2323+ result, err := parser.ParseFile(*filePath)
2424+ if err != nil {
2525+ fmt.Printf("Error parsing file: %v\n", err)
2626+ os.Exit(1)
2727+ }
2828+ if *printSexp {
2929+ fmt.Printf("%v\n", result.LispResult())
3030+ }
3131+ } else if *dirPath != "" {
3232+ parseDirectory(*dirPath)
3333+ } else {
3434+ fmt.Println("Please provide a file path using --file or a directory path using --dir")
3535+ os.Exit(1)
3636+ }
3737+}
3838+3939+func parseDirectory(dirPath string) {
4040+ start := time.Now()
4141+ fmt.Println("Reading .nix files...")
4242+4343+ var nixFiles []string
4444+ err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
4545+ if err != nil {
4646+ return err
4747+ }
4848+ if !info.IsDir() && strings.HasSuffix(info.Name(), ".nix") {
4949+ nixFiles = append(nixFiles, path)
5050+ }
5151+ return nil
5252+ })
5353+5454+ if err != nil {
5555+ fmt.Printf("Error walking the path %s: %v\n", dirPath, err)
5656+ return
5757+ }
5858+5959+ readDuration := time.Since(start)
6060+ fmt.Printf("Time taken to find %v .nix files: %v\n", len(nixFiles), readDuration)
6161+6262+ startParsing := time.Now()
6363+ var wg sync.WaitGroup
6464+ var mu sync.Mutex
6565+ parsedCount := 0
6666+ failedCount := 0
6767+ failedFiles := []string{}
6868+6969+ for _, file := range nixFiles {
7070+ wg.Add(1)
7171+ go func(file string) {
7272+ defer wg.Done()
7373+ _, err := parser.ParseFile(file)
7474+ mu.Lock()
7575+ if err != nil {
7676+ failedCount++
7777+ failedFiles = append(failedFiles, file)
7878+ } else {
7979+ parsedCount++
8080+ }
8181+ mu.Unlock()
8282+ }(file)
8383+ }
8484+8585+ wg.Wait()
8686+ parsingDuration := time.Since(startParsing)
8787+ fmt.Printf("Parsing completed in %v\n", parsingDuration)
8888+ fmt.Printf("Total files parsed: %d\n", parsedCount)
8989+ fmt.Printf("Total files failed: %d\n", failedCount)
9090+ if failedCount > 0 {
9191+ fmt.Println("Failed files:")
9292+ for _, file := range failedFiles {
9393+ fmt.Println(file)
9494+ }
9595+ }
9696+}