├── internal ├── stream │ ├── test_assets │ │ ├── test1.txt │ │ ├── dir1 │ │ │ └── test1-1.txt │ │ ├── dir2 │ │ │ └── test2-1.txt │ │ └── test2.txt │ ├── doc.go │ ├── writer.go │ ├── stream.go │ └── writer_test.go ├── debug │ ├── doc.go │ └── error.go ├── strings │ ├── doc.go │ ├── camel.go │ └── camel_test.go ├── sync │ ├── doc.go │ ├── action.go │ ├── action_test.go │ └── promise_test.go ├── assert │ ├── doc.go │ ├── helpers │ │ ├── doc.go │ │ └── helpers_test.go │ ├── eval_test.go │ ├── compiler_test.go │ ├── env_test.go │ ├── testenv.go │ ├── env.go │ ├── eval.go │ └── compiler.go ├── basics │ ├── doc.go │ ├── maps.go │ └── maps_test.go ├── compiler │ ├── asm │ │ ├── doc.go │ │ ├── asm_test.go │ │ ├── for-each_test.go │ │ └── special_test.go │ ├── ir │ │ ├── analysis │ │ │ ├── doc.go │ │ │ ├── analysis.go │ │ │ └── jump.go │ │ ├── optimize │ │ │ ├── doc.go │ │ │ ├── literals_test.go │ │ │ ├── calls.go │ │ │ ├── tailcalls_test.go │ │ │ ├── literals.go │ │ │ └── returns_test.go │ │ └── visitor │ │ │ ├── doc.go │ │ │ └── replace_test.go │ ├── doc.go │ ├── encoder │ │ ├── doc.go │ │ ├── label.go │ │ ├── constant_test.go │ │ ├── constant.go │ │ ├── scope.go │ │ ├── closure.go │ │ ├── label_test.go │ │ ├── params_test.go │ │ ├── params.go │ │ └── cells.go │ ├── generate │ │ ├── doc.go │ │ ├── generate.go │ │ ├── branch.go │ │ ├── value_test.go │ │ ├── branch_test.go │ │ └── sequence_test.go │ ├── procedure │ │ ├── doc.go │ │ ├── procedure.go │ │ └── procedure_test.go │ ├── compile.go │ ├── call_test.go │ ├── compile_test.go │ └── call.go ├── runtime │ ├── doc.go │ ├── isa │ │ ├── doc.go │ │ └── effect_test.go │ └── vm │ │ ├── doc.go │ │ ├── reference.go │ │ ├── reference_test.go │ │ └── procedure_test.go ├── sequence │ ├── doc.go │ ├── take.go │ ├── last.go │ ├── filter.go │ ├── concat.go │ ├── take_test.go │ ├── lazy_test.go │ ├── last_test.go │ └── concat_test.go ├── types │ ├── doc.go │ ├── any.go │ ├── any_test.go │ ├── tuple_test.go │ ├── tuple.go │ ├── sequence.go │ ├── pair_test.go │ ├── literal.go │ ├── compound.go │ └── sequence_test.go └── lang │ ├── lex │ └── doc.go │ ├── doc.go │ ├── params │ └── doc.go │ ├── env │ ├── doc.go │ └── env.go │ └── parse │ ├── doc.go │ └── parse.go ├── docs └── logo.png ├── cmd └── ale │ ├── internal │ ├── doc.go │ ├── console │ │ ├── doc.go │ │ ├── console.go │ │ ├── windows.go │ │ └── paint_test.go │ ├── markdown │ │ ├── doc.go │ │ └── format_test.go │ ├── docstring │ │ ├── doc.go │ │ ├── eval.md │ │ ├── read.md │ │ ├── eq.md │ │ ├── lt.md │ │ ├── gt.md │ │ ├── neq.md │ │ ├── lte.md │ │ ├── gte.md │ │ ├── repl-quit.md │ │ ├── repl-cls.md │ │ ├── repl-use.md │ │ ├── thread-some-first.md │ │ ├── thread-some-last.md │ │ ├── begin.md │ │ ├── add.md │ │ ├── list.md │ │ ├── current-time.md │ │ ├── repl-doc.md │ │ ├── mul.md │ │ ├── sub.md │ │ ├── div.md │ │ ├── mod.md │ │ ├── sym.md │ │ ├── not.md │ │ ├── apply.md │ │ ├── vector.md │ │ ├── conj.md │ │ ├── first.md │ │ ├── lazy-seq.md │ │ ├── repl-help.md │ │ ├── go.md │ │ ├── partition.md │ │ ├── embed.go │ │ ├── seq-to-list.md │ │ ├── seq-to-vector.md │ │ ├── thread-first.md │ │ ├── thread-last.md │ │ ├── seq-to-object.md │ │ ├── concat.md │ │ ├── quote.md │ │ ├── rest.md │ │ ├── seq.md │ │ ├── drop.md │ │ ├── filter.md │ │ ├── get.md │ │ ├── range.md │ │ ├── partial.md │ │ ├── comp.md │ │ ├── take.md │ │ ├── juxt.md │ │ ├── is-identical.md │ │ ├── gensym.md │ │ ├── if.md │ │ ├── dissoc.md │ │ ├── length.md │ │ ├── and.md │ │ ├── future.md │ │ ├── lambda.md │ │ ├── or.md │ │ ├── define-macro.md │ │ ├── when.md │ │ ├── assoc.md │ │ ├── fold-left.md │ │ ├── last.md │ │ ├── str.md │ │ ├── cond.md │ │ ├── define-lambda.md │ │ ├── define.md │ │ ├── delay.md │ │ ├── object.md │ │ ├── thread-let.md │ │ ├── declare.md │ │ ├── is-seq.md │ │ ├── lazy.md │ │ ├── is-null.md │ │ ├── is-list.md │ │ ├── is-string.md │ │ ├── is-zero.md │ │ ├── is-atom.md │ │ ├── is-mapped.md │ │ ├── let.md │ │ ├── is-object.md │ │ ├── is-true.md │ │ ├── is-vector.md │ │ ├── is-false.md │ │ ├── cons.md │ │ ├── is-promise.md │ │ ├── is-indexed.md │ │ ├── map.md │ │ ├── is-counted.md │ │ ├── empty.md │ │ ├── is-keyword.md │ │ ├── generate.md │ │ ├── try.md │ │ ├── thread-cond-first.md │ │ ├── chan.md │ │ ├── thread-cond-last.md │ │ └── nth.md │ ├── unix.go │ ├── windows.go │ ├── history.go │ └── repl_test.go │ ├── doc.go │ └── main.go ├── .qlty ├── .gitignore └── configs │ └── .yamllint.yaml ├── core ├── source │ ├── embed.go │ ├── doc.go │ ├── namespaces.ale │ ├── os.ale │ ├── bootstrap.ale │ └── builtins.ale ├── special │ ├── doc.go │ ├── conditionals_test.go │ ├── asm.go │ ├── binding_test.go │ ├── namespace_test.go │ ├── lambda_test.go │ └── branching_test.go ├── builtin │ ├── numeric.go │ ├── object.go │ ├── doc.go │ ├── syntax_test.go │ ├── pairs_test.go │ ├── symbols.go │ ├── sequences.go │ ├── strings_test.go │ ├── strings.go │ ├── os_test.go │ ├── os.go │ ├── macros.go │ └── concurrency.go └── bootstrap │ ├── doc.go │ ├── definer_test.go │ ├── assets.go │ └── special.go ├── data ├── internal │ └── doc.go ├── comparison.go ├── value_test.go ├── doc.go ├── slice.go ├── value.go ├── bool_test.go ├── keyword_test.go ├── procedure_test.go ├── arity_test.go ├── keyword.go └── bool.go ├── read ├── internal │ ├── doc.go │ ├── read_test.go │ └── read.go ├── data │ ├── doc.go │ ├── data.go │ └── data_test.go ├── doc.go ├── read_test.go └── read.go ├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── coverage.yml ├── version.go ├── tools.go ├── ffi ├── maxkind │ └── value_gen.go ├── doc.go ├── bool_test.go ├── string_test.go ├── marshaled_test.go ├── value.go ├── bool.go ├── ptr.go ├── string.go ├── helper_test.go ├── box.go ├── wrappers.go └── map_test.go ├── doc.go ├── eval └── doc.go ├── env ├── doc.go └── chain.go ├── macro ├── doc.go ├── call.go ├── macro_test.go └── macro.go ├── Makefile ├── .gitignore ├── CONTRIBUTORS.md ├── go.mod ├── value.go └── LICENSE.md /internal/stream/test_assets/test1.txt: -------------------------------------------------------------------------------- 1 | test file 1 in root 2 | -------------------------------------------------------------------------------- /internal/stream/test_assets/dir1/test1-1.txt: -------------------------------------------------------------------------------- 1 | test file in dir1 2 | -------------------------------------------------------------------------------- /internal/stream/test_assets/dir2/test2-1.txt: -------------------------------------------------------------------------------- 1 | test file in dir2 2 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kode4food/ale/HEAD/docs/logo.png -------------------------------------------------------------------------------- /internal/debug/doc.go: -------------------------------------------------------------------------------- 1 | // Package debug provides debugging utilities 2 | package debug -------------------------------------------------------------------------------- /internal/stream/doc.go: -------------------------------------------------------------------------------- 1 | // Package stream provides I/O stream utilities 2 | package stream -------------------------------------------------------------------------------- /internal/stream/test_assets/test2.txt: -------------------------------------------------------------------------------- 1 | test file 2 in root 2 | has multiple lines 3 | -------------------------------------------------------------------------------- /internal/strings/doc.go: -------------------------------------------------------------------------------- 1 | // Package strings provides string utilities 2 | package strings -------------------------------------------------------------------------------- /internal/sync/doc.go: -------------------------------------------------------------------------------- 1 | // Package sync provides synchronization utilities 2 | package sync -------------------------------------------------------------------------------- /internal/assert/doc.go: -------------------------------------------------------------------------------- 1 | // Package assert provides testing utilities 2 | package assert 3 | -------------------------------------------------------------------------------- /internal/basics/doc.go: -------------------------------------------------------------------------------- 1 | // Package basics provides utility functions 2 | package basics 3 | -------------------------------------------------------------------------------- /internal/compiler/asm/doc.go: -------------------------------------------------------------------------------- 1 | // Package asm implements the Ale assembler 2 | package asm 3 | -------------------------------------------------------------------------------- /internal/runtime/doc.go: -------------------------------------------------------------------------------- 1 | // Package runtime provides runtime infrastructure 2 | package runtime -------------------------------------------------------------------------------- /internal/sequence/doc.go: -------------------------------------------------------------------------------- 1 | // Package sequence provides sequence utilities 2 | package sequence -------------------------------------------------------------------------------- /internal/compiler/ir/analysis/doc.go: -------------------------------------------------------------------------------- 1 | // Package analysis provides IR analysis 2 | package analysis -------------------------------------------------------------------------------- /internal/runtime/isa/doc.go: -------------------------------------------------------------------------------- 1 | // Package isa provides instruction set architecture 2 | package isa -------------------------------------------------------------------------------- /internal/runtime/vm/doc.go: -------------------------------------------------------------------------------- 1 | // Package vm provides virtual machine implementation 2 | package vm -------------------------------------------------------------------------------- /internal/types/doc.go: -------------------------------------------------------------------------------- 1 | // Package types provides type system implementation 2 | package types 3 | -------------------------------------------------------------------------------- /cmd/ale/internal/doc.go: -------------------------------------------------------------------------------- 1 | // Package internal provides the REPL implementation 2 | package internal 3 | -------------------------------------------------------------------------------- /internal/compiler/doc.go: -------------------------------------------------------------------------------- 1 | // Package compiler provides compilation infrastructure 2 | package compiler -------------------------------------------------------------------------------- /internal/compiler/encoder/doc.go: -------------------------------------------------------------------------------- 1 | // Package encoder provides bytecode encoding 2 | package encoder 3 | -------------------------------------------------------------------------------- /internal/compiler/generate/doc.go: -------------------------------------------------------------------------------- 1 | // Package generate provides code generation 2 | package generate 3 | -------------------------------------------------------------------------------- /internal/compiler/ir/optimize/doc.go: -------------------------------------------------------------------------------- 1 | // Package optimize provides IR optimization 2 | package optimize -------------------------------------------------------------------------------- /internal/compiler/ir/visitor/doc.go: -------------------------------------------------------------------------------- 1 | // Package visitor provides IR visitor patterns 2 | package visitor -------------------------------------------------------------------------------- /internal/lang/lex/doc.go: -------------------------------------------------------------------------------- 1 | // Package lex provides the Ale lexer language tokenizer 2 | package lex 3 | -------------------------------------------------------------------------------- /.qlty/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !configs 3 | !configs/** 4 | !hooks 5 | !hooks/** 6 | !qlty.toml 7 | !.gitignore 8 | -------------------------------------------------------------------------------- /internal/assert/helpers/doc.go: -------------------------------------------------------------------------------- 1 | // Package helpers provides test helper functions 2 | package helpers 3 | -------------------------------------------------------------------------------- /internal/compiler/procedure/doc.go: -------------------------------------------------------------------------------- 1 | // Package procedure provides procedure compilation 2 | package procedure -------------------------------------------------------------------------------- /internal/lang/doc.go: -------------------------------------------------------------------------------- 1 | // Package lang provides language definition, lexing, and parsing 2 | package lang 3 | -------------------------------------------------------------------------------- /internal/lang/params/doc.go: -------------------------------------------------------------------------------- 1 | // Package params provides Ale language parameter parsing 2 | package params 3 | -------------------------------------------------------------------------------- /cmd/ale/internal/console/doc.go: -------------------------------------------------------------------------------- 1 | // Package console provides console formatting utilities 2 | package console 3 | -------------------------------------------------------------------------------- /core/source/embed.go: -------------------------------------------------------------------------------- 1 | package source 2 | 3 | import "embed" 4 | 5 | //go:embed *.ale 6 | var Assets embed.FS 7 | -------------------------------------------------------------------------------- /data/internal/doc.go: -------------------------------------------------------------------------------- 1 | // Package internal provides utility types for the data package 2 | package internal 3 | -------------------------------------------------------------------------------- /internal/lang/env/doc.go: -------------------------------------------------------------------------------- 1 | // Package env provides environment definitions for the language. 2 | package env 3 | -------------------------------------------------------------------------------- /internal/lang/parse/doc.go: -------------------------------------------------------------------------------- 1 | // Package parse provides Ale language parsing functionality 2 | package parse 3 | -------------------------------------------------------------------------------- /read/internal/doc.go: -------------------------------------------------------------------------------- 1 | // Package internal provides parsing and tokenization mechanisms 2 | package internal 3 | -------------------------------------------------------------------------------- /cmd/ale/internal/markdown/doc.go: -------------------------------------------------------------------------------- 1 | // Package markdown provides markdown formatting for the REPL 2 | package markdown 3 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/doc.go: -------------------------------------------------------------------------------- 1 | // Package docstring provides documentation access for core library functions 2 | package docstring 3 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/eval.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "eval" 3 | description: "expands and evaluates a form" 4 | names: ["eval"] 5 | usage: "(eval form)" 6 | --- 7 | -------------------------------------------------------------------------------- /cmd/ale/internal/unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package internal 4 | 5 | import "slices" 6 | 7 | var farewells = slices.Concat(farewellLatin1, farewellUtf8) 8 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/read.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "read" 3 | description: "reads raw Lisp code into unexpanded data structures" 4 | names: ["read"] 5 | usage: "(read str)" 6 | --- 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/eq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "equal to (=)" 3 | description: "tests a set of numbers for numeric equality" 4 | names: ["="] 5 | usage: "(= form+)" 6 | tags: ["relational", "number"] 7 | --- 8 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/lt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "less-than (<)" 3 | description: "true if each number is less than the next" 4 | names: ["<"] 5 | usage: "(< form+)" 6 | tags: ["relational", "number"] 7 | --- 8 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package ale 2 | 3 | const ( 4 | // AppName is the name of the application 5 | AppName = "Ale (A Lisp Environment)" 6 | 7 | // Version is the current version 8 | Version = "0.3" 9 | ) 10 | -------------------------------------------------------------------------------- /cmd/ale/internal/console/console.go: -------------------------------------------------------------------------------- 1 | package console 2 | 3 | import "github.com/chzyer/readline" 4 | 5 | // GetScreenWidth returns the screen width of the console 6 | var GetScreenWidth = readline.GetScreenWidth 7 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/gt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "greater-than (>)" 3 | description: "true if each number is greater than the next" 4 | names: [">"] 5 | usage: "(> form+)" 6 | tags: ["relational", "number"] 7 | --- 8 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/neq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "not equal to (!=)" 3 | description: "tests a set of numbers for lack of numeric equality" 4 | names: ["!="] 5 | usage: "(!= form+)" 6 | tags: ["relational", "number"] 7 | --- 8 | -------------------------------------------------------------------------------- /cmd/ale/doc.go: -------------------------------------------------------------------------------- 1 | // Package main provides the Ale command-line interface and REPL. This is the 2 | // interactive entry point for the Ale interpreter, supporting both REPL 3 | // sessions and script file execution. 4 | package main 5 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/lte.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "less or equal (<=)" 3 | description: "true if each number is less than or equal to the next" 4 | names: ["<="] 5 | usage: "(<= form+)" 6 | tags: ["relational", "number"] 7 | --- 8 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package ale 5 | 6 | import ( 7 | _ "github.com/kode4food/gen-maxkind" 8 | _ "golang.org/x/tools/cmd/stringer" 9 | _ "honnef.co/go/tools/cmd/staticcheck" 10 | ) 11 | -------------------------------------------------------------------------------- /.qlty/configs/.yamllint.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | document-start: disable 3 | quoted-strings: 4 | required: only-when-needed 5 | extra-allowed: ["{|}"] 6 | key-duplicates: {} 7 | octal-values: 8 | forbid-implicit-octal: true 9 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/gte.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "greater or equal (>=)" 3 | description: "true if each number is greater than or equal to the next" 4 | names: [">="] 5 | usage: "(>= form+)" 6 | tags: ["relational", "number"] 7 | --- 8 | -------------------------------------------------------------------------------- /ffi/maxkind/value_gen.go: -------------------------------------------------------------------------------- 1 | // Code generated by gen_maxkind.go; DO NOT EDIT. 2 | package maxkind 3 | 4 | import "reflect" 5 | 6 | // Value is the largest reflect.Kind value for this Go version. 7 | const Value = reflect.UnsafePointer 8 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/repl-quit.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "quit" 3 | names: ["quit"] 4 | description: "quits the REPL" 5 | usage: "(quit)" 6 | draft: true 7 | --- 8 | 9 | Immediately exits the Ale REPL. This is a REPL-only function. 10 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/repl-cls.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "cls" 3 | names: ["cls"] 4 | description: "clears the screen" 5 | usage: "(cls)" 6 | draft: true 7 | --- 8 | 9 | Clear the current content of the screen. This is a REPL-only function. 10 | -------------------------------------------------------------------------------- /core/special/doc.go: -------------------------------------------------------------------------------- 1 | // Package special provides Ale's special forms and compiler primitives that 2 | // emit instructions during compilation. These include fundamental constructs 3 | // like lambda and let, as well as namespace management. 4 | package special 5 | -------------------------------------------------------------------------------- /data/comparison.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | // Comparison represents the result of an equality comparison 4 | type Comparison int 5 | 6 | // Comparison results 7 | const ( 8 | LessThan Comparison = iota - 1 9 | EqualTo 10 | GreaterThan 11 | Incomparable 12 | ) 13 | -------------------------------------------------------------------------------- /internal/compiler/encoder/label.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import "github.com/kode4food/ale/internal/runtime/isa" 4 | 5 | // NewLabel allocates a Label ( 6 | func (e *encoder) NewLabel() isa.Operand { 7 | res := e.nextLabel 8 | e.nextLabel++ 9 | return res 10 | } 11 | -------------------------------------------------------------------------------- /core/builtin/numeric.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | ) 7 | 8 | func isNaN(v ale.Value) bool { 9 | if num, ok := v.(data.Number); ok { 10 | return num.IsNaN() 11 | } 12 | return false 13 | } 14 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package ale provides a Lisp environment and interpreter implemented in Go. 2 | // It includes a complete Lisp runtime with bytecode compilation, virtual 3 | // machine execution, and standard library functions for building embedded Lisp 4 | // applications. 5 | package ale 6 | -------------------------------------------------------------------------------- /internal/compiler/generate/generate.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import "github.com/kode4food/ale/internal/compiler/encoder" 4 | 5 | // Builder is a callback that many of the functions in the package will invoke 6 | // to encode in-place instructions 7 | type Builder func(encoder.Encoder) error 8 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/repl-use.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "use" 3 | names: ["use"] 4 | description: "changes namespace" 5 | usage: "(use ns)" 6 | draft: true 7 | --- 8 | 9 | Changes the current namespace. This is a REPL-only function. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (use foo) 15 | ``` 16 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/thread-some-first.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "some first (some->)" 3 | description: "-> macro with short-circuiting" 4 | names: ["some->"] 5 | usage: "(some-> expr forms*)" 6 | tags: ["function"] 7 | --- 8 | 9 | Like `->`, but returns the empty list as soon as any form evaluates as such. 10 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/thread-some-last.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "some last (some->>)" 3 | description: "->> macro with short-circuiting" 4 | names: ["some->>"] 5 | usage: "(some->> expr forms*)" 6 | tags: ["function"] 7 | --- 8 | 9 | Like `->>`, but returns the empty list as soon as any form evaluates as such. 10 | -------------------------------------------------------------------------------- /eval/doc.go: -------------------------------------------------------------------------------- 1 | // Package eval provides the high-level evaluation interface for executing Ale 2 | // code. It coordinates the compilation pipeline and virtual machine execution, 3 | // offering a clean API for evaluating Ale expressions and managing evaluation 4 | // contexts within specific environments. 5 | package eval 6 | -------------------------------------------------------------------------------- /data/value_test.go: -------------------------------------------------------------------------------- 1 | package data_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | . "github.com/kode4food/ale/internal/assert/helpers" 8 | ) 9 | 10 | func TestNames(t *testing.T) { 11 | as := assert.New(t) 12 | 13 | n := LS("hello") 14 | as.Equal(LS("hello"), n) 15 | } 16 | -------------------------------------------------------------------------------- /cmd/ale/internal/windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package internal 4 | 5 | import "os" 6 | 7 | var farewells = getFarewells() 8 | 9 | func getFarewells() []string { 10 | if os.Getenv("ShellId") == "Microsoft.PowerShell" { 11 | return append(farewellLatin1, farewellUtf8...) 12 | } 13 | return farewellLatin1 14 | } 15 | -------------------------------------------------------------------------------- /read/data/doc.go: -------------------------------------------------------------------------------- 1 | // Package data provides a specialized reader interface for parsing pure data 2 | // expressions without code evaluation. This is used for reading configuration 3 | // files, data literals, and other non-executable content that should be parsed 4 | // as data structures but not compiled or evaluated as Ale code. 5 | package data 6 | -------------------------------------------------------------------------------- /read/doc.go: -------------------------------------------------------------------------------- 1 | // Package read provides the reader interface for parsing Ale source code into 2 | // abstract syntax trees. It handles tokenization of s-expressions, literal 3 | // parsing (strings, numbers, symbols), and construction of data structures 4 | // that represent the parsed program ready for compilation and evaluation. 5 | package read 6 | -------------------------------------------------------------------------------- /core/source/doc.go: -------------------------------------------------------------------------------- 1 | // Package source provides embedded Ale language source files that implement 2 | // the core standard library. These .ale files contain Lisp code for built-in 3 | // functions, macros, and utilities that are loaded during bootstrap to create 4 | // the complete Ale runtime environment with all standard library features. 5 | package source 6 | -------------------------------------------------------------------------------- /env/doc.go: -------------------------------------------------------------------------------- 1 | // Package env provides namespace and binding management for the Ale runtime. 2 | // It implements lexical scoping, symbol resolution, and environment chaining 3 | // that supports nested scopes, closures, and public/private visibility. This 4 | // is the foundation for global storage and lookup throughout the Ale 5 | // interpreter. 6 | package env 7 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/begin.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "begin" 3 | description: "evaluates a sequence of forms" 4 | names: ["begin"] 5 | usage: "(begin form*)" 6 | --- 7 | 8 | Evaluate each form in turn, returning the final evaluation as its result. 9 | 10 | #### An Example 11 | 12 | ```scheme 13 | (begin 14 | (println "hello") 15 | "returned") 16 | ``` 17 | -------------------------------------------------------------------------------- /core/bootstrap/doc.go: -------------------------------------------------------------------------------- 1 | // Package bootstrap performs initial setup of the Ale runtime environment. It 2 | // populates a new Environment with all core functions, macros, special forms, 3 | // and standard library definitions. Create a new `*env.Environment` and pass 4 | // it to `Into` to initialize a complete Ale runtime ready for code evaluation. 5 | package bootstrap 6 | -------------------------------------------------------------------------------- /macro/doc.go: -------------------------------------------------------------------------------- 1 | // Package macro provides Ale's macro expansion system for compile-time code 2 | // transformation. It handles macro definition, expansion, and the hygiene 3 | // mechanisms that enable powerful metaprogramming while avoiding variable 4 | // capture issues. This enables code generation and domain-specific language 5 | // creation within Ale. 6 | package macro 7 | -------------------------------------------------------------------------------- /core/builtin/object.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | ) 7 | 8 | // Object creates a new object instance 9 | var Object = data.MakeProcedure(func(args ...ale.Value) ale.Value { 10 | res, err := data.ValuesToObject(args...) 11 | if err != nil { 12 | panic(err) 13 | } 14 | return res 15 | }) 16 | -------------------------------------------------------------------------------- /data/doc.go: -------------------------------------------------------------------------------- 1 | // Package data provides the fundamental value types and interfaces that form 2 | // the core of Ale's type system. This includes all runtime data structures 3 | // (keywords, lists, vectors, objects, symbols, etc.) that implement the Value 4 | // interface, along with immutable collection types and equality semantics 5 | // essential for data manipulation. 6 | package data 7 | -------------------------------------------------------------------------------- /ffi/doc.go: -------------------------------------------------------------------------------- 1 | // Package ffi provides Foreign Function Interface capabilities that enable 2 | // seamless integration between Ale and Go code. It handles marshaling and 3 | // unmarshaling between native Go values and Ale's value types, allowing Go 4 | // functions to be called from Ale code and Ale values to be used in Go 5 | // applications that embed the Ale interpreter. 6 | package ffi 7 | -------------------------------------------------------------------------------- /internal/compiler/asm/asm_test.go: -------------------------------------------------------------------------------- 1 | package asm_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/internal/assert" 8 | "github.com/kode4food/ale/internal/compiler/ir/analysis" 9 | ) 10 | 11 | func TestAsmStackSizeError(t *testing.T) { 12 | as := assert.New(t) 13 | as.ErrorWith(`(asm pop)`, fmt.Errorf(analysis.ErrBadStackTermination, -2)) 14 | } 15 | -------------------------------------------------------------------------------- /core/source/namespaces.ale: -------------------------------------------------------------------------------- 1 | 2 | ;;;; ale core: namespaces 3 | 4 | (def-special %mk-ns) 5 | (def-special declared) 6 | (def-special import) 7 | 8 | (define-macro (define-namespace name . forms) 9 | (let [in-ns (gensym 'in-ns)] 10 | `(begin 11 | (eval '(define ,in-ns (%mk-ns ,name))) 12 | ,@(map! (lambda (f) `(eval '(,in-ns ,f))) forms) 13 | (eval '(declared ,name))))) 14 | -------------------------------------------------------------------------------- /internal/runtime/vm/reference.go: -------------------------------------------------------------------------------- 1 | package vm 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | ) 6 | 7 | // Ref encapsulates a reference to a Value 8 | type Ref struct { 9 | ale.Value 10 | } 11 | 12 | func (r *Ref) Equal(other ale.Value) bool { 13 | if other, ok := other.(*Ref); ok { 14 | return r == other || r.Value.Equal(other.Value) 15 | } 16 | return r.Value.Equal(other) 17 | } 18 | -------------------------------------------------------------------------------- /internal/types/any.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "github.com/kode4food/ale" 4 | 5 | // Any accepts a Value of any other Type 6 | type Any struct{ *basic } 7 | 8 | var BasicAny = &Any{ 9 | basic: makeBasic("any"), 10 | } 11 | 12 | func (*Any) Accepts(ale.Type) bool { 13 | return true 14 | } 15 | 16 | func (a *Any) Equal(other ale.Type) bool { 17 | _, ok := other.(*Any) 18 | return ok 19 | } 20 | -------------------------------------------------------------------------------- /core/builtin/doc.go: -------------------------------------------------------------------------------- 1 | // Package builtin provides core runtime functions implemented in Go that form 2 | // the foundation of Ale's standard library. These include essential operations 3 | // like I/O, type checking, sequence manipulation, and system integration that 4 | // require direct access to Go's capabilities or are more efficient when 5 | // implemented natively rather than in Ale itself. 6 | package builtin 7 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/add.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "addition (+)" 3 | description: "calculates the sum of a number sequence" 4 | names: ["+"] 5 | usage: "(+ form*)" 6 | tags: ["math", "number"] 7 | --- 8 | 9 | Calculates the sum of a set of numbers. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (+ 9 10 23) ;; returns 42 15 | (+ 50 12.6 34.8) ;; returns 97.4 16 | (+) ;; returns 0 17 | ``` 18 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "list" 3 | description: "creates a new list" 4 | names: ["list"] 5 | usage: "(list form*)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | Create a new list whose elements are the evaluated forms provided, or return the empty list if no forms are provided. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x "hello") 15 | (define y "there") 16 | (list x y) 17 | ``` 18 | -------------------------------------------------------------------------------- /core/builtin/syntax_test.go: -------------------------------------------------------------------------------- 1 | package builtin_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | ) 10 | 11 | func TestQuoteObject(t *testing.T) { 12 | as := assert.New(t) 13 | as.MustEvalTo( 14 | "(let [x :hello] `{,x 99})", 15 | O(data.NewCons(K("hello"), I(99))), 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/current-time.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "current-time" 3 | description: "returns the current system time (in nanoseconds)" 4 | names: ["current-time"] 5 | usage: "(current-time)" 6 | tags: ["os"] 7 | --- 8 | 9 | Returns the system's current time, measured in nanoseconds since January 1, 1970 UTC. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (current-time) ;; returns 1554720691499809478 15 | ``` 16 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/repl-doc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "doc" 3 | description: "displays documentation" 4 | usage: "(doc form?)" 5 | names: ["doc"] 6 | draft: true 7 | --- 8 | 9 | Displays the documentation for the specified form if any exists. This is a 10 | REPL-only function. 11 | 12 | #### An Example 13 | 14 | ```scheme 15 | (doc async) 16 | ``` 17 | 18 | Where `form` is any of the core language symbols: 19 | 20 | %s 21 | -------------------------------------------------------------------------------- /core/source/os.ale: -------------------------------------------------------------------------------- 1 | ;;;; ale core: os 2 | 3 | (def-builtin current-time) 4 | 5 | (declare *env* *args*) 6 | 7 | (define-macro (time . forms) 8 | `(let* ([start# (current-time)] 9 | [result# (begin ,@forms)] 10 | [end# (current-time)] 11 | [dur# (- end# start#)]) 12 | (if (< dur# 1000) 13 | (println dur# "ns") 14 | (println (/ dur# 1000000.0) "ms")) 15 | result#)) 16 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/mul.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "multiplication (*)" 3 | description: "calculates the product of a number sequence" 4 | names: ["*"] 5 | usage: "(* form*)" 6 | tags: ["math", "number"] 7 | --- 8 | 9 | Calculates the product of a set of numbers. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (\* 9 10 23) ;; returns 2070 15 | (\* 50 12.6 34.8) ;; returns 21924 16 | (\*) ;; returns 1 17 | ``` 18 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/sub.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "subtraction (-)" 3 | description: "calculates the difference of a number sequence" 4 | names: ["-"] 5 | usage: "(- form+)" 6 | tags: ["math", "number"] 7 | --- 8 | 9 | Takes a set of numbers and calculates their collective difference. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (- 50 35 6) ;; returns 9 15 | (- 12 3.5) ;; returns 8.5 16 | (- 6) ;; returns -6 17 | ``` 18 | -------------------------------------------------------------------------------- /internal/compiler/compile.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | ) 7 | 8 | // IsEvaluable returns whether the provided value is subject to further 9 | // evaluation 10 | func IsEvaluable(v ale.Value) bool { 11 | switch v.(type) { 12 | case data.Symbol, *data.List, data.Vector, *data.Object: 13 | return true 14 | default: 15 | return false 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/div.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "division (/)" 3 | description: "calculates the quotient of a number sequence" 4 | names: ["/"] 5 | usage: "(/ form+)" 6 | tags: ["math", "number"] 7 | --- 8 | 9 | Calculates the collective quotient of a set of numbers. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (/ 10 3) ;; returns 10/3 15 | (/ 10 3.0) ;; returns 3.3333333333333335 16 | (/ 20 2.0 4) ;; returns 2.5 17 | ``` 18 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/mod.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "remainder (mod)" 3 | description: "calculates the remainder of a number sequence" 4 | names: ["mod"] 5 | usage: "(mod form+)" 6 | tags: ["math", "number"] 7 | --- 8 | 9 | Takes a set of numbers and calculates the collective remainder of dividing each by the next. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (mod 10 3) ;; returns 1 15 | (mod 20 7.0 4) ;; returns 2.0 16 | ``` 17 | -------------------------------------------------------------------------------- /internal/assert/eval_test.go: -------------------------------------------------------------------------------- 1 | package assert_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/data" 8 | "github.com/kode4food/ale/internal/assert" 9 | ) 10 | 11 | func TestEval(t *testing.T) { 12 | as := assert.New(t) 13 | as.MustEvalTo(`2`, data.Integer(2)) 14 | } 15 | 16 | func TestPanicWith(t *testing.T) { 17 | as := assert.New(t) 18 | as.PanicWith(`(raise "boom")`, fmt.Errorf("boom")) 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v3 13 | - name: Set up Go 14 | uses: actions/setup-go@v4 15 | with: 16 | go-version: '1.24' 17 | - name: Build and Test 18 | run: make 19 | -------------------------------------------------------------------------------- /read/read_test.go: -------------------------------------------------------------------------------- 1 | package read_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | . "github.com/kode4food/ale/internal/assert/helpers" 8 | "github.com/kode4food/ale/read" 9 | ) 10 | 11 | func TestFromString(t *testing.T) { 12 | as := assert.New(t) 13 | ns := assert.GetTestNamespace() 14 | tr := read.MustFromString(ns, "99") 15 | if as.NotNil(tr) { 16 | as.Equal(I(99), tr.Car()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/sym.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "sym" 3 | description: "converts a string into a symbol" 4 | names: ["sym"] 5 | usage: "(sym str)" 6 | tags: ["symbol", "macro"] 7 | --- 8 | 9 | Convert the provided string into a symbol. Both qualified and local symbols are accepted. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define hello-sym (sym "hello")) 15 | (eq hello-sym 'hello) 16 | ``` 17 | 18 | This example will return _#t_ (true). 19 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/not.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "not" 3 | description: "logically inverts the truthiness of the provided form" 4 | names: ["not"] 5 | usage: "(not form)" 6 | tags: ["logic"] 7 | --- 8 | 9 | Return _#f_ (false) if the provided _form_ is truthy, otherwise will return _#t_ (true). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (not "hello") 15 | ``` 16 | 17 | This will return the boolean _#f_ (false) because the value _"hello"_ is truthy. 18 | -------------------------------------------------------------------------------- /cmd/ale/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/kode4food/ale/cmd/ale/internal" 7 | ) 8 | 9 | func main() { 10 | switch { 11 | case isStdInPiped(): 12 | internal.EvaluateStdIn() 13 | case len(os.Args) < 2: 14 | internal.NewREPL().Run() 15 | default: 16 | internal.EvaluateFile(os.Args[1]) 17 | } 18 | } 19 | 20 | func isStdInPiped() bool { 21 | s, _ := os.Stdin.Stat() 22 | return (s.Mode() & os.ModeCharDevice) == 0 23 | } 24 | -------------------------------------------------------------------------------- /ffi/bool_test.go: -------------------------------------------------------------------------------- 1 | package ffi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/ffi" 8 | "github.com/kode4food/ale/internal/assert" 9 | ) 10 | 11 | func TestBoolWrapper(t *testing.T) { 12 | as := assert.New(t) 13 | f := ffi.MustWrap(func(b bool) bool { 14 | return !b 15 | }).(data.Procedure) 16 | 17 | b := f.Call(data.False) 18 | as.True(b) 19 | 20 | b = f.Call(data.True) 21 | as.False(b) 22 | } 23 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/apply.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "apply" 3 | usage: "(apply func * seq)" 4 | names: ["apply"] 5 | description: "applies a function to the provided arguments" 6 | tags: ["function"] 7 | --- 8 | 9 | Evaluates the provided sequence and applies the provided function to its values and any explicitly included arguments. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x '(1 2 3)) 15 | (apply + x) 16 | ``` 17 | 18 | This example will return _6_. 19 | -------------------------------------------------------------------------------- /internal/sequence/take.go: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | ) 7 | 8 | func Take(s data.Sequence, count int) (data.Vector, data.Sequence, bool) { 9 | var f ale.Value 10 | var ok bool 11 | res := make(data.Vector, count) 12 | for i := range count { 13 | if f, s, ok = s.Split(); !ok { 14 | return data.EmptyVector, data.Null, false 15 | } 16 | res[i] = f 17 | } 18 | return res, s, true 19 | } 20 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/vector.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "vector" 3 | description: "creates a new vector" 4 | names: ["vector"] 5 | usage: "(vector form*)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | Create a new vector whose elements are the evaluated forms provided. This function is no different from the vector literal syntax except that it can be treated in a first-class fashion. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x "hello") 15 | (define y "there") 16 | (vector x y) 17 | ``` 18 | -------------------------------------------------------------------------------- /internal/lang/env/env.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import "github.com/kode4food/ale/data" 4 | 5 | const ( 6 | // RootDomain stores built-ins 7 | RootDomain = data.Local("ale") 8 | 9 | // AnonymousDomain identifies an anonymous namespace 10 | AnonymousDomain = data.Local("*anon*") 11 | ) 12 | 13 | const ( 14 | Args = data.Local("*args*") 15 | Env = data.Local("*env*") 16 | FS = data.Local("*fs*") 17 | In = data.Local("*in*") 18 | Out = data.Local("*out*") 19 | Err = data.Local("*err*") 20 | ) 21 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/conj.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "conj" 3 | description: "adds elements to a sequence" 4 | names: ["conj"] 5 | usage: "(conj seq form+)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | Adds elements to a conjoinable sequence. This behavior will differ depending on the concrete type. A list will prepend, a vector will append, while an object makes no guarantees about ordering. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (conj [1 2 3 4] 5 6 7 8) 15 | ``` 16 | 17 | Will return the vector _[1 2 3 4 5 6 7 8]_. 18 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/first.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "car" 3 | description: "returns the first element of a pair or sequence" 4 | names: ["car", "first"] 5 | usage: "(car form)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | This function will return the first element of the specified pair or sequence, or the empty list if the sequence is empty. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x '(99 64 32 48)) 15 | (car x) ;; will return 99 16 | 17 | (define y (100 . 200)) 18 | (car y) ;; will return 100 19 | ``` 20 | -------------------------------------------------------------------------------- /ffi/string_test.go: -------------------------------------------------------------------------------- 1 | package ffi_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/data" 8 | "github.com/kode4food/ale/ffi" 9 | "github.com/kode4food/ale/internal/assert" 10 | . "github.com/kode4food/ale/internal/assert/helpers" 11 | ) 12 | 13 | func TestStringWrapper(t *testing.T) { 14 | as := assert.New(t) 15 | f := ffi.MustWrap(func(s string) string { 16 | return fmt.Sprintf("Hello, %s!", s) 17 | }).(data.Procedure) 18 | s := f.Call(S("Ale")) 19 | as.String("Hello, Ale!", s) 20 | } 21 | -------------------------------------------------------------------------------- /read/read.go: -------------------------------------------------------------------------------- 1 | package read 2 | 3 | import ( 4 | "github.com/kode4food/ale/internal/lang/lex" 5 | "github.com/kode4food/ale/read/internal" 6 | ) 7 | 8 | var ( 9 | Tokenize = internal.MakeTokenizer(lex.ExhaustiveMatcher( 10 | lex.Ignorable, 11 | lex.Structure, 12 | lex.Quoting, 13 | lex.Values, 14 | lex.Preprocessors, 15 | )) 16 | 17 | MustTokenize = internal.MakeMustTokenizer(Tokenize) 18 | FromString = internal.MakeFromString(Tokenize) 19 | MustFromString = internal.MakeMustFromString(FromString) 20 | ) 21 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/lazy-seq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "lazy-seq" 3 | description: "produces a sequence that is evaluated lazily" 4 | names: ["lazy-seq"] 5 | usage: "(lazy-seq form*)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | #### An Example: 10 | 11 | ```scheme 12 | (define (fib-seq) 13 | (let [fib (lambda-rec fib (a b) 14 | (lazy-seq (cons a (fib b (+ a b)))))] 15 | (fib 0 1))) 16 | 17 | (for-each [x (take 300 (fib-seq))] 18 | (println x)) 19 | ``` 20 | 21 | This example prints the first 300 fibonacci numbers. 22 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/repl-help.md: -------------------------------------------------------------------------------- 1 | --- 2 | names: ["help"] 3 | draft: true 4 | --- 5 | 6 | **(help)** Display this help message 7 | **(doc form)** Display documentation. Example: `(doc go)` 8 | **(use ns)** Change current namespace. Example: `(use foo)` 9 | **(cls)** Clear the screen 10 | **(quit)** Quit the REPL 11 | 12 | The prompt below identifies the current namespace, followed by the number of expressions that will have been evaluated in the current REPL session. A single expression can span multiple lines. 13 | -------------------------------------------------------------------------------- /core/special/conditionals_test.go: -------------------------------------------------------------------------------- 1 | package special_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | ) 10 | 11 | func TestIfEval(t *testing.T) { 12 | as := assert.New(t) 13 | as.MustEvalTo(`(if false 1 0)`, F(0)) 14 | as.MustEvalTo(`(if true 1 0)`, F(1)) 15 | as.MustEvalTo(`(if '() 1 0)`, F(1)) 16 | as.MustEvalTo(`(if "hello" 1 0)`, F(1)) 17 | as.MustEvalTo(`(if false 1)`, data.Null) 18 | } 19 | -------------------------------------------------------------------------------- /read/data/data.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "github.com/kode4food/ale/internal/lang/lex" 5 | "github.com/kode4food/ale/read/internal" 6 | ) 7 | 8 | var ( 9 | Tokenize = internal.MakeTokenizer(lex.ExhaustiveMatcher( 10 | lex.Ignorable, 11 | lex.Structure, 12 | lex.Quoting.Error(), 13 | lex.Values, 14 | lex.Symbols, 15 | )) 16 | 17 | MustTokenize = internal.MakeMustTokenizer(Tokenize) 18 | FromString = internal.MakeFromString(Tokenize) 19 | MustFromString = internal.MakeMustFromString(FromString) 20 | ) 21 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/go.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "go" 3 | description: "asynchronously evaluates a block" 4 | names: ["go", "go!", "go-with-monitor"] 5 | usage: "(go form*) (go! form*) (go-with-monitor func form*)" 6 | tags: ["concurrency"] 7 | --- 8 | 9 | The provided forms will be evaluated in a separate thread of execution. Any resulting value of the block will be discarded. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define ch (chan)) 15 | (go (: ch :emit "hello") 16 | (: ch :close)) 17 | (str (first (ch :seq)) " world!") 18 | ``` 19 | -------------------------------------------------------------------------------- /internal/runtime/isa/effect_test.go: -------------------------------------------------------------------------------- 1 | package isa_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/internal/assert" 8 | "github.com/kode4food/ale/internal/runtime/isa" 9 | ) 10 | 11 | func TestEffects(t *testing.T) { 12 | as := assert.New(t) 13 | 14 | e1 := isa.MustGetEffect(isa.CondJump) 15 | as.Equal(isa.Labels, e1.Operand) 16 | 17 | _, err := isa.GetEffect(isa.Opcode(5000)) 18 | as.EqualError(err, fmt.Sprintf( 19 | "%s: %s", isa.ErrEffectNotDeclared.Error(), isa.Opcode(5000).String(), 20 | )) 21 | } 22 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/partition.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "partition" 3 | description: "partitions a sequence" 4 | names: ["partition"] 5 | usage: "(partition count step? seq)" 6 | tags: ["sequence", "comprehension"] 7 | --- 8 | 9 | Partition a sequence into groups of _count_ elements, incrementing by the number of elements defined in _step_ (or _count_ if _step_ is not provided). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (seq->list (partition 2 3 [1 2 3 4 5 6 7 8 9 10])) 15 | ``` 16 | 17 | This example will return _((1 2) (4 5) (7 8) (10))_. 18 | -------------------------------------------------------------------------------- /internal/compiler/ir/visitor/replace_test.go: -------------------------------------------------------------------------------- 1 | package visitor_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | "github.com/kode4food/ale/internal/compiler/ir/optimize" 8 | "github.com/kode4food/ale/internal/runtime/isa" 9 | ) 10 | 11 | func TestReplace(t *testing.T) { 12 | as := assert.New(t) 13 | 14 | e1 := assert.GetTestEncoder() 15 | e1.Emit(isa.False) 16 | e1.Emit(isa.Return) 17 | 18 | as.Instructions(isa.Instructions{ 19 | isa.RetFalse.New(), 20 | }, optimize.Encoded(e1.Encode()).Code) 21 | } 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DIST_DIR ?= ./dist 2 | EXE = $(DIST_DIR)/ale 3 | GO ?= go 4 | 5 | .PHONY: all install build test generate clean 6 | 7 | all: build 8 | 9 | install: test 10 | $(GO) install github.com/kode4food/ale/cmd/ale 11 | 12 | build: test 13 | @mkdir -p $(DIST_DIR) 14 | @rm -f $(EXE) 15 | $(GO) build -o $(EXE) ./cmd/ale 16 | 17 | test: generate 18 | $(GO) test ./... 19 | $(GO) vet ./... 20 | $(GO) run honnef.co/go/tools/cmd/staticcheck ./... 21 | 22 | generate: 23 | $(GO) generate ./... 24 | 25 | clean: 26 | @rm -f $(EXE) 27 | @rmdir $(DIST_DIR) 28 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/embed.go: -------------------------------------------------------------------------------- 1 | package docstring 2 | 3 | import ( 4 | "embed" 5 | "io/fs" 6 | 7 | "github.com/kode4food/ale/internal/basics" 8 | ) 9 | 10 | var ( 11 | //go:embed *.md 12 | assets embed.FS 13 | 14 | // getAsset exposes the assets FS ReadFile method 15 | getAsset = assets.ReadFile 16 | ) 17 | 18 | // assetNames returns the names of the available docstring files 19 | func assetNames() []string { 20 | files, _ := assets.ReadDir(".") 21 | return basics.Map(files, func(f fs.DirEntry) string { 22 | return f.Name() 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/seq-to-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "seq->list" 3 | description: "converts sequences to a list" 4 | names: ["seq->list"] 5 | usage: "(seq->list seq+)" 6 | tags: ["sequence", "conversion"] 7 | --- 8 | 9 | Concatenate a set of sequences into a list. Unlike the standard `concat` function, which is lazily computed, the result of this function will be materialized immediately. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x [1 2 3 4]) 15 | (define y 16 | (map (lambda (x) (+ x 4)) 17 | '(1 2 3 4))) 18 | (seq->list x y) 19 | ``` 20 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/seq-to-vector.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "seq->vector" 3 | description: "converts sequences to a vector" 4 | names: ["seq->vector"] 5 | usage: "(seq->vector seq+)" 6 | tags: ["sequence", "conversion"] 7 | --- 8 | 9 | Concatenate a set of sequences into a vector. Unlike the standard `concat` function, which is lazily computed, the result of this function will be materialized immediately. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x 15 | (map (lambda (x) (\* x 2)) 16 | '(1 2 3 4))) 17 | (seq->vector '(1 2 3 4) x) 18 | ``` 19 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/thread-first.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "thread first (->)" 3 | description: "threads value through calls as their first argument" 4 | names: ["->"] 5 | usage: "(-> expr forms*)" 6 | tags: ["function"] 7 | --- 8 | 9 | Evaluates _expr_ and threads it through the supplied forms as their first argument. Any form that is not already a function call will be converted into one before threading. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (-> 0 (+ 10) (\* 2) (/ 5)) 15 | ``` 16 | 17 | Will expand to `(/ (\* (+ 0 10) 2) 5)` and return _4_. 18 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/thread-last.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "thread last (->>)" 3 | description: "threads value through calls as their last argument" 4 | names: ["->>"] 5 | usage: "(->> expr forms*)" 6 | tags: ["function"] 7 | --- 8 | 9 | Evaluates _expr_ and threads it through the supplied forms as their last argument. Any form that is not already a function call will be converted into one before threading. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (->> 0 (+ 10) (\* 2) (/ 5.0)) 15 | ``` 16 | 17 | Will expand to `(/ 5.0 (\* 2 (+ 10 0)))` and return _0.25_. 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | vendor 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | dist 24 | 25 | *.exe 26 | *.test 27 | *.prof 28 | 29 | .idea 30 | .settings 31 | .vscode 32 | *.iml 33 | *~ 34 | \#*# 35 | 36 | *.sublime-workspace 37 | *.sublime-project 38 | 39 | .DS_Store 40 | 41 | .#* 42 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/seq-to-object.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "seq->object" 3 | description: "converts sequences to an object" 4 | names: ["seq->object"] 5 | usage: "(seq->object seq+)" 6 | tags: ["sequence", "conversion"] 7 | --- 8 | 9 | Concatenate a set of sequences into an object (hash-map). Unlike the standard `concat` function, which is lazily computed, the result of this function will be materialized immediately. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x [:name "ale" :age 0.3]) 15 | (define y '(:weight "light")) 16 | (seq->object x y) 17 | ``` 18 | -------------------------------------------------------------------------------- /internal/compiler/ir/analysis/analysis.go: -------------------------------------------------------------------------------- 1 | package analysis 2 | 3 | import "github.com/kode4food/ale/internal/runtime/isa" 4 | 5 | // Verify checks an ISA Instruction stream for validity. Specifically, it will 6 | // check that jumps do not target offsets outside the instructions provided and 7 | // that the stack is left in a consistent state upon exit 8 | func Verify(code isa.Instructions) error { 9 | if err := verifyJumps(code); err != nil { 10 | return err 11 | } 12 | if err := verifyStackSize(code); err != nil { 13 | return err 14 | } 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/concat.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "concat" 3 | description: "concatenates sequences" 4 | names: ["concat", "concat!"] 5 | usage: "(concat seq+) (concat! seq+)" 6 | tags: ["sequence", "comprehension"] 7 | --- 8 | 9 | Creates a lazy sequence whose content is the result of concatenating the elements of each provided sequence. To immediately materialize a complete concatenated sequence, use the `concat!` function. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (seq->list (concat [1 2 3] '(4 5 6))) 15 | ``` 16 | 17 | This will return the list _(1 2 3 4 5 6)_ 18 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/quote.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "quote" 3 | description: "returns the specified form in data mode" 4 | names: ["quote"] 5 | usage: "(quote form)" 6 | tags: ["macro"] 7 | --- 8 | 9 | Meaning that lists and symbols will not be evaluated. This macro is effectively the same as prepending an expression with an apostrophe (_'_). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (quote (1 2 3 4)) 15 | ``` 16 | 17 | This will return the literal list rather than trying to apply the number 1, as if it were a function. It is synonymous with the expression `'(1 2 3 4)`. 18 | -------------------------------------------------------------------------------- /internal/compiler/encoder/constant_test.go: -------------------------------------------------------------------------------- 1 | package encoder_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | . "github.com/kode4food/ale/internal/assert/helpers" 8 | ) 9 | 10 | func TestConstants(t *testing.T) { 11 | as := assert.New(t) 12 | 13 | e := assert.GetTestEncoder() 14 | i1 := e.AddConstant(S("hello")) 15 | i2 := e.AddConstant(I(42)) 16 | i3 := e.AddConstant(S("hello")) 17 | as.Equal(i1, i3) 18 | 19 | c := e.Encode().Constants 20 | as.Equal(2, len(c)) 21 | as.Equal(S("hello"), c[i1]) 22 | as.Equal(I(42), c[i2]) 23 | } 24 | -------------------------------------------------------------------------------- /internal/debug/error.go: -------------------------------------------------------------------------------- 1 | package debug 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | // ProgrammerError is raised when a condition arises that absolutely should not 9 | // happen unless one of the compiler authors screwed up royally. Represented as 10 | // a string to distinguish it from proper errors. 11 | func ProgrammerError(msg string) string { 12 | return errors.New(msg).Error() 13 | } 14 | 15 | // ProgrammerErrorf is a formatted version of ProgrammerError. 16 | func ProgrammerErrorf(format string, a ...any) string { 17 | return fmt.Errorf(format, a...).Error() 18 | } 19 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/rest.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "cdr" 3 | description: "returns the rest of a pair or sequence" 4 | names: ["cdr", "rest"] 5 | usage: "(cdr seq)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | This function will return the portion of a pair or sequence that excludes its first element. For sequences, this will be the remainder of the sequence. For cons pairs, this will be the cdr portion. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x '(99 64 32 48)) 15 | (cdr x) ;; will return (64, 32, 48) 16 | 17 | (define y (100 . 200)) 18 | (cdr y) ;; will return 200 19 | ``` 20 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/seq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "seq" 3 | description: "attempts to convert form to a sequence" 4 | names: ["seq"] 5 | usage: "(seq form)" 6 | tags: ["sequence", "conversion"] 7 | --- 8 | 9 | Attempt to convert the provided form to a sequence if it isn't already. If the form cannot be converted, or if the resulting sequence is empty, the empty list will be returned. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (when-let [s (seq "hello")] 15 | (seq->vector (map (lambda (x) (str x "-")) s))) 16 | ``` 17 | 18 | This example will return _["h-" "e-" "l-" "l-" "o-"]_. 19 | -------------------------------------------------------------------------------- /internal/compiler/call_test.go: -------------------------------------------------------------------------------- 1 | package compiler_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/internal/assert" 8 | "github.com/kode4food/ale/internal/compiler" 9 | "github.com/kode4food/ale/internal/compiler/encoder" 10 | ) 11 | 12 | func TestCall(t *testing.T) { 13 | as := assert.New(t) 14 | f1 := func(encoder.Encoder, ...ale.Value) error { return nil } 15 | c1 := compiler.Call(f1) 16 | as.True(compiler.CallType.Accepts(c1.Type())) 17 | as.False(c1.Type().Accepts(compiler.CallType)) 18 | as.Contains(`special(0x`, c1) 19 | } 20 | -------------------------------------------------------------------------------- /internal/sequence/last.go: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | ) 7 | 8 | // Last returns the final element of a Sequence, possibly by scanning 9 | func Last(s data.Sequence) (ale.Value, bool) { 10 | if s.IsEmpty() { 11 | return data.Null, false 12 | } 13 | 14 | if i, ok := s.(data.Indexed); ok { 15 | return i.ElementAt(i.Count() - 1) 16 | } 17 | 18 | var res ale.Value 19 | var lok bool 20 | for f, s, ok := s.Split(); ok; f, s, ok = s.Split() { 21 | res = f 22 | lok = ok 23 | } 24 | return res, lok 25 | } 26 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/drop.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "drop" 3 | description: "drops the first elements of a sequence" 4 | names: ["drop"] 5 | usage: "(drop count seq)" 6 | tags: ["sequence", "comprehension"] 7 | --- 8 | 9 | Return a lazy sequence that excludes the first _count_ elements of the provided sequence. If the source sequence is shorter than the requested count, an empty list will be returned. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x '(1 2 3 4)) 15 | (define y [5 6 7 8]) 16 | (drop 3 (concat x y)) 17 | ``` 18 | 19 | This example will return the lazy sequence _(4 5 6 7 8)_. 20 | -------------------------------------------------------------------------------- /core/source/bootstrap.ale: -------------------------------------------------------------------------------- 1 | 2 | ;;;; ale core: bootstrap 3 | 4 | (#include "builtins.ale") 5 | (#include "asm.ale") 6 | (#include "basics.ale") 7 | (#include "define.ale") 8 | (#include "convenience.ale") 9 | (#include "numerics.ale") 10 | (#include "binding.ale") 11 | (#include "branching.ale") 12 | (#include "predicates.ale") 13 | (#include "sequences.ale") 14 | (#include "functions.ale") 15 | (#include "lazy-seq.ale") 16 | (#include "threading.ale") 17 | (#include "exceptions.ale") 18 | (#include "concurrency.ale") 19 | (#include "namespaces.ale") 20 | (#include "io.ale") 21 | (#include "os.ale") 22 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/filter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "filter" 3 | description: "lazily filters a sequence" 4 | names: ["filter"] 5 | usage: "(filter func seq)" 6 | tags: ["sequence", "comprehension"] 7 | --- 8 | 9 | Creates a lazy sequence whose content is the result of applying the provided function to the elements of the provided sequence. If the result of the application is truthy (not false), then the value will be included in the resulting sequence. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (filter (lambda (x) (< x 3)) [1 2 3 4]) 15 | ``` 16 | 17 | This will return the lazy sequence _(1 2)_ 18 | -------------------------------------------------------------------------------- /internal/assert/helpers/helpers_test.go: -------------------------------------------------------------------------------- 1 | package helpers_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | . "github.com/kode4food/ale/internal/assert/helpers" 8 | ) 9 | 10 | func TestHelpers(t *testing.T) { 11 | as := assert.New(t) 12 | as.MustEvalTo( 13 | `'(1 1/2 hello 14 | {ale/hello "string"} 15 | {:kwd ["a" "vector"]} 16 | #t (1 . 2.3)) 17 | `, 18 | L( 19 | I(1), R(1, 2), LS("hello"), 20 | O(C(QS("ale", "hello"), S("string"))), 21 | O(C(K("kwd"), V(S("a"), S("vector")))), 22 | B(true), C(I(1), F(2.3)), 23 | ), 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/get.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "get" 3 | description: "retrieves a value by key" 4 | names: ["get"] 5 | usage: "(get seq key default?)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | Returns the value within a sequence that is associated with the specified key. If the key does not exist within the sequence, then either the default value is returned, or an error is raised. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define robert {:name "Bob" :age 45}) 15 | (get robert :address "wrong") 16 | ``` 17 | 18 | This example returns _"wrong"_ because the object doesn't contain an :address property. 19 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/range.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "range" 3 | description: "creates a range" 4 | names: ["range"] 5 | usage: "(range min max inc)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | Creates a lazy sequence that presents the numbers from _min_ (inclusive) to _max_ (exclusive), by _increment_. All parameters are optional. _min_ defaults to _0_, _max_ defaults to _\*pos-inf\*_, and _step_ defaults to _1_. If only one argument is provided, it is treated as _max_. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (seq->vector (take 5 (range 10 inf 5))) 15 | ``` 16 | 17 | Will return the vector _[10 15 20 25 30]_. 18 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/partial.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "partial" 3 | description: "generates a function based on a partial apply" 4 | names: ["partial"] 5 | usage: "(partial func arg+)" 6 | tags: ["function"] 7 | --- 8 | 9 | Returns a new function whose initial arguments are pre-bound to those provided. When that function is invoked, any provided arguments will simply be appended to the pre-bound arguments before calling the original function. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define plus10 (partial + 4 6)) 15 | (plus10 9) 16 | ``` 17 | 18 | This example will return _19_ as though `(+ 4 6 9)` were called. 19 | -------------------------------------------------------------------------------- /core/source/builtins.ale: -------------------------------------------------------------------------------- 1 | 2 | ;;;; ale core: builtins 3 | 4 | ;; encoders 5 | (def-special eval) 6 | (def-special lambda) 7 | (def-special let) 8 | (def-special let-rec) 9 | (def-special macroexpand-1) 10 | (def-special macroexpand) 11 | 12 | ;; procedures 13 | (def-builtin gensym) 14 | (def-builtin %is-a) 15 | (def-builtin macro) 16 | (def-builtin read) 17 | (def-builtin sym) 18 | (def-builtin %type-of) 19 | 20 | ;; sequence 21 | (def-builtin bytes) 22 | (def-builtin list) 23 | (def-builtin object) 24 | (def-builtin str!) 25 | (def-builtin str) 26 | (def-builtin vector) 27 | 28 | ;; macros 29 | (def-macro syntax-quote) 30 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | Five simple steps: 4 | 5 | 1. Create a [GitHub Issue](https://github.com/kode4food/ale/issues) 6 | to propose and discuss your potential contribution. 7 | 8 | 2. Fork our [Git Repository](https://github.com/kode4food/ale.git) 9 | 10 | 3. Issue an empty pull request. 11 | 12 | 4. Push change sets as they're committed, accompanied by unit and integration 13 | tests (test coverage should not drop). We will keep our eyes on the 14 | changes as they arrive to ensure that the final review goes smoothly. 15 | 16 | 5. When you're ready, label the pull request as "Ready for Review" 17 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/comp.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "comp" 3 | description: "composes a set of functions" 4 | names: ["comp"] 5 | usage: "(comp func*)" 6 | tags: ["function"] 7 | --- 8 | 9 | Returns a new function based on chained invocation of the provided functions, from left to right. The first composed function can accept multiple arguments, while any subsequent functions are applied with the result of the previous. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define mul2Add5 (comp (partial \* 2) (partial + 5))) 15 | (mul2Add5 10) 16 | ``` 17 | 18 | This example will return _25_ as though `(+ 5 (\* 2 10))` were called. 19 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/take.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "take" 3 | description: "takes the first elements of a sequence" 4 | names: ["take"] 5 | usage: "(take count seq)" 6 | tags: ["sequence", "comprehension"] 7 | --- 8 | 9 | Return a lazy sequence of either _count_ or fewer elements from the beginning of the provided sequence. If the source sequence is shorter than the requested count, the resulting sequence will be truncated. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x '(1 2 3 4)) 15 | (define y [5 6 7 8]) 16 | (take 6 (concat x y)) 17 | ``` 18 | 19 | This example will return the lazy sequence _(1 2 3 4 5 6)_. 20 | -------------------------------------------------------------------------------- /internal/strings/camel.go: -------------------------------------------------------------------------------- 1 | package strings 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | firstCamel = regexp.MustCompile(`(.)([A-Z][a-z]+)`) 10 | restCamel = regexp.MustCompile(`([a-z0-9])([A-Z])`) 11 | ) 12 | 13 | func CamelToWords(s string) string { 14 | res := firstCamel.ReplaceAllString(s, "${1} ${2}") 15 | res = restCamel.ReplaceAllString(res, "${1} ${2}") 16 | return strings.ToLower(res) 17 | } 18 | 19 | func CamelToSnake(s string) string { 20 | res := firstCamel.ReplaceAllString(s, "${1}-${2}") 21 | res = restCamel.ReplaceAllString(res, "${1}-${2}") 22 | return strings.ToLower(res) 23 | } 24 | -------------------------------------------------------------------------------- /internal/sync/action.go: -------------------------------------------------------------------------------- 1 | package sync 2 | 3 | import "sync" 4 | 5 | // Action is a callback interface for eventually triggering some action 6 | type Action func(func()) 7 | 8 | // Once creates a Do instance for performing an action only once 9 | func Once() Action { 10 | var once sync.Once 11 | return once.Do 12 | } 13 | 14 | // Always returns a Do instance for always performing an action 15 | func Always() Action { 16 | return func(f func()) { 17 | f() 18 | } 19 | } 20 | 21 | // Never returns a Do instance for never performing an action 22 | func Never() Action { 23 | return func(func()) { 24 | // no-op 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/juxt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "juxt" 3 | description: "juxtaposes a set of functions" 4 | names: ["juxt"] 5 | usage: "(juxt func*)" 6 | tags: ["function"] 7 | --- 8 | 9 | Returns a new function that represents the juxtaposition of the provided functions. This function returns a vector containing the result of applying each provided function to the juxtaposed function's arguments. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define juxt-math (juxt * + - /)) 15 | (juxt-math 32 10) 16 | ``` 17 | 18 | This example will return _[320 42 22 16/5]_ as though `[(* 32 10) (+ 32 10) (- 32 10) (/ 32 10)]` were called. 19 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-identical.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "identical (eq)" 3 | description: "tests if a set of values are identical to the first" 4 | names: ["eq", "!eq"] 5 | usage: "(eq form form+) (!eq form form+)" 6 | tags: ["comparison"] 7 | --- 8 | 9 | Return _#f_ (false) as soon as it encounters a form that is not identical to the first. Otherwise, will return _#t_ (true). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define h "hello") 15 | (eq "hello" h) 16 | ``` 17 | 18 | Like most predicates, this function can also be negated by prepending the `!` character. In this case, _#t_ (true) will be returned if not all forms are equal. 19 | -------------------------------------------------------------------------------- /internal/sequence/filter.go: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | ) 7 | 8 | type FilterFunc func(ale.Value) bool 9 | 10 | // Filter creates a new filtered Sequence 11 | func Filter(s data.Sequence, filter FilterFunc) data.Sequence { 12 | var res LazyResolver 13 | next := s 14 | 15 | res = func() (ale.Value, data.Sequence, bool) { 16 | for f, r, ok := next.Split(); ok; f, r, ok = r.Split() { 17 | next = r 18 | if filter(f) { 19 | return f, NewLazy(res), true 20 | } 21 | } 22 | return data.Null, data.Null, false 23 | } 24 | return NewLazy(res) 25 | } 26 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/gensym.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "gensym" 3 | description: "creates a unique symbol, useful in macros" 4 | names: ["gensym"] 5 | usage: "(gensym sym?)" 6 | tags: ["symbol", "macro"] 7 | --- 8 | 9 | If an unqualified symbol is provided, that symbol will be used to clarify the uniquely generated symbol. This function provides the underlying behavior for hash-tailed symbols in syntax-highlighting macros. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (let [s (gensym 'var)] 15 | (list 'ale/let [s "hello"] s)) 16 | 17 | ;; is equivalent to 18 | ``(let [var# "hello"] var#) 19 | ``` 20 | 21 | This example will return _"hello"_. 22 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/if.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "if" 3 | description: "performs simple branching" 4 | names: ["if", "unless"] 5 | usage: "(if pred then else?)" 6 | tags: ["conditional"] 7 | --- 8 | 9 | If the evaluated predicate is truthy (not _#f_ (false) or the empty list), the _then_ form is evaluated and returned, otherwise the _else_ form, if any, will be evaluated and returned. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x '(1 2 3 4 5 6 7 8)) 15 | 16 | (if (> (length x) 3) 17 | "x is big" 18 | "x is small") 19 | ``` 20 | 21 | If the symbol `unless` is used instead of `if`, then the logical branching will be inverted. 22 | -------------------------------------------------------------------------------- /internal/compiler/compile_test.go: -------------------------------------------------------------------------------- 1 | package compiler_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | "github.com/kode4food/ale/internal/compiler" 10 | ) 11 | 12 | func TestIsEvaluable(t *testing.T) { 13 | as := assert.New(t) 14 | 15 | as.True(compiler.IsEvaluable(L(LS("some-sym")))) 16 | as.False(compiler.IsEvaluable(S("some-string"))) 17 | as.True(compiler.IsEvaluable(LS("some-sym"))) 18 | as.False(compiler.IsEvaluable(C(K("keyword"), S("some-value")))) 19 | as.False(compiler.IsEvaluable(data.True)) 20 | } 21 | -------------------------------------------------------------------------------- /internal/assert/compiler_test.go: -------------------------------------------------------------------------------- 1 | package assert_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | "github.com/kode4food/ale/internal/runtime/isa" 9 | ) 10 | 11 | func TestEncodesAs(t *testing.T) { 12 | as := assert.New(t) 13 | as.MustEncodedAs(isa.Instructions{ 14 | isa.PosInt.New(2), 15 | }, `2`) 16 | } 17 | 18 | func TestGetRootSymbol(t *testing.T) { 19 | as := assert.New(t) 20 | 21 | e := assert.GetTestEncoder() 22 | v1 := assert.GetRootSymbol(e, "true") 23 | v2 := assert.GetRootSymbol(e, "false") 24 | as.Equal(data.True, v1) 25 | as.Equal(data.False, v2) 26 | } 27 | -------------------------------------------------------------------------------- /data/slice.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import "fmt" 4 | 5 | func sliceRangedCall[T any](s []T, args Vector) ([]T, error) { 6 | switch len(args) { 7 | case 1: 8 | start := int(args[0].(Integer)) 9 | if start < 0 || start > len(s) { 10 | return nil, fmt.Errorf(ErrInvalidStartIndex, start) 11 | } 12 | return s[start:], nil 13 | case 2: 14 | start := int(args[0].(Integer)) 15 | end := int(args[1].(Integer)) 16 | if start < 0 || end < start || end > len(s) { 17 | return nil, fmt.Errorf(ErrInvalidIndexes, start, end) 18 | } 19 | return s[start:end], nil 20 | default: 21 | return nil, fmt.Errorf(ErrRangedArity, 1, 2, len(args)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /internal/compiler/encoder/constant.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | "github.com/kode4food/ale/internal/runtime/isa" 7 | ) 8 | 9 | // AddConstant adds a value to the constant list (if necessary) 10 | func (e *encoder) AddConstant(val ale.Value) isa.Operand { 11 | c, idx := addConstant(e.constants, val) 12 | e.constants = c 13 | return idx 14 | } 15 | 16 | func addConstant(c data.Vector, val ale.Value) (data.Vector, isa.Operand) { 17 | if idx, ok := c.IndexOf(val); ok { 18 | return c, isa.Operand(idx) 19 | } 20 | c = append(c, val) 21 | return c, isa.Operand(len(c) - 1) 22 | } 23 | -------------------------------------------------------------------------------- /cmd/ale/internal/console/windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package console 4 | 5 | import "github.com/chzyer/readline" 6 | 7 | // Terminal codes 8 | const ( 9 | Bold = "" 10 | Italic = "" 11 | Clear = "" 12 | Reset = "" 13 | 14 | Header1 = "" 15 | Header2 = "" 16 | Domain = "" 17 | Comment = "" 18 | Code = "" 19 | Result = "" 20 | Error = "" 21 | NewLine = "" 22 | Paired = "" 23 | ) 24 | 25 | type painter struct{} 26 | 27 | // Painter implements the Painter interface for readline 28 | func Painter() readline.Painter { 29 | return new(painter) 30 | } 31 | 32 | func (*painter) Paint(line []rune, pos int) []rune { 33 | return line 34 | } 35 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/dissoc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "dissoc" 3 | description: "removes an association from a mapped sequence by key" 4 | names: ["dissoc", "dissoc*"] 5 | usage: "(dissoc seq key) (dissoc* seq key+)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | Returns a newly mapped sequence wherein the associations identified by the provided keys are removed. If the keys don't exist, the original sequence is returned. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define robert {:name "Bob" :age 45}) 15 | (dissoc robert :age) 16 | ``` 17 | 18 | This example returns a copy of _robert_ from which the _:age_ association has been removed. The original sequence is unaffected. 19 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/length.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "length" 3 | description: "returns the size of a sequence" 4 | names: ["length", "length!"] 5 | usage: "(length seq) (length! seq)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | The `length` function will return the number of elements in a sequence. 10 | 11 | If the sequence is lazily computed, asynchronous, or otherwise incapable of returning a count, this function will raise an error. To perform a brute-force count of a sequence, use the `length!` function, keeping in mind that it may never return a result. 12 | 13 | #### An Example 14 | 15 | ```scheme 16 | (length '(1 2 3 4)) 17 | ``` 18 | 19 | This example will return _4_. 20 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/and.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "and" 3 | description: "performs a short-circuiting and" 4 | names: ["and", "!and"] 5 | usage: "(and form*)" 6 | tags: ["conditional", "logic"] 7 | --- 8 | 9 | Evaluates forms from left to right. As soon as one evaluates to false, will return that value. Otherwise, it will proceed to evaluate the next form. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (and (+ 1 2 3) 15 | false 16 | "not returned") 17 | ``` 18 | 19 | Will return _#f_ (false), never evaluating _"not returned"_, whereas: 20 | 21 | ```scheme 22 | (and (+ 1 2 3) 23 | true 24 | "returned") 25 | ``` 26 | 27 | Will return the string _"returned"_. 28 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/future.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "future" 3 | description: "evaluates a set of forms asynchronously" 4 | names: ["future"] 5 | usage: "(future form*)" 6 | tags: ["concurrency"] 7 | --- 8 | 9 | Returns a promise whose expressions are immediately evaluated in another thread of execution. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define fut (future 15 | (seq->vector (generate 16 | (emit "red") 17 | (emit "orange") 18 | (emit "yellow"))))) 19 | 20 | (fut) 21 | ``` 22 | 23 | This example produces a promise called _fut_ that converts the results of an asynchronous block into a vector. The `(fut)` call will block until the future returns a value. 24 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/lambda.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "lambda" 3 | description: "creates a lambda" 4 | names: ["lambda", "λ", "lambda-rec"] 5 | usage: "(lambda (param*) form*) (lambda-rec name (param*) form*)" 6 | tags: ["function"] 7 | --- 8 | 9 | Creates a lambda function that may be passed around in a first-class manner. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define double 15 | (let [mul 2] 16 | (lambda (x) (\* x mul)))) 17 | 18 | (seq->vector 19 | (map double '(1 2 3 4 5 6))) 20 | ``` 21 | 22 | This example will return the vector _[2 4 6 8 10 12]_. 23 | 24 | Lambdas produce a closure that copies the bindings that have been referenced from the surrounding scope. 25 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/or.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "or" 3 | description: "performs a short-circuiting or" 4 | names: ["or", "!or"] 5 | usage: "(or form*)" 6 | tags: ["conditional", "logic"] 7 | --- 8 | 9 | Evaluate forms from left to right. As soon as one evaluates to a truthy value, will return that value. Otherwise, it will proceed to evaluate the next form. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (or (+ 1 2 3) 15 | false 16 | "not returned") 17 | ``` 18 | 19 | Will return _6_, never evaluating `false` (false) and `"not returned"`, whereas: 20 | 21 | ```scheme 22 | (or false 23 | '() 24 | "returned") 25 | ``` 26 | 27 | Will return the string _"returned"_. 28 | -------------------------------------------------------------------------------- /internal/types/any_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/types" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAnyAccepts(t *testing.T) { 11 | as := assert.New(t) 12 | 13 | a := types.BasicAny 14 | as.Equal("any", a.Name()) 15 | as.True(a.Accepts(types.BasicProcedure)) 16 | as.True(a.Accepts(types.BasicNumber)) 17 | as.True(a.Accepts(types.BasicAny)) 18 | } 19 | 20 | func TestAnyEqual(t *testing.T) { 21 | as := assert.New(t) 22 | 23 | as.True(types.BasicAny.Equal(types.BasicAny)) 24 | as.True(types.BasicAny.Equal(new(types.Any))) 25 | as.False(types.BasicAny.Equal(types.BasicBoolean)) 26 | } 27 | -------------------------------------------------------------------------------- /internal/compiler/ir/optimize/literals_test.go: -------------------------------------------------------------------------------- 1 | package optimize_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/env" 7 | "github.com/kode4food/ale/internal/assert" 8 | "github.com/kode4food/ale/internal/compiler/encoder" 9 | "github.com/kode4food/ale/internal/compiler/ir/optimize" 10 | "github.com/kode4food/ale/internal/runtime/isa" 11 | ) 12 | 13 | func TestLiterals(t *testing.T) { 14 | as := assert.New(t) 15 | 16 | ns := env.NewEnvironment().GetRoot() 17 | e1 := encoder.NewEncoder(ns) 18 | e1.Emit(isa.Null) 19 | e1.Emit(isa.Return) 20 | 21 | as.Instructions(isa.Instructions{ 22 | isa.RetNull.New(), 23 | }, optimize.Encoded(e1.Encode()).Code) 24 | } 25 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/define-macro.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "define-macro" 3 | description: "binds a reader macro" 4 | names: ["define-macro"] 5 | usage: "(define-macro name (param*) form*) (define-macro (name param*) form*)" 6 | tags: ["function", "macro", "binding"] 7 | --- 8 | 9 | Binds a macro to a global name. The reader expands a macro to alter the source code's data representation before it is evaluated. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define-macro (cond . clauses) 15 | (when (seq clauses) 16 | (if (= 1 (length clauses)) 17 | (clauses 0) 18 | (list 'ale/if 19 | (clauses 0) (clauses 1) 20 | (cons 'cond (rest (rest clauses))))))) 21 | ``` 22 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | coverage: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v3 13 | - name: Set up Go 14 | uses: actions/setup-go@v4 15 | with: 16 | go-version: "1.24" 17 | - name: Run tests with coverage 18 | run: go test ./... -coverpkg=./... -coverprofile=cover.out 19 | - name: Upload coverage to Qlty 20 | uses: qltysh/qlty-action/coverage@v1 21 | with: 22 | token: ${{ secrets.QLTY_COVERAGE_TOKEN }} 23 | files: cover.out 24 | -------------------------------------------------------------------------------- /core/builtin/pairs_test.go: -------------------------------------------------------------------------------- 1 | package builtin_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | . "github.com/kode4food/ale/internal/assert/helpers" 8 | ) 9 | 10 | func TestPairsEval(t *testing.T) { 11 | as := assert.New(t) 12 | 13 | as.MustEvalTo( 14 | `(cons 7 (cons 8 (cons (cons 10 11) 9)))`, 15 | S(`(7 8 (10 . 11) . 9)`), 16 | ) 17 | 18 | as.MustEvalTo( 19 | `(car (cons 7 (cons 8 (cons (cons 10 11) 9))))`, 20 | I(7), 21 | ) 22 | 23 | as.MustEvalTo( 24 | `(cdr (cons 7 (cons 8 (cons (cons 10 11) 9))))`, 25 | S(`(8 (10 . 11) . 9)`), 26 | ) 27 | 28 | as.MustEvalTo(`(pair? (cons 7 8))`, B(true)) 29 | as.MustEvalTo(`(pair? 99)`, B(false)) 30 | } 31 | -------------------------------------------------------------------------------- /ffi/marshaled_test.go: -------------------------------------------------------------------------------- 1 | package ffi_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/google/uuid" 8 | "github.com/kode4food/ale/data" 9 | "github.com/kode4food/ale/ffi" 10 | "github.com/kode4food/ale/internal/assert" 11 | ) 12 | 13 | func TestByteArrayWrapper(t *testing.T) { 14 | as := assert.New(t) 15 | 16 | id1 := uuid.New() 17 | s, err := ffi.Wrap(id1) 18 | if as.NoError(err) { 19 | as.Equal(id1.String(), string(s.(data.String))) 20 | 21 | w, err := ffi.WrapType(reflect.TypeOf(id1)) 22 | if as.NoError(err) && as.NotNil(w) { 23 | id2, err := w.Unwrap(s.(data.String)) 24 | if as.NoError(err) { 25 | as.Equal(id1, id2.Interface()) 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/when.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "when" 3 | description: "conditionally evaluates a block" 4 | names: ["when", "when-not"] 5 | usage: "(when pred form*)" 6 | tags: ["conditional"] 7 | --- 8 | 9 | If the evaluated predicate is truthy (not _#f_ (false) or the empty list), the forms are evaluated. Will evaluate each form in turn, returning the final evaluation as its result. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x '(1 2 3 4 5 6 7 8)) 15 | 16 | (when (> (length x) 3) 17 | (println "x is big") 18 | (length x)) 19 | ``` 20 | 21 | If the symbol `when-not` is used instead of `when`, then the predicate is evaluated and the block will be evaluated only if the result is not truthy 22 | -------------------------------------------------------------------------------- /core/builtin/symbols.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | ) 7 | 8 | const anonName = data.Local("anon") 9 | 10 | // Sym instantiates a new symbol 11 | var Sym = data.MakeProcedure(func(args ...ale.Value) ale.Value { 12 | if s, ok := args[0].(data.Symbol); ok { 13 | return s 14 | } 15 | s := args[0].(data.String) 16 | return data.MustParseSymbol(s) 17 | }, 1) 18 | 19 | // GenSym generates a unique symbol 20 | var GenSym = data.MakeProcedure(func(args ...ale.Value) ale.Value { 21 | if len(args) == 0 { 22 | return data.NewGeneratedSymbol(anonName) 23 | } 24 | s := args[0].(data.Local) 25 | return data.NewGeneratedSymbol(s) 26 | }, 0, 1) 27 | -------------------------------------------------------------------------------- /internal/sync/action_test.go: -------------------------------------------------------------------------------- 1 | package sync_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | "github.com/kode4food/ale/internal/sync" 8 | ) 9 | 10 | func TestConditionals(t *testing.T) { 11 | as := assert.New(t) 12 | 13 | i := 0 14 | inc := func() { 15 | i++ 16 | } 17 | 18 | once := sync.Once() 19 | never := sync.Never() 20 | always := sync.Always() 21 | 22 | as.Number(0, i) 23 | once(inc) 24 | as.Number(1, i) 25 | once(inc) 26 | as.Number(1, i) 27 | 28 | never(inc) 29 | as.Number(1, i) 30 | never(inc) 31 | as.Number(1, i) 32 | 33 | always(inc) 34 | as.Number(2, i) 35 | always(inc) 36 | as.Number(3, i) 37 | always(inc) 38 | as.Number(4, i) 39 | } 40 | -------------------------------------------------------------------------------- /core/builtin/sequences.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | ) 7 | 8 | var ( 9 | // Bytes constructs a new byte vector 10 | Bytes = makeConstructor(data.NewBytes) 11 | 12 | // List constructs a new list 13 | List = makeConstructor(data.NewList) 14 | 15 | // Vector creates a new vector 16 | Vector = makeConstructor(data.NewVector) 17 | ) 18 | 19 | func makeConstructor[T ale.Value](orig func(...ale.Value) T) data.Procedure { 20 | return data.MakeProcedure(func(args ...ale.Value) ale.Value { 21 | return orig(args...) 22 | }) 23 | } 24 | 25 | func isPair(v ale.Value) bool { 26 | p, ok := v.(data.Pair) 27 | return ok && p != data.Null 28 | } 29 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/assoc.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "assoc" 3 | description: "associates pairs with a mapped sequence" 4 | names: ["assoc", "assoc*"] 5 | usage: "(assoc seq pair) (assoc* seq pair+)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | Returns a newly mapped sequence wherein the specified key/value pairs are associated. If a key already exists, the value replaces the one previously stored; otherwise the pair is added to the sequence. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define robert {:name "Bob" :age 45}) 15 | (assoc robert (:age . 46)) 16 | ``` 17 | 18 | This example returns a copy of _robert_ wherein the value associated with _:age_ has been replaced by the number _46_. The original sequence is unaffected. 19 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/fold-left.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "fold-left" 3 | description: "left folds a sequence" 4 | names: ["reduce", "fold-left", "foldl"] 5 | usage: "(fold-left func val? seq)" 6 | tags: ["sequence", "comprehension"] 7 | --- 8 | 9 | Iterates over a sequence, reducing its elements to a single resulting value. The function provided must take two arguments. The first and second sequence elements encountered are the initial values applied to that function. Thereafter, the result of the previous calculation is used as the first argument, while the next element is used as the second argument. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (fold-left + 5 (range 1 11)) 15 | ``` 16 | 17 | This will return the value _60_. 18 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/last.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "last" 3 | description: "returns the last element of the sequence" 4 | names: ["last", "last!"] 5 | usage: "(last seq) (last! seq)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | This function will return the last element of the specified sequence, or the empty list if the sequence is empty. If the sequence is lazily computed, asynchronous, or otherwise incapable of returning a count, this function will raise an error. 10 | 11 | To perform a brute-force scan of the sequence, use the `last!` function, keeping in mind that `last!` may never return a result. 12 | 13 | #### An Example 14 | 15 | ```scheme 16 | (define x '(99 64 32 48)) 17 | (last x) 18 | ``` 19 | 20 | This example will return _48_. 21 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/str.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "str" 3 | description: "converts forms to a string" 4 | names: ["str", "str!"] 5 | usage: "(str form*) (str! form*)" 6 | tags: ["sequence", "conversion"] 7 | --- 8 | 9 | Creates a new string from the stringified values of the provided forms. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (str "hello" [1 2 3 4]) 15 | ``` 16 | 17 | This example will return the string _"hello[1 2 3 4]"_. 18 | 19 | #### Reader Strings 20 | 21 | Alternatively, one can use the `str!` function to produce a stringified version that _may_ be able to be read by the Ale reader. 22 | 23 | ```scheme 24 | (str! "hello" [1 2 3 4]) 25 | ``` 26 | 27 | This example will return the string _"\"hello\" [1 2 3 4]"_. 28 | -------------------------------------------------------------------------------- /data/value.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import "github.com/kode4food/ale" 4 | 5 | type ( 6 | // Name represents a Value's name. Not itself a Value 7 | Name string 8 | 9 | // Named is the generic interface for values that are named 10 | Named interface { 11 | ale.Value 12 | 13 | // Name returns the name of the value 14 | Name() Name 15 | } 16 | 17 | // Typed is the generic interface for values that are typed 18 | 19 | // Mapped is the interface for Values that have accessible properties 20 | Mapped interface { 21 | ale.Value 22 | 23 | // Get returns the value associated with the given key 24 | Get(ale.Value) (ale.Value, bool) 25 | } 26 | ) 27 | 28 | func Equal(l, r ale.Value) bool { 29 | return l.Equal(r) 30 | } 31 | -------------------------------------------------------------------------------- /data/bool_test.go: -------------------------------------------------------------------------------- 1 | package data_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | ) 10 | 11 | func TestBools(t *testing.T) { 12 | as := assert.New(t) 13 | 14 | as.True(data.True.Equal(data.True)) 15 | as.False(data.True.Equal(data.False)) 16 | as.False(data.True.Equal(L())) 17 | as.False(data.False.Equal(L())) 18 | 19 | as.String("#t", data.True) 20 | as.String("#f", data.False) 21 | 22 | obj := O( 23 | C(data.True, S("is true")), 24 | C(data.False, S("is false")), 25 | ) 26 | as.String("is true", as.MustGet(obj, data.True)) 27 | as.String("is false", as.MustGet(obj, data.False)) 28 | } 29 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/cond.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "cond" 3 | description: "performs conditional branching" 4 | names: ["cond"] 5 | usage: "(cond [pred then]*)" 6 | tags: ["conditional"] 7 | --- 8 | 9 | For each _pred-then_ clause, the predicate will be evaluated, and if it is truthy (not false), the _then_ form is evaluated and returned, otherwise the next clause is processed. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x 99) 15 | 16 | (cond 17 | [(< x 50) "was less than 50" ] 18 | [(> x 100) "was greater than 100"] 19 | [:else "was in between" ]) 20 | ``` 21 | 22 | In this case, _"was in between"_ will be returned. The reason that this works is that the `:else` keyword, like all keywords, evaluates to truthy. 23 | -------------------------------------------------------------------------------- /cmd/ale/internal/markdown/format_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package markdown_test 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/kode4food/ale/cmd/ale/internal/docstring" 9 | "github.com/kode4food/ale/cmd/ale/internal/markdown" 10 | "github.com/kode4food/ale/internal/assert" 11 | . "github.com/kode4food/ale/internal/assert/helpers" 12 | ) 13 | 14 | func TestFormatMarkdown(t *testing.T) { 15 | as := assert.New(t) 16 | 17 | s, err := docstring.Get("if") 18 | if as.NoError(err) { 19 | as.NotEmpty(s) 20 | } 21 | 22 | res, err := markdown.FormatMarkdown(s) 23 | if as.NoError(err) { 24 | r := S(res) 25 | as.NotContains("---", r) 26 | as.Contains("\x1b[35m\x1b[1mperforms simple branching\x1b[0m\n\n", r) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /internal/runtime/vm/reference_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | . "github.com/kode4food/ale/internal/assert/helpers" 8 | "github.com/kode4food/ale/internal/runtime/vm" 9 | ) 10 | 11 | func TestRef(t *testing.T) { 12 | as := assert.New(t) 13 | 14 | r1 := &vm.Ref{Value: S("hello")} 15 | r2 := &vm.Ref{Value: S("non-matching")} 16 | r3 := &vm.Ref{Value: S("hello")} 17 | r4 := &vm.Ref{Value: L(I(1), I(2))} 18 | r5 := &vm.Ref{Value: L(I(1), I(2))} 19 | r6 := &vm.Ref{} 20 | 21 | as.Equal(r1, r1) 22 | as.Equal(r1, r3) 23 | as.Equal(r1, S("hello")) 24 | as.NotEqual(r1, r2) 25 | as.Equal(r4, r5) 26 | as.Equal(r4, L(I(1), I(2))) 27 | as.NotEqual(r5, r6) 28 | } 29 | -------------------------------------------------------------------------------- /internal/types/tuple_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/types" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestTuple(t *testing.T) { 11 | as := assert.New(t) 12 | 13 | t1 := types.MakeTuple(types.BasicKeyword, types.BasicNumber) 14 | t2 := types.MakeTuple(types.BasicNumber, types.BasicKeyword) 15 | t3 := types.MakeTuple(types.BasicKeyword, types.BasicNumber) 16 | t4 := types.MakeTuple() 17 | 18 | as.Equal("tuple(keyword,number)", t1.Name()) 19 | 20 | as.True(t1.Accepts(t1)) 21 | as.True(t1.Accepts(t3)) 22 | as.False(t2.Accepts(t1)) 23 | as.False(t2.Accepts(t4)) 24 | as.False(t4.Accepts(t1)) 25 | 26 | as.False(t1.Accepts(types.BasicNull)) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/define-lambda.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "define-lambda" 3 | description: "binds a namespace function" 4 | names: ["define-lambda"] 5 | usage: "(define-lambda name (param*) form*) (define-lambda (name param*) form*)" 6 | tags: ["function", "binding"] 7 | --- 8 | 9 | Bind a function by name to the current namespace. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define-lambda (fib i) 15 | (cond 16 | [(= i 0) 0] 17 | [(= i 1) 1] 18 | [(= i 2) 1] 19 | [:else (+ (fib (- i 2)) (fib (- i 1)))])) 20 | ``` 21 | 22 | This example performs recursion with no tail call optimization, and no memoization. For a more performant and stack-friendly fibonacci sequence generation example, see the documentation of `lazy-seq`. 23 | -------------------------------------------------------------------------------- /internal/sequence/concat.go: -------------------------------------------------------------------------------- 1 | package sequence 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | ) 7 | 8 | func Concat(s ...data.Sequence) data.Sequence { 9 | switch len(s) { 10 | case 0: 11 | return data.Null 12 | case 1: 13 | return s[0] 14 | default: 15 | var next LazyResolver 16 | curr := s[0] 17 | rest := s[1:] 18 | 19 | next = func() (ale.Value, data.Sequence, bool) { 20 | var f ale.Value 21 | var ok bool 22 | if f, curr, ok = curr.Split(); ok { 23 | return f, NewLazy(next), true 24 | } 25 | if len(rest) == 1 { 26 | return rest[0].Split() 27 | } 28 | curr = rest[0] 29 | rest = rest[1:] 30 | return next() 31 | } 32 | return NewLazy(next) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/define.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "define" 3 | description: "binds a namespace entry" 4 | names: ["define"] 5 | usage: "(define name form) (define (name param*) form*)" 6 | tags: ["binding"] 7 | --- 8 | 9 | Binds a value to a global name. All bindings are immutable and result in an error being raised if an attempt is made to re-bind them. This behavior is different from most Lisps, as they will generally fail silently in such cases. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define x 15 | (map 16 | (lambda (y) (\* y 2)) 17 | seq1 seq2 seq3)) 18 | ``` 19 | 20 | This example will create a lazy map where each element of the three provided sequences is doubled upon request. It will then bind that lazy map to the name _x_. 21 | -------------------------------------------------------------------------------- /env/chain.go: -------------------------------------------------------------------------------- 1 | package env 2 | 3 | import "github.com/kode4food/ale/data" 4 | 5 | type chainedNamespace struct { 6 | Namespace // child 7 | parent Namespace 8 | } 9 | 10 | func chain(parent Namespace, child Namespace) *chainedNamespace { 11 | return &chainedNamespace{ 12 | Namespace: child, 13 | parent: parent, 14 | } 15 | } 16 | 17 | func (ns *chainedNamespace) Snapshot(e *Environment) Namespace { 18 | return &chainedNamespace{ 19 | Namespace: ns.Namespace.Snapshot(e), 20 | parent: ns.parent.Snapshot(e), 21 | } 22 | } 23 | 24 | func (ns *chainedNamespace) Resolve(n data.Local) (*Entry, Namespace, error) { 25 | if e, _, err := ns.Namespace.Resolve(n); err == nil { 26 | return e, ns, nil 27 | } 28 | return resolvePublic(ns, ns.parent, n) 29 | } 30 | -------------------------------------------------------------------------------- /internal/compiler/ir/optimize/calls.go: -------------------------------------------------------------------------------- 1 | package optimize 2 | 3 | import ( 4 | "github.com/kode4food/ale/internal/compiler/ir/visitor" 5 | "github.com/kode4food/ale/internal/runtime/isa" 6 | ) 7 | 8 | var ( 9 | // dedicatedCalls convert generic Call instructions into dedicated ones 10 | dedicatedCalls = globalReplace( 11 | visitor.Pattern{{isa.Call}}, 12 | dedicatedCallMapper, 13 | ) 14 | 15 | mappedDedicatedCalls = map[isa.Operand]isa.Instructions{ 16 | 0: {isa.Call0.New()}, 17 | 1: {isa.Call1.New()}, 18 | 2: {isa.Call2.New()}, 19 | 3: {isa.Call3.New()}, 20 | } 21 | ) 22 | 23 | func dedicatedCallMapper(i isa.Instructions) isa.Instructions { 24 | if res, ok := mappedDedicatedCalls[i[0].Operand()]; ok { 25 | return res 26 | } 27 | return i 28 | } 29 | -------------------------------------------------------------------------------- /core/special/asm.go: -------------------------------------------------------------------------------- 1 | package special 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | "github.com/kode4food/ale/internal/compiler/asm" 7 | "github.com/kode4food/ale/internal/compiler/encoder" 8 | "github.com/kode4food/ale/internal/lang/params" 9 | ) 10 | 11 | // Asm provides indirect access to the Encoder's methods and generators 12 | func Asm(e encoder.Encoder, args ...ale.Value) error { 13 | return asm.Encode(e, asm.MakeAsm(args...)) 14 | } 15 | 16 | // Special emits an encoder function for the provided param cases 17 | func Special(e encoder.Encoder, args ...ale.Value) error { 18 | pc, err := params.ParseCases(data.Vector(args)) 19 | if err != nil { 20 | return err 21 | } 22 | return asm.Encode(e, asm.MakeSpecial(pc)) 23 | } 24 | -------------------------------------------------------------------------------- /internal/compiler/procedure/procedure.go: -------------------------------------------------------------------------------- 1 | package procedure 2 | 3 | import ( 4 | "github.com/kode4food/ale/data" 5 | "github.com/kode4food/ale/internal/compiler/encoder" 6 | "github.com/kode4food/ale/internal/compiler/ir/analysis" 7 | "github.com/kode4food/ale/internal/compiler/ir/optimize" 8 | "github.com/kode4food/ale/internal/runtime/vm" 9 | ) 10 | 11 | // FromEncoded instantiates an abstract machine Procedure from the provided 12 | // Encoded representation 13 | func FromEncoded(e *encoder.Encoded) (*vm.Procedure, error) { 14 | if err := analysis.Verify(e.Code); err != nil { 15 | return nil, err 16 | } 17 | run, err := optimize.Encoded(e).Runnable() 18 | if err != nil { 19 | return nil, err 20 | } 21 | return vm.MakeProcedure(run, data.CheckAnyArity), nil 22 | } 23 | -------------------------------------------------------------------------------- /internal/sequence/take_test.go: -------------------------------------------------------------------------------- 1 | package sequence_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | "github.com/kode4food/ale/internal/sequence" 10 | ) 11 | 12 | func TestTake(t *testing.T) { 13 | as := assert.New(t) 14 | l := L(I(1), I(2), I(3), I(4)) 15 | 16 | v, r, ok := sequence.Take(l, 3) 17 | as.Equal(V(I(1), I(2), I(3)), v) 18 | as.Equal(L(I(4)), r) 19 | as.True(ok) 20 | 21 | v, r, ok = sequence.Take(l, 4) 22 | as.Equal(V(I(1), I(2), I(3), I(4)), v) 23 | as.Equal(data.Null, r) 24 | as.True(ok) 25 | 26 | v, r, ok = sequence.Take(l, 5) 27 | as.Equal(data.EmptyVector, v) 28 | as.Equal(data.Null, r) 29 | as.False(ok) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/delay.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "delay" 3 | description: "produces a delayed evaluation" 4 | names: ["delay", "force", "force!", "delay-force"] 5 | usage: "(delay expr*)" 6 | tags: ["concurrency"] 7 | --- 8 | 9 | Returns a promise that, when forced, evaluates the expressions, returning the final evaluated result. The result is then cached, so further uses of force return the cached value immediately. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define p (delay 15 | (println "hello once") 16 | "hello")) 17 | (force p) ;; prints "hello once" 18 | (force p) 19 | ``` 20 | 21 | The first invocation of `p` will print _"hello once"_ to the console, and also return the string _"hello"_. Subsequent invocations of `p` will only return _"hello"_. 22 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/object.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "object" 3 | description: "creates a new object instance" 4 | names: ["object"] 5 | usage: "(object *)" 6 | tags: ["data"] 7 | --- 8 | 9 | Create a new object (hash-map) based on the provided key-value pairs, or return an empty object if no forms are provided. This function is no different from the object literal syntax except that it can be treated in a first-class fashion. 10 | 11 | An object can be iterated over as a sequence. The resulting sequence is guaranteed to have no duplicated keys, but is not guaranteed to return in any particular order. 12 | 13 | #### An Example 14 | 15 | ```scheme 16 | (object :name "ale" :age 0.3 :lang "Go") 17 | 18 | ;; which is the same as 19 | {:name "ale" :age 0.3 :lang "Go"} 20 | ``` 21 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/thread-let.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "let thread (let->)" 3 | description: "threaded let binding with intermediate value access" 4 | names: ["let->"] 5 | usage: "(let-> [binding-name initial-value] forms*)" 6 | tags: ["function"] 7 | --- 8 | 9 | A threaded let binding that threads the bound value through each form by name. Each form receives the current value of the binding and the result becomes the new value of the binding. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (let-> [x 10] 15 | (+ x 5) 16 | (\* x 2) 17 | (/ x 2)) 18 | ``` 19 | 20 | #### Another Example 21 | 22 | ```scheme 23 | (let-> [user {}] 24 | (assoc user (:name . "John")) 25 | (assoc user (:age . 25)) 26 | (assoc user (:status . "active"))) 27 | ``` 28 | -------------------------------------------------------------------------------- /internal/compiler/encoder/scope.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import "github.com/kode4food/ale/data" 4 | 5 | // Scope describes the scope of a name 6 | type Scope int 7 | 8 | // Scope locations 9 | const ( 10 | LocalScope Scope = iota 11 | ArgScope 12 | ClosureScope 13 | ) 14 | 15 | func (e *encoder) ResolveScoped(n data.Local) (*ScopedCell, bool) { 16 | if i, ok := e.ResolveLocal(n); ok { 17 | return newScopedCell(e, LocalScope, i.Cell), true 18 | } 19 | if _, ok := e.ResolveParam(n); ok { 20 | return newScopedCell(e, ArgScope, newCell(ValueCell, n)), true 21 | } 22 | if e.parent == nil { 23 | return nil, false 24 | } 25 | if s, ok := e.parent.ResolveScoped(n); ok { 26 | return newScopedCell(s.Encoder, ClosureScope, s.Cell), true 27 | } 28 | return nil, false 29 | } 30 | -------------------------------------------------------------------------------- /internal/strings/camel_test.go: -------------------------------------------------------------------------------- 1 | package strings_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | "github.com/kode4food/ale/internal/strings" 8 | ) 9 | 10 | func TestCamelToSnake(t *testing.T) { 11 | as := assert.New(t) 12 | 13 | as.Equal("this-was-camel", strings.CamelToSnake("thisWasCamel")) 14 | as.Equal("this-was-camel", strings.CamelToSnake("ThisWasCamel")) 15 | as.Equal("this-was-a-camel", strings.CamelToSnake("thisWasACamel")) 16 | } 17 | 18 | func TestCamelToWords(t *testing.T) { 19 | as := assert.New(t) 20 | 21 | as.Equal("this was camel", strings.CamelToWords("thisWasCamel")) 22 | as.Equal("this was camel", strings.CamelToWords("ThisWasCamel")) 23 | as.Equal("this was a camel", strings.CamelToWords("thisWasACamel")) 24 | } 25 | -------------------------------------------------------------------------------- /cmd/ale/internal/history.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "os/user" 5 | "path" 6 | "strings" 7 | 8 | "github.com/kode4food/ale/data" 9 | ) 10 | 11 | func (r *REPL) saveHistory() { 12 | defer func() { _ = recover() }() 13 | seq := r.scanBuffer() 14 | hist := toHistory(seq) 15 | _ = r.rl.SaveHistory(hist) 16 | } 17 | 18 | func getHistoryFile() string { 19 | if usr, err := user.Current(); err == nil { 20 | return path.Join(usr.HomeDir, ".ale-history") 21 | } 22 | return "" 23 | } 24 | 25 | func toHistory(s data.Sequence) string { 26 | var buf strings.Builder 27 | for f, r, ok := s.Split(); ok; f, r, ok = r.Split() { 28 | if buf.Len() > 0 { 29 | buf.WriteString(" ") 30 | } 31 | buf.WriteString(data.ToQuotedString(f)) 32 | } 33 | return buf.String() 34 | } 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kode4food/ale 2 | 3 | go 1.24.6 4 | 5 | require ( 6 | github.com/chzyer/readline v1.5.1 7 | github.com/google/uuid v1.6.0 8 | github.com/kode4food/gen-maxkind v0.1.2 9 | github.com/stretchr/testify v1.11.1 10 | golang.org/x/tools v0.40.0 11 | gopkg.in/yaml.v3 v3.0.1 12 | honnef.co/go/tools v0.6.1 13 | ) 14 | 15 | require ( 16 | github.com/BurntSushi/toml v1.5.0 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | golang.org/x/exp/typeparams v0.0.0-20250606033433-dcc06ee1d476 // indirect 20 | golang.org/x/mod v0.31.0 // indirect 21 | golang.org/x/sync v0.19.0 // indirect 22 | golang.org/x/sys v0.39.0 // indirect 23 | golang.org/x/tools/go/expect v0.1.1-deprecated // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /read/data/data_test.go: -------------------------------------------------------------------------------- 1 | package data_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | "github.com/kode4food/ale/internal/lang/lex" 10 | rdata "github.com/kode4food/ale/read/data" 11 | ) 12 | 13 | func TestFromString(t *testing.T) { 14 | as := assert.New(t) 15 | 16 | ns := assert.GetTestNamespace() 17 | d1 := rdata.MustFromString(ns, `(1 2 3)`) 18 | as.Equal(L(I(1), I(2), I(3)), d1.Car()) 19 | 20 | d2 := rdata.MustFromString(ns, `(#include "hello")`) 21 | as.Equal(L(LS("#include"), S("hello")), d2.Car()) 22 | 23 | as.Panics( 24 | func() { _ = rdata.MustFromString(ns, `(1 2 '3)`).Car() }, 25 | fmt.Errorf("%w: %s", lex.ErrUnexpectedCharacters, "'"), 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/declare.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "declare" 3 | description: "forward declares a binding" 4 | names: ["declare", "private"] 5 | usage: "(declare +) (private +)" 6 | tags: ["binding"] 7 | --- 8 | 9 | Forward declares bindings. This means that the names will be known in the current namespace, but not yet assigned. This can be useful when two functions refer to one another. 10 | 11 | The `private` variant makes the binding private to the current namespace. 12 | 13 | #### An Example 14 | 15 | ```scheme 16 | (declare is-odd-number) 17 | 18 | (define (is-even-number n) 19 | (cond [(= n 0) true] 20 | [:else (is-odd-number (- n 1))])) 21 | 22 | (define (is-odd-number n) 23 | (cond [(= n 0) false] 24 | [:else (is-even-number (- n 1))])) 25 | ``` 26 | -------------------------------------------------------------------------------- /internal/compiler/generate/branch.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "github.com/kode4food/ale/internal/compiler/encoder" 5 | "github.com/kode4food/ale/internal/runtime/isa" 6 | ) 7 | 8 | // Branch constructs predicate, consequent, alternative branching 9 | func Branch( 10 | e encoder.Encoder, predicate, consequent, alternative Builder, 11 | ) error { 12 | thenLabel := e.NewLabel() 13 | endLabel := e.NewLabel() 14 | 15 | if err := predicate(e); err != nil { 16 | return err 17 | } 18 | e.Emit(isa.CondJump, thenLabel) 19 | if err := alternative(e); err != nil { 20 | return err 21 | } 22 | e.Emit(isa.Jump, endLabel) 23 | e.Emit(isa.Label, thenLabel) 24 | if err := consequent(e); err != nil { 25 | return err 26 | } 27 | e.Emit(isa.Label, endLabel) 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /core/builtin/strings_test.go: -------------------------------------------------------------------------------- 1 | package builtin_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | ) 10 | 11 | func TestStrEval(t *testing.T) { 12 | as := assert.New(t) 13 | as.MustEvalTo(` 14 | (str "hello" '() [1 2 3 4]) 15 | `, S("hello[1 2 3 4]")) 16 | 17 | as.MustEvalTo(` 18 | (string? "hello" "there") 19 | `, data.True) 20 | 21 | as.MustEvalTo(` 22 | (string? "hello" 99) 23 | `, data.False) 24 | } 25 | 26 | func TestReadableStrEval(t *testing.T) { 27 | as := assert.New(t) 28 | as.MustEvalTo("(str! \"hello\nyou\")", S("\"hello\\nyou\"")) 29 | as.MustEvalTo(`(str! "hello" "you")`, S(`"hello" "you"`)) 30 | as.MustEvalTo(`(str!)`, S("")) 31 | } 32 | -------------------------------------------------------------------------------- /value.go: -------------------------------------------------------------------------------- 1 | package ale 2 | 3 | type ( 4 | // Value is the generic interface for all data 5 | Value interface { 6 | // Equal compares this Value to another for equality 7 | Equal(Value) bool 8 | } 9 | 10 | // Type describes the type compatibility for a Value 11 | Type interface { 12 | // Name identifies this Type 13 | Name() string 14 | 15 | // Accepts determines if this Type will accept the provided Type for 16 | // binding. This will generally mean that the provided Type satisfies 17 | // the contract of the receiver. 18 | Accepts(Type) bool 19 | 20 | // Equal determines if the provided Type is an equivalent definition 21 | Equal(Type) bool 22 | } 23 | 24 | Typed interface { 25 | Value 26 | 27 | // Type returns the Type for this Value 28 | Type() Type 29 | } 30 | ) 31 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-seq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "seq?" 3 | description: "tests whether the provided forms are sequences" 4 | names: ["seq?", "!seq?"] 5 | usage: "(seq? form+) (!seq? form+)" 6 | tags: ["sequence", "predicate"] 7 | --- 8 | 9 | If all forms evaluate to a valid sequence, then this function will return _#t_ (true). The first non-sequence will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (seq? '(1 2 3 4) [5 6 7 8]) 15 | ``` 16 | 17 | This example will return _#t_ (true). 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be valid sequences. 20 | 21 | ```scheme 22 | (!seq? "hello" 99) 23 | ``` 24 | 25 | This example will return _#t_ (true). 26 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/lazy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "lazy" 3 | description: "produces a lazy evaluation" 4 | names: ["lazy"] 5 | usage: "(lazy expr*)" 6 | tags: ["concurrency"] 7 | --- 8 | 9 | Like `delay`, but if the first forced result is a promise, it will continue to be forced until a non-promise result is capable of being returned. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define p (lazy 15 | (println "hello once") 16 | (delay 17 | (println "hello twice") 18 | "hello"))) 19 | (force p) ;; prints "hello once / hello twice" 20 | (force p) 21 | ``` 22 | 23 | The first invocation of `p` will print _"hello once"_ followed by _"hello twice"_ to the console, also returning the string _"hello"_. Subsequent invocations of `p` will only return _"hello"_. 24 | -------------------------------------------------------------------------------- /data/keyword_test.go: -------------------------------------------------------------------------------- 1 | package data_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | "github.com/kode4food/ale/internal/types" 10 | ) 11 | 12 | func TestKeyword(t *testing.T) { 13 | as := assert.New(t) 14 | 15 | k1 := data.Keyword("hello") 16 | as.String("hello", string(k1)) 17 | as.String(":hello", k1) 18 | 19 | as.True(types.BasicKeyword.Accepts(k1.Type())) 20 | } 21 | 22 | func TestKeywordCaller(t *testing.T) { 23 | as := assert.New(t) 24 | 25 | m1 := O(C(data.Keyword("name"), S("Ale"))) 26 | c1 := data.Keyword("name") 27 | as.String("Ale", c1.Call(m1)) 28 | 29 | c2 := data.Keyword("missing") 30 | as.String("defaulted", c2.Call(m1, S("defaulted"))) 31 | } 32 | -------------------------------------------------------------------------------- /internal/compiler/ir/optimize/tailcalls_test.go: -------------------------------------------------------------------------------- 1 | package optimize_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | . "github.com/kode4food/ale/internal/assert/helpers" 8 | "github.com/kode4food/ale/internal/compiler/generate" 9 | "github.com/kode4food/ale/internal/compiler/ir/optimize" 10 | "github.com/kode4food/ale/internal/runtime/isa" 11 | ) 12 | 13 | func TestTailCalls(t *testing.T) { 14 | as := assert.New(t) 15 | 16 | e1 := assert.GetTestEncoder() 17 | as.NoError(generate.Call(e1, L(assert.GetRootSymbol(e1, "+"), I(1), I(2)))) 18 | e1.Emit(isa.Return) 19 | 20 | as.Instructions(isa.Instructions{ 21 | isa.PosInt.New(2), 22 | isa.PosInt.New(1), 23 | isa.Const.New(0), 24 | isa.TailClos.New(2), 25 | }, optimize.Encoded(e1.Encode()).Code) 26 | } 27 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-null.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "null?" 3 | description: "tests whether the provided forms are null" 4 | names: ["null?", "!null?"] 5 | usage: "(null? form+) (!null? form+)" 6 | tags: ["predicate"] 7 | --- 8 | 9 | If all forms evaluate to null (empty list), then this function will return _#t_ (true). The first non-null will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (null? '(1 2 3 4) '()) 15 | ``` 16 | 17 | This example will return _#f_ (false) because the first form is a list. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be null. 20 | 21 | ```scheme 22 | (!null? "hello" [99]) 23 | ``` 24 | 25 | This example will return _#t_ (true). 26 | -------------------------------------------------------------------------------- /core/special/binding_test.go: -------------------------------------------------------------------------------- 1 | package special_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/core/special" 8 | "github.com/kode4food/ale/internal/assert" 9 | . "github.com/kode4food/ale/internal/assert/helpers" 10 | ) 11 | 12 | func TestLet(t *testing.T) { 13 | as := assert.New(t) 14 | as.MustEvalTo(`(let ([x 99][y 1]) (+ x y))`, I(100)) 15 | } 16 | 17 | func TestBindingErrors(t *testing.T) { 18 | as := assert.New(t) 19 | 20 | as.ErrorWith(`(let ([x 99][y]) (+ x y))`, 21 | special.ErrUnpairedBindings, 22 | ) 23 | 24 | as.ErrorWith(`(let ([x 99][x 99]) (+ x y))`, 25 | fmt.Errorf("%w: %s", special.ErrNameAlreadyBound, "x"), 26 | ) 27 | 28 | as.ErrorWith(`(let (x . 99) (+ x x))`, 29 | fmt.Errorf("%w: %s", special.ErrUnexpectedLetSyntax, "(x . 99)"), 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /internal/assert/env_test.go: -------------------------------------------------------------------------------- 1 | package assert_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/env" 8 | "github.com/kode4food/ale/internal/assert" 9 | ) 10 | 11 | func TestIsBound(t *testing.T) { 12 | as := assert.New(t) 13 | ns := assert.GetTestNamespace() 14 | as.NoError(env.BindPublic(ns, "found", data.True)) 15 | as.True(as.IsBound(ns, "found")) 16 | } 17 | 18 | func TestIsNotBound(t *testing.T) { 19 | as := assert.New(t) 20 | ns := assert.GetTestNamespace() 21 | e, err := ns.Public("not-bound") 22 | if as.NoError(err) && as.NotNil(e) { 23 | as.IsNotBound(ns, "not-bound") 24 | } 25 | } 26 | 27 | func TestIsNotDeclared(t *testing.T) { 28 | as := assert.New(t) 29 | ns := assert.GetTestNamespace() 30 | as.IsNotDeclared(ns, "not-declared") 31 | } 32 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "list?" 3 | description: "tests whether the provided forms are lists" 4 | names: ["list?", "!list?"] 5 | usage: "(list? form+) (!list? form+)" 6 | tags: ["sequence", "predicate"] 7 | --- 8 | 9 | If all forms evaluate to a list, then this function will return _#t_ (true). The first non-list will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (list? '(1 2 3 4) [5 6 7 8]) 15 | ``` 16 | 17 | This example will return _#f_ (false) because the second form is a vector. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be lists. 20 | 21 | ```scheme 22 | (!list? "hello" [99]) 23 | ``` 24 | 25 | This example will return _#t_ (true). 26 | -------------------------------------------------------------------------------- /ffi/value.go: -------------------------------------------------------------------------------- 1 | package ffi 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | 7 | "github.com/kode4food/ale" 8 | ) 9 | 10 | type valueWrapper struct{} 11 | 12 | // ErrMustImplementValue is raised when a value Unwrap call can't treat its 13 | // source as a data.Value 14 | var ErrMustImplementValue = errors.New("must implement value") 15 | 16 | var dataValue = reflect.TypeOf((*ale.Value)(nil)).Elem() 17 | 18 | func wrapDataValue(_ reflect.Type) (Wrapper, error) { 19 | return valueWrapper{}, nil 20 | } 21 | 22 | func (d valueWrapper) Wrap(_ *Context, v reflect.Value) (ale.Value, error) { 23 | if v, ok := v.Interface().(ale.Value); ok { 24 | return v, nil 25 | } 26 | return nil, ErrMustImplementValue 27 | } 28 | 29 | func (d valueWrapper) Unwrap(v ale.Value) (reflect.Value, error) { 30 | return reflect.ValueOf(v), nil 31 | } 32 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-string.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "str?" 3 | description: "tests whether the provided forms are strings" 4 | names: ["string?", "!string?"] 5 | usage: "(string? form+) (!string? form+)" 6 | tags: ["sequence", "predicate"] 7 | --- 8 | 9 | If all forms evaluate to string, then this function will return _#t_ (true). The first non-string will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (string? '(1 2 3 4) "hello") 15 | ``` 16 | 17 | This example will return _#f_ (false) because the first form is a list. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be strings. 20 | 21 | ```scheme 22 | (!string? '(1 2 3) [99]) 23 | ``` 24 | 25 | This example will return _#t_ (true). 26 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-zero.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "zero?" 3 | description: "tests whether the provided forms are numeric zero" 4 | names: ["zero?", "!zero?"] 5 | usage: "(zero? form+) (!zero? form+)" 6 | tags: ["predicate"] 7 | --- 8 | 9 | If all forms evaluate to zero (0, 0.0, or 0/x), then this function will return _#t_ (true). The first non-zero will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (zero? (- 3 2 1) 0 0/1 (/ 3 3)) 15 | ``` 16 | 17 | This example will return _#f_ (false) because the last form evaluates to _1_. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be zero. 20 | 21 | ```scheme 22 | (!zero? "hello" [99]) 23 | ``` 24 | 25 | This example will return _#t_ (true). 26 | -------------------------------------------------------------------------------- /ffi/bool.go: -------------------------------------------------------------------------------- 1 | package ffi 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | 7 | "github.com/kode4food/ale" 8 | "github.com/kode4food/ale/data" 9 | ) 10 | 11 | type boolWrapper struct{} 12 | 13 | // ErrValueMustBeBool is raised when a boolean Unwrap call can't treat its 14 | // source as a data.Bool 15 | var ErrValueMustBeBool = errors.New("value must be a bool") 16 | 17 | var ( 18 | boolTrue = reflect.ValueOf(true) 19 | boolFalse = reflect.ValueOf(false) 20 | ) 21 | 22 | func (boolWrapper) Wrap(_ *Context, v reflect.Value) (ale.Value, error) { 23 | return data.Bool(v.Bool()), nil 24 | } 25 | 26 | func (boolWrapper) Unwrap(v ale.Value) (reflect.Value, error) { 27 | if b, ok := v.(data.Bool); ok { 28 | if b { 29 | return boolTrue, nil 30 | } 31 | return boolFalse, nil 32 | } 33 | return _zero, ErrValueMustBeBool 34 | } 35 | -------------------------------------------------------------------------------- /data/procedure_test.go: -------------------------------------------------------------------------------- 1 | package data_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/data" 8 | "github.com/kode4food/ale/internal/assert" 9 | . "github.com/kode4food/ale/internal/assert/helpers" 10 | ) 11 | 12 | func TestMakeProcedure(t *testing.T) { 13 | as := assert.New(t) 14 | 15 | f1 := data.MakeProcedure(func(...ale.Value) ale.Value { 16 | return S("hello!") 17 | }) 18 | 19 | as.Contains(":type procedure", f1) 20 | as.NoError(f1.CheckArity(99)) 21 | } 22 | 23 | func TestProcedureEquality(t *testing.T) { 24 | as := assert.New(t) 25 | f1 := data.MakeProcedure(func(...ale.Value) ale.Value { return nil }) 26 | f2 := data.MakeProcedure(func(...ale.Value) ale.Value { return nil }) 27 | as.True(f1.Equal(f1)) 28 | as.False(f1.Equal(f2)) 29 | as.False(f1.Equal(I(42))) 30 | } 31 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-atom.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "atom?" 3 | description: "tests whether the provided forms are atomic" 4 | names: ["atom?", "!atom?"] 5 | usage: "(atom? form+) (!atom? form+)" 6 | tags: ["predicate"] 7 | --- 8 | 9 | A form is considered to be atomic if it cannot be further evaluated and would otherwise evaluate to itself. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (atom? '() :hello "there") 15 | ``` 16 | 17 | This example will return _#t_ (true) because each value is atomic. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be atomic. 20 | 21 | ```scheme 22 | (!atom? '(+ 1 2 3) [4 5 6]) 23 | ``` 24 | 25 | This example will return _#t_ (true) because compound types such as lists and vectors are not considered to be atomic. 26 | -------------------------------------------------------------------------------- /internal/compiler/generate/value_test.go: -------------------------------------------------------------------------------- 1 | package generate_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | "github.com/kode4food/ale/internal/compiler/generate" 10 | "github.com/kode4food/ale/internal/runtime/isa" 11 | ) 12 | 13 | func TestPair(t *testing.T) { 14 | as := assert.New(t) 15 | 16 | e1 := assert.GetTestEncoder() 17 | as.NoError(generate.Value(e1, data.NewCons(S("left"), S("right")))) 18 | e1.Emit(isa.Return) 19 | 20 | enc1 := e1.Encode() 21 | as.Instructions(isa.Instructions{ 22 | isa.Const.New(0), 23 | isa.Const.New(1), 24 | isa.Cons.New(), 25 | isa.Return.New(), 26 | }, enc1.Code) 27 | 28 | c := enc1.Constants 29 | as.Equal(S("right"), c[0]) 30 | as.Equal(S("left"), c[1]) 31 | } 32 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-mapped.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "mapped?" 3 | description: "tests whether the provided forms are mapped" 4 | names: ["mapped?", "!mapped?"] 5 | usage: "(mapped? form+) (!mapped? form+)" 6 | tags: ["sequence", "predicate"] 7 | --- 8 | 9 | If all forms evaluate to a mapped type, then this function will return _#t_ (true). The first non-mapped will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (mapped? {:name "bill"} {:name "peggy"} [1 2 3]) 15 | ``` 16 | 17 | This example will return _#f_ (false) because the third form is a vector. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be mapped. 20 | 21 | ```scheme 22 | (!mapped? "hello" [1 2 3]) 23 | ``` 24 | 25 | This example will return _#t_ (true). 26 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/let.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "let" 3 | description: "binds local values" 4 | names: ["let", "let*"] 5 | usage: "(let ([name expr]*) form*) (let* ([name expr]*) form*)" 6 | tags: ["binding"] 7 | --- 8 | 9 | Create a new local scope, evaluate the provided expressions, and then bind the resulting value to their respective names. It will then evaluate the specified forms within that scope and return the result of the last evaluation. The `let` form performs these bindings in parallel, whereas the `let*` form performs them sequentially. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (let ([x '(1 2 3 4)] 15 | [y [5 6 7 8] ]) 16 | (concat x y)) 17 | ``` 18 | 19 | This example will create a list called _x_ and a vector called _y_ and return the lazy concatenation of those sequences. Note that the two names do not exist outside the `let` form. 20 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-object.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "object?" 3 | description: "tests whether the provided forms are objects" 4 | names: ["object?", "!object?"] 5 | usage: "(object? form+) (!object? form+)" 6 | tags: ["sequence", "predicate"] 7 | --- 8 | 9 | If all forms evaluate to an object (hash-map), then this function will return _#t_ (true). The first non-object will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (object? {:name "bill"} {:name "peggy"} [1 2 3]) 15 | ``` 16 | 17 | This example will return _#f_ (false) because the third form is a vector. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be objects. 20 | 21 | ```scheme 22 | (!object? "hello" [1 2 3]) 23 | ``` 24 | 25 | This example will return _#t_ (true). 26 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-true.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "true?" 3 | description: "tests whether the provided forms are boolean true" 4 | names: ["true?", "!true?"] 5 | usage: "(true? form+) (!true? form+)" 6 | tags: ["predicate"] 7 | --- 8 | 9 | If all forms evaluate to true (_#t_), then this function will return _#t_ (true). The first non-true will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (true? (> 3 2) (< 5 10) (and #t #f)) 15 | ``` 16 | 17 | This example will return _#f_ (false) because the last form evaluates to _#f_. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be true. This is equivalent to the _false?_ predicate. 20 | 21 | ```scheme 22 | (!true? #f (> 5 10)) 23 | ``` 24 | 25 | This example will return _#t_ (true). 26 | -------------------------------------------------------------------------------- /internal/types/tuple.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kode4food/ale" 7 | ) 8 | 9 | var makeTupleBase = [...]*basic{BasicCons, BasicList, BasicVector} 10 | 11 | // MakeTuple declares a new Type that will only allow a List or Vector with 12 | // positional elements of the provided Types 13 | func MakeTuple(elems ...ale.Type) ale.Type { 14 | res := make([]ale.Type, 3) 15 | for idx, t := range makeTupleBase { 16 | var comp ale.Type = BasicNull 17 | for i := len(elems) - 1; i >= 0; i = i - 1 { 18 | comp = &Pair{ 19 | Basic: t, 20 | name: fmt.Sprintf("tuple(%s)", typeList(elems).name()), 21 | car: elems[i], 22 | cdr: comp, 23 | } 24 | } 25 | res[idx] = comp 26 | } 27 | return &Union{ 28 | name: fmt.Sprintf("tuple(%s)", typeList(elems).name()), 29 | Basic: BasicUnion, 30 | options: res, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-vector.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "vector?" 3 | description: "tests whether the provided forms are vectors" 4 | names: ["vector?", "!vector?"] 5 | usage: "(vector? form+) (!vector? form+)" 6 | tags: ["sequence", "predicate"] 7 | --- 8 | 9 | If all forms evaluate to a vector, then this function will return _#t_ (true). The first non-vector will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (vector? '(1 2 3 4) [5 6 7 8]) 15 | ``` 16 | 17 | This example will return _#f_ (false) because the first form is a list. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be vectors. 20 | 21 | ```scheme 22 | (!vector? "hello" [99]) 23 | ``` 24 | 25 | This example will return _#f_ (false) because the second form is a vector. 26 | -------------------------------------------------------------------------------- /macro/call.go: -------------------------------------------------------------------------------- 1 | package macro 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | "github.com/kode4food/ale/env" 7 | "github.com/kode4food/ale/internal/types" 8 | ) 9 | 10 | // Call represents a macro's calling signature 11 | type Call func(env.Namespace, ...ale.Value) ale.Value 12 | 13 | var ( 14 | CallType = types.MakeBasic("macro") 15 | 16 | // compile-time checks for interface implementation 17 | _ interface { 18 | data.Mapped 19 | ale.Typed 20 | } = Call(nil) 21 | ) 22 | 23 | // Type makes Call a typed value 24 | func (c Call) Type() ale.Type { 25 | return types.MakeLiteral(CallType, c) 26 | } 27 | 28 | // Equal compares this Call to another for equality 29 | func (Call) Equal(ale.Value) bool { 30 | return false 31 | } 32 | 33 | func (c Call) Get(key ale.Value) (ale.Value, bool) { 34 | return data.DumpMapped(c).Get(key) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-false.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "false?" 3 | description: "tests whether the provided forms are boolean false" 4 | names: ["false?", "!false?"] 5 | usage: "(false? form+) (!false? form+)" 6 | tags: ["predicate"] 7 | --- 8 | 9 | If all forms evaluate to false (_#f_), then this function will return _#t_ (true). The first non-false will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (false? (< 3 2) (> 5 10)) 15 | ``` 16 | 17 | This example will return _#t_ (true) because all the equalities result in _#f_ (false). 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be true. This is equivalent to the _true?_ predicate. 20 | 21 | ```scheme 22 | (!false? #t (< 5 10)) 23 | ``` 24 | 25 | This example will return _#t_ (true). 26 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/cons.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "cons" 3 | description: "combines two values into a pair" 4 | names: ["cons"] 5 | usage: "(cons car cdr)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | When `cdr` is an ordered sequence, such as a list or vector, the result is a new list or vector with the `car` value prepended to the original. With an unordered sequence, such as an object array, there is no guarantee regarding position. If `cdr` is not a sequence, then a new cons cell will be constructed. 10 | 11 | The name _cons_ is a vestige of when Lisp implementations constructed new lists or cells by pairing a _car_ (contents of the address part of register) with a _cdr_ (contents of the decrement part of register). 12 | 13 | #### An Example 14 | 15 | ```scheme 16 | (define x '(3 4 5 6)) 17 | (define y (cons 2 x)) 18 | (cons 1 y) 19 | ``` 20 | 21 | This example will return _(1 2 3 4 5 6)_. 22 | -------------------------------------------------------------------------------- /internal/compiler/procedure/procedure_test.go: -------------------------------------------------------------------------------- 1 | package procedure_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | "github.com/kode4food/ale/internal/compiler/procedure" 10 | "github.com/kode4food/ale/internal/runtime/isa" 11 | ) 12 | 13 | func TestFromEncoder(t *testing.T) { 14 | as := assert.New(t) 15 | 16 | e1 := assert.GetTestEncoder() 17 | e1.Emit(isa.ArgsLen) 18 | e1.Emit(isa.Return) 19 | 20 | l, err := procedure.FromEncoded(e1.Encode()) 21 | if as.NoError(err) && as.NotNil(l) { 22 | as.NoError(l.CheckArity(-1)) 23 | 24 | c, ok := l.Call().(data.Procedure) 25 | as.True(ok) 26 | if as.NotNil(c) { 27 | as.Equal(I(4), c.Call(S("one"), S("two"), S("three"), S("four"))) 28 | as.Contains(":type procedure", c) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /internal/compiler/encoder/closure.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import ( 4 | "github.com/kode4food/ale/data" 5 | "github.com/kode4food/ale/internal/basics" 6 | "github.com/kode4food/ale/internal/runtime/isa" 7 | ) 8 | 9 | func (e *encoder) ResolveClosure(n data.Local) (*IndexedCell, bool) { 10 | c, ok := basics.Find(e.closure, func(c *IndexedCell) bool { 11 | return c.Name == n 12 | }) 13 | if ok { 14 | return c, ok 15 | } 16 | return e.resolveClosureParent(n) 17 | } 18 | 19 | func (e *encoder) resolveClosureParent(n data.Local) (*IndexedCell, bool) { 20 | parent := e.parent 21 | if parent == nil { 22 | return nil, false 23 | } 24 | s, ok := parent.ResolveScoped(n) 25 | if !ok { 26 | return nil, false 27 | } 28 | closure := e.closure 29 | idx := isa.Operand(len(closure)) 30 | res := newIndexedCell(idx, s.Cell) 31 | e.closure = append(closure, res) 32 | return res, true 33 | } 34 | -------------------------------------------------------------------------------- /ffi/ptr.go: -------------------------------------------------------------------------------- 1 | package ffi 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/data" 8 | ) 9 | 10 | type pointerWrapper struct { 11 | elem Wrapper 12 | } 13 | 14 | func makeWrappedPointer(t reflect.Type) (Wrapper, error) { 15 | w, err := WrapType(t.Elem()) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return &pointerWrapper{ 20 | elem: w, 21 | }, nil 22 | } 23 | 24 | func (w *pointerWrapper) Wrap(c *Context, v reflect.Value) (ale.Value, error) { 25 | c, err := c.Push(v) 26 | if err != nil { 27 | return data.Null, err 28 | } 29 | e := v.Elem() 30 | if e.IsValid() { 31 | return w.elem.Wrap(c, e) 32 | } 33 | return data.Null, nil 34 | } 35 | 36 | func (w *pointerWrapper) Unwrap(v ale.Value) (reflect.Value, error) { 37 | e, err := w.elem.Unwrap(v) 38 | if err != nil { 39 | return _zero, err 40 | } 41 | return e.Addr(), nil 42 | } 43 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-promise.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "promise?" 3 | description: "tests whether the provided forms are promises" 4 | names: ["promise?", "!promise?"] 5 | usage: "(promise? form+) (!promise? form+)" 6 | tags: ["concurrency", "predicate"] 7 | --- 8 | 9 | If all forms evaluate to a promise, then this function will return _#t_ (true). The first non-promise will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define p1 (delay "one")) 15 | (define p2 (delay "two")) 16 | (promise? p1 p2 [1 2 3]) 17 | ``` 18 | 19 | This example will return _#f_ (false) because the third form is a vector. 20 | 21 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be promises. 22 | 23 | ```scheme 24 | (!promise? "hello" [1 2 3]) 25 | ``` 26 | 27 | This example will return _#t_ (true). 28 | -------------------------------------------------------------------------------- /internal/assert/testenv.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/kode4food/ale/core/bootstrap" 7 | "github.com/kode4food/ale/env" 8 | "github.com/kode4food/ale/internal/compiler/encoder" 9 | ) 10 | 11 | var ( 12 | testOnce sync.Once 13 | testEnv *env.Environment 14 | ) 15 | 16 | // GetTestEnvironment returns an immutable root testing Environment 17 | func GetTestEnvironment() *env.Environment { 18 | testOnce.Do(func() { 19 | testEnv = bootstrap.DevNullEnvironment() 20 | }) 21 | return testEnv 22 | } 23 | 24 | // GetTestNamespace returns a new anonymous namespace for testing purposes 25 | func GetTestNamespace() env.Namespace { 26 | return GetTestEnvironment().GetAnonymous() 27 | } 28 | 29 | // GetTestEncoder returns a new Encoder, rooted at a test Namespace 30 | func GetTestEncoder() encoder.Encoder { 31 | ns := GetTestNamespace() 32 | return encoder.NewEncoder(ns) 33 | } 34 | -------------------------------------------------------------------------------- /internal/compiler/call.go: -------------------------------------------------------------------------------- 1 | package compiler 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | "github.com/kode4food/ale/internal/compiler/encoder" 7 | "github.com/kode4food/ale/internal/types" 8 | ) 9 | 10 | // Call represents a code-generating function for the compiler 11 | type Call func(encoder.Encoder, ...ale.Value) error 12 | 13 | var ( 14 | CallType = types.MakeBasic("special") 15 | 16 | // compile-time checks for interface implementation 17 | _ interface { 18 | data.Mapped 19 | ale.Typed 20 | } = Call(nil) 21 | ) 22 | 23 | // Type makes Call a typed value 24 | func (c Call) Type() ale.Type { 25 | return types.MakeLiteral(CallType, c) 26 | } 27 | 28 | // Equal makes Call a typed Value 29 | func (Call) Equal(ale.Value) bool { 30 | return false 31 | } 32 | 33 | func (c Call) Get(key ale.Value) (ale.Value, bool) { 34 | return data.DumpMapped(c).Get(key) 35 | } 36 | -------------------------------------------------------------------------------- /internal/compiler/ir/optimize/literals.go: -------------------------------------------------------------------------------- 1 | package optimize 2 | 3 | import ( 4 | "github.com/kode4food/ale/internal/basics" 5 | "github.com/kode4food/ale/internal/compiler/ir/visitor" 6 | "github.com/kode4food/ale/internal/runtime/isa" 7 | ) 8 | 9 | var ( 10 | literalReturnMap = map[isa.Opcode]isa.Opcode{ 11 | isa.False: isa.RetFalse, 12 | isa.Null: isa.RetNull, 13 | isa.True: isa.RetTrue, 14 | } 15 | 16 | // literalReturns convert some literal instructions followed by a return 17 | // instruction into single instruction (ret-true, ret-zero, etc...) 18 | literalReturns = globalReplace( 19 | visitor.Pattern{ 20 | basics.MapKeys(literalReturnMap), 21 | {isa.Return}, 22 | }, 23 | literalReturnMapper, 24 | ) 25 | ) 26 | 27 | func literalReturnMapper(i isa.Instructions) isa.Instructions { 28 | oc := i[0].Opcode() 29 | res := literalReturnMap[oc] 30 | return isa.Instructions{res.New()} 31 | } 32 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-indexed.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "indexed?" 3 | description: "tests whether the provided forms are indexed sequences" 4 | names: ["indexed?", "!indexed?"] 5 | usage: "(indexed? form+) (!indexed? form+)" 6 | tags: ["sequence", "predicate"] 7 | --- 8 | 9 | If all forms evaluate to a valid sequence that can be accessed by index, then this function will return _#t_ (true). The first non-indexed sequence will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (indexed? '(1 2 3 4) [5 6 7 8]) 15 | ``` 16 | 17 | This example will return _#t_ (true). 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be valid indexed sequences. 20 | 21 | ```scheme 22 | (!indexed? "hello" 99) 23 | ``` 24 | 25 | This example will return _#f_ (false) because strings are indexed sequences. 26 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/map.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "map" 3 | description: "lazily maps sequences" 4 | names: ["map"] 5 | usage: "(map func seq+)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | Creates a lazy sequence whose elements are the result of applying the provided function to the sequence elements. If more than one sequence is provided, their elements are retrieved in parallel to supply additional arguments to the mapped function. Mapping will terminate as soon as any sequence is exhausted. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (map (lambda (x) (\* x 2)) [1 2 3 4]) 15 | ``` 16 | 17 | This will return the lazy sequence _(2 4 6 8)_. The following example performs mapping in parallel. 18 | 19 | ```scheme 20 | (map + [1 2 3 4] '(4 6 8) '(30 20 10 56)) 21 | ``` 22 | 23 | This will return the lazy sequence _(35 28 21)_. Mapping only occurs three times in this example because the second sequence only has three elements. 24 | -------------------------------------------------------------------------------- /core/special/namespace_test.go: -------------------------------------------------------------------------------- 1 | package special_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | ) 10 | 11 | func TestNamespaceDefinition(t *testing.T) { 12 | as := assert.New(t) 13 | 14 | d := as.MustEval(` 15 | (define-namespace some-namespace 16 | (define x 99) 17 | (define y 100) 18 | ) 19 | `).(data.Vector) 20 | _, ok1 := d.IndexOf(LS("x")) 21 | _, ok2 := d.IndexOf(LS("y")) 22 | as.True(ok1 && ok2) 23 | 24 | i := as.MustEval(`(import some-namespace)`).(data.Vector) 25 | _, ok1 = i.IndexOf(LS("x")) 26 | _, ok2 = i.IndexOf(LS("y")) 27 | as.True(ok1 && ok2) 28 | 29 | as.MustEvalTo(`(import some-namespace y) y`, I(100)) 30 | as.MustEvalTo(`(import some-namespace [x99 x]) x99`, I(99)) 31 | as.MustEvalTo(`(import some-namespace (x y)) [y x]`, V(I(100), I(99))) 32 | } 33 | -------------------------------------------------------------------------------- /internal/types/sequence.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kode4food/ale" 7 | ) 8 | 9 | // MakeListOf declares a new PairType that will only allow a BasicList with 10 | // elements of the provided elem Type 11 | func MakeListOf(elem ale.Type) ale.Type { 12 | return sequenceOf(BasicList, elem) 13 | } 14 | 15 | // MakeVectorOf declares a new VectorType that will only allow a BasicVector 16 | // with elements of the provided elem Type 17 | func MakeVectorOf(elem ale.Type) ale.Type { 18 | return sequenceOf(BasicVector, elem) 19 | } 20 | 21 | func sequenceOf(base *basic, elem ale.Type) ale.Type { 22 | name := fmt.Sprintf("%s(%s)", base.Name(), elem.Name()) 23 | first := &Pair{ 24 | Basic: base, 25 | name: name, 26 | car: elem, 27 | } 28 | rest := &Union{ 29 | name: name, 30 | Basic: base, 31 | options: typeList{first, BasicNull}, 32 | } 33 | first.cdr = rest 34 | return rest 35 | } 36 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-counted.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "counted?" 3 | description: "tests whether the provided forms are counted sequences" 4 | names: ["counted?", "!counted?"] 5 | usage: "(counted? form+) (!counted? form+)" 6 | tags: ["sequence", "predicate"] 7 | --- 8 | 9 | If all forms evaluate to a valid sequence than can report its length without counting, then this function will return _#t_ (true). The first non-counted sequence will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (counted? '(1 2 3 4) [5 6 7 8]) 15 | ``` 16 | 17 | This example will return _#t_ (true). 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be valid counted sequences. 20 | 21 | ```scheme 22 | (!counted? "hello" 99) 23 | ``` 24 | 25 | This example will return _#f_ (false) because strings are counted sequences. 26 | -------------------------------------------------------------------------------- /internal/sequence/lazy_test.go: -------------------------------------------------------------------------------- 1 | package sequence_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/data" 8 | "github.com/kode4food/ale/internal/assert" 9 | . "github.com/kode4food/ale/internal/assert/helpers" 10 | "github.com/kode4food/ale/internal/sequence" 11 | ) 12 | 13 | func TestLazySeq(t *testing.T) { 14 | var inc sequence.LazyResolver 15 | as := assert.New(t) 16 | 17 | i := 0 18 | inc = func() (ale.Value, data.Sequence, bool) { 19 | if i >= 10 { 20 | return data.Null, data.Null, false 21 | } 22 | i++ 23 | first := F(float64(i)) 24 | return first, sequence.NewLazy(inc), true 25 | } 26 | 27 | l := sequence.NewLazy(inc).(data.Prepender).Prepend(F(0)) 28 | as.False(l.IsEmpty()) 29 | as.Number(0, l.Car()) 30 | as.Number(1, l.Cdr().(data.Pair).Car()) 31 | as.Number(2, l.Cdr().(data.Pair).Cdr().(data.Pair).Car()) 32 | as.Contains(":type lazy-sequence", l) 33 | } 34 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/empty.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "empty?" 3 | description: "tests whether the provided forms are empty sequences" 4 | names: ["empty?", "!empty?"] 5 | usage: "(empty? form+) (!empty? form+)" 6 | tags: ["predicate"] 7 | --- 8 | 9 | If all forms evaluate to empty sequences, then this function will return _#t_ (true). The first evaluation that is not an empty sequence will result in the function returning _#f_ (false). 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (empty? '(1 2 3 4) [] {}) 15 | ``` 16 | 17 | This example will return _#f_ (false) because the first form is a list with four elements. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be nil. 20 | 21 | ```scheme 22 | (!empty? '(1) [2] "3" {4 5}) 23 | ``` 24 | 25 | This example will return _#t_ (true) because all the arguments are non-empty sequences of some kind. 26 | -------------------------------------------------------------------------------- /ffi/string.go: -------------------------------------------------------------------------------- 1 | package ffi 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | 7 | "github.com/kode4food/ale" 8 | "github.com/kode4food/ale/data" 9 | ) 10 | 11 | type stringWrapper struct{} 12 | 13 | // ErrValueMustBeString is raised when a string Unwrap call can't treat its 14 | // source as a data.String 15 | var ErrValueMustBeString = errors.New("value must be a byte slice or string") 16 | 17 | func (stringWrapper) Wrap(_ *Context, v reflect.Value) (ale.Value, error) { 18 | return data.String(v.String()), nil 19 | } 20 | 21 | func (stringWrapper) Unwrap(v ale.Value) (reflect.Value, error) { 22 | return asValueOf[string](v) 23 | } 24 | 25 | func asValueOf[T string | []byte](v ale.Value) (reflect.Value, error) { 26 | switch v := v.(type) { 27 | case data.String: 28 | return reflect.ValueOf(T(v)), nil 29 | case data.Bytes: 30 | return reflect.ValueOf(T(v)), nil 31 | default: 32 | return _zero, ErrValueMustBeString 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/is-keyword.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "keyword?" 3 | description: "tests whether the provided forms are keywords" 4 | names: ["keyword?", "!keyword?"] 5 | usage: "(keyword? form+) (!keyword? form+)" 6 | tags: ["predicate"] 7 | --- 8 | 9 | A form is considered to be a keyword if it is a symbol with a colon (":") prefix. Keywords are often used as unique identifiers and are typically used to access values in objects or define objects keys. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (keyword? :keyword1 :keyword2 :keyword3) 15 | ``` 16 | 17 | This example will return _#t_ (true) because each provided form is a keyword. 18 | 19 | Like most predicates, this function can also be negated by prepending the `!` character. This means that all the provided forms must not be keywords. 20 | 21 | ```scheme 22 | (!keyword? "string1" "string2" :keyword1) 23 | ``` 24 | 25 | This example will return _#f_ (false) because _:keyword1_ is provided. 26 | -------------------------------------------------------------------------------- /core/builtin/strings.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/data" 8 | "github.com/kode4food/ale/internal/lang" 9 | "github.com/kode4food/ale/internal/sequence" 10 | ) 11 | 12 | const emptyString = data.String("") 13 | 14 | // Str converts the provided arguments to an undelimited string 15 | var Str = data.MakeProcedure(func(args ...ale.Value) ale.Value { 16 | v := data.Vector(args) 17 | return sequence.ToString(v) 18 | }) 19 | 20 | // ReaderStr converts the provided arguments to a delimited string 21 | var ReaderStr = data.MakeProcedure(func(args ...ale.Value) ale.Value { 22 | if len(args) == 0 { 23 | return emptyString 24 | } 25 | 26 | var b strings.Builder 27 | b.WriteString(data.ToQuotedString(args[0])) 28 | for _, f := range args[1:] { 29 | b.WriteString(lang.Space) 30 | b.WriteString(data.ToQuotedString(f)) 31 | } 32 | return data.String(b.String()) 33 | }) 34 | -------------------------------------------------------------------------------- /internal/assert/env.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | "github.com/kode4food/ale/env" 7 | ) 8 | 9 | func (w *Wrapper) IsNotDeclared(ns env.Namespace, n data.Local) { 10 | w.Helper() 11 | e, in, err := ns.Resolve(n) 12 | w.Nil(e) 13 | w.Nil(in) 14 | w.NotNil(err) 15 | } 16 | 17 | func (w *Wrapper) IsNotBound(ns env.Namespace, n data.Local) { 18 | w.Helper() 19 | e, in, err := ns.Resolve(n) 20 | if w.NoError(err) { 21 | w.NotNil(e) 22 | w.NotNil(in) 23 | w.False(e.IsBound()) 24 | 25 | v, err := e.Value() 26 | w.Nil(v) 27 | w.NotNil(err) 28 | } 29 | } 30 | 31 | func (w *Wrapper) IsBound(ns env.Namespace, n data.Local) ale.Value { 32 | w.Helper() 33 | e, in, err := ns.Resolve(n) 34 | if w.NoError(err) { 35 | w.NotNil(e) 36 | w.NotNil(in) 37 | 38 | w.True(e.IsBound()) 39 | v, err := e.Value() 40 | w.NoError(err) 41 | return v 42 | } 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /core/bootstrap/definer_test.go: -------------------------------------------------------------------------------- 1 | package bootstrap_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/core/bootstrap" 8 | "github.com/kode4food/ale/data" 9 | "github.com/kode4food/ale/env" 10 | "github.com/kode4food/ale/eval" 11 | "github.com/kode4food/ale/internal/assert" 12 | ) 13 | 14 | func TestDefiner(t *testing.T) { 15 | as := assert.New(t) 16 | 17 | e := bootstrap.DevNullEnvironment() 18 | ns := e.GetRoot() 19 | res, err := eval.String(ns, "read") 20 | if as.NoError(err) { 21 | _, ok := res.(data.Procedure) 22 | as.True(ok) 23 | } 24 | 25 | _, err = eval.String(ns, "(def-builtin read)") 26 | as.ExpectError(fmt.Errorf(env.ErrNameAlreadyBound, "read"), err) 27 | 28 | _, err = eval.String(ns, "(def-builtin nope)") 29 | as.ExpectError(fmt.Errorf(bootstrap.ErrBuiltInNotFound, "nope"), err) 30 | 31 | _, err = eval.String(ns, "(def-builtin too many)") 32 | as.ExpectError(fmt.Errorf(data.ErrFixedArity, 1, 2), err) 33 | } 34 | -------------------------------------------------------------------------------- /core/builtin/os_test.go: -------------------------------------------------------------------------------- 1 | package builtin_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/kode4food/ale/core/builtin" 8 | "github.com/kode4food/ale/data" 9 | "github.com/kode4food/ale/internal/assert" 10 | . "github.com/kode4food/ale/internal/assert/helpers" 11 | ) 12 | 13 | func TestEnv(t *testing.T) { 14 | as := assert.New(t) 15 | 16 | env := builtin.Env().(*data.Object) 17 | if as.NotNil(env) { 18 | as.False(env.IsEmpty()) 19 | p := as.MustGet(env, K("PATH")).(data.String) 20 | as.True(len(p) > 0) 21 | } 22 | } 23 | 24 | func TestArgs(t *testing.T) { 25 | as := assert.New(t) 26 | 27 | args := builtin.Args().(data.Vector) 28 | if as.NotNil(args) { 29 | as.False(args.IsEmpty()) 30 | as.Contains("test", args[0]) 31 | } 32 | } 33 | 34 | func TestCurrentTime(t *testing.T) { 35 | as := assert.New(t) 36 | 37 | t1 := time.Now().UnixNano() 38 | t2 := int64(builtin.CurrentTime.Call().(data.Integer)) 39 | as.Equal(t1-(t1%1000000), t2-(t2%1000000)) 40 | } 41 | -------------------------------------------------------------------------------- /internal/stream/writer.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | 7 | "github.com/kode4food/ale" 8 | "github.com/kode4food/ale/data" 9 | ) 10 | 11 | // OutputFunc is a callback used to marshal values to a Writer 12 | type OutputFunc func(*bufio.Writer, ale.Value) 13 | 14 | // NewWriter wraps a Go Writer, coupling it with an output function 15 | func NewWriter(w io.Writer, o OutputFunc) *data.Object { 16 | buf := bufio.NewWriter(w) 17 | writer := func(v ale.Value) { 18 | o(buf, v) 19 | _ = buf.Flush() 20 | } 21 | 22 | if c, ok := w.(io.Closer); ok { 23 | return data.NewObject( 24 | data.NewCons(WriteKey, bindWriter(writer)), 25 | data.NewCons(CloseKey, bindCloser(c)), 26 | ) 27 | } 28 | 29 | return data.NewObject( 30 | data.NewCons(WriteKey, bindWriter(writer)), 31 | ) 32 | } 33 | 34 | // StrOutput is the standard string-based output function 35 | func StrOutput(w *bufio.Writer, v ale.Value) { 36 | _, _ = w.Write([]byte(data.ToString(v))) 37 | } 38 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/generate.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "generate" 3 | description: "generates a sequence asynchronously" 4 | names: ["generate"] 5 | usage: "(generate form+)" 6 | tags: ["sequence", "concurrency"] 7 | --- 8 | 9 | Evaluate the specified forms in a separate thread of execution. Returns a sequence that will iterate over any of the values that are emitted. Values are emitted using a locally scoped function of the form `(emit value)`. The forms are executed as a co-routine, meaning that a call to emit **will block** until the corresponding element is resolved by a consumer of the sequence. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (define colors (generate 15 | (emit "red") 16 | (emit "orange") 17 | (emit "yellow"))) 18 | 19 | (seq->vector colors) 20 | ``` 21 | 22 | This example will bind the lazy sequence returned by the call to generate to the name `colors`. The seq->vector call will block until that variable is fully consumed, and then return the vector _["red" "orange" "yellow"]_. 23 | -------------------------------------------------------------------------------- /internal/compiler/generate/branch_test.go: -------------------------------------------------------------------------------- 1 | package generate_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | "github.com/kode4food/ale/internal/compiler/encoder" 8 | "github.com/kode4food/ale/internal/compiler/generate" 9 | "github.com/kode4food/ale/internal/runtime/isa" 10 | ) 11 | 12 | func TestBranch(t *testing.T) { 13 | as := assert.New(t) 14 | 15 | e := assert.GetTestEncoder() 16 | as.NoError(generate.Branch(e, 17 | func(encoder.Encoder) error { e.Emit(isa.True); return nil }, 18 | func(encoder.Encoder) error { e.Emit(isa.PosInt, 1); return nil }, 19 | func(encoder.Encoder) error { e.Emit(isa.Zero); return nil }, 20 | )) 21 | e.Emit(isa.Return) 22 | 23 | as.Instructions( 24 | isa.Instructions{ 25 | isa.True.New(), 26 | isa.CondJump.New(0), 27 | isa.Zero.New(), 28 | isa.Jump.New(1), 29 | isa.Label.New(0), 30 | isa.PosInt.New(1), 31 | isa.Label.New(1), 32 | isa.Return.New(), 33 | }, 34 | e.Encode().Code, 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /data/arity_test.go: -------------------------------------------------------------------------------- 1 | package data_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/data" 8 | "github.com/kode4food/ale/internal/assert" 9 | ) 10 | 11 | func TestMakeChecker(t *testing.T) { 12 | as := assert.New(t) 13 | fn1, err := data.MakeArityChecker() 14 | if as.NoError(err) && as.NotNil(fn1) { 15 | as.Nil(fn1(-1)) 16 | as.Nil(fn1(1000)) 17 | } 18 | 19 | fn2, err := data.MakeArityChecker(1) 20 | if as.NoError(err) { 21 | as.Nil(fn2(1)) 22 | as.EqualError(fn2(2), fmt.Sprintf(data.ErrFixedArity, 1, 2)) 23 | } 24 | 25 | fn3, err := data.MakeArityChecker(2, data.OrMore) 26 | if as.NoError(err) { 27 | as.Nil(fn3(5)) 28 | as.EqualError(fn3(1), fmt.Sprintf(data.ErrMinimumArity, 2, 1)) 29 | } 30 | 31 | fn4, err := data.MakeArityChecker(2, 7) 32 | if as.NoError(err) { 33 | as.Nil(fn4(4)) 34 | as.EqualError(fn4(8), fmt.Sprintf(data.ErrRangedArity, 2, 7, 8)) 35 | } 36 | 37 | _, err = data.MakeArityChecker(1, 2, 3) 38 | as.EqualError(err, data.ErrTooManyArguments) 39 | } 40 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/try.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "try/catch" 3 | description: "handles raised errors" 4 | names: ["try"] 5 | usage: "(try form* catch-clause* finally-clause?)" 6 | --- 7 | 8 | Evaluate the provided forms, immediately short-circuiting if an error is raised, at which point control is passed into the first `catch` clause whose `predicate` evaluates to _#t_ (true). If a `finally` clause is defined, control will be passed into it after a successful evaluation of the provided forms or after a `catch` clause is evaluated. 9 | 10 | `catch-clause` is defined as `(catch [name predicate] form*)` 11 | 12 | `finally-clause` is defined as `(finally form*)` 13 | 14 | #### An Example 15 | 16 | ```scheme 17 | (try 18 | (raise "hello!") 19 | (println "won't reach me") 20 | (catch [n inf?] (println "won't match me")) 21 | (catch [s str?] (println "was a string ->" s)) 22 | (finally (println "done"))) 23 | ``` 24 | 25 | This will print the following to the console. 26 | 27 | ``` 28 | was a string -> hello! 29 | done 30 | ``` 31 | -------------------------------------------------------------------------------- /ffi/helper_test.go: -------------------------------------------------------------------------------- 1 | package ffi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/core/bootstrap" 8 | "github.com/kode4food/ale/data" 9 | "github.com/kode4food/ale/env" 10 | "github.com/kode4food/ale/eval" 11 | "github.com/kode4food/ale/ffi" 12 | "github.com/kode4food/ale/internal/assert" 13 | ) 14 | 15 | type ( 16 | EvalWrapped struct { 17 | *assert.Wrapper 18 | } 19 | 20 | Env map[data.Local]any 21 | ) 22 | 23 | var testEnv = bootstrap.DevNullEnvironment() 24 | 25 | func NewWrapped(t *testing.T) *EvalWrapped { 26 | return &EvalWrapped{ 27 | Wrapper: assert.New(t), 28 | } 29 | } 30 | 31 | func (e *EvalWrapped) EvalTo(src string, en Env, expect ale.Value) { 32 | e.Helper() 33 | ns := testEnv.GetAnonymous() 34 | for n, v := range en { 35 | v, err := ffi.Wrap(v) 36 | if e.NoError(err) { 37 | e.NoError(env.BindPublic(ns, n, v)) 38 | } 39 | } 40 | res, err := eval.String(ns, data.String(src)) 41 | if e.NoError(err) { 42 | e.Equal(expect, res) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /internal/types/pair_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/types" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestConsAccepts(t *testing.T) { 11 | as := assert.New(t) 12 | 13 | c1 := types.MakeCons(types.BasicNull, types.BasicString) 14 | c2 := types.MakeCons(types.BasicNumber, types.BasicString) 15 | c3 := types.MakeCons(types.BasicNull, types.BasicString) 16 | 17 | as.True(c1.Accepts(c1)) 18 | as.False(c1.Accepts(c2)) 19 | as.True(c1.Accepts(c3)) 20 | } 21 | 22 | func TestConsEqual(t *testing.T) { 23 | as := assert.New(t) 24 | 25 | c1 := types.MakeCons(types.BasicNull, types.BasicString) 26 | c2 := types.MakeCons(types.BasicNumber, types.BasicString) 27 | c3 := types.MakeCons(types.BasicNull, types.BasicString) 28 | 29 | as.True(c1.Equal(c1)) 30 | as.False(c1.Equal(c2)) 31 | as.True(c1.Equal(c3)) 32 | 33 | as.False(types.BasicObject.Equal(c1)) 34 | as.False(c1.Equal(types.BasicObject)) 35 | as.False(c1.Equal(types.BasicBoolean)) 36 | } 37 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/thread-cond-first.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "conditional thread first (cond->)" 3 | description: "-> macro with conditional forms" 4 | names: ["cond->"] 5 | usage: "(cond-> expr [test form]*)" 6 | tags: ["function"] 7 | --- 8 | 9 | Like `->`, but each form is paired with a test condition. The form is only applied if the test evaluates to true. Each test is evaluated with the current threaded value in scope. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (cond-> 10 15 | [true (+ 5)] ; Always applies: 10 + 5 = 15 16 | [(> 12) (\* 2)] ; Applies if > 12: 15 \* 2 = 30 17 | [(< 25) (/ 3)] ; Doesn't apply (30 is not < 25) 18 | [true (- 1)]) ; Always applies: 30 - 1 = 29 19 | ``` 20 | 21 | #### Another Example 22 | 23 | ```scheme 24 | (cond-> {} 25 | [empty? (assoc (:name . "John"))] 26 | [!empty? (assoc (:size . "non-empty"))] 27 | [(contains? :name) (assoc (:greeting . "Hello"))] 28 | [object? (assoc (:type . "object"))]) 29 | ``` 30 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/chan.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "chan" 3 | description: "creates a unidirectional channel" 4 | names: ["chan"] 5 | usage: "(chan size?)" 6 | tags: ["concurrency"] 7 | --- 8 | 9 | A channel is a data structure used to generate a lazy sequence of values. The result is a hash-map consisting of an `emit` function, a `close` function, and a sequence. Depending on the size of the channel's buffer, retrieving an element from the sequence _may block_, waiting for the next value to be emitted or for the channel to be closed. Emitting a value to a channel will also block until the buffer is flushed as a result of iterating over the sequence. 10 | 11 | #### Channel Keys 12 | 13 | ``` 14 | *:seq* the sequence to be generated 15 | *:emit* an emitter function of the form (emit value+) 16 | *:close* a function to close the channel (close) 17 | ``` 18 | 19 | #### An Example 20 | 21 | ```scheme 22 | (let [ch (chan)] 23 | (go (: ch :emit "foo") 24 | (: ch :emit "bar") 25 | (: ch :close)) 26 | 27 | (seq->vector (ch :seq))) 28 | ``` 29 | -------------------------------------------------------------------------------- /internal/stream/stream.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/data" 8 | ) 9 | 10 | // Writer is used to emit or write values to a stream 11 | type Writer func(ale.Value) 12 | 13 | const ( 14 | // WriteKey is key used to write to a Writer 15 | WriteKey = data.Keyword("write") 16 | 17 | // CloseKey is the key used to close a file 18 | CloseKey = data.Keyword("close") 19 | 20 | // EmitKey is the key used to emit to a Channel 21 | EmitKey = data.Keyword("emit") 22 | 23 | // SequenceKey is the key used to retrieve the Sequence from a Channel 24 | SequenceKey = data.Keyword("seq") 25 | ) 26 | 27 | func bindWriter(w Writer) data.Procedure { 28 | return data.MakeProcedure(func(args ...ale.Value) ale.Value { 29 | for _, f := range args { 30 | w(f) 31 | } 32 | return data.Null 33 | }) 34 | } 35 | 36 | func bindCloser(c io.Closer) data.Procedure { 37 | return data.MakeProcedure(func(args ...ale.Value) ale.Value { 38 | _ = c.Close() 39 | return data.Null 40 | }, 0) 41 | } 42 | -------------------------------------------------------------------------------- /core/bootstrap/assets.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/kode4food/ale/core/source" 8 | "github.com/kode4food/ale/data" 9 | "github.com/kode4food/ale/eval" 10 | "github.com/kode4food/ale/read" 11 | ) 12 | 13 | const InitFile = "bootstrap.ale" 14 | 15 | func (b *bootstrap) populateAssets() { 16 | defer func() { 17 | if rec := recover(); rec != nil { 18 | _, _ = fmt.Fprint(os.Stderr, "\nBootstrap Error\n\n") 19 | if ev, ok := rec.(error); ok { 20 | msg := ev.Error() 21 | _, _ = fmt.Fprintf(os.Stderr, "%s\n\n", msg) 22 | } else { 23 | _, _ = fmt.Fprintf(os.Stderr, "%s\n\n", rec) 24 | } 25 | os.Exit(-1) 26 | } 27 | }() 28 | 29 | ns := b.environment.GetRoot() 30 | if err := BindFileSystem(ns, source.Assets); err != nil { 31 | panic(err) 32 | } 33 | 34 | src, err := source.Assets.ReadFile(InitFile) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | seq := read.MustFromString(ns, data.String(src)) 40 | if _, err := eval.Block(ns, seq); err != nil { 41 | panic(err) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /internal/basics/maps.go: -------------------------------------------------------------------------------- 1 | package basics 2 | 3 | import ( 4 | "cmp" 5 | "slices" 6 | ) 7 | 8 | // MapKeys returns the keys of the provided map as a slice 9 | func MapKeys[K comparable, V any](m map[K]V) []K { 10 | s := make([]K, len(m)) 11 | var i int 12 | for k := range m { 13 | s[i] = k 14 | i++ 15 | } 16 | return s 17 | } 18 | 19 | // MapValues returns the values of the provided map as a slice 20 | func MapValues[K comparable, V any](m map[K]V) []V { 21 | s := make([]V, len(m)) 22 | var i int 23 | for _, v := range m { 24 | s[i] = v 25 | i++ 26 | } 27 | return s 28 | } 29 | 30 | // SortedKeys returns the keys of the provided map as a sorted slice 31 | func SortedKeys[K cmp.Ordered, V any](m map[K]V) []K { 32 | s := MapKeys(m) 33 | slices.Sort(s) 34 | return s 35 | } 36 | 37 | // SortedKeysFunc returns the keys of the provided map as a sorted slice using 38 | // the provided comparison function 39 | func SortedKeysFunc[K comparable, V any](m map[K]V, cmp func(l, r K) int) []K { 40 | s := MapKeys(m) 41 | slices.SortFunc(s, cmp) 42 | return s 43 | } 44 | -------------------------------------------------------------------------------- /core/bootstrap/special.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "github.com/kode4food/ale/core/special" 5 | "github.com/kode4food/ale/data" 6 | "github.com/kode4food/ale/internal/compiler" 7 | "github.com/kode4food/ale/internal/lang/env" 8 | ) 9 | 10 | func (b *bootstrap) populateSpecialForms() { 11 | b.specials(map[data.Local]compiler.Call{ 12 | env.Asm: special.Asm, 13 | env.Eval: special.Eval, 14 | env.Import: special.Import, 15 | env.Lambda: special.Lambda, 16 | env.Let: special.Let, 17 | env.LetMutual: special.LetMutual, 18 | env.MacroExpand1: special.MacroExpand1, 19 | env.MacroExpand: special.MacroExpand, 20 | env.Special: special.Special, 21 | 22 | env.Declared: special.Declared, 23 | env.MakeNamespace: special.MakeNamespace, 24 | }) 25 | } 26 | 27 | func (b *bootstrap) specials(s map[data.Local]compiler.Call) { 28 | for k, v := range s { 29 | b.special(k, v) 30 | } 31 | } 32 | 33 | func (b *bootstrap) special(name data.Local, call compiler.Call) { 34 | b.specialMap[name] = call 35 | } 36 | -------------------------------------------------------------------------------- /internal/lang/parse/parse.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | "github.com/kode4food/ale/env" 7 | "github.com/kode4food/ale/internal/lang/lex" 8 | "github.com/kode4food/ale/internal/sequence" 9 | ) 10 | 11 | type Tokenizer func(data.String) (data.Sequence, error) 12 | 13 | // FromString returns a Lazy Sequence of scanned data structures 14 | func FromString( 15 | ns env.Namespace, tokenize Tokenizer, str data.String, 16 | ) (data.Sequence, error) { 17 | lexer, err := tokenize(str) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | var res sequence.LazyResolver 23 | p := &parser{ 24 | ns: ns, 25 | tokenize: tokenize, 26 | seq: lex.StripWhitespace(lexer), 27 | } 28 | 29 | res = func() (ale.Value, data.Sequence, bool) { 30 | f, ok, err := p.nextValue() 31 | if err != nil { 32 | panic(err) 33 | } 34 | if ok { 35 | return f, sequence.NewLazy(res), true 36 | } 37 | return data.Null, data.Null, false 38 | } 39 | 40 | return sequence.NewLazy(res), nil 41 | } 42 | -------------------------------------------------------------------------------- /internal/sequence/last_test.go: -------------------------------------------------------------------------------- 1 | package sequence_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/data" 8 | "github.com/kode4food/ale/internal/assert" 9 | . "github.com/kode4food/ale/internal/assert/helpers" 10 | "github.com/kode4food/ale/internal/sequence" 11 | ) 12 | 13 | func TestLast(t *testing.T) { 14 | as := assert.New(t) 15 | 16 | v, ok := sequence.Last(data.Null) 17 | as.Nil(v) 18 | as.False(ok) 19 | 20 | v, ok = sequence.Last(L(S("this"), S("is"), S("last"))) 21 | as.String("last", v) 22 | as.True(ok) 23 | 24 | v, ok = sequence.Last(V(S("this"), S("is"), S("last"))) 25 | as.String("last", v) 26 | as.True(ok) 27 | 28 | v, ok = sequence.Last(sequence.NewLazy( 29 | func() (ale.Value, data.Sequence, bool) { 30 | return S("hello"), data.Null, true 31 | }, 32 | )) 33 | as.String("hello", v) 34 | as.True(ok) 35 | 36 | _, ok = sequence.Last(sequence.NewLazy( 37 | func() (ale.Value, data.Sequence, bool) { 38 | return data.Null, data.Null, false 39 | }, 40 | )) 41 | as.False(ok) 42 | } 43 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/thread-cond-last.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "conditional thread last (cond->>)" 3 | description: "->> macro with conditional forms" 4 | names: ["cond->>"] 5 | usage: "(cond->> expr [test form]*)" 6 | tags: ["function"] 7 | --- 8 | 9 | Like `->>`, but each form is paired with a test condition. The form is only applied if the test evaluates to true. Each test is evaluated with the current threaded value in scope. 10 | 11 | #### An Example 12 | 13 | ```scheme 14 | (cond->> [1 2 3 4 5] 15 | [seq? (map (lambda (x) (\* x 2)))] ; doubles: [2 4 6 8 10] 16 | [!empty? (filter even?)] ; keeps evens: [2 4 6 8 10] 17 | [(> 2) (take 3)] ; takes first 3: [2 4 6] 18 | [false (concat [0])]) ; skipped 19 | ``` 20 | 21 | #### Another Example 22 | 23 | ```scheme 24 | (cond->> [1 2 3] 25 | [seq? (filter even?)] ; predicate true, filters to [2] 26 | [empty? (concat [4 5])] ; predicate false, skips concat 27 | [true (take 1)]) ; takes first element 28 | ``` 29 | -------------------------------------------------------------------------------- /core/builtin/os.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | import ( 4 | "os" 5 | "regexp" 6 | "time" 7 | 8 | "github.com/kode4food/ale" 9 | "github.com/kode4food/ale/data" 10 | "github.com/kode4food/ale/internal/basics" 11 | ) 12 | 13 | var envPairRegex = regexp.MustCompile("^(?P[^=]+)=(?P.*)$") 14 | 15 | // Env returns an object containing the operating system's environment 16 | func Env() ale.Value { 17 | var p data.Pairs 18 | for _, v := range os.Environ() { 19 | if e := envPairRegex.FindStringSubmatch(v); len(e) == 3 { 20 | p = append(p, data.NewCons(data.Keyword(e[1]), data.String(e[2]))) 21 | } 22 | } 23 | return data.NewObject(p...) 24 | } 25 | 26 | // Args returns a vector containing the args passed to this program 27 | func Args() ale.Value { 28 | return data.Vector(basics.Map(os.Args, func(v string) ale.Value { 29 | return data.String(v) 30 | })) 31 | } 32 | 33 | // CurrentTime returns the current system time in nanoseconds 34 | var CurrentTime = data.MakeProcedure(func(...ale.Value) ale.Value { 35 | return data.Integer(time.Now().UnixNano()) 36 | }, 0) 37 | -------------------------------------------------------------------------------- /read/internal/read_test.go: -------------------------------------------------------------------------------- 1 | package internal_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | . "github.com/kode4food/ale/internal/assert/helpers" 8 | "github.com/kode4food/ale/internal/lang/lex" 9 | "github.com/kode4food/ale/read/internal" 10 | ) 11 | 12 | var ( 13 | Tokenize = internal.MakeTokenizer(lex.ExhaustiveMatcher( 14 | lex.Ignorable, 15 | lex.Structure, 16 | lex.Quoting, 17 | lex.Values, 18 | lex.Symbols, 19 | )) 20 | 21 | MustTokenize = internal.MakeMustTokenizer(Tokenize) 22 | FromString = internal.MakeFromString(Tokenize) 23 | MustFromString = internal.MakeMustFromString(FromString) 24 | ) 25 | 26 | func TestFromString(t *testing.T) { 27 | as := assert.New(t) 28 | ns := assert.GetTestNamespace() 29 | tr := MustFromString(ns, "99") 30 | if as.NotNil(tr) { 31 | as.Equal(I(99), tr.Car()) 32 | } 33 | } 34 | 35 | func TestTokenize(t *testing.T) { 36 | as := assert.New(t) 37 | tr := MustTokenize("99") 38 | if as.NotNil(tr) { 39 | as.Equal(lex.Number.FromValue("99", I(99)), tr.Car()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/builtin/macros.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/kode4food/ale" 8 | "github.com/kode4food/ale/data" 9 | "github.com/kode4food/ale/env" 10 | "github.com/kode4food/ale/internal/compiler" 11 | "github.com/kode4food/ale/macro" 12 | ) 13 | 14 | // ErrProcedureRequired is raised when a call to the Macro built-in doesn't 15 | // receive a data.Procedure to wrap 16 | var ErrProcedureRequired = errors.New("argument must be a procedure") 17 | 18 | // Macro converts a function into a macro 19 | var Macro = data.MakeProcedure(func(args ...ale.Value) ale.Value { 20 | switch body := args[0].(type) { 21 | case data.Procedure: 22 | wrapper := func(_ env.Namespace, args ...ale.Value) ale.Value { 23 | if err := body.CheckArity(len(args)); err != nil { 24 | panic(err) 25 | } 26 | return body.Call(args...) 27 | } 28 | return macro.Call(wrapper) 29 | default: 30 | panic(fmt.Errorf("%w: %s", ErrProcedureRequired, args[0])) 31 | } 32 | }, 1) 33 | 34 | func isAtom(v ale.Value) bool { 35 | return !compiler.IsEvaluable(v) 36 | } 37 | -------------------------------------------------------------------------------- /internal/stream/writer_test.go: -------------------------------------------------------------------------------- 1 | package stream_test 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/kode4food/ale/data" 9 | "github.com/kode4food/ale/internal/assert" 10 | . "github.com/kode4food/ale/internal/assert/helpers" 11 | "github.com/kode4food/ale/internal/stream" 12 | ) 13 | 14 | type mockWriterCloser struct { 15 | io.Writer 16 | closed bool 17 | } 18 | 19 | func (m *mockWriterCloser) Close() error { 20 | m.closed = true 21 | return nil 22 | } 23 | 24 | func TestWriter(t *testing.T) { 25 | as := assert.New(t) 26 | 27 | var buf strings.Builder 28 | c := &mockWriterCloser{ 29 | Writer: &buf, 30 | } 31 | 32 | w := stream.NewWriter(c, stream.StrOutput) 33 | 34 | var write data.Procedure 35 | v, _ := w.Get(stream.WriteKey) 36 | write = v.(data.Procedure) 37 | 38 | var cl data.Procedure 39 | v, _ = w.Get(stream.CloseKey) 40 | cl = v.(data.Procedure) 41 | 42 | write.Call(S("hello")) 43 | write.Call(V(S("there"), S("you"))) 44 | cl.Call() 45 | 46 | as.String(`hello["there" "you"]`, buf.String()) 47 | as.True(c.closed) 48 | } 49 | -------------------------------------------------------------------------------- /ffi/box.go: -------------------------------------------------------------------------------- 1 | package ffi 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "unsafe" 7 | 8 | "github.com/kode4food/ale" 9 | ) 10 | 11 | type ( 12 | // Boxed is a wrapper for reflected values that are not addressable in Go, 13 | // such as unsafe pointers or uintptrs 14 | Boxed[T boxedTypes] struct { 15 | Value reflect.Value 16 | } 17 | 18 | boxedWrapper[T boxedTypes] struct{} 19 | 20 | boxedTypes interface { 21 | ~uintptr | unsafe.Pointer 22 | } 23 | ) 24 | 25 | const ( 26 | ErrValueMustBeBoxed = "value must be a boxed value" 27 | ) 28 | 29 | func (b boxedWrapper[T]) Wrap(_ *Context, v reflect.Value) (ale.Value, error) { 30 | return &Boxed[T]{Value: v}, nil 31 | } 32 | 33 | func (b boxedWrapper[T]) Unwrap(v ale.Value) (reflect.Value, error) { 34 | if box, ok := v.(*Boxed[T]); ok { 35 | return box.Value, nil 36 | } 37 | return _zero, errors.New(ErrValueMustBeBoxed) 38 | } 39 | 40 | func (b *Boxed[T]) Equal(other ale.Value) bool { 41 | if o, ok := other.(*Boxed[T]); ok { 42 | return b == o || b.Value == o.Value || b.Value.Equal(o.Value) 43 | } 44 | return false 45 | } 46 | -------------------------------------------------------------------------------- /internal/types/literal.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kode4food/ale" 7 | ) 8 | 9 | type Literal struct { 10 | Basic 11 | value ale.Value 12 | } 13 | 14 | func MakeLiteral(b Basic, v ale.Value) *Literal { 15 | return &Literal{ 16 | Basic: b, 17 | value: v, 18 | } 19 | } 20 | 21 | func (l *Literal) Type() ale.Type { 22 | return l.Basic 23 | } 24 | 25 | func (l *Literal) Value() ale.Value { 26 | return l.value 27 | } 28 | 29 | func (l *Literal) Name() string { 30 | if v, ok := l.value.(fmt.Stringer); ok { 31 | return fmt.Sprintf("%s(%s)", l.Basic.Name(), v) 32 | } 33 | return fmt.Sprintf("%s(%p)", l.Basic.Name(), l.value) 34 | } 35 | 36 | func (l *Literal) Accepts(other ale.Type) bool { 37 | if other, ok := other.(*Literal); ok { 38 | return l.Basic.Accepts(other.Basic) && l.value.Equal(other.value) 39 | } 40 | return false 41 | } 42 | 43 | func (l *Literal) Equal(other ale.Type) bool { 44 | if other, ok := other.(*Literal); ok { 45 | return l.Basic.Equal(other.Basic) && l.value.Equal(other.value) 46 | } 47 | return false 48 | } 49 | -------------------------------------------------------------------------------- /internal/compiler/ir/optimize/returns_test.go: -------------------------------------------------------------------------------- 1 | package optimize_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | "github.com/kode4food/ale/internal/compiler/encoder" 8 | "github.com/kode4food/ale/internal/compiler/generate" 9 | "github.com/kode4food/ale/internal/compiler/ir/optimize" 10 | "github.com/kode4food/ale/internal/runtime/isa" 11 | ) 12 | 13 | func TestSplitReturns(t *testing.T) { 14 | as := assert.New(t) 15 | 16 | e1 := assert.GetTestEncoder() 17 | as.NoError(generate.Branch(e1, 18 | func(encoder.Encoder) error { e1.Emit(isa.True); return nil }, 19 | func(encoder.Encoder) error { e1.Emit(isa.PosInt, 1); return nil }, 20 | func(encoder.Encoder) error { e1.Emit(isa.Zero); return nil }, 21 | )) 22 | e1.Emit(isa.Return) 23 | 24 | as.Instructions(isa.Instructions{ 25 | isa.True.New(), 26 | isa.CondJump.New(0), 27 | isa.Zero.New(), 28 | isa.Return.New(), 29 | isa.Jump.New(1), 30 | isa.Label.New(0), 31 | isa.PosInt.New(1), 32 | isa.Return.New(), 33 | isa.Label.New(1), 34 | }, optimize.Encoded(e1.Encode()).Code) 35 | } 36 | -------------------------------------------------------------------------------- /internal/sequence/concat_test.go: -------------------------------------------------------------------------------- 1 | package sequence_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | "github.com/kode4food/ale/internal/sequence" 10 | ) 11 | 12 | func TestConcat(t *testing.T) { 13 | as := assert.New(t) 14 | 15 | c0 := sequence.Concat() 16 | as.Equal(data.Null, c0) 17 | 18 | l1 := L(I(1), I(2), I(3)) 19 | l2 := L(I(4), I(5), I(6)) 20 | l3 := L(I(7), I(8), I(9)) 21 | 22 | c1 := sequence.Concat(l1) 23 | as.True(c1 == l1) 24 | as.Equal( 25 | V(I(1), I(2), I(3)), 26 | sequence.ToVector(c1), 27 | ) 28 | 29 | c2 := sequence.Concat(l1, l2, l3) 30 | as.Equal( 31 | V(I(1), I(2), I(3), I(4), I(5), I(6), I(7), I(8), I(9)), 32 | sequence.ToVector(c2), 33 | ) 34 | 35 | c3 := sequence.Concat(l1, l2) 36 | _, r, _ := c3.Split() 37 | _, r, _ = r.Split() 38 | _, r, _ = r.Split() 39 | _, r, _ = r.Split() 40 | as.Equal(l2.Cdr(), r) 41 | 42 | as.Equal( 43 | V(I(1), I(2), I(3), I(4), I(5), I(6)), 44 | sequence.ToVector(c3), 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /core/builtin/concurrency.go: -------------------------------------------------------------------------------- 1 | package builtin 2 | 3 | import ( 4 | "slices" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/data" 8 | "github.com/kode4food/ale/internal/runtime" 9 | "github.com/kode4food/ale/internal/stream" 10 | "github.com/kode4food/ale/internal/sync" 11 | ) 12 | 13 | // Go runs the provided function asynchronously 14 | var Go = data.MakeProcedure(func(args ...ale.Value) ale.Value { 15 | fn := args[0].(data.Procedure) 16 | callArgs := slices.Clone(args[1:]) 17 | go func() { 18 | defer runtime.NormalizeGoRuntimeErrors() 19 | fn.Call(callArgs...) 20 | }() 21 | return data.Null 22 | }, 1, data.OrMore) 23 | 24 | // Chan instantiates a new go channel 25 | var Chan = data.MakeProcedure(func(args ...ale.Value) ale.Value { 26 | var size int 27 | if len(args) != 0 { 28 | size = int(args[0].(data.Integer)) 29 | } 30 | return stream.NewChannel(size) 31 | }, 0, 1) 32 | 33 | // isResolved returns whether the specified promise has been resolved 34 | func isResolved(v ale.Value) bool { 35 | if p, ok := v.(*sync.Promise); ok { 36 | return p.IsResolved() 37 | } 38 | return true 39 | } 40 | -------------------------------------------------------------------------------- /internal/compiler/generate/sequence_test.go: -------------------------------------------------------------------------------- 1 | package generate_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | . "github.com/kode4food/ale/internal/assert/helpers" 8 | "github.com/kode4food/ale/internal/compiler/generate" 9 | "github.com/kode4food/ale/internal/runtime/isa" 10 | ) 11 | 12 | func TestBlock(t *testing.T) { 13 | as := assert.New(t) 14 | 15 | e1 := assert.GetTestEncoder() 16 | as.NoError(generate.Block(e1, V())) 17 | e1.Emit(isa.Return) 18 | 19 | as.Instructions(isa.Instructions{ 20 | isa.Null.New(), 21 | isa.Return.New(), 22 | }, e1.Encode().Code) 23 | 24 | e2 := assert.GetTestEncoder() 25 | as.NoError(generate.Block(e2, V( 26 | L(LS("+"), I(1), I(2)), 27 | B(true), 28 | ))) 29 | e2.Emit(isa.Return) 30 | 31 | enc2 := e2.Encode() 32 | as.Instructions(isa.Instructions{ 33 | isa.PosInt.New(2), 34 | isa.PosInt.New(1), 35 | isa.Const.New(0), 36 | isa.Call.New(2), 37 | isa.Pop.New(), 38 | isa.True.New(), 39 | isa.Return.New(), 40 | }, enc2.Code) 41 | 42 | c := enc2.Constants 43 | as.Equal(assert.GetRootSymbol(e2, "+"), c[0]) 44 | } 45 | -------------------------------------------------------------------------------- /internal/types/compound.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "github.com/kode4food/ale" 4 | 5 | type ( 6 | checker struct { 7 | parent *checker 8 | receiver ale.Type 9 | } 10 | 11 | // compound is an internal interface for types that need cycle checking 12 | compound interface { 13 | accepts(*checker, ale.Type) bool 14 | } 15 | ) 16 | 17 | func compoundAccepts(left, right ale.Type) bool { 18 | c := &checker{ 19 | receiver: left, 20 | } 21 | return c.accepts(right) 22 | } 23 | 24 | func (c *checker) accepts(right ale.Type) bool { 25 | if r, ok := c.receiver.(compound); ok { 26 | return r.accepts(c, right) 27 | } 28 | return c.receiver.Accepts(right) 29 | } 30 | 31 | func (c *checker) willCycleOn(t ale.Type) bool { 32 | if c.receiver == t { 33 | return true 34 | } 35 | if c.parent == nil { 36 | return false 37 | } 38 | return c.parent.willCycleOn(t) 39 | } 40 | 41 | func (c *checker) acceptsChild(left, right ale.Type) bool { 42 | if c.willCycleOn(left) { 43 | return true 44 | } 45 | child := &checker{ 46 | parent: c, 47 | receiver: left, 48 | } 49 | return child.accepts(right) 50 | } 51 | -------------------------------------------------------------------------------- /cmd/ale/internal/repl_test.go: -------------------------------------------------------------------------------- 1 | package internal_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/cmd/ale/internal" 8 | "github.com/kode4food/ale/internal/assert" 9 | . "github.com/kode4food/ale/internal/assert/helpers" 10 | "github.com/kode4food/ale/internal/compiler" 11 | "github.com/kode4food/ale/internal/compiler/encoder" 12 | ) 13 | 14 | func TestREPL(t *testing.T) { 15 | as := assert.New(t) 16 | 17 | r := internal.NewREPL() 18 | as.NotNil(r) 19 | } 20 | 21 | func asEncoder(t *testing.T, v ale.Value) compiler.Call { 22 | t.Helper() 23 | if f, ok := v.(compiler.Call); ok { 24 | return f 25 | } 26 | as := assert.New(t) 27 | as.Fail("value is not an encoder") 28 | return nil 29 | } 30 | 31 | func TestBuiltInUse(t *testing.T) { 32 | as := assert.New(t) 33 | 34 | repl := internal.NewREPL() 35 | ns1 := repl.GetNS() 36 | v := as.IsBound(ns1, "use") 37 | use := asEncoder(t, v) 38 | nsName := LS("test-ns") 39 | as.Nil(use(encoder.NewEncoder(ns1), nsName)) 40 | 41 | ns2 := repl.GetNS() 42 | if as.NotNil(ns2) { 43 | as.String("test-ns", ns2.Domain()) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/special/lambda_test.go: -------------------------------------------------------------------------------- 1 | package special_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/kode4food/ale/internal/assert" 9 | . "github.com/kode4food/ale/internal/assert/helpers" 10 | "github.com/kode4food/ale/internal/lang/params" 11 | ) 12 | 13 | func TestLambda(t *testing.T) { 14 | as := assert.New(t) 15 | 16 | as.MustEvalTo(`((lambda [(x y) (+ x y)] [(x) (* x 2)]) 20 30)`, I(50)) 17 | as.MustEvalTo(`((lambda [(x y) (+ x y)] [(x) (* x 2)]) 20)`, I(40)) 18 | 19 | as.MustEvalTo( 20 | `((lambda [(x y) (+ x y)] [(x) (* x 2)] [x x]) 20 30 40)`, 21 | V(I(20), I(30), I(40)), 22 | ) 23 | 24 | as.MustEvalTo(`((lambda x x) 1 2 3)`, V(I(1), I(2), I(3))) 25 | as.MustEvalTo(`((lambda (x) x) 1)`, I(1)) 26 | } 27 | 28 | func TestLambdaErrors(t *testing.T) { 29 | as := assert.New(t) 30 | 31 | as.ErrorWith(`(lambda :kwd '())`, 32 | fmt.Errorf(params.ErrUnexpectedCaseSyntax, ":kwd"), 33 | ) 34 | 35 | as.ErrorWith(`(lambda [:kwd] '())`, 36 | fmt.Errorf(params.ErrUnexpectedParamSyntax, ":kwd"), 37 | ) 38 | 39 | as.ErrorWith(`(lambda)`, 40 | errors.New(params.ErrNoCasesDefined), 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2017-2025 Thomas S. Bradford 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /core/special/branching_test.go: -------------------------------------------------------------------------------- 1 | package special_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/data" 8 | "github.com/kode4food/ale/internal/assert" 9 | . "github.com/kode4food/ale/internal/assert/helpers" 10 | ) 11 | 12 | func TestCondEval(t *testing.T) { 13 | as := assert.New(t) 14 | as.MustEvalTo(`(cond)`, data.Null) 15 | 16 | as.MustEvalTo(` 17 | (cond 18 | [false "goodbye"] 19 | [true "hello"] 20 | ["hi" "ignored"]) 21 | `, S("hello")) 22 | 23 | as.MustEvalTo(` 24 | (cond 25 | [false "goodbye"] 26 | [:else "hello"] 27 | ["hi" "ignored"]) 28 | `, S("hello")) 29 | 30 | as.MustEvalTo(` 31 | (cond 32 | [false "goodbye"] 33 | ['() "hello"]) 34 | `, S("hello")) 35 | } 36 | 37 | func TestBadCond(t *testing.T) { 38 | as := assert.New(t) 39 | 40 | as.PanicWith(` 41 | (cond 42 | [true "hello"] 43 | [99]) 44 | `, errors.New("invalid cond clause: [99]")) 45 | 46 | as.PanicWith(` 47 | (cond 99) 48 | `, errors.New("invalid cond clause: 99")) 49 | 50 | as.PanicWith(` 51 | (cond 52 | false "hello" 53 | 99) 54 | `, errors.New("invalid cond clause: false")) 55 | } 56 | -------------------------------------------------------------------------------- /internal/compiler/encoder/label_test.go: -------------------------------------------------------------------------------- 1 | package encoder_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/internal/assert" 8 | "github.com/kode4food/ale/internal/compiler/ir/analysis" 9 | "github.com/kode4food/ale/internal/runtime/isa" 10 | ) 11 | 12 | func TestLabels(t *testing.T) { 13 | as := assert.New(t) 14 | 15 | e := assert.GetTestEncoder() 16 | l1 := e.NewLabel() 17 | l2 := e.NewLabel() 18 | e.Emit(isa.Jump, l2) 19 | e.Emit(isa.NoOp) 20 | e.Emit(isa.Jump, l1) 21 | e.Emit(isa.Label, l2) 22 | e.Emit(isa.NoOp) 23 | e.Emit(isa.Label, l1) 24 | 25 | as.Instructions(isa.Instructions{ 26 | isa.Jump.New(1), 27 | isa.NoOp.New(), 28 | isa.Jump.New(0), 29 | isa.Label.New(1), 30 | isa.NoOp.New(), 31 | isa.Label.New(0), 32 | }, e.Encode().Code) 33 | } 34 | 35 | func TestLabelDoubleAnchor(t *testing.T) { 36 | as := assert.New(t) 37 | 38 | e := assert.GetTestEncoder() 39 | l1 := e.NewLabel() 40 | e.Emit(isa.Label, l1) 41 | e.Emit(isa.Label, l1) 42 | e.Emit(isa.Jump, l1) 43 | 44 | err := analysis.Verify(e.Encode().Code) 45 | as.EqualError(err, fmt.Sprintf(analysis.ErrLabelMultipleAnchors, 0)) 46 | } 47 | -------------------------------------------------------------------------------- /internal/compiler/encoder/params_test.go: -------------------------------------------------------------------------------- 1 | package encoder_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | . "github.com/kode4food/ale/internal/assert/helpers" 9 | "github.com/kode4food/ale/internal/compiler/encoder" 10 | ) 11 | 12 | func TestArgs(t *testing.T) { 13 | as := assert.New(t) 14 | 15 | e := assert.GetTestEncoder() 16 | e.PushParams(data.Locals{"param0"}, false) 17 | e.PushParams(data.Locals{"param1", "param2", "param3"}, true) 18 | 19 | c, ok := e.ResolveParam("param2") 20 | as.True(ok) 21 | as.Equal(LS("param2"), c.Name) 22 | as.Equal(encoder.ValueCell, c.Type) 23 | 24 | c, ok = e.ResolveParam("param3") 25 | as.True(ok) 26 | as.Equal(LS("param3"), c.Name) 27 | as.Equal(encoder.RestCell, c.Type) 28 | 29 | c, ok = e.ResolveParam("param0") 30 | as.True(ok) 31 | as.Equal(LS("param0"), c.Name) 32 | as.Equal(encoder.ValueCell, c.Type) 33 | 34 | e.PopParams() 35 | _, ok = e.ResolveParam("param2") 36 | as.False(ok) 37 | _, ok = e.ResolveParam("param0") 38 | as.True(ok) 39 | 40 | e.PopParams() 41 | _, ok = e.ResolveParam("param0") 42 | as.False(ok) 43 | } 44 | -------------------------------------------------------------------------------- /internal/sync/promise_test.go: -------------------------------------------------------------------------------- 1 | package sync_test 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/kode4food/ale" 8 | "github.com/kode4food/ale/data" 9 | "github.com/kode4food/ale/internal/assert" 10 | . "github.com/kode4food/ale/internal/assert/helpers" 11 | "github.com/kode4food/ale/internal/sync" 12 | ) 13 | 14 | func TestPromiseCaller(t *testing.T) { 15 | as := assert.New(t) 16 | p1 := sync.NewPromise(data.MakeProcedure(func(...ale.Value) ale.Value { 17 | return S("hello") 18 | }, 0)) 19 | as.String("hello", p1.Call()) 20 | } 21 | 22 | func TestPromiseFailure(t *testing.T) { 23 | as := assert.New(t) 24 | p1 := sync.NewPromise(data.MakeProcedure(func(...ale.Value) ale.Value { 25 | panic(errors.New("explosion")) 26 | }, 0)) 27 | as.Panics(func() { _ = p1.Call() }, errors.New("explosion")) 28 | } 29 | 30 | func TestPromiseEval(t *testing.T) { 31 | as := assert.New(t) 32 | 33 | as.MustEvalTo(` 34 | (define p (delay "hello")) 35 | (let* ([p? (promise? p)] 36 | [r1? (resolved? p)] 37 | [f (p)] 38 | [r2? (resolved? p)]) 39 | [p? r1? f r2?]) 40 | `, V(data.True, data.False, S("hello"), data.True)) 41 | } 42 | -------------------------------------------------------------------------------- /ffi/wrappers.go: -------------------------------------------------------------------------------- 1 | package ffi 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/kode4food/ale/data" 7 | ) 8 | 9 | // Wrappers is a set of Wrapper 10 | type Wrappers []Wrapper 11 | 12 | func (w Wrappers) mustUnwrap(args data.Vector) []reflect.Value { 13 | res, err := w.unwrap(args) 14 | if err != nil { 15 | panic(err) 16 | } 17 | return res 18 | } 19 | 20 | func (w Wrappers) unwrap(args data.Vector) ([]reflect.Value, error) { 21 | unwrapped := make([]reflect.Value, len(w)) 22 | for i, wrapped := range w { 23 | arg, err := wrapped.Unwrap(args[i]) 24 | if err != nil { 25 | return nil, err 26 | } 27 | unwrapped[i] = arg 28 | } 29 | return unwrapped, nil 30 | } 31 | 32 | func (w Wrappers) mustWrap(args []reflect.Value) data.Vector { 33 | res, err := w.wrap(args) 34 | if err != nil { 35 | panic(err) 36 | } 37 | return res 38 | } 39 | 40 | func (w Wrappers) wrap(args []reflect.Value) (data.Vector, error) { 41 | wc := new(Context) 42 | in := make(data.Vector, len(args)) 43 | for i, arg := range args { 44 | arg, err := w[i].Wrap(wc, arg) 45 | if err != nil { 46 | return nil, err 47 | } 48 | in[i] = arg 49 | } 50 | return in, nil 51 | } 52 | -------------------------------------------------------------------------------- /internal/assert/eval.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/data" 8 | "github.com/kode4food/ale/eval" 9 | ) 10 | 11 | // MustEval will evaluate source code on behalf of the test framework 12 | func (w *Wrapper) MustEval(src string) ale.Value { 13 | w.Helper() 14 | res, err := w.Eval(src) 15 | if err != nil { 16 | panic(err) 17 | } 18 | return res 19 | } 20 | 21 | func (w *Wrapper) Eval(src string) (ale.Value, error) { 22 | w.Helper() 23 | ns := GetTestNamespace() 24 | return eval.String(ns, data.String(src)) 25 | } 26 | 27 | // MustEvalTo will evaluate source code and test for an expected result 28 | func (w *Wrapper) MustEvalTo(src string, expect ale.Value) { 29 | w.Helper() 30 | res := w.MustEval(src) 31 | fmt.Println(res) 32 | w.Equal(expect, res) 33 | } 34 | 35 | func (w *Wrapper) ErrorWith(src string, err any) { 36 | w.Helper() 37 | _, res := w.Eval(src) 38 | w.ExpectError(err, res) 39 | } 40 | 41 | // PanicWith evaluates source code and expects a panic to happen 42 | func (w *Wrapper) PanicWith(src string, err any) { 43 | w.Helper() 44 | w.Panics(func() { _ = w.MustEval(src) }, err) 45 | } 46 | -------------------------------------------------------------------------------- /internal/types/sequence_test.go: -------------------------------------------------------------------------------- 1 | package types_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/types" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestList(t *testing.T) { 11 | as := assert.New(t) 12 | 13 | v1 := types.MakeListOf(types.BasicNumber) 14 | v2 := types.MakeListOf(types.BasicString) 15 | v3 := types.MakeListOf(types.BasicNumber) 16 | 17 | as.Equal("list(number)", v1.Name()) 18 | 19 | as.True(v1.Accepts(v1)) 20 | as.False(v1.Accepts(v2)) 21 | as.True(v3.Accepts(v1)) 22 | 23 | as.False(types.BasicVector.Accepts(v1)) 24 | as.True(types.BasicList.Accepts(v1)) 25 | as.False(v1.Accepts(types.BasicList)) 26 | } 27 | 28 | func TestVector(t *testing.T) { 29 | as := assert.New(t) 30 | 31 | v1 := types.MakeVectorOf(types.BasicNumber) 32 | v2 := types.MakeVectorOf(types.BasicString) 33 | v3 := types.MakeVectorOf(types.BasicNumber) 34 | 35 | as.Equal("vector(number)", v1.Name()) 36 | 37 | as.True(v1.Accepts(v1)) 38 | as.False(v1.Accepts(v2)) 39 | as.True(v3.Accepts(v1)) 40 | 41 | as.False(types.BasicList.Accepts(v1)) 42 | as.True(types.BasicVector.Accepts(v1)) 43 | as.False(v1.Accepts(types.BasicVector)) 44 | } 45 | -------------------------------------------------------------------------------- /macro/macro_test.go: -------------------------------------------------------------------------------- 1 | package macro_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | "github.com/kode4food/ale/macro" 8 | "github.com/kode4food/ale/read" 9 | ) 10 | 11 | func TestMacroCall(t *testing.T) { 12 | as := assert.New(t) 13 | 14 | d, ok := as.MustEval(`define`).(macro.Call) 15 | as.True(ok) 16 | if as.NotNil(d) { 17 | as.False(d.Type().Accepts(macro.CallType)) 18 | as.True(macro.CallType.Accepts(d.Type())) 19 | as.False(d.Equal(d)) 20 | as.Contains(":type macro", d) 21 | } 22 | } 23 | 24 | func TestExpand(t *testing.T) { 25 | as := assert.New(t) 26 | 27 | ns := assert.GetTestNamespace() 28 | as.MustEvalTo( 29 | `(macroexpand '(define (name . _) "hello"))`, 30 | read.MustFromString(ns, 31 | `(ale/%define name (ale/label name (ale/lambda _ "hello")))`, 32 | ).Car(), 33 | ) 34 | } 35 | 36 | func TestExpand1(t *testing.T) { 37 | as := assert.New(t) 38 | 39 | ns := assert.GetTestNamespace() 40 | as.MustEvalTo( 41 | `(macroexpand-1 '(define (name . _) (or false true)))`, 42 | read.MustFromString(ns, 43 | `(ale/%define name (ale/label name (ale/lambda _ (or false true))))`, 44 | ).Car(), 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /macro/macro.go: -------------------------------------------------------------------------------- 1 | package macro 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | "github.com/kode4food/ale/env" 7 | "github.com/kode4food/ale/internal/sequence" 8 | ) 9 | 10 | // Expand performs a complete macro expansion 11 | func Expand(ns env.Namespace, v ale.Value) (ale.Value, error) { 12 | if res, ok := expand1(ns, v); ok { 13 | return Expand(ns, res) 14 | } 15 | return v, nil 16 | } 17 | 18 | // Expand1 performs a single macro expansion 19 | func Expand1(ns env.Namespace, v ale.Value) (ale.Value, error) { 20 | res, _ := expand1(ns, v) 21 | return res, nil 22 | } 23 | 24 | func expand1(ns env.Namespace, v ale.Value) (ale.Value, bool) { 25 | l, ok := v.(*data.List) // it's got to be a list 26 | if !ok { 27 | return v, false 28 | } 29 | f, r, _ := l.Split() // starting with a symbol 30 | s, ok := f.(data.Symbol) 31 | if !ok { 32 | return v, false 33 | } 34 | rv, err := env.ResolveValue(ns, s) // that actually resolves 35 | if err != nil { 36 | return v, false 37 | } 38 | m, ok := rv.(Call) // to a macro call 39 | if !ok { 40 | return v, false 41 | } 42 | args := sequence.ToVector(r) 43 | return m(ns, args...), true 44 | } 45 | -------------------------------------------------------------------------------- /internal/compiler/encoder/params.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import ( 4 | "github.com/kode4food/ale/data" 5 | "github.com/kode4food/ale/internal/basics" 6 | "github.com/kode4food/ale/internal/runtime/isa" 7 | ) 8 | 9 | type paramStack []IndexedCells 10 | 11 | func (e *encoder) PushParams(names data.Locals, rest bool) { 12 | cells := basics.IndexedMap(names, func(n data.Local, i int) *IndexedCell { 13 | c := newCell(ValueCell, n) 14 | return newIndexedCell(isa.Operand(i), c) 15 | }) 16 | if rest { 17 | cells[len(cells)-1].Type = RestCell 18 | } 19 | e.params = append(e.params, cells) 20 | } 21 | 22 | func (e *encoder) PopParams() { 23 | params := e.params 24 | pl := len(params) 25 | e.params = params[0 : pl-1] 26 | } 27 | 28 | func (e *encoder) ResolveParam(n data.Local) (*IndexedCell, bool) { 29 | params := e.params 30 | for i := len(params) - 1; i >= 0; i-- { 31 | p := params[i] 32 | if c, ok := resolveParam(p, n); ok { 33 | return c, ok 34 | } 35 | } 36 | return nil, false 37 | } 38 | 39 | func resolveParam(cells IndexedCells, lookup data.Local) (*IndexedCell, bool) { 40 | return basics.Find(cells, func(c *IndexedCell) bool { 41 | return c.Name == lookup 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /internal/runtime/vm/procedure_test.go: -------------------------------------------------------------------------------- 1 | package vm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/internal/assert" 8 | "github.com/kode4food/ale/internal/runtime/vm" 9 | ) 10 | 11 | func TestProcedureHashCode(t *testing.T) { 12 | as := assert.New(t) 13 | p1 := as.MustEval(`(lambda (x) (* x 2))`).(*vm.Closure) 14 | p2 := as.MustEval(`(lambda (y) (* y 2))`).(*vm.Closure) 15 | p3 := as.MustEval(`(lambda (x) (/ x 2))`).(*vm.Closure) 16 | as.True(p1.HashCode() == p2.HashCode()) 17 | as.False(p1.HashCode() == p3.HashCode()) 18 | as.False(p3.HashCode() == p2.HashCode()) 19 | } 20 | 21 | func TestProcedureCaptured(t *testing.T) { 22 | as := assert.New(t) 23 | res := as.MustEval(` 24 | (define (make op left) (lambda (x) (op left x))) 25 | [(make + 1) (make + 1) (make + 2) (make - 1)] 26 | `).(data.Vector) 27 | 28 | as.Equal(4, len(res)) 29 | as.True(res[0].Equal(res[1])) 30 | as.True(data.HashCode(res[0]) == data.HashCode(res[1])) 31 | as.False(res[0].Equal(res[2])) 32 | as.False(data.HashCode(res[0]) == data.HashCode(res[2])) 33 | as.False(res[0].Equal(res[3])) 34 | as.False(data.HashCode(res[0]) == data.HashCode(res[3])) 35 | } 36 | -------------------------------------------------------------------------------- /data/keyword.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "fmt" 5 | "math/rand/v2" 6 | 7 | "github.com/kode4food/ale" 8 | "github.com/kode4food/ale/internal/lang" 9 | "github.com/kode4food/ale/internal/types" 10 | ) 11 | 12 | // Keyword is a Value that represents a name that resolves to itself 13 | type Keyword string 14 | 15 | var ( 16 | kwdSalt = rand.Uint64() 17 | 18 | // compile-time checks for interface implementation 19 | _ interface { 20 | Hashed 21 | Procedure 22 | ale.Typed 23 | fmt.Stringer 24 | } = Keyword("") 25 | ) 26 | 27 | func (k Keyword) Call(args ...ale.Value) ale.Value { 28 | m := args[0].(Mapped) 29 | res, ok := m.Get(k) 30 | if !ok && len(args) > 1 { 31 | return args[1] 32 | } 33 | return res 34 | } 35 | 36 | func (k Keyword) CheckArity(argc int) error { 37 | return CheckRangedArity(1, 2, argc) 38 | } 39 | 40 | func (k Keyword) Equal(other ale.Value) bool { 41 | return k == other 42 | } 43 | 44 | func (k Keyword) String() string { 45 | return lang.KwdPrefix + string(k) 46 | } 47 | 48 | func (k Keyword) Type() ale.Type { 49 | return types.MakeLiteral(types.BasicKeyword, k) 50 | } 51 | 52 | func (k Keyword) HashCode() uint64 { 53 | return kwdSalt ^ HashString(string(k)) 54 | } 55 | -------------------------------------------------------------------------------- /internal/compiler/ir/analysis/jump.go: -------------------------------------------------------------------------------- 1 | package analysis 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kode4food/ale/internal/runtime/isa" 7 | ) 8 | 9 | const ( 10 | // ErrLabelNotAnchored is raised when a label hasn't been placed in an 11 | // isa.Instructions stream 12 | ErrLabelNotAnchored = "label not anchored: %d" 13 | 14 | // ErrLabelMultipleAnchors is raised when a label has been placed more than 15 | // once in an isa.Instructions stream 16 | ErrLabelMultipleAnchors = "label anchored multiple times: %d" 17 | ) 18 | 19 | func verifyJumps(code isa.Instructions) error { 20 | for _, l := range code { 21 | if oc, op := l.Split(); oc == isa.CondJump || oc == isa.Jump { 22 | if _, err := findLabel(code, op); err != nil { 23 | return err 24 | } 25 | } 26 | } 27 | return nil 28 | } 29 | 30 | func findLabel(code isa.Instructions, lbl isa.Operand) (int, error) { 31 | res := -1 32 | for pc, inst := range code { 33 | if oc, op := inst.Split(); oc == isa.Label && op == lbl { 34 | if res != -1 { 35 | return res, fmt.Errorf(ErrLabelMultipleAnchors, lbl) 36 | } 37 | res = pc 38 | } 39 | } 40 | if res == -1 { 41 | return res, fmt.Errorf(ErrLabelNotAnchored, lbl) 42 | } 43 | return res, nil 44 | } 45 | -------------------------------------------------------------------------------- /cmd/ale/internal/docstring/nth.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "nth" 3 | description: "retrieves a value by index" 4 | names: ["nth", "nth!"] 5 | usage: "(nth seq index default?) (nth! seq index default?)" 6 | tags: ["sequence"] 7 | --- 8 | 9 | Returns the value that can be found at the specified index of its sequence. If the index is out of the bounds of the sequence, then either the default value is returned or an error is raised. Keep in mind that indexes are zero-based. 10 | 11 | If the sequence is lazily computed, asynchronous, or otherwise incapable of indexed lookup, this function will raise an error. To perform a brute-force lookup, use the `nth!` function, keeping in mind that it may never return a result. 12 | 13 | #### An Example 14 | 15 | ```scheme 16 | (define l '(1 2 3 4)) 17 | (nth l 4 "wrong") 18 | ``` 19 | 20 | This example returns _"wrong"_ because index 4 (the fifth index) is beyond the end of the specified list. 21 | 22 | #### Indexed Sequence Application 23 | 24 | Instead of using the `nth` function, indexed sequences such as lists and vectors can also have arguments applied directly to them. 25 | 26 | ```scheme 27 | (define l '(1 2 3 4)) 28 | (l 4 "wrong") 29 | ``` 30 | 31 | This will yield the same result as the previous example. 32 | -------------------------------------------------------------------------------- /internal/compiler/asm/for-each_test.go: -------------------------------------------------------------------------------- 1 | package asm_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/kode4food/ale/internal/assert" 8 | "github.com/kode4food/ale/internal/compiler/asm" 9 | ) 10 | 11 | func TestForEachParsingErrors(t *testing.T) { 12 | as := assert.New(t) 13 | 14 | as.ErrorWith( 15 | `(asm for-each)`, 16 | fmt.Errorf(asm.ErrExpectedType, "binding pair", ""), 17 | ) 18 | 19 | as.ErrorWith(` 20 | (asm 21 | for-each (val) 22 | .eval val 23 | end) 24 | `, fmt.Errorf(asm.ErrExpectedType, "binding pair", "(val)")) 25 | 26 | as.ErrorWith(` 27 | (asm 28 | for-each [val] 29 | eval val 30 | end) 31 | `, fmt.Errorf(asm.ErrPairExpected, 1)) 32 | 33 | as.ErrorWith(` 34 | (asm 35 | for-each [val body] 36 | eval val 37 | end) 38 | `, fmt.Errorf(asm.ErrUnexpectedParameter, "body")) 39 | 40 | as.ErrorWith(` 41 | (define t (special (body) 42 | for-each [val body] 43 | definitely-not-a-directive 44 | end)) 45 | `, fmt.Errorf(asm.ErrUnknownDirective, "definitely-not-a-directive")) 46 | 47 | as.ErrorWith(` 48 | (define t (special (body) 49 | for-each [val body] 50 | eval val 51 | end)) 52 | (t 1) 53 | `, fmt.Errorf(asm.ErrExpectedType, "sequence", "1")) 54 | } 55 | -------------------------------------------------------------------------------- /cmd/ale/internal/console/paint_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package console_test 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/kode4food/ale/cmd/ale/internal/console" 9 | "github.com/kode4food/ale/internal/assert" 10 | ) 11 | 12 | func TestREPLPaint(t *testing.T) { 13 | as := assert.New(t) 14 | p := console.Painter() 15 | 16 | pair := "\033[7m" 17 | reset := "\033[0m\033[94m" 18 | 19 | src := "this is (hello)" 20 | p1 := "this is " + pair + "(" + reset + "hello)" 21 | p2 := "this is (hello" + pair + ")" + reset 22 | rs := []rune(src) 23 | 24 | s1 := p.Paint(rs, 0) 25 | as.String(src, string(s1)) 26 | s2 := p.Paint(rs, -1) 27 | as.String(src, string(s2)) 28 | s3 := p.Paint(rs, len(src)) 29 | as.String(p1, string(s3)) 30 | s4 := p.Paint(rs, len(src)-1) 31 | as.String(p1, string(s4)) 32 | s5 := p.Paint(rs, 8) 33 | as.String(p2, string(s5)) 34 | } 35 | 36 | func TestREPLNonPaint(t *testing.T) { 37 | as := assert.New(t) 38 | p := console.Painter() 39 | 40 | src1 := "(no match" 41 | src2 := "no match)" 42 | 43 | s1 := p.Paint([]rune(src1), 0) 44 | as.String(src1, string(s1)) 45 | s2 := p.Paint([]rune(src2), len(src1)-1) 46 | as.String(src2, string(s2)) 47 | s3 := p.Paint([]rune{}, 0) 48 | as.String("", string(s3)) 49 | } 50 | -------------------------------------------------------------------------------- /data/bool.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "math/rand/v2" 5 | 6 | "github.com/kode4food/ale" 7 | "github.com/kode4food/ale/internal/lang" 8 | "github.com/kode4food/ale/internal/types" 9 | ) 10 | 11 | // Bool represents the data True or False 12 | type Bool bool 13 | 14 | const ( 15 | // True represents the boolean value of True 16 | True Bool = true 17 | 18 | // False represents the boolean value of false 19 | False Bool = false 20 | ) 21 | 22 | var ( 23 | trueHash = rand.Uint64() 24 | falseHash = rand.Uint64() 25 | 26 | // compile-time checks for interface implementation 27 | _ interface { 28 | Hashed 29 | Procedure 30 | ale.Typed 31 | } = False 32 | ) 33 | 34 | func (b Bool) Call(...ale.Value) ale.Value { 35 | return b 36 | } 37 | 38 | func (b Bool) CheckArity(int) error { 39 | return nil 40 | } 41 | 42 | func (b Bool) Equal(other ale.Value) bool { 43 | return b == other 44 | } 45 | 46 | func (b Bool) String() string { 47 | if b { 48 | return lang.TrueLiteral 49 | } 50 | return lang.FalseLiteral 51 | } 52 | 53 | func (b Bool) Type() ale.Type { 54 | return types.MakeLiteral(types.BasicBoolean, b) 55 | } 56 | 57 | func (b Bool) HashCode() uint64 { 58 | if b { 59 | return trueHash 60 | } 61 | return falseHash 62 | } 63 | -------------------------------------------------------------------------------- /internal/compiler/asm/special_test.go: -------------------------------------------------------------------------------- 1 | package asm_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/internal/assert" 7 | . "github.com/kode4food/ale/internal/assert/helpers" 8 | ) 9 | 10 | func TestMakeSpecial(t *testing.T) { 11 | as := assert.New(t) 12 | 13 | as.MustEvalTo(` 14 | (%define if' 15 | (special 16 | [(predicate consequent alternative) 17 | eval predicate 18 | cond-jump :consequent 19 | eval alternative 20 | jump :end 21 | :consequent 22 | eval consequent 23 | :end] 24 | [(predicate consequent) 25 | eval predicate 26 | cond-jump :consequent 27 | null 28 | jump :end 29 | :consequent 30 | eval consequent 31 | :end])) 32 | 33 | (if' true "yep" "nope") 34 | `, S("yep")) 35 | } 36 | 37 | func TestMakeRestSpecial(t *testing.T) { 38 | as := assert.New(t) 39 | 40 | as.MustEvalTo(` 41 | (%define test 42 | (special 43 | [(head . rest) 44 | eval head])) 45 | [(test 1 2 3 4) (test 5 6) (test 7)] 46 | `, V(I(1), I(5), I(7))) 47 | 48 | as.MustEvalTo(` 49 | (%define test 50 | (special 51 | [(head . rest) 52 | eval rest])) 53 | [(test 1 2 3 4) (test 5 6) (test 7)] 54 | `, V(V(I(2), I(3), I(4)), V(I(6)), V())) 55 | } 56 | -------------------------------------------------------------------------------- /internal/compiler/encoder/cells.go: -------------------------------------------------------------------------------- 1 | package encoder 2 | 3 | import ( 4 | "github.com/kode4food/ale/data" 5 | "github.com/kode4food/ale/internal/runtime/isa" 6 | ) 7 | 8 | type ( 9 | // CellType marks a cell as having a certain disposition 10 | CellType int 11 | 12 | // Cell attaches a name to a type/disposition 13 | Cell struct { 14 | Name data.Local 15 | Type CellType 16 | } 17 | 18 | // IndexedCells encapsulates a group of IndexedCells 19 | IndexedCells []*IndexedCell 20 | 21 | // IndexedCell attaches an Index to a Cell 22 | IndexedCell struct { 23 | *Cell 24 | Index isa.Operand 25 | } 26 | 27 | // ScopedCell attaches a Scope to a Cell 28 | ScopedCell struct { 29 | Encoder 30 | *Cell 31 | Scope 32 | } 33 | ) 34 | 35 | // Cell dispositions 36 | const ( 37 | ValueCell CellType = iota 38 | ReferenceCell 39 | RestCell 40 | ) 41 | 42 | func newCell(t CellType, n data.Local) *Cell { 43 | return &Cell{ 44 | Name: n, 45 | Type: t, 46 | } 47 | } 48 | 49 | func newIndexedCell(i isa.Operand, c *Cell) *IndexedCell { 50 | return &IndexedCell{ 51 | Cell: c, 52 | Index: i, 53 | } 54 | } 55 | 56 | func newScopedCell(e Encoder, s Scope, c *Cell) *ScopedCell { 57 | return &ScopedCell{ 58 | Encoder: e, 59 | Scope: s, 60 | Cell: c, 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ffi/map_test.go: -------------------------------------------------------------------------------- 1 | package ffi_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/kode4food/ale/data" 7 | "github.com/kode4food/ale/ffi" 8 | "github.com/kode4food/ale/internal/assert" 9 | . "github.com/kode4food/ale/internal/assert/helpers" 10 | ) 11 | 12 | type cycleMap map[string]any 13 | 14 | var stateMap = map[string]int{ 15 | "California": 40, 16 | "Massachusetts": 7, 17 | "Virginia": 8, 18 | } 19 | 20 | func TestMapWrap(t *testing.T) { 21 | as := assert.New(t) 22 | m := ffi.MustWrap(stateMap).(*data.Object) 23 | if as.NotNil(m) { 24 | as.Equal(I(40), as.MustGet(m, S("California"))) 25 | as.Equal(I(7), as.MustGet(m, S("Massachusetts"))) 26 | } 27 | } 28 | 29 | func TestMapCycle(t *testing.T) { 30 | as := assert.New(t) 31 | m := cycleMap{ 32 | "k1": 99, 33 | "k2": 100, 34 | } 35 | m["k3"] = m 36 | 37 | res, err := ffi.Wrap(m) 38 | if as.Nil(res) && as.NotNil(err) { 39 | as.ErrorIs(err, ffi.ErrCycleDetected) 40 | } 41 | } 42 | 43 | func TestMapUnwrap(t *testing.T) { 44 | as := assert.New(t) 45 | f := ffi.MustWrap(func(k string, m map[string]int) int { 46 | return m[k] 47 | }).(data.Procedure) 48 | m := ffi.MustWrap(stateMap).(*data.Object) 49 | as.Equal(I(40), f.Call(S("California"), m)) 50 | as.Equal(I(8), f.Call(S("Virginia"), m)) 51 | } 52 | -------------------------------------------------------------------------------- /internal/basics/maps_test.go: -------------------------------------------------------------------------------- 1 | package basics_test 2 | 3 | import ( 4 | "cmp" 5 | "slices" 6 | "testing" 7 | 8 | "github.com/kode4food/ale/internal/basics" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestMapKeys(t *testing.T) { 13 | as := assert.New(t) 14 | k := basics.MapKeys(map[string]any{ 15 | "age": 42, 16 | "name": "bob", 17 | }) 18 | slices.Sort(k) 19 | as.Equal([]string{"age", "name"}, k) 20 | } 21 | 22 | func TestMapValues(t *testing.T) { 23 | as := assert.New(t) 24 | v := basics.MapValues(map[string]string{ 25 | "age": "forty-two", 26 | "name": "bob", 27 | }) 28 | slices.Sort(v) 29 | as.Equal([]string{"bob", "forty-two"}, v) 30 | } 31 | 32 | func TestSortedKeys(t *testing.T) { 33 | as := assert.New(t) 34 | sk := basics.SortedKeys(map[string]any{ 35 | "occupation": "worker bee", 36 | "name": "bob", 37 | "age": 42, 38 | }) 39 | as.Equal([]string{"age", "name", "occupation"}, sk) 40 | } 41 | 42 | func TestSortedKeysFunc(t *testing.T) { 43 | as := assert.New(t) 44 | sk := basics.SortedKeysFunc(map[string]any{ 45 | "occupation": "worker bee", 46 | "name": "bob", 47 | "age": 42, 48 | }, func(l, r string) int { 49 | return -cmp.Compare(l, r) 50 | }) 51 | as.Equal([]string{"occupation", "name", "age"}, sk) 52 | } 53 | -------------------------------------------------------------------------------- /read/internal/read.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "github.com/kode4food/ale/data" 5 | "github.com/kode4food/ale/env" 6 | "github.com/kode4food/ale/internal/lang/lex" 7 | "github.com/kode4food/ale/internal/lang/parse" 8 | ) 9 | 10 | type ( 11 | FromString func(env.Namespace, data.String) (data.Sequence, error) 12 | MustFromString func(env.Namespace, data.String) data.Sequence 13 | MustTokenizer func(data.String) data.Sequence 14 | ) 15 | 16 | func MakeTokenizer(matcher lex.Matcher) parse.Tokenizer { 17 | return func(src data.String) (data.Sequence, error) { 18 | return lex.Match(src, matcher), nil 19 | } 20 | } 21 | 22 | func MakeFromString(fn parse.Tokenizer) FromString { 23 | return func(ns env.Namespace, src data.String) (data.Sequence, error) { 24 | return parse.FromString(ns, fn, src) 25 | } 26 | 27 | } 28 | 29 | func MakeMustFromString(fn FromString) MustFromString { 30 | return func(ns env.Namespace, str data.String) data.Sequence { 31 | seq, err := fn(ns, str) 32 | if err != nil { 33 | panic(err) 34 | } 35 | return seq 36 | } 37 | } 38 | 39 | func MakeMustTokenizer(fn parse.Tokenizer) MustTokenizer { 40 | return func(str data.String) data.Sequence { 41 | seq, err := fn(str) 42 | if err != nil { 43 | panic(err) 44 | } 45 | return seq 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /internal/assert/compiler.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "github.com/kode4food/ale" 5 | "github.com/kode4food/ale/data" 6 | "github.com/kode4food/ale/env" 7 | "github.com/kode4food/ale/internal/compiler/encoder" 8 | "github.com/kode4food/ale/internal/compiler/generate" 9 | "github.com/kode4food/ale/internal/runtime/isa" 10 | "github.com/kode4food/ale/read" 11 | ) 12 | 13 | // MustEncodedAs tests that a string generates the expected set of Instructions 14 | func (w *Wrapper) MustEncodedAs(expected isa.Instructions, src data.String) { 15 | e := GetTestEncoder() 16 | v := read.MustFromString(e.Globals(), src) 17 | if err := generate.Block(e, v); err != nil { 18 | panic(err) 19 | } 20 | w.Instructions(expected, e.Encode().Code) 21 | } 22 | 23 | // Instructions test that two sets of Instructions are identical 24 | func (w *Wrapper) Instructions(expected, actual isa.Instructions) { 25 | w.Helper() 26 | w.Equal(expected.String(), actual.String()) 27 | } 28 | 29 | // GetRootSymbol is a test helper that retrieves the value for a named symbol 30 | // from the Encoder's global environment or dies trying 31 | func GetRootSymbol(e encoder.Encoder, n data.Local) ale.Value { 32 | s := env.RootSymbol(n) 33 | ge := e.Globals().Environment() 34 | root := ge.GetRoot() 35 | return env.MustResolveValue(root, s) 36 | } 37 | --------------------------------------------------------------------------------