├── stdlib ├── cmp │ ├── Potfile │ ├── equatableFrom.lithia │ ├── numeric.lithia │ ├── comparables.lithia │ └── contravariant.lithia ├── eq │ ├── Potfile │ ├── negated.lithia │ ├── contravariant.lithia │ └── equatable.lithia ├── fs │ ├── Potfile │ └── shim.lithia ├── os │ ├── Potfile │ ├── os_t │ │ └── shim.lithia │ └── shim.lithia ├── rx │ ├── Potfile │ ├── rx_t │ │ └── variable.lithia │ ├── variable.lithia │ └── future.lithia ├── booleans │ ├── Potfile │ ├── bool.lithia │ └── not.lithia ├── controls │ ├── Potfile │ ├── monad.lithia │ ├── contravariant.lithia │ └── functor.lithia ├── docs │ ├── Potfile │ └── markdown.lithia ├── lists │ ├── Potfile │ ├── count.lithia │ ├── prepend.lithia │ ├── is-empty.lithia │ ├── lists_t │ │ ├── is-empty_t.lithia │ │ ├── append_t.lithia │ │ ├── first_t.lithia │ │ ├── count_t.lithia │ │ ├── for-each_t.lithia │ │ ├── replicate_t.lithia │ │ ├── zip_t.lithia │ │ ├── drop-last_t.lithia │ │ ├── reduce_t.lithia │ │ ├── foldable_t.lithia │ │ ├── concat_t.lithia │ │ ├── filter_t.lithia │ │ ├── monad_t.lithia │ │ ├── functor_t.lithia │ │ └── drop_t.lithia │ ├── append.lithia │ ├── first.lithia │ ├── replicate.lithia │ ├── zip.lithia │ ├── drop-last.lithia │ ├── foldable.lithia │ ├── concat.lithia │ ├── filter.lithia │ ├── monad.lithia │ ├── functor.lithia │ ├── for-each.lithia │ ├── reduce.lithia │ └── drop.lithia ├── markdown │ └── Potfile ├── markup │ ├── Potfile │ └── format.lithia ├── prelude │ ├── Potfile │ ├── prelude_t │ │ ├── shim.lithia │ │ ├── never.lithia │ │ ├── when.lithia │ │ ├── bool.lithia │ │ └── composability_t.lithia │ ├── void.lithia │ ├── never.lithia │ ├── entries.lithia │ ├── optional.lithia │ ├── bool.lithia │ ├── if.lithia │ ├── list.lithia │ ├── when.lithia │ ├── composability.lithia │ └── shim.lithia ├── ranges │ ├── Potfile │ └── open-numbers.lithia ├── results │ ├── Potfile │ ├── result.lithia │ ├── functors.lithia │ └── monads.lithia ├── strings │ ├── Potfile │ ├── strings.lithia │ ├── strings_t │ │ ├── strings.lithia │ │ ├── concat.lithia │ │ └── join.lithia │ ├── concat.lithia │ └── join.lithia ├── tests │ ├── Potfile │ ├── tests_t │ │ └── includes.lithia │ └── internal │ │ └── test-cases.lithia ├── optionals │ ├── Potfile │ ├── or-default.lithia │ ├── is-none.lithia │ ├── optional.lithia │ ├── functor.lithia │ └── equatable.lithia ├── pot │ ├── Potfile │ ├── deps │ │ ├── deps.lithia │ │ └── git.lithia │ └── src │ │ └── store.lithia ├── os.md ├── markdown.md ├── booleans.md ├── ranges.md ├── README.md ├── strings.md ├── eq.md ├── fs.md ├── optionals.md ├── tests.md ├── cmp.md └── rx.md ├── .dockerignore ├── examples ├── hello-world.lithia ├── greeter │ ├── cmd │ │ ├── main.lithia │ │ └── test.lithia │ ├── src │ │ └── greet.lithia │ └── Potfile ├── fib-if.lithia └── fib-type.lithia ├── ast ├── identifier.go ├── meta.go ├── operator.go ├── expr.go ├── meta-decl.go ├── meta-expr.go ├── documented.go ├── decl.go ├── context-module.go ├── expr-int.go ├── expr-group.go ├── expr-float.go ├── expr-string.go ├── expr-identifier.go ├── expr-array.go ├── source.go ├── expr-operator-unary.go ├── expr-member-access.go ├── decl-enum-case.go ├── expr-operator-binary.go ├── expr-invocation.go ├── decl-parameter.go ├── docs.go ├── decl-module.go ├── expr-type-switch.go ├── expr-dict.go ├── decl-import-member.go ├── decl-constant.go ├── decl-func.go ├── expr-func.go ├── decl-field.go ├── decl-extern-func.go ├── decl-data.go ├── context-file.go ├── decl-extern-type.go ├── decl-import.go └── decl-enum.go ├── .devcontainer ├── lithia ├── Dockerfile └── devcontainer.json ├── assets └── lithia.png ├── runtime ├── prelude-any.go ├── external-definition.go ├── lazy-evaluation-cache.go ├── prelude-float.go ├── prelude-int.go ├── prelude-module.go ├── environment-evaluate.go ├── interpreter-prelude.go ├── evaluatable-lazy.go ├── prelude-callable-type.go ├── interpreter-module.go ├── runtime-core.go ├── prelude-string.go ├── operators-unary.go ├── prelude-primitive-extern-type.go ├── prelude-anonymous-function.go ├── prelude-curried-callable.go ├── interpreter-context.go ├── runtime-type-ref.go ├── prelude-data-decl.go ├── prelude-extern-function.go └── prelude-extern-type-method.go ├── info └── globals.go ├── world ├── osworld.go ├── osenv.go ├── world.go └── osfs.go ├── langsrv ├── handler-shutdown.go ├── handler-set-trace.go ├── handler-type-definition.go ├── document-cache.go ├── handler-workspace-did-delete-files.go ├── handler-hover.go ├── handler-initialized.go ├── node-position-helpers_test.go ├── handler-workspace-symbol.go ├── handler-declaration.go ├── handler-text-document-did-change.go └── handler-definition.go ├── .github ├── ISSUE_TEMPLATE │ ├── library.md │ ├── docs.md │ ├── proposal.md │ └── bug_report.md └── workflows │ ├── go.yml │ └── docker-publish.yml ├── parser ├── parse-expr-identifier.go ├── parse-comment.go ├── parse-expr-group.go ├── parse-decl-module.go ├── parse-expr-string.go ├── parse-expr-int.go ├── parse-expr-float.go ├── syntax-error-group.go ├── parse-decl-function.go ├── parse-decl-let.go ├── parse-expr-unary.go ├── parse-expr-array.go ├── parse-expr-member-access.go ├── parse-expr-binary.go ├── parser.go ├── parse-decl-enum-case.go ├── parse-expr-invocation.go ├── parse-decl-parameter.go ├── parse-decl-data.go ├── parse-expr-dict.go ├── parse-source-file.go ├── parse-decl-enum.go ├── parse-decl-import.go ├── parse-expr-type-switch.go ├── parse-decl-field.go ├── parse-expr-function.go ├── parse-decl-extern.go ├── parse-decl.go └── syntax-parsing-error.go ├── app └── lithia │ ├── main.go │ └── cmd │ ├── root.go │ ├── repl.go │ └── run.go ├── testing └── worldtest │ ├── world.go │ ├── mapenv.go │ └── mapfs.go ├── .gitignore ├── Dockerfile ├── .vscode └── extensions.json ├── reporting └── reporting.go ├── resolution ├── resolution-error_test.go └── resolution-error.go ├── lithia.go ├── external └── rx │ ├── external-rx.go │ ├── extern-rx-variable-type.go │ └── extern-rx-future-type.go ├── proposals ├── README.md ├── LE-000-template.md └── LE-003-string-concatenation.md ├── LICENSE ├── potfile └── state.go └── go.mod /stdlib/cmp/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/eq/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/fs/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/os/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/rx/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/booleans/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/controls/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/docs/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/lists/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/markdown/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/markup/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/prelude/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/ranges/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/results/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/strings/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/tests/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/optionals/Potfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/os/os_t/shim.lithia: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | dist 2 | .envrc 3 | -------------------------------------------------------------------------------- /stdlib/rx/rx_t/variable.lithia: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /stdlib/pot/Potfile: -------------------------------------------------------------------------------- 1 | module potfile 2 | -------------------------------------------------------------------------------- /stdlib/prelude/prelude_t/shim.lithia: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/hello-world.lithia: -------------------------------------------------------------------------------- 1 | print "Hello World" 2 | -------------------------------------------------------------------------------- /ast/identifier.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Identifier string 4 | -------------------------------------------------------------------------------- /.devcontainer/lithia: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | go run ./app/lithia 4 | -------------------------------------------------------------------------------- /examples/greeter/cmd/main.lithia: -------------------------------------------------------------------------------- 1 | import root 2 | 3 | root.greet "World" 4 | -------------------------------------------------------------------------------- /assets/lithia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vknabel/lithia/HEAD/assets/lithia.png -------------------------------------------------------------------------------- /ast/meta.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Meta interface { 4 | SourceLocation() *Source 5 | } 6 | -------------------------------------------------------------------------------- /ast/operator.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type OperatorBinary string 4 | type OperatorUnary string 5 | -------------------------------------------------------------------------------- /stdlib/prelude/void.lithia: -------------------------------------------------------------------------------- 1 | module prelude 2 | 3 | /// Represents a single value. 4 | data Void 5 | -------------------------------------------------------------------------------- /examples/fib-if.lithia: -------------------------------------------------------------------------------- 1 | func fib { n => 2 | if (n <= 1), 1, (fib n - 1) + (fib n - 2) 3 | } 4 | 5 | print fib 30 6 | -------------------------------------------------------------------------------- /stdlib/pot/deps/deps.lithia: -------------------------------------------------------------------------------- 1 | module deps 2 | 3 | enum Dependency { 4 | data Branch { 5 | name 6 | } 7 | } -------------------------------------------------------------------------------- /stdlib/prelude/never.lithia: -------------------------------------------------------------------------------- 1 | 2 | /// An enum with no valid values. 3 | /// Allows empty, but valid type expressions. 4 | enum Never 5 | -------------------------------------------------------------------------------- /examples/greeter/src/greet.lithia: -------------------------------------------------------------------------------- 1 | import strings 2 | 3 | func greet { name => 4 | print strings.concat ["Hello ", name, "!"] 5 | } 6 | -------------------------------------------------------------------------------- /stdlib/strings/strings.lithia: -------------------------------------------------------------------------------- 1 | /// Provides convenience functions around the basic String type. 2 | /// Currently pretty limited. 3 | module strings -------------------------------------------------------------------------------- /ast/expr.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Expr interface { 4 | Meta() *MetaExpr 5 | EnumerateNestedDecls(enumerate func(interface{}, []Decl)) 6 | } 7 | -------------------------------------------------------------------------------- /runtime/prelude-any.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | var PreludeAnyTypeRef = MakeRuntimeTypeRef("Any", "prelude") 4 | 5 | type PreludeAnyType struct{} 6 | -------------------------------------------------------------------------------- /stdlib/docs/markdown.lithia: -------------------------------------------------------------------------------- 1 | module docs 2 | 3 | import markdown 4 | 5 | func docsToMarkdown { docs => 6 | markdown.convert docsToMarkup docs 7 | } 8 | -------------------------------------------------------------------------------- /stdlib/prelude/prelude_t/never.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | 3 | test "Never allows empty type expressions", { fail => 4 | type Never { } 5 | } 6 | -------------------------------------------------------------------------------- /stdlib/lists/count.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /// Counts all elements of a list. 4 | func count { list => 5 | reduce { into, next => into + 1 }, 0, list 6 | } 7 | -------------------------------------------------------------------------------- /stdlib/lists/prepend.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /// Prepends an element to the start of a list. 4 | func prepend { element, list => 5 | Cons element, list 6 | } 7 | -------------------------------------------------------------------------------- /stdlib/prelude/entries.lithia: -------------------------------------------------------------------------------- 1 | /// A pair of values. 2 | data Pair { 3 | /// The associated key. 4 | key 5 | /// The associated value. 6 | value 7 | } 8 | -------------------------------------------------------------------------------- /ast/meta-decl.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type MetaDecl struct { 4 | *Source 5 | } 6 | 7 | func (m MetaDecl) SourceLocation() *Source { 8 | return m.Source 9 | } 10 | -------------------------------------------------------------------------------- /stdlib/strings/strings_t/strings.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | 3 | test "string comparisions", { fail => 4 | when "" != "", fail "empty strings are not equal" 5 | } 6 | -------------------------------------------------------------------------------- /ast/meta-expr.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type MetaExpr struct { 4 | Source *Source 5 | } 6 | 7 | func (m MetaExpr) SourceLocation() *Source { 8 | return m.Source 9 | } 10 | -------------------------------------------------------------------------------- /ast/documented.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Documented interface { 4 | ProvidedDocs() *Docs 5 | } 6 | 7 | type Overviewable interface { 8 | DeclOverview() string 9 | } 10 | -------------------------------------------------------------------------------- /examples/fib-type.lithia: -------------------------------------------------------------------------------- 1 | func fib { n => 2 | type Bool { 3 | True: { _ => 1 }, 4 | False: { _ => (fib n - 1) + (fib n - 2) } 5 | } (n <= 1) 6 | } 7 | 8 | print fib 30 9 | -------------------------------------------------------------------------------- /ast/decl.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Decl interface { 4 | DeclName() Identifier 5 | IsExportedDecl() bool 6 | Meta() *MetaDecl 7 | 8 | EnumerateNestedDecls(enumerate func(interface{}, []Decl)) 9 | } 10 | -------------------------------------------------------------------------------- /stdlib/eq/negated.lithia: -------------------------------------------------------------------------------- 1 | import booleans 2 | 3 | /// Negates the result of the given `Equatable`. 4 | func negated { witness => 5 | Equatable { lhs, rhs => 6 | booleans.not (witness lhs, rhs) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /runtime/external-definition.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import "github.com/vknabel/lithia/ast" 4 | 5 | type ExternalDefinition interface { 6 | Lookup(name string, env *Environment, decl ast.Decl) (RuntimeValue, bool) 7 | } 8 | -------------------------------------------------------------------------------- /info/globals.go: -------------------------------------------------------------------------------- 1 | package info 2 | 3 | var Version = "dev" 4 | var Commit string 5 | var Date string 6 | var BuiltBy = "dev" 7 | var Debug bool 8 | 9 | func init() { 10 | if BuiltBy == "dev" { 11 | Debug = true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /stdlib/os/shim.lithia: -------------------------------------------------------------------------------- 1 | /// Exits the whole program with the given exit code. 2 | extern exit code 3 | /// Returns an Optional value of the environment variable. 4 | extern env name 5 | /// The arguments passed to the program. 6 | extern args 7 | -------------------------------------------------------------------------------- /stdlib/lists/is-empty.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /// True if the given list is Nil. Otherwise False. 4 | func isEmpty { list => 5 | with list, type List { 6 | Cons: { _ => False }, 7 | Nil: { _ => True } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/is-empty_t.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | import lists 3 | 4 | test "lists.isEmpty", { fail => 5 | unless (lists.isEmpty []), fail "empty list is empty" 6 | when (lists.isEmpty [1]), fail "non empty list is not empty" 7 | } 8 | -------------------------------------------------------------------------------- /stdlib/strings/strings_t/concat.lithia: -------------------------------------------------------------------------------- 1 | import strings 2 | import tests { test } 3 | 4 | test "strings.concat rocks", { fail => 5 | let expected "Hello!" 6 | let actual strings.concat ["Hello", "!"] 7 | when expected != actual, fail actual 8 | } 9 | -------------------------------------------------------------------------------- /world/osworld.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | import "os" 4 | 5 | func New() World { 6 | return World{ 7 | Stdin: os.Stdin, 8 | Stdout: os.Stdout, 9 | Stderr: os.Stderr, 10 | FS: OSFS{}, 11 | Env: OSEnv{}, 12 | Args: os.Args, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/append_t.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | import lists 3 | 4 | test "lists.append", { fail => 5 | unless (lists.append 3, []) == [3], fail "appending to empty list" 6 | unless (lists.append 3, [1, 2]) == [1, 2, 3], fail "adds to end of tail" 7 | } 8 | -------------------------------------------------------------------------------- /stdlib/lists/append.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /// Appends an element to the end of a list. 4 | func append { element => 5 | type List { 6 | Cons: { list => Cons list.head, (append element, list.tail) }, 7 | Nil: { _ => Cons element, Nil } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /stdlib/prelude/prelude_t/when.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | 3 | test "when true succeeds", { fail => 4 | let isTrue = when True, True 5 | with isTrue, type Bool { 6 | True: { true => "" }, 7 | False: { false => fail "should be True" } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /langsrv/handler-shutdown.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | "github.com/tliron/glsp" 5 | protocol "github.com/tliron/glsp/protocol_3_16" 6 | ) 7 | 8 | func shutdown(context *glsp.Context) error { 9 | protocol.SetTraceValue(protocol.TraceValueOff) 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/library.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Submit library 3 | about: You created a library? Link it! 4 | title: "Library: " 5 | labels: docs 6 | assignees: "" 7 | --- 8 | 9 | **Library** 10 | Name and link to your library. 11 | 12 | **Description** 13 | Describe your library in a few words. 14 | -------------------------------------------------------------------------------- /stdlib/lists/first.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /// Gets the first element of the list as `Optional`. 4 | /// When empty `Nil`, otherwise `Some`. 5 | func first { list => 6 | with list, type List { 7 | Cons: { cons => Some cons.head }, 8 | Nil: { cons => None } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /langsrv/handler-set-trace.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | "github.com/tliron/glsp" 5 | protocol "github.com/tliron/glsp/protocol_3_16" 6 | ) 7 | 8 | func setTrace(context *glsp.Context, params *protocol.SetTraceParams) error { 9 | protocol.SetTraceValue(params.Value) 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /parser/parse-expr-identifier.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/vknabel/lithia/ast" 4 | 5 | func (fp *FileParser) parseExprIdentifier() (*ast.ExprIdentifier, []SyntaxError) { 6 | content := fp.Node.Content(fp.Source) 7 | return ast.MakeExprIdentifier(ast.Identifier(content), fp.AstSource()), nil 8 | } 9 | -------------------------------------------------------------------------------- /stdlib/pot/deps/git.lithia: -------------------------------------------------------------------------------- 1 | module deps 2 | 3 | import apps 4 | import pot 5 | 6 | enum Dependency { 7 | data Git { 8 | name 9 | url 10 | version 11 | } 12 | } 13 | 14 | func git { name, url, version => 15 | apps.send pot.store, pot.SetDep (Git name, url, version) 16 | } 17 | -------------------------------------------------------------------------------- /stdlib/prelude/optional.lithia: -------------------------------------------------------------------------------- 1 | module prelude 2 | 3 | /// An optional value. Either some value or none. 4 | enum Optional { 5 | data Some { value } 6 | data None 7 | } 8 | 9 | /// An uknown value. Might be an optional, the value itself or None. 10 | enum Maybe { 11 | Some 12 | None 13 | Any 14 | } 15 | -------------------------------------------------------------------------------- /app/lithia/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/vknabel/lithia/app/lithia/cmd" 7 | "github.com/vknabel/lithia/world" 8 | ) 9 | 10 | func main() { 11 | err := cmd.Execute() 12 | if err != nil { 13 | fmt.Fprint(world.Current.Stderr, err) 14 | world.Current.Env.Exit(1) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /world/osenv.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | import "os" 4 | 5 | type OSEnv struct{} 6 | 7 | func (OSEnv) Exit(code int) { 8 | os.Exit(code) 9 | } 10 | 11 | func (OSEnv) LookupEnv(key string) (string, bool) { 12 | return os.LookupEnv(key) 13 | } 14 | 15 | func (OSEnv) Environ() []string { 16 | return os.Environ() 17 | } 18 | -------------------------------------------------------------------------------- /stdlib/os.md: -------------------------------------------------------------------------------- 1 | # os 2 | 3 | _module_ 4 | 5 | - _extern_ [env](#env) name 6 | - _extern_ [exit](#exit) code 7 | 8 | ## env 9 | 10 | _func_ `env name` 11 | 12 | Returns an Optional value of the environment variable. 13 | 14 | ## exit 15 | 16 | _func_ `exit code` 17 | 18 | Exits the whole program with the given exit code. 19 | 20 | -------------------------------------------------------------------------------- /stdlib/prelude/bool.lithia: -------------------------------------------------------------------------------- 1 | module prelude 2 | 3 | /** 4 | * Represents boolean values like `True` and `False`. 5 | * Typically used for conditionals and flags. 6 | */ 7 | enum Bool { 8 | /// A constant to represent valid conditions. 9 | data True 10 | /// A constant to represent invalid conditions. 11 | data False 12 | } 13 | -------------------------------------------------------------------------------- /stdlib/lists/replicate.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /// Creates a list with a count of `n`. Every item will be the given element. 4 | /// Negative numbers will be treated as `0`. 5 | func replicate { n, element => 6 | if n <= 0, Nil, ( 7 | Cons element, ( 8 | replicate n - 1, element 9 | ) 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /stdlib/optionals/or-default.lithia: -------------------------------------------------------------------------------- 1 | module optionals 2 | 3 | /// Returns a default, if None given. 4 | /// Otherwise unwraps Some value or keeps Any as-is. 5 | func orDefault { default => 6 | type Maybe { 7 | Some: { some => some.value }, 8 | None: { none => default }, 9 | Any: { any => any }, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /parser/parse-comment.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import sitter "github.com/smacker/go-tree-sitter" 4 | 5 | func (fp *FileParser) ParseChildCommentIfNeeded(child *sitter.Node) bool { 6 | if child.Type() == TYPE_NODE_COMMENT { 7 | fp.Comments = append(fp.Comments, child.Content(fp.Source)) 8 | return true 9 | } else { 10 | return false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /stdlib/strings/concat.lithia: -------------------------------------------------------------------------------- 1 | module strings 2 | 3 | import lists 4 | 5 | /** 6 | * Concatenates a list of given strings in order. 7 | * 8 | * ``` 9 | * strings.concat ["Hello ", "World", "!"] 10 | * // "Hello World!" 11 | * ``` 12 | */ 13 | func concat { listOfStrings => 14 | lists.reduce { into, next => into.append next }, "", listOfStrings 15 | } 16 | -------------------------------------------------------------------------------- /parser/parse-expr-group.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/vknabel/lithia/ast" 4 | 5 | func (fp *FileParser) ParseGroupExpr() (*ast.ExprGroup, []SyntaxError) { 6 | exprNode := fp.Node.ChildByFieldName("expression") 7 | expr, errors := fp.ChildParserConsumingComments(exprNode).ParseExpression() 8 | return ast.MakeExprGroup(expr, fp.AstSource()), errors 9 | } 10 | -------------------------------------------------------------------------------- /stdlib/markdown.md: -------------------------------------------------------------------------------- 1 | # markdown 2 | 3 | _module_ 4 | Implements the markdown format. 5 | Currently only handles converting markup.Markup to markdown strings. 6 | Planned to be able of parsing markdown strings to markup.Markup in the future. 7 | 8 | - _func_ [convert](#convert) 9 | 10 | ## convert 11 | 12 | _func_ `convert` 13 | 14 | Converts markup.Markup to a markdown string. 15 | 16 | -------------------------------------------------------------------------------- /stdlib/booleans.md: -------------------------------------------------------------------------------- 1 | # booleans 2 | 3 | _module_ 4 | The booleans module declares more helpers around `prelude.Bool`. 5 | Also see `prelude.if`, `prelude.when`, `prelude.unless`. 6 | 7 | - _func_ [not](#not) flag 8 | 9 | ## not 10 | 11 | _func_ `not flag` 12 | 13 | Transforms a given `Bool` into the opposing value. 14 | When flag is `True`, it will return `False` and vise versa. 15 | 16 | -------------------------------------------------------------------------------- /testing/worldtest/world.go: -------------------------------------------------------------------------------- 1 | package worldtest 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/vknabel/lithia/world" 7 | ) 8 | 9 | func NewTestWorld(env map[string]string, files map[string][]byte) world.World { 10 | return world.World{ 11 | Stdin: os.Stdin, 12 | Stdout: os.Stdout, 13 | Stderr: os.Stderr, 14 | Env: NewMapEnv(env), 15 | FS: NewMapFS(files), 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /stdlib/lists/zip.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /// Combines two lists pairwise. Has the length of the shortest list. 4 | /// `combine left, right`. 5 | func zipWith { combine, left, right => 6 | (if (isEmpty left) || (isEmpty right), 7 | Nil, 8 | (Cons 9 | (combine left.head, right.head), 10 | (zipWith combine, left.tail, right.tail) 11 | )) 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | __debug_bin 8 | 9 | # Test binary, built with `go test -c` 10 | *.test 11 | 12 | # Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | dist/ 19 | /lithia 20 | .envrc 21 | .DS_Store -------------------------------------------------------------------------------- /parser/parse-decl-module.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseModuleDeclaration() (*ast.DeclModule, []SyntaxError) { 8 | internalName := ast.Identifier(fp.Node.ChildByFieldName("name").Content(fp.Source)) 9 | decl := ast.MakeDeclModule(internalName, fp.AstSource()) 10 | decl.Docs = fp.ConsumeDocs() 11 | return decl, nil 12 | } 13 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/first_t.lithia: -------------------------------------------------------------------------------- 1 | import lists 2 | import strings 3 | import tests { test } 4 | 5 | test "lists.first", { fail => 6 | unless (lists.first []) == None, fail "should be nil for empty list" 7 | unless (lists.first ["Hello"]).value == (Some "Hello").value, fail "should return Some head for Cons" 8 | unless (lists.first [42]).value == 42, fail "should return Some head for Cons" 9 | } 10 | -------------------------------------------------------------------------------- /stdlib/lists/drop-last.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /// Returns the list without the last element. 4 | func dropLast { list => 5 | with list, type List { 6 | Cons: { cons => 7 | with cons.tail, type List { 8 | Cons: { inner => Cons cons.head, dropLast inner }, 9 | Nil: { _ => Nil }, 10 | } 11 | }, 12 | Nil: { _ => Nil } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /stdlib/lists/foldable.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /** 4 | * Starts folding at the end of the list, from the right. 5 | * 6 | * `accumulator nextValue, transformedTail` 7 | */ 8 | func foldr { accumulator, initial => 9 | type List { 10 | Cons: { cons => 11 | accumulator cons.head, (foldr accumulator, initial, cons.tail) 12 | }, 13 | Nil: { nil => initial } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /stdlib/lists/concat.lithia: -------------------------------------------------------------------------------- 1 | /// Prepends a given prefix with another postfix list. 2 | func prependList { prefix, postfix => 3 | foldr { nextHead, postfix => 4 | Cons nextHead, postfix 5 | }, postfix, prefix 6 | } 7 | 8 | /// Concats a list of lists. 9 | func concat { nestedLists => 10 | foldr { nextList, appendedLists => 11 | prependList nextList, appendedLists 12 | }, Nil, nestedLists 13 | } 14 | -------------------------------------------------------------------------------- /examples/greeter/Potfile: -------------------------------------------------------------------------------- 1 | module potfile 2 | 3 | import pot 4 | import pot.cmds 5 | import pot.deps 6 | 7 | cmds.add "test", { c => 8 | c.script "cmd/test.lithia" 9 | c.summary "runs all tests" 10 | c.env "LITHIA_TESTS", "1" 11 | c.flag "verbose", { f => 12 | f.short "v" 13 | f.summary "verbose logging" 14 | } 15 | } 16 | 17 | deps.git "tests", "https://github.com/vknabel/lithia-tests.git", deps.Branch "main" 18 | -------------------------------------------------------------------------------- /parser/parse-expr-string.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/vknabel/lithia/ast" 7 | ) 8 | 9 | func (fp *FileParser) ParseExprString() (*ast.ExprString, []SyntaxError) { 10 | string, err := strconv.Unquote(fp.Node.Content(fp.Source)) 11 | if err != nil { 12 | return nil, []SyntaxError{*fp.SyntaxErrorOrConvert(err)} 13 | } 14 | return ast.MakeExprString(string, fp.AstSource()), nil 15 | } 16 | -------------------------------------------------------------------------------- /stdlib/booleans/bool.lithia: -------------------------------------------------------------------------------- 1 | /// The booleans module declares more helpers around `prelude.Bool`. 2 | /// Also see `prelude.if`, `prelude.when`, `prelude.unless`. 3 | module booleans 4 | 5 | import tests { test } 6 | 7 | test "booleans are cool", { fail => 8 | with True, type Bool { 9 | True: { _ => }, 10 | False: { _ => 11 | fail "type switch not working for Bool" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/count_t.lithia: -------------------------------------------------------------------------------- 1 | import lists 2 | import strings 3 | import tests { test } 4 | 5 | test "lists.count", { fail => 6 | unless (lists.count []) == 0, fail "count starts at 0" 7 | unless (lists.count [0]) == 1, fail "one element has a count 1" 8 | unless (lists.count [[42, 13]]) == 1, fail "does not merge children" 9 | unless (lists.count ["abc", "def"]) == 2, fail "two elements have count 2" 10 | } 11 | -------------------------------------------------------------------------------- /stdlib/prelude/if.lithia: -------------------------------------------------------------------------------- 1 | module prelude 2 | 3 | /** 4 | * When the given condition evaluates to `True`, returns `then`. Otherwise `false`. 5 | * Both, `then` and `else` are evaluted lazily. 6 | * 7 | * ``` 8 | * if True, print "Succeeded", exit 1 9 | * ``` 10 | */ 11 | func if { condition, then, else => 12 | with condition, type Bool { 13 | True: { _ => then }, 14 | False: { _ => else } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: If the documentation is unclear or wrong, please let us know! 4 | title: "Question: " 5 | labels: docs 6 | assignees: "" 7 | --- 8 | 9 | **Question** 10 | What is your question? Please be as specific as possible. 11 | 12 | **Context** 13 | Where did you find the documentation? What is the URL? 14 | 15 | **Additional context** 16 | Add any other context about the problem here. 17 | -------------------------------------------------------------------------------- /ast/context-module.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type ModuleName string 4 | 5 | type ContextModule struct { 6 | Name ModuleName 7 | 8 | Files []*SourceFile 9 | } 10 | 11 | func MakeContextModule(name ModuleName) *ContextModule { 12 | return &ContextModule{ 13 | Name: name, 14 | Files: []*SourceFile{}, 15 | } 16 | } 17 | 18 | func (m *ContextModule) AddSourceFile(sourceFile *SourceFile) { 19 | m.Files = append(m.Files, sourceFile) 20 | } 21 | -------------------------------------------------------------------------------- /examples/greeter/cmd/test.lithia: -------------------------------------------------------------------------------- 1 | module example 2 | 3 | import tests 4 | 5 | tests.test "example", { fail => 6 | unless True, fail "should never happen" 7 | with Some 42, type Maybe { 8 | Some: { some => unless some.value == 42, fail "should be 42" }, 9 | None: { none => fail "none instead of 42" }, 10 | Any: { any => fail "should be Some 42" }, 11 | } 12 | } 13 | 14 | when tests.enabled, tests.runTests 15 | -------------------------------------------------------------------------------- /parser/parse-expr-int.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/vknabel/lithia/ast" 7 | ) 8 | 9 | func (fp *FileParser) ParseIntExpr() (*ast.ExprInt, []SyntaxError) { 10 | literal := fp.Node.Content(fp.Source) 11 | integer, err := strconv.ParseInt(literal, 10, 64) 12 | if err != nil { 13 | return nil, []SyntaxError{*fp.SyntaxErrorOrConvert(err)} 14 | } 15 | return ast.MakeExprInt(integer, fp.AstSource()), nil 16 | } 17 | -------------------------------------------------------------------------------- /stdlib/ranges/open-numbers.lithia: -------------------------------------------------------------------------------- 1 | /// An early concept of ranges. 2 | /// Might change largely in future. 3 | module ranges 4 | 5 | /// An infinite list of all numbers greater than or equal 0. 6 | /// e.g. [0, 1, 2, 3, 4, ...] 7 | func indices { => numbersFrom 0 } 8 | 9 | /// An infinite list of all numbers greater than or equal a given one. 10 | /// e.g. [n, n+1, n+2, n+3, n+4, ...] 11 | func numbersFrom { n => 12 | Cons n, numbersFrom (n+1) 13 | } 14 | -------------------------------------------------------------------------------- /parser/parse-expr-float.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/vknabel/lithia/ast" 7 | ) 8 | 9 | func (fp *FileParser) ParseFloatExpr() (*ast.ExprFloat, []SyntaxError) { 10 | literal := fp.Node.Content(fp.Source) 11 | integer, err := strconv.ParseFloat(literal, 64) 12 | if err != nil { 13 | return nil, []SyntaxError{*fp.SyntaxErrorOrConvert(err)} 14 | } 15 | return ast.MakeExprFloat(integer, fp.AstSource()), nil 16 | } 17 | -------------------------------------------------------------------------------- /parser/syntax-error-group.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | type GroupedSyntaxError struct { 4 | Errors []SyntaxError 5 | } 6 | 7 | func NewGroupedSyntaxError(errors []SyntaxError) GroupedSyntaxError { 8 | return GroupedSyntaxError{ 9 | Errors: errors, 10 | } 11 | } 12 | 13 | func (err GroupedSyntaxError) Error() string { 14 | errorString := "" 15 | for _, err := range err.Errors { 16 | errorString += err.Error() 17 | } 18 | return errorString 19 | } 20 | -------------------------------------------------------------------------------- /langsrv/handler-type-definition.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | "github.com/tliron/glsp" 5 | protocol "github.com/tliron/glsp/protocol_3_16" 6 | ) 7 | 8 | func textDocumentTypeDefinition(context *glsp.Context, params *protocol.TypeDefinitionParams) (interface{}, error) { 9 | rc := NewReqContextAtPosition(¶ms.TextDocumentPositionParams) 10 | sourceFile := rc.sourceFile 11 | if sourceFile == nil { 12 | return nil, nil 13 | } 14 | return nil, nil 15 | } 16 | -------------------------------------------------------------------------------- /stdlib/rx/variable.lithia: -------------------------------------------------------------------------------- 1 | /// A very early concept of implementing functional reactive programming. 2 | /// Currently only used to provide mutability. 3 | module rx 4 | 5 | /// Holds a value and enables replacing it. 6 | /// Planned to propagate value changes to observers, but not implemented, yet. 7 | extern Variable { 8 | /// Changes the currently hold value of the variable. 9 | accept value 10 | /// Returns the currently hold value. 11 | current 12 | } 13 | -------------------------------------------------------------------------------- /stdlib/lists/filter.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /// Returns the list only with elements where the given predicate is True. 4 | func filter { predicate, list => 5 | with list, type List { 6 | Cons: { cons => 7 | let filteredTail = filter predicate, cons.tail 8 | 9 | (if predicate cons.head, 10 | (Cons cons.head, filteredTail), 11 | filteredTail) 12 | }, 13 | Nil: { _ => Nil } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/for-each_t.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | import lists 3 | 4 | test "lists.forEach", { fail => 5 | unless (lists.forEach [], { i => i }) == Void, fail "Void when empty" 6 | unless (lists.forEach [1], { i => i }) == 1, fail "returns first and last value" 7 | unless (lists.forEach [1, 2, 3], { i => i }) == 3, fail "returns last value" 8 | unless (lists.forEach [1, 2, 3], { i => Some i }) == (Some 3), fail "last value regardless of type" 9 | } 10 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/replicate_t.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | import lists 3 | 4 | test "lists.replicate", { fail => 5 | unless (lists.replicate -1, "hi") == [], fail "negative replications are empty" 6 | unless (lists.replicate 0, "hi") == [], fail "zero replications are empty" 7 | unless (lists.replicate 1, "hi") == ["hi"], fail "one replication is one element" 8 | unless (lists.replicate 3, "hi") == ["hi", "hi", "hi"], fail "multiple replications" 9 | } 10 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/zip_t.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | import lists 3 | 4 | test "lists.zipWith", { fail => 5 | let sum = { l, r => l + r } 6 | unless (lists.zipWith sum, [], []) == [], fail "all empty lists stay empty" 7 | unless (lists.zipWith sum, [], [1]) == [], fail "left empty list stays empty" 8 | unless (lists.zipWith sum, [1], []) == [], fail "right empty list stays empty" 9 | unless (lists.zipWith sum, [1], [2]) == [3], fail "combines values" 10 | } 11 | -------------------------------------------------------------------------------- /stdlib/lists/monad.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | import controls { Monad } 4 | 5 | /// The Monad witness of List. 6 | let monad = Monad pure, flatMap 7 | 8 | /// Creates a list with one single element. 9 | func pure { value => 10 | Cons value 11 | } 12 | 13 | /// Transforms a list's values into a list of more values. 14 | /// Concats these values into one single list while keeping their order. 15 | func flatMap { transform, list => 16 | lists.concat (lists.map transform, list) 17 | } 18 | -------------------------------------------------------------------------------- /stdlib/eq/contravariant.lithia: -------------------------------------------------------------------------------- 1 | import controls { Contravariant } 2 | 3 | /** 4 | * Transforms the inputs of an `Equatable`-witness. 5 | * 6 | * ``` 7 | * cmp.pullback { person => person.name }, insensitiveEquatable, Person "Somebody" 8 | * ``` 9 | */ 10 | func pullback { transform, witness => 11 | Equatable { lhs, rhs => 12 | witness.equal transform lhs, transform rhs 13 | } 14 | } 15 | 16 | /// The Contravariant over eq.Equatable. 17 | let contravariant = Contravariant pullback 18 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/drop-last_t.lithia: -------------------------------------------------------------------------------- 1 | import lists 2 | import strings 3 | import tests { test } 4 | 5 | test "lists.dropLast", { fail => 6 | unless (lists.dropLast []) == [], fail "empty lists stay empty" 7 | unless (lists.dropLast [0]) == [], fail "single elements will be empty" 8 | unless (lists.dropLast [1, 2]) == [1], fail "last element will be removed, when having 2" 9 | unless (lists.dropLast [1, 2, 3]) == [1, 2], fail "last element will be removed, when having 3" 10 | } 11 | -------------------------------------------------------------------------------- /stdlib/prelude/list.lithia: -------------------------------------------------------------------------------- 1 | module prelude 2 | 3 | /** 4 | * A list of arbiratry elements. 5 | * 6 | * ``` 7 | * import lists 8 | * 9 | * let myList = [1, 2, 3, 4] 10 | * lists.reduce { l, r => l + r }, 0, myList 11 | * ``` 12 | */ 13 | enum List { 14 | /// Represents a non-empty List. 15 | data Cons { 16 | /// The first element 17 | head 18 | /// The remaining list. 19 | /// @type List 20 | tail 21 | } 22 | /// Marks the end of the list. 23 | data Nil 24 | } 25 | -------------------------------------------------------------------------------- /stdlib/strings/strings_t/join.lithia: -------------------------------------------------------------------------------- 1 | import strings 2 | import tests { test } 3 | 4 | test "strings.join", { fail => 5 | when (strings.join ",", []) != "", fail "for empty list" 6 | when (strings.join ",", [1]) != "1", fail "string for one non-string" 7 | when (strings.join ",", ["1"]) != "1", fail "for one element" 8 | when (strings.join ",", ["1", "2"]) != "1,2", fail "for two elements" 9 | when (strings.join ",", ["1", "2", "3"]) != "1,2,3", fail "for three elements" 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/proposal.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Proposal or idea 3 | about: Pitch a proposal or an idea 4 | title: "Proposal: " 5 | labels: proposal 6 | --- 7 | 8 | **Introduction** 9 | What is the proposed feature? What problem does it solve? 10 | 11 | **Motivation** 12 | Why is this change important to you? How would you use it? How can it benefit other users? 13 | 14 | **Proposed Solution** 15 | Describe the solution you'd like. Provide examples and describe how it works. Define any new terminology. 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | ## 4 | ## Build 5 | ## 6 | FROM golang:1.18-bullseye AS build 7 | 8 | WORKDIR /app 9 | 10 | COPY go.mod ./ 11 | COPY go.sum ./ 12 | RUN go mod download 13 | 14 | COPY ./ ./ 15 | 16 | RUN go build ./app/lithia 17 | 18 | ## 19 | ## Deploy 20 | ## 21 | FROM debian:stable-slim 22 | 23 | WORKDIR / 24 | COPY --from=build /app/lithia /bin/lithia 25 | COPY ./stdlib /opt/lithia/stdlib 26 | ENV LITHIA_STDLIB=/opt/lithia/stdlib 27 | 28 | ENTRYPOINT ["/bin/lithia"] 29 | -------------------------------------------------------------------------------- /stdlib/ranges.md: -------------------------------------------------------------------------------- 1 | # ranges 2 | 3 | _module_ 4 | An early concept of ranges. 5 | Might change largely in future. 6 | 7 | - _func_ [indices](#indices) 8 | - _func_ [numbersFrom](#numbersFrom) n 9 | 10 | ## indices 11 | 12 | _func_ `indices` 13 | 14 | An infinite list of all numbers greater than or equal 0. 15 | e.g. [0, 1, 2, 3, 4, ...] 16 | 17 | ## numbersFrom 18 | 19 | _func_ `numbersFrom n` 20 | 21 | An infinite list of all numbers greater than or equal a given one. 22 | e.g. [n, n+1, n+2, n+3, n+4, ...] 23 | 24 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/reduce_t.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | import lists 3 | import strings 4 | 5 | test "lists.reduce", { fail => 6 | let concatAllAsStrings = lists.reduce { into, next => 7 | strings.concat [into, next] 8 | } 9 | unless (concatAllAsStrings "", []) == "", fail "no elements and no suffix empty" 10 | unless (concatAllAsStrings "!", []) == "!", fail "no elements is only suffix" 11 | unless (concatAllAsStrings "!", [1, 2, 3]) == "!123", fail "concats elements to suffix" 12 | } 13 | -------------------------------------------------------------------------------- /stdlib/optionals/is-none.lithia: -------------------------------------------------------------------------------- 1 | module optionals 2 | 3 | import booleans 4 | 5 | /// True if None. Otherwise False. 6 | func isNone { => 7 | type Optional { 8 | None: { _ => True }, 9 | Any: { _ => False } 10 | } 11 | } 12 | 13 | // describe "is none", { it => 14 | // it "true for none", { expect => 15 | // expect isNone None 16 | // } 17 | 18 | // it "false for some", { expect => 19 | // expect booleans.not isNone (Some 41) 20 | // expect booleans.not isNone (Some None) 21 | // } 22 | // } 23 | -------------------------------------------------------------------------------- /ast/expr-int.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprInt{} 4 | 5 | type ExprInt struct { 6 | Literal int64 7 | 8 | MetaInfo *MetaExpr 9 | } 10 | 11 | func (e ExprInt) Meta() *MetaExpr { 12 | return e.MetaInfo 13 | } 14 | 15 | func MakeExprInt(literal int64, source *Source) *ExprInt { 16 | return &ExprInt{ 17 | Literal: literal, 18 | MetaInfo: &MetaExpr{ 19 | Source: source, 20 | }, 21 | } 22 | } 23 | 24 | func (e ExprInt) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 25 | // no nested decls 26 | } 27 | -------------------------------------------------------------------------------- /runtime/lazy-evaluation-cache.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import "sync" 4 | 5 | type LazyEvaluationCache struct { 6 | once *sync.Once 7 | value RuntimeValue 8 | err *RuntimeError 9 | } 10 | 11 | func NewLazyEvaluationCache() *LazyEvaluationCache { 12 | return &LazyEvaluationCache{once: &sync.Once{}} 13 | } 14 | 15 | func (c *LazyEvaluationCache) Evaluate(f func() (RuntimeValue, *RuntimeError)) (RuntimeValue, *RuntimeError) { 16 | c.once.Do(func() { 17 | c.value, c.err = f() 18 | }) 19 | return c.value, c.err 20 | } 21 | -------------------------------------------------------------------------------- /stdlib/lists/functor.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | import controls { Functor } 4 | 5 | /// The Functor for lists. Allows transforming values, while keeping their order. 6 | let functor = Functor map 7 | 8 | /// Transforms the list values, while keeping their order. 9 | func map { transform, list => 10 | with list, type List { 11 | Cons: { part => 12 | let tail = lists.map transform, part.tail 13 | Cons transform part.head, tail 14 | }, 15 | Nil: { nil => Nil } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/foldable_t.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | import lists 3 | import strings 4 | 5 | test "lists.foldr", { fail => 6 | let concatAllAsStrings = lists.foldr { next, transformed => 7 | strings.concat [next, transformed] 8 | } 9 | unless (concatAllAsStrings "", []) == "", fail "no elements and no suffix empty" 10 | unless (concatAllAsStrings "!", []) == "!", fail "no elements is only suffix" 11 | unless (concatAllAsStrings "!", [1, 2, 3]) == "123!", fail "concats elements to suffix" 12 | } 13 | -------------------------------------------------------------------------------- /ast/expr-group.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprGroup{} 4 | 5 | type ExprGroup struct { 6 | Expr Expr 7 | 8 | MetaInfo *MetaExpr 9 | } 10 | 11 | func (e ExprGroup) Meta() *MetaExpr { 12 | return e.MetaInfo 13 | } 14 | 15 | func MakeExprGroup(expr Expr, source *Source) *ExprGroup { 16 | return &ExprGroup{ 17 | Expr: expr, 18 | MetaInfo: &MetaExpr{Source: source}, 19 | } 20 | } 21 | 22 | func (e ExprGroup) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 23 | e.Expr.EnumerateNestedDecls(enumerate) 24 | } 25 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.18 20 | 21 | - name: Build 22 | run: go build -v ./... 23 | 24 | - name: Test 25 | env: 26 | LITHIA_STDLIB: ./stdlib 27 | run: go test -v -test.v ./... 28 | -------------------------------------------------------------------------------- /ast/expr-float.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprFloat{} 4 | 5 | type ExprFloat struct { 6 | Literal float64 7 | 8 | MetaInfo *MetaExpr 9 | } 10 | 11 | func (e ExprFloat) Meta() *MetaExpr { 12 | return e.MetaInfo 13 | } 14 | 15 | func MakeExprFloat(literal float64, source *Source) *ExprFloat { 16 | return &ExprFloat{ 17 | Literal: literal, 18 | MetaInfo: &MetaExpr{ 19 | Source: source, 20 | }, 21 | } 22 | } 23 | 24 | func (e ExprFloat) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 25 | // no nested decls 26 | } 27 | -------------------------------------------------------------------------------- /ast/expr-string.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprString{} 4 | 5 | type ExprString struct { 6 | Literal string 7 | 8 | MetaInfo *MetaExpr 9 | } 10 | 11 | func (e ExprString) Meta() *MetaExpr { 12 | return e.MetaInfo 13 | } 14 | 15 | func MakeExprString(literal string, source *Source) *ExprString { 16 | return &ExprString{ 17 | Literal: literal, 18 | MetaInfo: &MetaExpr{ 19 | Source: source, 20 | }, 21 | } 22 | } 23 | 24 | func (e ExprString) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 25 | // no nested decls 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | // List of extensions which should be recommended for users of this workspace. 5 | "recommendations": [ 6 | "ms-vscode-remote.remote-containers", 7 | "golang.go", 8 | "vknabel.vscode-lithia" 9 | ], 10 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 11 | "unwantedRecommendations": [] 12 | } -------------------------------------------------------------------------------- /parser/parse-decl-function.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/vknabel/lithia/ast" 4 | 5 | func (fp *FileParser) ParseFunctionDeclaration() (*ast.DeclFunc, []SyntaxError) { 6 | functionNode := fp.Node.ChildByFieldName("function") 7 | name := ast.Identifier(fp.Node.ChildByFieldName("name").Content(fp.Source)) 8 | function, errs := fp.NewScopeChildParser(functionNode).ParseFunctionExpr(string(name)) 9 | 10 | funcDecl := ast.MakeDeclFunc(name, function, fp.AstSource()) 11 | funcDecl.Docs = fp.ConsumeDocs() 12 | 13 | return funcDecl, errs 14 | } 15 | -------------------------------------------------------------------------------- /langsrv/document-cache.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | protocol "github.com/tliron/glsp/protocol_3_16" 5 | "github.com/vknabel/lithia/ast" 6 | "github.com/vknabel/lithia/parser" 7 | "github.com/vknabel/lithia/resolution" 8 | ) 9 | 10 | type documentCache struct { 11 | documents map[protocol.URI]*textDocumentEntry 12 | } 13 | 14 | type textDocumentEntry struct { 15 | item protocol.TextDocumentItem 16 | parser *parser.Parser 17 | fileParser *parser.FileParser 18 | sourceFile *ast.SourceFile 19 | module resolution.ResolvedModule 20 | } 21 | -------------------------------------------------------------------------------- /reporting/reporting.go: -------------------------------------------------------------------------------- 1 | package reporting 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/vknabel/lithia/world" 7 | ) 8 | 9 | func ReportErrorOrPanic(err error) { 10 | fmt.Fprintln(world.Current.Stderr, err) 11 | } 12 | 13 | func ReportError(line int, message string) { 14 | report(line, "", message) 15 | } 16 | 17 | func report(line int, where string, message string) { 18 | fmt.Fprintln(world.Current.Stderr, "[line"+fmt.Sprint(line)+"] Error"+where+": "+message) 19 | } 20 | 21 | type LocatableError interface { 22 | error 23 | SourceLocation() string 24 | } 25 | -------------------------------------------------------------------------------- /resolution/resolution-error_test.go: -------------------------------------------------------------------------------- 1 | package resolution_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/vknabel/lithia/resolution" 7 | ) 8 | 9 | func TestModuleFoundError(t *testing.T) { 10 | e := resolution.ModuleNotFoundError{ 11 | FromPackage: resolution.ResolvedPackage{ 12 | Path: "foo/bar", 13 | }, 14 | ModuleParts: []string{"foo", "bar"}, 15 | } 16 | if e.Error() != "module foo.bar not found from package foo/bar" { 17 | t.Errorf("Expected error to be \"module foo.bar not found from package foo/bar\", got %s", e.Error()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /stdlib/optionals/optional.lithia: -------------------------------------------------------------------------------- 1 | module optionals 2 | 3 | /// Implements some helpers around prelude.Optional and prelude.Maybe. 4 | import prelude 5 | 6 | // describe "optional", { it => 7 | // it "can have some value", { _ => 8 | // Some 41 9 | // } 10 | 11 | // it "can be none", { _ => 12 | // None 13 | // } 14 | // } 15 | 16 | /// Creates an optional from a Maybe-value. 17 | func from { maybe => 18 | type Maybe { 19 | Some: { _ => maybe }, 20 | None: { _ => maybe }, 21 | Any: { _ => Some maybe }, 22 | } maybe 23 | } 24 | -------------------------------------------------------------------------------- /stdlib/prelude/when.lithia: -------------------------------------------------------------------------------- 1 | module prelude 2 | 3 | /// Only if a condition is True, the right side will be executed and returned. 4 | /// Otherwise Void. 5 | func when { condition, then => 6 | with condition, type Bool { 7 | True: { _ => then }, 8 | False: { _ => Void } 9 | } 10 | } 11 | 12 | /// Only if a condition is False, the right side will be executed and returned. 13 | /// Otherwise Void. 14 | func unless { condition, then => 15 | with condition, type Bool { 16 | True: { _ => Void }, 17 | False: { _ => then } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /runtime/prelude-float.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import "fmt" 4 | 5 | var _ RuntimeValue = PreludeFloat(0.0) 6 | var PreludeFloatTypeRef = MakeRuntimeTypeRef("Float", "prelude") 7 | 8 | type PreludeFloat float64 9 | 10 | func (i PreludeFloat) Lookup(member string) (Evaluatable, *RuntimeError) { 11 | return nil, NewRuntimeError(fmt.Errorf("float %f has no member %s", i, member)) 12 | } 13 | 14 | func (PreludeFloat) RuntimeType() RuntimeTypeRef { 15 | return PreludeFloatTypeRef 16 | } 17 | 18 | func (i PreludeFloat) String() string { 19 | return fmt.Sprintf("%f", i) 20 | } 21 | -------------------------------------------------------------------------------- /stdlib/lists/for-each.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | import rx 4 | 5 | /// Iterates over all values of a list to generate side effects. 6 | /// Returns last result or `prelude.Void`. 7 | func forEach { list, action => 8 | let lastValue = rx.Variable Void 9 | 10 | func iterate { => 11 | type List { 12 | Cons: { cons => 13 | lastValue.accept action cons.head 14 | iterate cons.tail 15 | }, 16 | Nil: { nil => Void } 17 | } 18 | } 19 | 20 | iterate list 21 | lastValue.current 22 | } 23 | -------------------------------------------------------------------------------- /ast/expr-identifier.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprIdentifier{} 4 | 5 | type ExprIdentifier struct { 6 | Name Identifier 7 | 8 | MetaInfo *MetaExpr 9 | } 10 | 11 | func (e ExprIdentifier) Meta() *MetaExpr { 12 | return e.MetaInfo 13 | } 14 | 15 | func MakeExprIdentifier(name Identifier, source *Source) *ExprIdentifier { 16 | return &ExprIdentifier{ 17 | Name: name, 18 | MetaInfo: &MetaExpr{ 19 | Source: source, 20 | }, 21 | } 22 | } 23 | 24 | func (e ExprIdentifier) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 25 | // no nested decls 26 | } 27 | -------------------------------------------------------------------------------- /stdlib/booleans/not.lithia: -------------------------------------------------------------------------------- 1 | module booleans 2 | 3 | /// Transforms a given `Bool` into the opposing value. 4 | /// When flag is `True`, it will return `False` and vise versa. 5 | func not { flag => 6 | type Bool { 7 | True: { _ => False }, 8 | False: { _ => True } 9 | } flag 10 | } 11 | 12 | import tests { test } 13 | 14 | test "booleans.not is False for True", { fail => 15 | when booleans.not True, fail "not True should be False" 16 | } 17 | 18 | test "booleans.not is True for False", { fail => 19 | unless booleans.not False, fail "not False should be True" 20 | } 21 | -------------------------------------------------------------------------------- /stdlib/tests/tests_t/includes.lithia: -------------------------------------------------------------------------------- 1 | /// Dependencies of the `tests` module cannot be tested in place. 2 | /// Instead there are special `.t` modules for `tests` and its dependencies. 3 | /// To run these, import `tests.t`. 4 | module tests_t 5 | 6 | import apps.apps_t 7 | import booleans 8 | import cmp 9 | import controls 10 | import docs 11 | import eq 12 | import fs 13 | import lists.lists_t 14 | import markup 15 | import markdown.markdown_t 16 | import optionals 17 | import os.os_t 18 | import prelude.prelude_t 19 | import results 20 | import rx.rx_t 21 | import strings.strings_t 22 | -------------------------------------------------------------------------------- /parser/parse-decl-let.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/vknabel/lithia/ast" 4 | 5 | func (fp *FileParser) ParseLetDeclaration() (*ast.DeclConstant, []SyntaxError) { 6 | nameIdentifier := ast.Identifier(fp.Node.ChildByFieldName("name").Content(fp.Source)) 7 | 8 | valueNode := fp.Node.ChildByFieldName("value") 9 | value, errs := fp.SameScopeChildParser(valueNode).ParseExpression() 10 | if len(errs) != 0 { 11 | return nil, errs 12 | } 13 | decl := ast.MakeDeclConstant(nameIdentifier, value, fp.AstSource()) 14 | decl.Docs = fp.ConsumeDocs() 15 | return decl, nil 16 | } 17 | -------------------------------------------------------------------------------- /stdlib/lists/reduce.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /** 4 | * Recursively walk a tree of nodes, calling a function on each node. 5 | * The given accumulator function merges each element into a new one for the next call. 6 | * 7 | * ``` 8 | * lists.reduce { into, next => into + next.length }, 0, ["count", "chars"] 9 | * ``` 10 | */ 11 | func reduce { accumulator, initial => 12 | type List { 13 | Cons: { cons => 14 | let next = (accumulator initial, cons.head) 15 | reduce accumulator, next, cons.tail 16 | }, 17 | Nil: { nil => initial } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ast/expr-array.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprArray{} 4 | 5 | type ExprArray struct { 6 | Elements []Expr 7 | 8 | MetaInfo *MetaExpr 9 | } 10 | 11 | func (e ExprArray) Meta() *MetaExpr { 12 | return e.MetaInfo 13 | } 14 | 15 | func MakeExprArray(elements []Expr, source *Source) *ExprArray { 16 | return &ExprArray{ 17 | Elements: elements, 18 | MetaInfo: &MetaExpr{ 19 | Source: source, 20 | }, 21 | } 22 | } 23 | 24 | func (e ExprArray) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 25 | for _, el := range e.Elements { 26 | el.EnumerateNestedDecls(enumerate) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /langsrv/handler-workspace-did-delete-files.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | "github.com/tliron/glsp" 5 | protocol "github.com/tliron/glsp/protocol_3_16" 6 | ) 7 | 8 | func workspaceDidDeleteFiles(context *glsp.Context, params *protocol.DeleteFilesParams) error { 9 | for _, deleted := range params.Files { 10 | context.Notify(protocol.ServerTextDocumentPublishDiagnostics, protocol.PublishDiagnosticsParams{ 11 | URI: deleted.URI, 12 | Version: nil, 13 | Diagnostics: []protocol.Diagnostic{}, 14 | }) 15 | delete(ls.documentCache.documents, deleted.URI) 16 | } 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /resolution/resolution-error.go: -------------------------------------------------------------------------------- 1 | package resolution 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type ModuleNotFoundError struct { 9 | FromPackage ResolvedPackage 10 | ModuleParts []string 11 | } 12 | 13 | func (e ModuleNotFoundError) Error() string { 14 | return fmt.Sprintf("module %s not found from package %s", strings.Join(e.ModuleParts, "."), e.FromPackage.Path) 15 | } 16 | 17 | type PackageNotFoundError struct { 18 | ForReferencePath string 19 | } 20 | 21 | func (e PackageNotFoundError) Error() string { 22 | return fmt.Sprintf("package not found for reference path %s", e.ForReferencePath) 23 | } 24 | -------------------------------------------------------------------------------- /testing/worldtest/mapenv.go: -------------------------------------------------------------------------------- 1 | package worldtest 2 | 3 | type MapEnv struct { 4 | Map map[string]string 5 | ExitCode *int 6 | } 7 | 8 | func NewMapEnv(m map[string]string) *MapEnv { 9 | return &MapEnv{ 10 | Map: m, 11 | } 12 | } 13 | 14 | func (e *MapEnv) LookupEnv(key string) (string, bool) { 15 | val, ok := e.Map[key] 16 | return val, ok 17 | } 18 | 19 | func (e *MapEnv) Exit(code int) { 20 | e.ExitCode = &code 21 | } 22 | 23 | func (e *MapEnv) Environ() []string { 24 | env := make([]string, 0, len(e.Map)) 25 | for key, val := range e.Map { 26 | env = append(env, key+"="+val) 27 | } 28 | return env 29 | } 30 | -------------------------------------------------------------------------------- /runtime/prelude-int.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import "fmt" 4 | 5 | var _ RuntimeValue = PreludeInt(0) 6 | var PreludeIntTypeRef = MakeRuntimeTypeRef("Int", "prelude") 7 | 8 | type PreludeInt int64 9 | 10 | func (PreludeInt) EagerEvaluate() *RuntimeError { 11 | return nil 12 | } 13 | 14 | func (i PreludeInt) Lookup(member string) (Evaluatable, *RuntimeError) { 15 | return nil, NewRuntimeError(fmt.Errorf("int %d has no member %s", i, member)) 16 | } 17 | 18 | func (PreludeInt) RuntimeType() RuntimeTypeRef { 19 | return PreludeIntTypeRef 20 | } 21 | 22 | func (i PreludeInt) String() string { 23 | return fmt.Sprintf("%d", i) 24 | } 25 | -------------------------------------------------------------------------------- /stdlib/README.md: -------------------------------------------------------------------------------- 1 | # Lithia Standard Library 2 | 3 | There is one implicitly imported module for all files, called `prelude`. 4 | Other modules are not imported implicitly. 5 | 6 | - [apps](./apps.md) 7 | - [booleans](./booleans.md) 8 | - [cmp](./cmp.md) 9 | - [controls](./controls.md) 10 | - [eq](./eq.md) 11 | - [docs](./docs.md) 12 | - [fs](./fs.md) 13 | - [lists](./lists.md) 14 | - [markdown](./markdown.md) 15 | - [markup](./markup.md) 16 | - [optionals](./optionals.md) 17 | - [os](./os.md) 18 | - [prelude](./prelude.md) 19 | - [ranges](./ranges.md) 20 | - [rx](./rx.md) 21 | - [results](./results.md) 22 | - [strings](./strings.md) 23 | - [tests](./tests.md) 24 | -------------------------------------------------------------------------------- /ast/source.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type Source struct { 4 | ModuleName ModuleName 5 | FileName string 6 | Start Position 7 | End Position 8 | } 9 | 10 | type Position struct { 11 | Line int 12 | Column int 13 | } 14 | 15 | func MakeSource( 16 | moduleName ModuleName, 17 | fileName string, 18 | start Position, 19 | end Position, 20 | ) *Source { 21 | return &Source{ 22 | ModuleName: moduleName, 23 | FileName: fileName, 24 | Start: start, 25 | End: end, 26 | } 27 | } 28 | 29 | func MakePosition( 30 | line int, 31 | column int, 32 | ) Position { 33 | return Position{ 34 | Line: line, 35 | Column: column, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /parser/parse-expr-unary.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseUnaryExpr() (*ast.ExprOperatorUnary, []SyntaxError) { 8 | if fp.Node.NamedChildCount() != 1 { 9 | return nil, []SyntaxError{fp.SyntaxErrorf("expected one child, got %d", fp.Node.NamedChildCount())} 10 | } 11 | operator := fp.Node.ChildByFieldName("operator").Content(fp.Source) 12 | 13 | exprP := fp.SameScopeChildParser(fp.Node.NamedChild(0)) 14 | expr, errs := exprP.ParseExpression() 15 | if len(errs) > 0 { 16 | return nil, errs 17 | } 18 | return ast.MakeExprOperatorUnary(ast.OperatorUnary(operator), expr, fp.AstSource()), nil 19 | } 20 | -------------------------------------------------------------------------------- /stdlib/fs/shim.lithia: -------------------------------------------------------------------------------- 1 | module fs 2 | 3 | // used as returns 4 | import results { 5 | Success, 6 | Failure 7 | } 8 | 9 | /// Write a given string to a path. 10 | /// Returns `results.Result`. 11 | extern writeString toPath, newContents 12 | /// Reads a file at a given path. 13 | /// Returns `result.Result`. 14 | extern readString fromPath 15 | /// Returns a Bool if a file or directory exists at a given path. 16 | extern exists atPath 17 | /// Deletes a file or empty directory at a given path. 18 | /// Returns a `result.Result`. 19 | extern delete atPath 20 | /// Deletes a file or any directory at a given path. 21 | /// Returns a `result.Result`. 22 | extern deleteAll atPath 23 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/concat_t.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | import lists 3 | 4 | test "lists.prependList", { fail => 5 | unless (lists.prependList [], []) == [], fail "prepends two empty lists to one empty" 6 | unless (lists.prependList [1, 2], [3, 4]) == [1, 2, 3, 4], fail "prepends two lists" 7 | } 8 | 9 | test "lists.concat", { fail => 10 | unless (lists.concat []) == [], fail "is empty for empty lists" 11 | unless (lists.concat [[], []]) == [], fail "list of empty lists is empty" 12 | unless (lists.concat [[[]], []]) == [[]], fail "more deeply nested lists are elements" 13 | unless (lists.concat [[1, 2], [3], [4]]) == [1, 2, 3, 4], fail "concats three elements" 14 | } 15 | -------------------------------------------------------------------------------- /ast/expr-operator-unary.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprOperatorUnary{} 4 | 5 | type ExprOperatorUnary struct { 6 | Operator OperatorUnary 7 | Expr Expr 8 | 9 | MetaInfo *MetaExpr 10 | } 11 | 12 | func (e ExprOperatorUnary) Meta() *MetaExpr { 13 | return e.MetaInfo 14 | } 15 | 16 | func MakeExprOperatorUnary(operator OperatorUnary, expr Expr, source *Source) *ExprOperatorUnary { 17 | return &ExprOperatorUnary{ 18 | Operator: operator, 19 | Expr: expr, 20 | MetaInfo: &MetaExpr{ 21 | Source: source, 22 | }, 23 | } 24 | } 25 | 26 | func (e ExprOperatorUnary) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 27 | e.Expr.EnumerateNestedDecls(enumerate) 28 | } 29 | -------------------------------------------------------------------------------- /runtime/prelude-module.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | var _ RuntimeValue = PreludeModule{} 4 | var PreludeModuleTypeRef = MakeRuntimeTypeRef("Module", "prelude") 5 | 6 | type PreludeModule struct { 7 | Module *RuntimeModule 8 | } 9 | 10 | func (m PreludeModule) Lookup(member string) (Evaluatable, *RuntimeError) { 11 | value, ok := m.Module.Environment.GetExported(member) 12 | if !ok { 13 | return nil, NewRuntimeErrorf("module %s has no member %s", m.Module.Name, member) 14 | } 15 | return value, nil 16 | } 17 | 18 | func (PreludeModule) RuntimeType() RuntimeTypeRef { 19 | return PreludeModuleTypeRef 20 | } 21 | 22 | func (m PreludeModule) String() string { 23 | return string(m.Module.Name) 24 | } 25 | -------------------------------------------------------------------------------- /ast/expr-member-access.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprMemberAccess{} 4 | 5 | type ExprMemberAccess struct { 6 | Target Expr 7 | AccessPath []Identifier 8 | 9 | MetaInfo *MetaExpr 10 | } 11 | 12 | func (e ExprMemberAccess) Meta() *MetaExpr { 13 | return e.MetaInfo 14 | } 15 | 16 | func MakeExprMemberAccess(target Expr, accessPath []Identifier, source *Source) *ExprMemberAccess { 17 | return &ExprMemberAccess{ 18 | Target: target, 19 | AccessPath: accessPath, 20 | MetaInfo: &MetaExpr{ 21 | Source: source, 22 | }, 23 | } 24 | } 25 | 26 | func (e ExprMemberAccess) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 27 | e.Target.EnumerateNestedDecls(enumerate) 28 | } 29 | -------------------------------------------------------------------------------- /lithia.go: -------------------------------------------------------------------------------- 1 | package lithia 2 | 3 | import ( 4 | extdocs "github.com/vknabel/lithia/external/docs" 5 | extfs "github.com/vknabel/lithia/external/fs" 6 | extos "github.com/vknabel/lithia/external/os" 7 | extrx "github.com/vknabel/lithia/external/rx" 8 | "github.com/vknabel/lithia/runtime" 9 | ) 10 | 11 | func NewDefaultInterpreter(referenceFile string, importRoots ...string) *runtime.Interpreter { 12 | inter := runtime.NewIsolatedInterpreter(referenceFile, importRoots...) 13 | inter.ExternalDefinitions["os"] = extos.New(inter) 14 | inter.ExternalDefinitions["rx"] = extrx.New(inter) 15 | inter.ExternalDefinitions["docs"] = extdocs.New(inter) 16 | inter.ExternalDefinitions["fs"] = extfs.New(inter) 17 | return inter 18 | } 19 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/filter_t.lithia: -------------------------------------------------------------------------------- 1 | import lists 2 | import strings 3 | import tests { test } 4 | 5 | test "lists.filter", { fail => 6 | func constTrue { _ => True } 7 | func constFalse { _ => False } 8 | func greaterThan { n, i => i > n } 9 | 10 | unless (lists.filter constFalse, []) == [], fail "filter none when empty is empty" 11 | unless (lists.filter constFalse, [1, 2, 3]) == [], fail "const false is empty" 12 | unless (lists.filter constTrue, [1, 2, 3]) == [1, 2, 3], fail "const true is equal" 13 | 14 | unless (lists.filter greaterThan 1, [2, 1, 3]) == [2, 3], fail "should filter to [2, 3]" 15 | unless (lists.filter greaterThan 1, [3, 2, 1]) == [3, 2], fail "should filter to [3, 2]" 16 | } 17 | -------------------------------------------------------------------------------- /stdlib/results/result.lithia: -------------------------------------------------------------------------------- 1 | /// The results module is all about failable operations. 2 | module results 3 | 4 | /// A result of a failable operation. 5 | /// 6 | /// ```lithia 7 | /// func positive { n => 8 | /// if n < 0, 9 | /// Failure "negative values not supported!", 10 | /// Success n 11 | /// } 12 | /// 13 | /// with positive, type Result { 14 | /// Success: { success => print success.value }, 15 | /// Failure: { failure => print strings.concat ["failed: ", failure.error] }, 16 | /// } 17 | /// ``` 18 | enum Result { 19 | /// Represents a successful result with a value. 20 | data Success { value } 21 | /// Represents a failed result due to an error. 22 | data Failure { error } 23 | } 24 | -------------------------------------------------------------------------------- /world/world.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | import ( 4 | "io" 5 | "io/fs" 6 | ) 7 | 8 | type World struct { 9 | Stdin io.Reader 10 | Stdout io.Writer 11 | Stderr io.Writer 12 | 13 | FS WorldFS 14 | Env WorldEnv 15 | Args []string 16 | } 17 | 18 | type WorldFS interface { 19 | Getwd() (string, error) 20 | PathSeparator() rune 21 | WriteFile(name string, data []byte, perm fs.FileMode) error 22 | ReadFile(name string) ([]byte, error) 23 | Remove(name string) error 24 | Stat(name string) (fs.FileInfo, error) 25 | Glob(pattern string) (matches []string, err error) 26 | } 27 | 28 | type WorldEnv interface { 29 | Exit(code int) 30 | LookupEnv(key string) (string, bool) 31 | Environ() []string 32 | } 33 | 34 | var Current World = New() 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Versions:** 27 | 28 | - Operating System: 29 | - `lithia --version` 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /runtime/environment-evaluate.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | func (env *Environment) GetEvaluatedRuntimeValue(key string) (RuntimeValue, *RuntimeError) { 4 | if lazyValue, ok := env.GetPrivate(key); ok { 5 | value, err := lazyValue.Evaluate() 6 | if err != nil { 7 | return nil, err 8 | } 9 | return value, nil 10 | } else { 11 | return nil, NewRuntimeErrorf("undefined %s", key) 12 | } 13 | } 14 | 15 | func (env *Environment) GetExportedEvaluatedRuntimeValue(key string) (RuntimeValue, *RuntimeError) { 16 | if lazyValue, ok := env.GetPrivate(key); ok { 17 | value, err := lazyValue.Evaluate() 18 | if err != nil { 19 | return nil, err 20 | } 21 | return value, nil 22 | } else { 23 | return nil, NewRuntimeErrorf("undefined %s", key) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /stdlib/prelude/prelude_t/bool.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | 3 | test "binary || operator", { fail => 4 | when False || False, fail "(False || False) == False" 5 | unless True || False, fail "(True || False) == True" 6 | unless False || True, fail "(False || True) == True" 7 | unless True || True, fail "(True || True) == True" 8 | } 9 | 10 | test "unary ! operator", { fail => 11 | when !True, fail "!True == False" 12 | unless !False, fail "!False == True" 13 | } 14 | 15 | test "binary && operator", { fail => 16 | when False && False, fail "(False && False) == False" 17 | when True && False, fail "(True && False) == False" 18 | when False && True, fail "(False && True) == False" 19 | unless True && True, fail "(True && True) == True" 20 | } 21 | -------------------------------------------------------------------------------- /parser/parse-expr-array.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseExprArray() (*ast.ExprArray, []SyntaxError) { 8 | numberOfElements := int(fp.Node.NamedChildCount()) 9 | elements := make([]ast.Expr, 0, numberOfElements) 10 | for i := 0; i < numberOfElements; i++ { 11 | elementNode := fp.Node.NamedChild(i) 12 | if elementNode.Type() == TYPE_NODE_COMMENT { 13 | fp.Comments = append(fp.Comments, elementNode.Content(fp.Source)) 14 | continue 15 | } 16 | expr, errs := fp.ChildParserConsumingComments(elementNode).ParseExpression() 17 | if len(errs) > 0 { 18 | return nil, errs 19 | } 20 | elements = append(elements, expr) 21 | } 22 | return ast.MakeExprArray(elements, fp.AstSource()), nil 23 | } 24 | -------------------------------------------------------------------------------- /langsrv/handler-hover.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | "github.com/tliron/glsp" 5 | protocol "github.com/tliron/glsp/protocol_3_16" 6 | ) 7 | 8 | func textDocumentHover(context *glsp.Context, params *protocol.HoverParams) (*protocol.Hover, error) { 9 | rc := NewReqContextAtPosition(¶ms.TextDocumentPositionParams) 10 | 11 | name, tokenRange, err := rc.findToken() 12 | if err != nil && tokenRange == nil { 13 | return nil, nil 14 | } 15 | 16 | for _, imported := range rc.accessibleDeclarations(context) { 17 | decl := imported.decl 18 | if string(decl.DeclName()) != name { 19 | continue 20 | } 21 | return &protocol.Hover{ 22 | Contents: documentationMarkupContentForDecl(decl), 23 | Range: tokenRange, 24 | }, nil 25 | } 26 | return nil, nil 27 | } 28 | -------------------------------------------------------------------------------- /langsrv/handler-initialized.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | "path" 5 | "strings" 6 | 7 | "github.com/tliron/glsp" 8 | protocol "github.com/tliron/glsp/protocol_3_16" 9 | "github.com/vknabel/lithia/world" 10 | ) 11 | 12 | func initialized(context *glsp.Context, params *protocol.InitializedParams) error { 13 | for _, root := range ls.workspaceRoots { 14 | matches, err := world.Current.FS.Glob(path.Join(strings.TrimPrefix("file://", root), "**/*.lithia")) 15 | if err != nil { 16 | ls.server.Log.Errorf("package detection failed, due %s", err) 17 | continue 18 | } 19 | for _, match := range matches { 20 | mod := ls.resolver.ResolvePackageAndModuleForReferenceFile(match) 21 | openModuleTextDocumentsIfNeeded(context, mod) 22 | } 23 | } 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /ast/decl-enum-case.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Decl = DeclEnumCase{} 4 | 5 | type DeclEnumCase struct { 6 | Name Identifier 7 | 8 | Docs *Docs 9 | MetaInfo *MetaDecl 10 | } 11 | 12 | func (e DeclEnumCase) DeclName() Identifier { 13 | return e.Name 14 | } 15 | 16 | func (e DeclEnumCase) Meta() *MetaDecl { 17 | return e.MetaInfo 18 | } 19 | 20 | func (e DeclEnumCase) IsExportedDecl() bool { 21 | return true 22 | } 23 | 24 | func MakeDeclEnumCase(name Identifier) *DeclEnumCase { 25 | return &DeclEnumCase{ 26 | Name: name, 27 | MetaInfo: &MetaDecl{}, 28 | } 29 | } 30 | 31 | func (decl DeclEnumCase) ProvidedDocs() *Docs { 32 | return decl.Docs 33 | } 34 | 35 | func (DeclEnumCase) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 36 | // no nested decls 37 | } 38 | -------------------------------------------------------------------------------- /stdlib/cmp/equatableFrom.lithia: -------------------------------------------------------------------------------- 1 | module cmp 2 | 3 | import eq 4 | 5 | /// Creates an `eq.Equatable` from a `cmp.Comparable`. 6 | /// `cmp.Equal` will result in `True`, 7 | /// `cmp.Ascending` and `cmp.Descending` will be `False`. 8 | /// 9 | /// @returns eq.Equatable 10 | func equatableFrom { comparableWitness => 11 | eq.Equatable { lhs, rhs => 12 | with (comparableWitness.compare lhs, rhs), type Order { 13 | Equal: { _ => True }, 14 | Any: { _ => False } 15 | } 16 | } 17 | } 18 | 19 | import tests { test } 20 | 21 | test "cmp.equatableFrom creates an equatable", { fail => 22 | let numericEq = equatableFrom cmp.numeric 23 | when (numericEq.equal 1, 2), fail "1 is not 2" 24 | unless (numericEq.equal 1, 1), fail "1 should equal 1" 25 | } 26 | -------------------------------------------------------------------------------- /stdlib/strings.md: -------------------------------------------------------------------------------- 1 | # strings 2 | 3 | _module_ 4 | Provides convenience functions around the basic String type. 5 | Currently pretty limited. 6 | 7 | - _func_ [concat](#concat) listOfStrings 8 | - _func_ [join](#join) separator, listOfStrings 9 | 10 | ## concat 11 | 12 | _func_ `concat listOfStrings` 13 | 14 | Concatenates a list of given strings in order. 15 | 16 | ``` 17 | strings.concat ["Hello ", "World", "!"] 18 | // "Hello World!" 19 | ``` 20 | 21 | ## join 22 | 23 | _func_ `join separator, listOfStrings` 24 | 25 | Joins a list of strings with a given separator. 26 | The separator will only be inserted between two elements. 27 | If there are none or just one element, there won't be any separator. 28 | 29 | ``` 30 | strings.join " ", ["Hello", "World!"] 31 | // "Hello World!" 32 | ``` 33 | 34 | -------------------------------------------------------------------------------- /ast/expr-operator-binary.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprOperatorBinary{} 4 | 5 | type ExprOperatorBinary struct { 6 | Operator OperatorBinary 7 | Left Expr 8 | Right Expr 9 | 10 | MetaInfo *MetaExpr 11 | } 12 | 13 | func (e ExprOperatorBinary) Meta() *MetaExpr { 14 | return e.MetaInfo 15 | } 16 | 17 | func MakeExprOperatorBinary(operator OperatorBinary, left, right Expr, source *Source) *ExprOperatorBinary { 18 | return &ExprOperatorBinary{ 19 | Operator: operator, 20 | Left: left, 21 | Right: right, 22 | MetaInfo: &MetaExpr{ 23 | Source: source, 24 | }, 25 | } 26 | } 27 | 28 | func (e ExprOperatorBinary) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 29 | e.Left.EnumerateNestedDecls(enumerate) 30 | e.Right.EnumerateNestedDecls(enumerate) 31 | } 32 | -------------------------------------------------------------------------------- /world/osfs.go: -------------------------------------------------------------------------------- 1 | package world 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | "path/filepath" 7 | ) 8 | 9 | type OSFS struct{} 10 | 11 | func (OSFS) Getwd() (string, error) { 12 | return os.Getwd() 13 | } 14 | 15 | func (OSFS) PathSeparator() rune { 16 | return os.PathSeparator 17 | } 18 | 19 | func (OSFS) WriteFile(name string, data []byte, perm fs.FileMode) error { 20 | return os.WriteFile(name, data, os.FileMode(perm)) 21 | } 22 | 23 | func (OSFS) ReadFile(name string) ([]byte, error) { 24 | return os.ReadFile(name) 25 | } 26 | 27 | func (OSFS) Remove(name string) error { 28 | return os.Remove(name) 29 | } 30 | 31 | func (OSFS) Stat(name string) (fs.FileInfo, error) { 32 | return os.Stat(name) 33 | } 34 | 35 | func (OSFS) Glob(name string) ([]string, error) { 36 | return filepath.Glob(name) 37 | } 38 | -------------------------------------------------------------------------------- /ast/expr-invocation.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprInvocation{} 4 | 5 | type ExprInvocation struct { 6 | Function Expr 7 | Arguments []*Expr 8 | 9 | MetaInfo *MetaExpr 10 | } 11 | 12 | func (e ExprInvocation) Meta() *MetaExpr { 13 | return e.MetaInfo 14 | } 15 | 16 | func MakeExprInvocation(function Expr, source *Source) *ExprInvocation { 17 | return &ExprInvocation{ 18 | Function: function, 19 | MetaInfo: &MetaExpr{Source: source}, 20 | } 21 | } 22 | 23 | func (e *ExprInvocation) AddArgument(argument Expr) { 24 | e.Arguments = append(e.Arguments, &argument) 25 | } 26 | 27 | func (e ExprInvocation) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 28 | e.Function.EnumerateNestedDecls(enumerate) 29 | 30 | for _, arg := range e.Arguments { 31 | (*arg).EnumerateNestedDecls(enumerate) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /langsrv/node-position-helpers_test.go: -------------------------------------------------------------------------------- 1 | package langsrv_test 2 | 3 | // import ( 4 | // "testing" 5 | 6 | // "github.com/TobiasYin/go-lsp/lsp/defines" 7 | // "github.com/vknabel/lithia/langsrv" 8 | // "github.com/vknabel/lithia/parser" 9 | // ) 10 | 11 | // func TestNodeAtPosition(t *testing.T) { 12 | // p := parser.NewParser() 13 | // fp, err := p.Parse("module", "test.go", `module test`) 14 | // if len(err) > 0 { 15 | // t.Error(err) 16 | // return 17 | // } 18 | 19 | // root := fp.Tree.RootNode() 20 | // node := langsrv.NodeAtPosition(root, defines.Position{Line: 0, Character: 9}) 21 | // if node == nil { 22 | // t.Error("node not found") 23 | // return 24 | // } 25 | // if node.Type() != parser.TYPE_NODE_IDENTIFIER { 26 | // t.Errorf("expected TYPE_NODE_IDENTIFIER, got %s", node.Type()) 27 | // } 28 | // } 29 | -------------------------------------------------------------------------------- /ast/decl-parameter.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Decl = DeclParameter{} 4 | 5 | type DeclParameter struct { 6 | Name Identifier 7 | 8 | Docs *Docs 9 | MetaInfo *MetaDecl 10 | } 11 | 12 | func (e DeclParameter) DeclName() Identifier { 13 | return e.Name 14 | } 15 | 16 | func (e DeclParameter) Meta() *MetaDecl { 17 | return e.MetaInfo 18 | } 19 | 20 | func (e DeclParameter) IsExportedDecl() bool { 21 | return true 22 | } 23 | 24 | func MakeDeclParameter(name Identifier, source *Source) *DeclParameter { 25 | return &DeclParameter{ 26 | Name: name, 27 | MetaInfo: &MetaDecl{ 28 | Source: source, 29 | }, 30 | } 31 | } 32 | 33 | func (decl DeclParameter) ProvidedDocs() *Docs { 34 | return decl.Docs 35 | } 36 | 37 | func (DeclParameter) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 38 | // no nested decls 39 | } 40 | -------------------------------------------------------------------------------- /parser/parse-expr-member-access.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseExprMemberAccess() (*ast.ExprMemberAccess, []SyntaxError) { 8 | if fp.Node.NamedChildCount() < 2 { 9 | return nil, []SyntaxError{fp.SyntaxErrorf("expected at least 2 children, got %d", fp.Node.NamedChildCount())} 10 | } 11 | targetExpr, errs := fp.SameScopeChildParser(fp.Node.NamedChild(0)).ParseExpression() 12 | if len(errs) > 0 { 13 | return nil, errs 14 | } 15 | 16 | keyPath := make([]ast.Identifier, 0, fp.Node.NamedChildCount()-1) 17 | for i := 1; i < int(fp.Node.NamedChildCount()); i++ { 18 | child := fp.Node.NamedChild(i) 19 | keyPath = append(keyPath, ast.Identifier(child.Content(fp.Source))) 20 | } 21 | return ast.MakeExprMemberAccess(targetExpr, keyPath, fp.AstSource()), nil 22 | } 23 | -------------------------------------------------------------------------------- /runtime/interpreter-prelude.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/vknabel/lithia/ast" 7 | "github.com/vknabel/lithia/resolution" 8 | ) 9 | 10 | func (inter *Interpreter) NewPreludeEnvironment(resolvedModule resolution.ResolvedModule) (*Environment, error) { 11 | if inter.Prelude != nil { 12 | return inter.Prelude, nil 13 | } 14 | env := NewEnvironment(nil) 15 | inter.Prelude = env 16 | 17 | module, err := inter.LoadModuleIfNeeded(ast.ModuleName("prelude"), resolvedModule) 18 | if err != nil { 19 | return nil, fmt.Errorf("error loading prelude: %v\nIs $LITHIA_STDLIB set up correctly?", err) 20 | } 21 | // These declares override the ones in the prelude. 22 | env.Parent = &Environment{Parent: nil, Scope: module.Environment.Scope, Unexported: module.Environment.Unexported} 23 | 24 | return env, nil 25 | } 26 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/monad_t.lithia: -------------------------------------------------------------------------------- 1 | import lists 2 | import strings 3 | import tests { test } 4 | import controls 5 | 6 | test "lists.flatMap", { fail => 7 | func stringifiedReplicate { list => 8 | let replications = lists.flatMap { i => lists.replicate i, i }, list 9 | strings.join "", replications 10 | } 11 | when (stringifiedReplicate []) != "", fail "empty" 12 | when (stringifiedReplicate [1]) != "1", fail "one replication" 13 | when (stringifiedReplicate [2]) != "22", fail "two replications" 14 | when (stringifiedReplicate [2, 3]) != "22333", fail "two and three replications" 15 | } 16 | 17 | test "lists is a monad instance", { fail => 18 | unless (controls.monadFrom lists) != Void, fail "module lists is a monad" 19 | unless (controls.monadFrom lists.monad) != Void, fail "var lists.monad is a monad" 20 | } 21 | -------------------------------------------------------------------------------- /stdlib/pot/src/store.lithia: -------------------------------------------------------------------------------- 1 | module pot 2 | 3 | import apps 4 | import lists 5 | 6 | data State { 7 | cmds 8 | deps 9 | } 10 | 11 | enum Msg { 12 | data SetCmd { 13 | cmd 14 | } 15 | data SetDep { 16 | dependency 17 | } 18 | } 19 | 20 | let model = apps.defineModel (State [:], [:]), { state, msg => 21 | with msg, type Msg { 22 | SetCmd: { action => 23 | let cmds = state.cmds.set action.cmd.name, action.cmd 24 | let next = State cmds, state.deps 25 | apps.Update next, None 26 | }, 27 | SetDep: { action => 28 | let deps = state.deps.set action.dependency.name, action.dependency 29 | let next = State state.cmds, deps 30 | apps.Update next, None 31 | }, 32 | } 33 | } 34 | 35 | let store = apps.storeFrom model 36 | -------------------------------------------------------------------------------- /parser/parse-expr-binary.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseBinaryExpr() (*ast.ExprOperatorBinary, []SyntaxError) { 8 | if fp.Node.NamedChildCount() != 2 { 9 | return nil, []SyntaxError{fp.SyntaxErrorf("expected 2 children, got %d", fp.Node.NamedChildCount())} 10 | } 11 | operator := fp.Node.ChildByFieldName("operator").Content(fp.Source) 12 | 13 | leftP := fp.SameScopeChildParser(fp.Node.NamedChild(0)) 14 | left, lerrs := leftP.ParseExpression() 15 | if len(lerrs) > 0 { 16 | return nil, lerrs 17 | } 18 | rightP := fp.SameScopeChildParser(fp.Node.NamedChild(1)) 19 | right, rerrs := rightP.ParseExpression() 20 | if len(rerrs) > 0 { 21 | return nil, rerrs 22 | } 23 | return ast.MakeExprOperatorBinary(ast.OperatorBinary(operator), left, right, fp.AstSource()), nil 24 | } 25 | -------------------------------------------------------------------------------- /stdlib/eq.md: -------------------------------------------------------------------------------- 1 | # eq 2 | 3 | _module_ 4 | 5 | - _data_ [Equatable](#Equatable) 6 | - _func_ [negated](#negated) witness 7 | - _func_ [pullback](#pullback) transform, witness 8 | 9 | ## Equatable 10 | 11 | _data_ Allows comparision of values for equality. 12 | Declare and pass a witness for custom equality. 13 | 14 | In contrast to the default equality operator ==, you can define custom equality. 15 | If you explicitly want the strict behavior, pick the `strict` witness. 16 | 17 | ### Properties 18 | 19 | - `equal lhs, rhs` 20 | 21 | ## negated 22 | 23 | _func_ `negated witness` 24 | 25 | Negates the result of the given `Equatable`. 26 | 27 | ## pullback 28 | 29 | _func_ `pullback transform, witness` 30 | 31 | Transforms the inputs of an `Equatable`-witness. 32 | 33 | ``` 34 | cmp.pullback { person => person.name }, insensitiveEquatable, Person "Somebody" 35 | ``` 36 | 37 | -------------------------------------------------------------------------------- /stdlib/rx/future.lithia: -------------------------------------------------------------------------------- 1 | module rx 2 | import results 3 | 4 | /// Represents a value calculated in background. 5 | /// It will arrive some time in the future. 6 | /// 7 | /// ```lithia 8 | /// import results 9 | /// import rx 10 | /// 11 | /// let future = rx.Future { receive => 12 | /// // will be performed in background 13 | /// receive results.Success 42 14 | /// } 15 | /// 16 | /// // the .await will block and wait for the result 17 | /// with future.await, type results.Result { 18 | /// Success: { value => value }, 19 | /// Failure: { err => 20 | /// print err 21 | /// 0 // as default 22 | /// }, 23 | /// } 24 | /// ``` 25 | extern Future { 26 | /// Waits for the future to complete. 27 | /// This will lock the current function until the result has arrived. 28 | /// At the end, returns the `results.Result`. 29 | await 30 | } 31 | -------------------------------------------------------------------------------- /runtime/evaluatable-lazy.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import "sync" 4 | 5 | var _ Evaluatable = &LazyEvaluatable{} 6 | 7 | type LazyEvaluatable struct { 8 | once *sync.Once 9 | value RuntimeValue 10 | err *RuntimeError 11 | eval func() (RuntimeValue, *RuntimeError) 12 | } 13 | 14 | func NewLazyRuntimeValue(eval func() (RuntimeValue, *RuntimeError)) *LazyEvaluatable { 15 | return &LazyEvaluatable{ 16 | once: &sync.Once{}, 17 | eval: eval, 18 | value: nil, 19 | } 20 | } 21 | 22 | func NewConstantRuntimeValue(value RuntimeValue) *LazyEvaluatable { 23 | return &LazyEvaluatable{ 24 | once: &sync.Once{}, 25 | eval: func() (RuntimeValue, *RuntimeError) { return value, nil }, 26 | value: value, 27 | } 28 | } 29 | 30 | func (l *LazyEvaluatable) Evaluate() (RuntimeValue, *RuntimeError) { 31 | l.once.Do(func() { 32 | l.value, l.err = l.eval() 33 | }) 34 | return l.value, l.err 35 | } 36 | -------------------------------------------------------------------------------- /parser/parser.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "context" 5 | 6 | sitter "github.com/smacker/go-tree-sitter" 7 | "github.com/vknabel/lithia/ast" 8 | syntax "github.com/vknabel/tree-sitter-lithia" 9 | ) 10 | 11 | type Parser struct { 12 | } 13 | 14 | func NewParser() *Parser { 15 | return &Parser{} 16 | } 17 | 18 | func (*Parser) Parse(moduleName ast.ModuleName, file string, contents string) (*FileParser, []SyntaxError) { 19 | parser := sitter.NewParser() 20 | lang := syntax.GetLanguage() 21 | parser.SetLanguage(lang) 22 | 23 | input := []byte(contents) 24 | tree, err := parser.ParseCtx(context.TODO(), nil, input) 25 | if err != nil { 26 | panic(err) 27 | } 28 | fileParser := NewFileParser(moduleName, file, tree.RootNode(), tree, input) 29 | if tree.RootNode().HasError() { 30 | return fileParser, MakeSyntaxParsingError(file, contents, tree).SyntaxErrors() 31 | } 32 | return fileParser, nil 33 | } 34 | -------------------------------------------------------------------------------- /ast/docs.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "strings" 4 | 5 | type Docs struct { 6 | // not only text, but also parsing @type and @param, ... 7 | // or more general? 8 | Content string 9 | } 10 | 11 | func MakeDocs(comments []string) *Docs { 12 | docsStrings := make([]string, 0) 13 | for _, comment := range comments { 14 | if strings.HasPrefix(comment, "///") { 15 | docsStrings = append(docsStrings, strings.TrimPrefix(strings.TrimPrefix(strings.TrimSpace(comment), "///"), " ")) 16 | } else if strings.HasPrefix(comment, "/**") { 17 | trimmed := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(comment, "/**"), "*/")) 18 | lines := strings.Split(trimmed, "\n") 19 | for _, line := range lines { 20 | docsStrings = append(docsStrings, strings.TrimPrefix(strings.TrimPrefix(strings.TrimSpace(line), "*"), " ")) 21 | } 22 | } 23 | } 24 | return &Docs{ 25 | Content: strings.Join(docsStrings, "\n"), 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /external/rx/external-rx.go: -------------------------------------------------------------------------------- 1 | package rx 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | "github.com/vknabel/lithia/runtime" 6 | ) 7 | 8 | var _ runtime.ExternalDefinition = ExternalRx{} 9 | 10 | type ExternalRx struct { 11 | inter *runtime.Interpreter 12 | } 13 | 14 | func New(inter *runtime.Interpreter) ExternalRx { 15 | return ExternalRx{inter} 16 | } 17 | 18 | func (e ExternalRx) Lookup(name string, env *runtime.Environment, decl ast.Decl) (runtime.RuntimeValue, bool) { 19 | switch name { 20 | case "Variable": 21 | if decl, ok := decl.(ast.DeclExternType); ok { 22 | return RxVariableType{decl}, true 23 | } else { 24 | panic("rx.Variable must be an extern type") 25 | } 26 | case "Future": 27 | if decl, ok := decl.(ast.DeclExternType); ok { 28 | return RxFutureType{decl, e}, true 29 | } else { 30 | panic("rx.Future must be an extern type") 31 | } 32 | default: 33 | return nil, false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ast/decl-module.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | var _ Decl = DeclModule{} 6 | var _ Overviewable = DeclModule{} 7 | 8 | type DeclModule struct { 9 | Name Identifier 10 | 11 | Docs *Docs 12 | MetaInfo *MetaDecl 13 | } 14 | 15 | func (e DeclModule) DeclName() Identifier { 16 | return e.Name 17 | } 18 | 19 | func (e DeclModule) DeclOverview() string { 20 | return fmt.Sprintf("module %s", e.Name) 21 | } 22 | 23 | func (e DeclModule) Meta() *MetaDecl { 24 | return e.MetaInfo 25 | } 26 | 27 | func (e DeclModule) IsExportedDecl() bool { 28 | return false 29 | } 30 | 31 | func MakeDeclModule(internalName Identifier, source *Source) *DeclModule { 32 | return &DeclModule{Name: internalName, MetaInfo: &MetaDecl{source}} 33 | } 34 | 35 | func (decl DeclModule) ProvidedDocs() *Docs { 36 | return decl.Docs 37 | } 38 | 39 | func (DeclModule) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 40 | // no nested decls 41 | } 42 | -------------------------------------------------------------------------------- /stdlib/lists/drop.lithia: -------------------------------------------------------------------------------- 1 | module lists 2 | 3 | /// Returns the list without the first element. 4 | func dropFirst { list => 5 | with list, type List { 6 | Cons: { cons => cons.tail }, 7 | Nil: { _ => Nil } 8 | } 9 | } 10 | 11 | /// Returns the list without the first `n` elements. 12 | /// Negative numbers will be treated as `0`. 13 | func dropN { n, list => 14 | let zeroResult = list 15 | let positiveResult = with list, type List { 16 | Cons: { cons => dropN n - 1, cons.tail }, 17 | Nil: { _ => Nil } 18 | } 19 | 20 | if n > 0, positiveResult, zeroResult 21 | } 22 | 23 | /// Returns the list without the first elements where the given predicate is True. 24 | func dropWhile { predicate, list => 25 | with list, type List { 26 | Cons: { cons => 27 | (if (predicate cons.head), (dropWhile predicate, cons.tail), list) 28 | }, 29 | Nil: { _ => Nil } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /stdlib/markup/format.lithia: -------------------------------------------------------------------------------- 1 | /// A generalized data format to represent human readable contents. 2 | /// Though it is not meant to represent HTML nodes. 3 | module markup 4 | 5 | /// A specific markup format to convert to. 6 | data Format { 7 | /// Converts given markup to this format. 8 | convert markup 9 | } 10 | 11 | /// A more generalized form of a markup.Format. 12 | /// Allows to use plain functions and whole modules as format. 13 | enum FormatWitness { 14 | Format 15 | Module 16 | Function 17 | } 18 | 19 | /// Creates a Format from a given FormatWitness. 20 | func from { witness => 21 | with witness, type FormatWitness { 22 | Format: { s => s }, 23 | Module: { m => Format m.serialize }, 24 | Function: { f => Format f }, 25 | } 26 | } 27 | 28 | /// Converts some markup to a target format. 29 | func convert { markup, targetFormatWitness => 30 | (from targetFormatWitness).convert markup 31 | } 32 | -------------------------------------------------------------------------------- /langsrv/handler-workspace-symbol.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | "github.com/tliron/glsp" 5 | protocol "github.com/tliron/glsp/protocol_3_16" 6 | ) 7 | 8 | func workspaceSymbol(context *glsp.Context, params *protocol.WorkspaceSymbolParams) ([]protocol.SymbolInformation, error) { 9 | symbols := make([]protocol.SymbolInformation, 0) 10 | 11 | for uri, document := range ls.documentCache.documents { 12 | exported := document.sourceFile.ExportedDeclarations() 13 | for _, decl := range exported { 14 | containerName := string(decl.Meta().ModuleName) 15 | symbols = append(symbols, protocol.SymbolInformation{ 16 | Name: string(decl.DeclName()), 17 | ContainerName: &containerName, 18 | Kind: symbolKindClassForDecl(decl), 19 | Location: protocol.Location{ 20 | URI: uri, 21 | Range: rangeFromAstSourceLocation(decl.Meta().Source), 22 | }, 23 | }) 24 | } 25 | } 26 | return symbols, nil 27 | } 28 | -------------------------------------------------------------------------------- /stdlib/strings/join.lithia: -------------------------------------------------------------------------------- 1 | module strings 2 | 3 | import lists 4 | 5 | /** 6 | * Joins a list of strings with a given separator. 7 | * The separator will only be inserted between two elements. 8 | * If there are none or just one element, there won't be any separator. 9 | * 10 | * ``` 11 | * strings.join " ", ["Hello", "World!"] 12 | * // "Hello World!" 13 | * ``` 14 | */ 15 | func join { separator, listOfStrings => 16 | with listOfStrings, type List { 17 | Cons: { first => 18 | with first.tail, type List { 19 | Cons: { lookahead => 20 | strings.concat [ 21 | first.head, 22 | separator, 23 | (strings.join separator, lookahead) 24 | ] 25 | }, 26 | Nil: { nil => "".append first.head } 27 | } 28 | }, 29 | Nil: { nil => "" } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /proposals/README.md: -------------------------------------------------------------------------------- 1 | # Lithia Evolution Proposals 2 | 3 | The development of Lithia is now driven by evolution proposals. New proposals can be created by anyone. As a starting point, you can [copy the template](./LE-000-template.md). 4 | 5 | ## Proposals 6 | 7 | | Proposal | Status | Title | 8 | | --------------------------------------------- | -------- | ----------------------- | 9 | | [LE-001](./LE-001-sandboxing-and-timeouts.md) | Accepted | Sandboxing and Timeouts | 10 | | [LE-002](./LE-002-potfile-scripting.md) | v0.0.19 | Potfile Scripting | 11 | | [LE-003](./LE-003-string-concatenation.md) | Accepted | String Concatenation | 12 | | [LE-004](./LE-004-new-concurrency-model.md) | Accepted | New Concurreny Model | 13 | 14 | If you want to have a look on all proposals including drafts, head over to the [issues tab](https://github.com/vknabel/lithia/issues?q=label%3Aproposal+). 15 | -------------------------------------------------------------------------------- /app/lithia/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | "github.com/vknabel/lithia/info" 8 | ) 9 | 10 | func Execute() error { 11 | return rootCmd.Execute() 12 | } 13 | 14 | var rootCmd = &cobra.Command{ 15 | Use: "lithia", 16 | Short: "Lithia programming language", 17 | Long: "Lithia is an experimental functional programming language " + 18 | "with an implicit but strong and dynamic type system.\n" + 19 | "It is designed around a few core concepts in mind " + 20 | "all language features contribute to.\n" + 21 | "\n" + 22 | "Lean more at https://github.com/vknabel/lithia", 23 | Version: fmt.Sprintf("%s\ncommit: %s\nbuilt by: %s\nbuilt at: %s", info.Version, info.Commit, info.Date, info.BuiltBy), 24 | Args: cobra.MinimumNArgs(0), 25 | Run: func(cmd *cobra.Command, args []string) { 26 | if len(args) >= 1 { 27 | runFile(args[0], args) 28 | } else { 29 | runPrompt() 30 | } 31 | }, 32 | } 33 | -------------------------------------------------------------------------------- /stdlib/fs.md: -------------------------------------------------------------------------------- 1 | # fs 2 | 3 | _module_ 4 | 5 | - _extern_ [delete](#delete) atPath 6 | - _extern_ [deleteAll](#deleteAll) atPath 7 | - _extern_ [exists](#exists) atPath 8 | - _extern_ [readString](#readString) fromPath 9 | - _extern_ [writeString](#writeString) toPath, newContents 10 | 11 | ## delete 12 | 13 | _func_ `delete atPath` 14 | 15 | Deletes a file or empty directory at a given path. 16 | Returns a `result.Result`. 17 | 18 | ## deleteAll 19 | 20 | _func_ `deleteAll atPath` 21 | 22 | Deletes a file or any directory at a given path. 23 | Returns a `result.Result`. 24 | 25 | ## exists 26 | 27 | _func_ `exists atPath` 28 | 29 | Returns a Bool if a file or directory exists at a given path. 30 | 31 | ## readString 32 | 33 | _func_ `readString fromPath` 34 | 35 | Reads a file at a given path. 36 | Returns `result.Result`. 37 | 38 | ## writeString 39 | 40 | _func_ `writeString toPath, newContents` 41 | 42 | Write a given string to a path. 43 | Returns `results.Result`. 44 | 45 | -------------------------------------------------------------------------------- /ast/expr-type-switch.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprTypeSwitch{} 4 | 5 | type ExprTypeSwitch struct { 6 | Type Expr 7 | CaseOrder []Identifier 8 | Cases map[Identifier]Expr 9 | 10 | MetaInfo *MetaExpr 11 | } 12 | 13 | func (e ExprTypeSwitch) Meta() *MetaExpr { 14 | return e.MetaInfo 15 | } 16 | 17 | func MakeExprTypeSwitch(type_ Expr, source *Source) *ExprTypeSwitch { 18 | return &ExprTypeSwitch{ 19 | Type: type_, 20 | CaseOrder: make([]Identifier, 0), 21 | Cases: make(map[Identifier]Expr), 22 | MetaInfo: &MetaExpr{ 23 | Source: source, 24 | }, 25 | } 26 | } 27 | 28 | func (e *ExprTypeSwitch) AddCase(key Identifier, value Expr) { 29 | e.CaseOrder = append(e.CaseOrder, key) 30 | e.Cases[key] = value 31 | } 32 | 33 | func (e ExprTypeSwitch) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 34 | e.Type.EnumerateNestedDecls(enumerate) 35 | for _, ident := range e.CaseOrder { 36 | e.Cases[ident].EnumerateNestedDecls(enumerate) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ast/expr-dict.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprDict{} 4 | 5 | type ExprDict struct { 6 | Entries []ExprDictEntry 7 | 8 | MetaInfo *MetaExpr 9 | } 10 | 11 | func (e ExprDict) Meta() *MetaExpr { 12 | return e.MetaInfo 13 | } 14 | 15 | func MakeExprDict(entries []ExprDictEntry, source *Source) *ExprDict { 16 | return &ExprDict{ 17 | Entries: entries, 18 | MetaInfo: &MetaExpr{ 19 | Source: source, 20 | }, 21 | } 22 | } 23 | 24 | func (e ExprDict) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 25 | for _, el := range e.Entries { 26 | el.Key.EnumerateNestedDecls(enumerate) 27 | el.Value.EnumerateNestedDecls(enumerate) 28 | } 29 | } 30 | 31 | type ExprDictEntry struct { 32 | Key Expr 33 | Value Expr 34 | MetaInfo *MetaExpr 35 | } 36 | 37 | func MakeExprDictEntry(key Expr, value Expr, source *Source) *ExprDictEntry { 38 | return &ExprDictEntry{ 39 | Key: key, 40 | Value: value, 41 | MetaInfo: &MetaExpr{ 42 | Source: source, 43 | }, 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /runtime/prelude-callable-type.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import "github.com/vknabel/lithia/ast" 4 | 5 | var PreludeFunctionTypeRef = MakeRuntimeTypeRef("Function", "prelude") 6 | 7 | func Call(function RuntimeValue, args []Evaluatable, fromExpr ast.Expr) (RuntimeValue, *RuntimeError) { 8 | callable, ok := function.(CallableRuntimeValue) 9 | if !ok { 10 | return nil, NewRuntimeErrorf("%s is not callable", function).CascadeCall(nil, fromExpr) 11 | } 12 | 13 | arity := callable.Arity() 14 | if len(args) < arity { 15 | return MakeCurriedCallable(callable, args), nil 16 | } 17 | intermediate, err := callable.Call(args[:arity], fromExpr) 18 | if err != nil { 19 | return nil, err.CascadeCall(callable, fromExpr) 20 | } 21 | if len(args) == arity { 22 | return intermediate, nil 23 | } 24 | if g, ok := intermediate.(CallableRuntimeValue); ok { 25 | return g.Call(args[arity:], fromExpr) 26 | } else { 27 | return nil, NewRuntimeErrorf("%s is not callable", g).CascadeCall(nil, fromExpr) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ast/decl-import-member.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | var _ Decl = DeclImportMember{} 6 | var _ Overviewable = DeclImportMember{} 7 | 8 | type DeclImportMember struct { 9 | Name Identifier 10 | ModuleName ModuleName 11 | 12 | MetaInfo *MetaDecl 13 | } 14 | 15 | func (e DeclImportMember) DeclName() Identifier { 16 | return e.Name 17 | } 18 | 19 | func (e DeclImportMember) DeclOverview() string { 20 | return fmt.Sprintf("import %s { %s }", e.ModuleName, e.Name) 21 | } 22 | 23 | func (e DeclImportMember) Meta() *MetaDecl { 24 | return e.MetaInfo 25 | } 26 | 27 | func (e DeclImportMember) IsExportedDecl() bool { 28 | return false 29 | } 30 | 31 | func MakeDeclImportMember(moduleName ModuleName, name Identifier, source *Source) DeclImportMember { 32 | return DeclImportMember{ 33 | Name: name, 34 | ModuleName: moduleName, 35 | MetaInfo: &MetaDecl{source}, 36 | } 37 | } 38 | 39 | func (DeclImportMember) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 40 | // no nested decls 41 | } 42 | -------------------------------------------------------------------------------- /runtime/interpreter-module.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | "github.com/vknabel/lithia/resolution" 6 | ) 7 | 8 | type FileName string 9 | 10 | type RuntimeModule struct { 11 | Name ast.ModuleName 12 | Environment *Environment 13 | Files map[FileName]*InterpreterContext 14 | resolved resolution.ResolvedModule 15 | 16 | Decl *ast.ContextModule 17 | 18 | // docs can be derived from the files 19 | } 20 | 21 | func (inter *Interpreter) NewModule(resolvedModule resolution.ResolvedModule) (*RuntimeModule, error) { 22 | env, err := inter.NewPreludeEnvironment(resolvedModule) 23 | if err != nil { 24 | return nil, err 25 | } 26 | name := resolvedModule.AbsoluteModuleName() 27 | module := &RuntimeModule{ 28 | Name: name, 29 | Environment: NewEnvironment(env), 30 | Files: make(map[FileName]*InterpreterContext), 31 | Decl: ast.MakeContextModule(name), 32 | resolved: resolvedModule, 33 | } 34 | inter.Modules[name] = module 35 | return module, nil 36 | } 37 | -------------------------------------------------------------------------------- /ast/decl-constant.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import "fmt" 4 | 5 | var _ Decl = DeclConstant{} 6 | var _ Overviewable = DeclConstant{} 7 | 8 | type DeclConstant struct { 9 | Name Identifier 10 | Value Expr 11 | 12 | Docs *Docs 13 | MetaInfo *MetaDecl 14 | } 15 | 16 | func (e DeclConstant) DeclName() Identifier { 17 | return e.Name 18 | } 19 | 20 | func (e DeclConstant) DeclOverview() string { 21 | return fmt.Sprintf("let %s", e.Name) 22 | } 23 | 24 | func (e DeclConstant) Meta() *MetaDecl { 25 | return e.MetaInfo 26 | } 27 | 28 | func (e DeclConstant) IsExportedDecl() bool { 29 | return true 30 | } 31 | 32 | func MakeDeclConstant(name Identifier, value Expr, source *Source) *DeclConstant { 33 | return &DeclConstant{ 34 | Name: name, 35 | Value: value, 36 | MetaInfo: &MetaDecl{source}, 37 | } 38 | } 39 | 40 | func (e DeclConstant) ProvidedDocs() *Docs { 41 | return e.Docs 42 | } 43 | 44 | func (e DeclConstant) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 45 | e.Value.EnumerateNestedDecls(enumerate) 46 | } 47 | -------------------------------------------------------------------------------- /stdlib/eq/equatable.lithia: -------------------------------------------------------------------------------- 1 | module eq 2 | 3 | import controls { Contravariant } 4 | 5 | /** 6 | * Allows comparision of values for equality. 7 | * Declare and pass a witness for custom equality. 8 | * 9 | * In contrast to the default equality operator ==, you can define custom equality. 10 | * If you explicitly want the strict behavior, pick the `strict` witness. 11 | */ 12 | data Equatable { 13 | equal lhs, rhs 14 | } 15 | 16 | /// An Equatable witness for strict comparision as the == operator does. 17 | let strict = Equatable { lhs, rhs => lhs == rhs } 18 | 19 | import tests { test } 20 | 21 | test "eq.strict", { fail => 22 | when (strict.equal 1, 2), fail "1 != 2" 23 | when (strict.equal 1, -1), fail "1 != -1" 24 | when (strict.equal 1, "1"), fail "1 != '1'" 25 | when (strict.equal 0, False), fail "0 != False" 26 | 27 | unless (strict.equal 1, 1), fail "1 == 1" 28 | unless (strict.equal "a", "a"), fail "a == a" 29 | unless (strict.equal True, True), fail "True == True" 30 | unless (strict.equal False, False), fail "False == False" 31 | } 32 | -------------------------------------------------------------------------------- /stdlib/prelude/composability.lithia: -------------------------------------------------------------------------------- 1 | module prelude 2 | 3 | /** 4 | * Pipes a given value through a list of functions. 5 | * The first function is applied to the value, the second to the result of the first, etc. 6 | */ 7 | func pipe { functions, initial => 8 | type List { 9 | Cons: { fs => 10 | pipe fs.tail, fs.head initial 11 | }, 12 | Nil: { nil => initial } 13 | } functions 14 | } 15 | 16 | /** 17 | * Applies the given body to the given value. 18 | * Mostly useful for readability, e.g. in destructings. 19 | * 20 | * ``` 21 | * with True, Bool(True: { _ => }, False: { _ => }) 22 | * ``` 23 | */ 24 | func with { value, body => 25 | body value 26 | } 27 | 28 | /** 29 | * Composes two given functions. 30 | * Calls the second function first and pipes the result into the second one. 31 | */ 32 | func compose { f, g, value => f g value } 33 | 34 | /// Always returns the given argument. 35 | func identity { value => value } 36 | 37 | /// Always returns the first argument. 38 | func const { value, _ => value } 39 | -------------------------------------------------------------------------------- /parser/parse-decl-enum-case.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseEnumCaseDeclaration() (*ast.DeclEnumCase, []ast.Decl, []SyntaxError) { 8 | switch fp.Node.Type() { 9 | case TYPE_NODE_ENUM_CASE_REFERENCE: 10 | return fp.parseEnumCaseReference() 11 | case TYPE_NODE_DATA_DECLARATION: 12 | dataDecl, errors := fp.ParseDataDeclaration() 13 | return ast.MakeDeclEnumCase(dataDecl.Name), []ast.Decl{*dataDecl}, errors 14 | case TYPE_NODE_ENUM_DECLARATION: 15 | enumDecl, childDecls, errors := fp.ParseEnumDeclaration() 16 | enumCase := ast.MakeDeclEnumCase(enumDecl.Name) 17 | return enumCase, append(childDecls, *enumDecl), errors 18 | default: 19 | return nil, nil, []SyntaxError{fp.SyntaxErrorf("unexpected node")} 20 | } 21 | } 22 | 23 | func (fp *FileParser) parseEnumCaseReference() (*ast.DeclEnumCase, []ast.Decl, []SyntaxError) { 24 | enumCase := ast.MakeDeclEnumCase(ast.Identifier(fp.Node.Content(fp.Source))) 25 | enumCase.Docs = fp.ConsumeDocs() 26 | return enumCase, nil, nil 27 | } 28 | -------------------------------------------------------------------------------- /stdlib/prelude/prelude_t/composability_t.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | 3 | test "pipe", { fail => 4 | func incr { i => i + 1 } 5 | func double { i => 2 * i } 6 | 7 | unless (pipe [incr, double], 0) == 2, fail "2 * (1 + 0)" 8 | unless (pipe [double, incr], 0) == 1, fail "1 + (2 * 0)" 9 | unless (pipe [incr, double, incr], 0) == 3, fail "((1 + 0) * 2) + 1" 10 | } 11 | 12 | test "with", { fail => 13 | func incr { i => i + 1 } 14 | 15 | unless (with 3, incr) == 4, fail "just applies" 16 | } 17 | 18 | test "compose", { fail => 19 | func incr { i => i + 1 } 20 | func double { i => 2 * i } 21 | 22 | unless (compose incr, double, 0) == 1, fail "1 + (2 * 0)" 23 | unless (compose double, incr, 0) == 2, fail "2 * (1 + 0)" 24 | } 25 | 26 | test "identity", { fail => 27 | unless (identity 1) == 1, fail "return first for int" 28 | unless (identity "2") == "2", fail "return first for strings" 29 | } 30 | 31 | test "const", { fail => 32 | unless (const True, False), fail "return first" 33 | when (const False, True), fail "return first" 34 | } 35 | -------------------------------------------------------------------------------- /parser/parse-expr-invocation.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseInvocationExpr() (*ast.ExprInvocation, []SyntaxError) { 8 | errors := []SyntaxError{} 9 | functionParser := fp.SameScopeChildParser(fp.Node.ChildByFieldName("function")) 10 | functionExpr, functionErrors := functionParser.ParseExpression() 11 | 12 | if functionErrors != nil { 13 | errors = append(errors, functionErrors...) 14 | } 15 | if functionExpr == nil { 16 | return nil, errors 17 | } 18 | 19 | function := ast.MakeExprInvocation(functionExpr, functionParser.AstSource()) 20 | for i := 1; i < int(fp.Node.NamedChildCount()); i++ { 21 | child := fp.Node.NamedChild(i) 22 | if fp.ParseChildCommentIfNeeded(child) { 23 | continue 24 | } 25 | childParser := fp.SameScopeChildParser(child) 26 | expr, childErrs := childParser.ParseExpression() 27 | if len(childErrs) > 0 { 28 | errors = append(errors, childErrs...) 29 | } 30 | if expr != nil { 31 | function.AddArgument(expr) 32 | } 33 | } 34 | return function, errors 35 | } 36 | -------------------------------------------------------------------------------- /runtime/runtime-core.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import "github.com/vknabel/lithia/ast" 4 | 5 | type Evaluatable interface { 6 | Evaluate() (RuntimeValue, *RuntimeError) 7 | } 8 | 9 | type RuntimeValue interface { 10 | // for printing 11 | String() string 12 | // Member access 13 | Lookup(name string) (Evaluatable, *RuntimeError) 14 | RuntimeType() RuntimeTypeRef 15 | } 16 | 17 | type RuntimeType interface { 18 | Declaration(inter *Interpreter) (ast.Decl, *RuntimeError) 19 | HasInstance(inter *Interpreter, value RuntimeValue) (bool, *RuntimeError) 20 | } 21 | 22 | type EagerEvaluatableRuntimeValue interface { 23 | EagerEvaluate() *RuntimeError 24 | } 25 | 26 | type CallableRuntimeValue interface { 27 | RuntimeValue 28 | Arity() int 29 | Call(args []Evaluatable, fromExpr ast.Expr) (RuntimeValue, *RuntimeError) 30 | // An optional source for stack traces 31 | // If nil, no stack trace will be printed 32 | Source() *ast.Source 33 | } 34 | 35 | type DeclRuntimeValue interface { 36 | RuntimeValue 37 | HasInstance(inter *Interpreter, value RuntimeValue) (bool, *RuntimeError) 38 | } 39 | -------------------------------------------------------------------------------- /parser/parse-decl-parameter.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseParameterDeclaration() (*ast.DeclParameter, []SyntaxError) { 8 | name := ast.Identifier(fp.Node.Content(fp.Source)) 9 | param := ast.MakeDeclParameter(name, fp.AstSource()) 10 | param.Docs = fp.ConsumeDocs() 11 | return param, nil 12 | } 13 | 14 | func (fp *FileParser) ParseParameterDeclarationList() ([]ast.DeclParameter, []SyntaxError) { 15 | params := []ast.DeclParameter{} 16 | errors := []SyntaxError{} 17 | for i := 0; i < int(fp.Node.NamedChildCount()); i++ { 18 | child := fp.Node.NamedChild(i) 19 | if child.Type() == TYPE_NODE_COMMENT { 20 | fp.Comments = append(fp.Comments, child.Content(fp.Source)) 21 | continue 22 | } 23 | param, errs := fp.ChildParserConsumingComments(child).ParseParameterDeclaration() 24 | if len(errs) > 0 { 25 | errors = append(errors, errs...) 26 | } 27 | params = append(params, *param) 28 | } 29 | if len(errors) > 0 { 30 | return params, errors 31 | } else { 32 | return params, nil 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /proposals/LE-000-template.md: -------------------------------------------------------------------------------- 1 | # Template Proposal 2 | 3 | - Proposal: LE-000 4 | - Status: **Draft** 5 | - Author: [**@vknabel**](https://github.com/vknabel) 6 | 7 | ## Introduction 8 | 9 | What is the proposed feature? What problem does it solve? 10 | 11 | ## Motivation 12 | 13 | Why is this change important to you? How would you use it? How can it benefit other users? 14 | 15 | ## Proposed Solution 16 | 17 | Describe the solution you'd like. Provide examples and describe how it works. Define any new terminology. 18 | 19 | ## Detailed Design 20 | 21 | Describe the design of the solution in detail. How will it be implemented? 22 | 23 | ## Changes to the Standard Library 24 | 25 | List any new modules, types, functions, etc. that will be added to the standard library. Highlight any breaking changes. 26 | 27 | ## Alternatives Considered 28 | 29 | Describe alternative approaches to addressing the same problem, and why you chose this approach instead. 30 | 31 | ## Acknowledgements 32 | 33 | List people who have contributed to the design of this proposal. Also mention any prior art, such as how other languages have solved this problem. 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Valentin Knabel 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 | -------------------------------------------------------------------------------- /runtime/prelude-string.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | var _ RuntimeValue = PreludeString("") 4 | var PreludeStringTypeRef = MakeRuntimeTypeRef("String", "prelude") 5 | 6 | type PreludeString string 7 | 8 | func (PreludeString) RuntimeType() RuntimeTypeRef { 9 | return PreludeStringTypeRef 10 | } 11 | 12 | func (s PreludeString) String() string { 13 | return string(s) 14 | } 15 | 16 | func (s PreludeString) EagerEvaluate() *RuntimeError { 17 | return nil 18 | } 19 | 20 | func (s PreludeString) Lookup(member string) (Evaluatable, *RuntimeError) { 21 | switch member { 22 | case "length": 23 | return NewConstantRuntimeValue(PreludeInt(len(s))), nil 24 | case "append": 25 | return NewConstantRuntimeValue(MakeAnonymousFunction( 26 | "append", 27 | []string{"str"}, 28 | func(args []Evaluatable) (RuntimeValue, *RuntimeError) { 29 | value, err := args[0].Evaluate() 30 | if err != nil { 31 | return nil, err 32 | } 33 | return PreludeString(s) + PreludeString(value.String()), nil 34 | })), nil 35 | default: 36 | return nil, NewRuntimeErrorf("no such member: %s for %s", member, s.RuntimeType().String()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /stdlib/optionals.md: -------------------------------------------------------------------------------- 1 | # optionals 2 | 3 | _module_ 4 | 5 | - _func_ [equalFor](#equalFor) someWitness, lhs, rhs 6 | - _func_ [equatableFor](#equatableFor) someWitness 7 | - _func_ [from](#from) maybe 8 | - _func_ [isNone](#isNone) 9 | - _func_ [map](#map) transform 10 | - _func_ [orDefault](#orDefault) default 11 | 12 | ## equalFor 13 | 14 | _func_ `equalFor someWitness, lhs, rhs` 15 | 16 | Creates an equal function, that understands optionals and maybes for a given witness. 17 | 18 | ## equatableFor 19 | 20 | _func_ `equatableFor someWitness` 21 | 22 | Creates an Equatable witness for Optionals on top of an existing witness. 23 | 24 | ## from 25 | 26 | _func_ `from maybe` 27 | 28 | Creates an optional from a Maybe-value. 29 | 30 | ## isNone 31 | 32 | _func_ `isNone` 33 | 34 | True if None. Otherwise False. 35 | 36 | ## map 37 | 38 | _func_ `map transform` 39 | 40 | Transforms Some value to a new one. 41 | Keeps None as-is. 42 | Any other values will still be mapped, but not wrapped. 43 | 44 | ## orDefault 45 | 46 | _func_ `orDefault default` 47 | 48 | Returns a default, if None given. 49 | Otherwise unwraps Some value or keeps Any as-is. 50 | 51 | -------------------------------------------------------------------------------- /stdlib/results/functors.lithia: -------------------------------------------------------------------------------- 1 | module results 2 | 3 | import controls 4 | 5 | /// A witness for controls.Functor for results.Result. 6 | /// In contrast to failureFunctor, implemented to map Success values. 7 | let successFunctor = controls.Functor mapSuccess 8 | 9 | /// Transorms only successful results. 10 | func mapSuccess { transform, result => 11 | with result, type Result { 12 | Success: { success => Success transform success.value }, 13 | Failure: { failure => failure } 14 | } 15 | } 16 | 17 | /// A witness for controls.Functor for results.Result. 18 | /// In contrast to successFunctor, implemented to map Failure errors. 19 | let failureFunctor = controls.Functor mapFailure 20 | 21 | /// Transorms only failed results. 22 | func mapFailure { transform, result => 23 | with result, type Result { 24 | Success: { success => success }, 25 | Failure: { failure => Failure transform failure.error } 26 | } 27 | } 28 | 29 | /// The default witness for controls.Functor for results.Result. 30 | /// Is equal to successFunctor. 31 | let functor = successFunctor 32 | /// The default map for results.Result. 33 | /// Is equal to successFunctor. 34 | let map = mapSuccess 35 | -------------------------------------------------------------------------------- /potfile/state.go: -------------------------------------------------------------------------------- 1 | package potfile 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/exec" 8 | 9 | "github.com/vknabel/lithia/world" 10 | ) 11 | 12 | type PotfileState struct { 13 | Cmds map[string]PotfileCmd 14 | } 15 | 16 | type PotfileCmd struct { 17 | Name string 18 | Summary string 19 | Flags map[string]PotfileFlag 20 | Envs map[string]string 21 | Bin string 22 | Args []string 23 | } 24 | 25 | type PotfileFlag struct { 26 | Name string 27 | Short string 28 | Summary string 29 | DefaultValue string 30 | Required bool 31 | } 32 | 33 | func (potCmd PotfileCmd) RunCmd(args []string) { 34 | bin := potCmd.Bin 35 | if bin == "lithia" { 36 | bin = os.Args[0] 37 | } 38 | cmd := exec.CommandContext(context.Background(), bin, (append(potCmd.Args, args...))...) 39 | cmd.Env = append(cmd.Env, os.Environ()...) 40 | 41 | for key, val := range potCmd.Envs { 42 | cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%q", key, val)) 43 | } 44 | cmd.Stdout = os.Stdout 45 | cmd.Stderr = os.Stderr 46 | cmd.Stdin = os.Stdin 47 | 48 | err := cmd.Run() 49 | if err != nil { 50 | fmt.Fprint(world.Current.Stderr, err) 51 | world.Current.Env.Exit(1) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /runtime/operators-unary.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | func (ex *InterpreterContext) unaryOperatorFunction(operator string) (func(Evaluatable) (RuntimeValue, *RuntimeError), *RuntimeError) { 4 | switch operator { 5 | case "!": 6 | return func(expr Evaluatable) (RuntimeValue, *RuntimeError) { 7 | value, err := expr.Evaluate() 8 | if err != nil { 9 | return nil, err 10 | } 11 | flag, err := ex.boolFromRuntimeValue(value) 12 | if err != nil { 13 | return nil, err 14 | } 15 | return ex.boolToRuntimeValue(!flag) 16 | }, nil 17 | default: 18 | return nil, NewRuntimeErrorf("unknown binary operator: %s", operator) 19 | } 20 | } 21 | 22 | func (ex *InterpreterContext) boolFromRuntimeValue(value RuntimeValue) (bool, *RuntimeError) { 23 | trueRef := MakeRuntimeTypeRef("True", "prelude") 24 | isTrue, err := trueRef.HasInstance(ex.interpreter, value) 25 | if err != nil { 26 | return false, NewRuntimeError(err) 27 | } else if isTrue { 28 | return true, nil 29 | } 30 | falseRef := MakeRuntimeTypeRef("True", "prelude") 31 | _, err = falseRef.HasInstance(ex.interpreter, value) 32 | if err != nil { 33 | return false, NewRuntimeError(err) 34 | } else { 35 | return false, nil 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /langsrv/handler-declaration.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | "github.com/tliron/glsp" 5 | protocol "github.com/tliron/glsp/protocol_3_16" 6 | ) 7 | 8 | func textDocumentDeclaration(context *glsp.Context, params *protocol.DeclarationParams) (interface{}, error) { 9 | rc := NewReqContextAtPosition(¶ms.TextDocumentPositionParams) 10 | 11 | token, _, err := rc.findToken() 12 | if err != nil { 13 | return nil, nil 14 | } 15 | 16 | for _, imported := range rc.accessibleDeclarations(context) { 17 | if string(imported.decl.DeclName()) != token || imported.decl.Meta().Source == nil { 18 | continue 19 | } 20 | return &[]protocol.LocationLink{ 21 | { 22 | TargetURI: protocol.DocumentUri(imported.decl.Meta().Source.FileName), 23 | TargetRange: protocol.Range{ 24 | Start: protocol.Position{ 25 | Line: uint32(imported.decl.Meta().Source.Start.Line), 26 | Character: uint32(imported.decl.Meta().Source.Start.Column), 27 | }, 28 | End: protocol.Position{ 29 | Line: uint32(imported.decl.Meta().Source.End.Line), 30 | Character: uint32(imported.decl.Meta().Source.End.Column), 31 | }, 32 | }, 33 | }, 34 | }, nil 35 | } 36 | return nil, nil 37 | } 38 | -------------------------------------------------------------------------------- /runtime/prelude-primitive-extern-type.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import "github.com/vknabel/lithia/ast" 4 | 5 | var _ RuntimeValue = PreludePrimitiveExternType{} 6 | var _ DeclRuntimeValue = PreludePrimitiveExternType{} 7 | 8 | type PreludePrimitiveExternType struct { 9 | *ast.DeclExternType 10 | hasInstance func(inter *Interpreter, value RuntimeValue) (bool, *RuntimeError) 11 | } 12 | 13 | func MakePrimitiveExternType(decl ast.DeclExternType, hasInstance func(inter *Interpreter, value RuntimeValue) (bool, *RuntimeError)) PreludePrimitiveExternType { 14 | return PreludePrimitiveExternType{ 15 | DeclExternType: &decl, 16 | hasInstance: hasInstance, 17 | } 18 | } 19 | 20 | func (t PreludePrimitiveExternType) String() string { 21 | return string(t.DeclExternType.Name) 22 | } 23 | 24 | func (t PreludePrimitiveExternType) Lookup(name string) (Evaluatable, *RuntimeError) { 25 | return nil, NewRuntimeErrorf("type %s has no member %s", t.DeclExternType.Name, name) 26 | } 27 | 28 | func (t PreludePrimitiveExternType) RuntimeType() RuntimeTypeRef { 29 | return PreludeAnyTypeRef 30 | } 31 | 32 | func (t PreludePrimitiveExternType) HasInstance(inter *Interpreter, value RuntimeValue) (bool, *RuntimeError) { 33 | return t.hasInstance(inter, value) 34 | } 35 | -------------------------------------------------------------------------------- /app/lithia/cmd/repl.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/spf13/cobra" 9 | "github.com/vknabel/lithia" 10 | "github.com/vknabel/lithia/reporting" 11 | "github.com/vknabel/lithia/world" 12 | ) 13 | 14 | func init() { 15 | rootCmd.AddCommand(replCmd) 16 | } 17 | 18 | var replCmd = &cobra.Command{ 19 | Use: "repl", 20 | Short: "Runs interactive Lithia REPL.", 21 | Long: ``, 22 | Args: cobra.NoArgs, 23 | Run: func(cmd *cobra.Command, args []string) { 24 | runPrompt() 25 | }, 26 | } 27 | 28 | func runPrompt() { 29 | importRoot, err := world.Current.FS.Getwd() 30 | if err != nil { 31 | fmt.Fprint(world.Current.Stderr, err) 32 | world.Current.Env.Exit(1) 33 | } 34 | reader := bufio.NewReader(world.Current.Stdin) 35 | inter := lithia.NewDefaultInterpreter(importRoot) 36 | for { 37 | fmt.Print("> ") 38 | line, err := reader.ReadString('\n') 39 | if err == io.EOF { 40 | return 41 | } 42 | if err != nil { 43 | reporting.ReportErrorOrPanic(err) 44 | continue 45 | } 46 | value, err := inter.InterpretEmbed("prompt", line) 47 | if err != nil { 48 | reporting.ReportErrorOrPanic(err) 49 | continue 50 | } 51 | if value != nil { 52 | fmt.Println("- ", value) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /testing/worldtest/mapfs.go: -------------------------------------------------------------------------------- 1 | package worldtest 2 | 3 | import ( 4 | "io/fs" 5 | "testing/fstest" 6 | "time" 7 | ) 8 | 9 | type MapFS struct { 10 | fstest.MapFS 11 | Cwd struct { 12 | string 13 | error 14 | } 15 | } 16 | 17 | func NewMapFS(m map[string][]byte) *MapFS { 18 | mapFS := fstest.MapFS{} 19 | for k, v := range m { 20 | mapFS[k] = &fstest.MapFile{ 21 | Data: v, 22 | } 23 | } 24 | return &MapFS{ 25 | MapFS: mapFS, 26 | } 27 | } 28 | 29 | func (m *MapFS) Getwd() (string, error) { 30 | return m.Cwd.string, m.Cwd.error 31 | } 32 | 33 | func (m *MapFS) PathSeparator() rune { 34 | return '/' 35 | } 36 | 37 | func (m *MapFS) WriteFile(name string, data []byte, perm fs.FileMode) error { 38 | m.MapFS[name] = &fstest.MapFile{ 39 | Data: data, 40 | Mode: perm, 41 | ModTime: time.Now(), 42 | Sys: nil, 43 | } 44 | return nil 45 | } 46 | 47 | func (m *MapFS) ReadFile(name string) ([]byte, error) { 48 | return m.MapFS.ReadFile(name) 49 | } 50 | 51 | func (m *MapFS) Remove(name string) error { 52 | delete(m.MapFS, name) 53 | return nil 54 | } 55 | 56 | func (m *MapFS) Stat(name string) (fs.FileInfo, error) { 57 | return m.MapFS.Stat(name) 58 | } 59 | 60 | func (m *MapFS) Glob(name string) ([]string, error) { 61 | return m.MapFS.Glob(name) 62 | } 63 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.202.5/containers/go/.devcontainer/base.Dockerfile 2 | 3 | # [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.16, 1.17, 1-bullseye, 1.16-bullseye, 1.17-bullseye, 1-buster, 1.16-buster, 1.17-buster 4 | ARG VARIANT="1.19-bullseye" 5 | FROM mcr.microsoft.com/vscode/devcontainers/go:0-${VARIANT} 6 | 7 | # [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 8 | ARG NODE_VERSION="none" 9 | RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi 10 | 11 | # [Optional] Uncomment this section to install additional OS packages. 12 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 13 | # && apt-get -y install --no-install-recommends 14 | 15 | # [Optional] Uncomment the next line to use go get to install anything else you need 16 | # RUN go get -x 17 | 18 | # [Optional] Uncomment this line to install global node packages. 19 | # RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 20 | 21 | COPY ./lithia /usr/local/bin/lithia 22 | ENV LITHIA_STDLIB /workspaces/lithia/stdlib 23 | -------------------------------------------------------------------------------- /stdlib/optionals/functor.lithia: -------------------------------------------------------------------------------- 1 | module optionals 2 | 3 | import booleans 4 | import controls { Functor } 5 | 6 | /// A Functor instance for Optional. 7 | let functor = Functor map 8 | 9 | /// Transforms Some value to a new one. 10 | /// Keeps None as-is. 11 | /// Any other values will still be mapped, but not wrapped. 12 | func map { transform => 13 | type Maybe { 14 | Some: { some => transform some.value }, 15 | None: { _ => None }, 16 | Any: { any => transform any } 17 | } 18 | } 19 | 20 | // describe "optional map", { it => 21 | // let equality = optionals.equalityWitness booleans 22 | 23 | // it "always returns None if None", { expect => 24 | // let sut = None 25 | // let result = map { n => n + 1 }, sut 26 | // expect.toEqual equality, result, None 27 | // } 28 | 29 | // it "applies function to some value", { expect => 30 | // let sut = Some 41 31 | // let result = map { n => n + 1 }, sut 32 | // expect.toEqual equality, result, Some 42 33 | // } 34 | // } 35 | 36 | // describe "is none", { it => 37 | // it "is false for some", { expect => 38 | // expect booleans.not isNone, Some 41 39 | // expect booleans.not isNone, Some Some "nested" 40 | // } 41 | 42 | // it "is true for none" { assert => 43 | // expect isNone None 44 | // } 45 | // } 46 | -------------------------------------------------------------------------------- /parser/parse-decl-data.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import "github.com/vknabel/lithia/ast" 4 | 5 | func (fp *FileParser) ParseDataDeclaration() (*ast.DeclData, []SyntaxError) { 6 | name := ast.Identifier(fp.Node.ChildByFieldName("name").Content(fp.Source)) 7 | 8 | dataDecl := ast.MakeDeclData(name, fp.AstSource()) 9 | dataDecl.Docs = fp.ConsumeDocs() 10 | 11 | propertiesNode := fp.Node.ChildByFieldName("properties") 12 | if propertiesNode == nil { 13 | return dataDecl, nil 14 | } 15 | fp.addAllChildComments() 16 | propsp := fp.ChildParserConsumingComments(propertiesNode) 17 | 18 | var numberOfFields int 19 | if propertiesNode != nil { 20 | numberOfFields = int(propertiesNode.ChildCount()) 21 | } else { 22 | numberOfFields = 0 23 | } 24 | errors := []SyntaxError{} 25 | for i := 0; i < numberOfFields; i++ { 26 | child := propertiesNode.NamedChild(i) 27 | if child.Type() == TYPE_NODE_COMMENT { 28 | propsp.Comments = append(propsp.Comments, child.Content(fp.Source)) 29 | continue 30 | } 31 | 32 | childp := propsp.ChildParserConsumingComments(child) 33 | field, propErrors := childp.ParseFieldDeclaration() 34 | if len(propErrors) > 0 { 35 | errors = append(errors, propErrors...) 36 | } 37 | if field != nil { 38 | dataDecl.AddField(*field) 39 | } 40 | } 41 | 42 | return dataDecl, errors 43 | } 44 | -------------------------------------------------------------------------------- /ast/decl-func.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var _ Decl = DeclFunc{} 9 | var _ Overviewable = DeclFunc{} 10 | 11 | type DeclFunc struct { 12 | Name Identifier 13 | Impl *ExprFunc 14 | 15 | Docs *Docs 16 | MetaInfo *MetaDecl 17 | } 18 | 19 | func (e DeclFunc) DeclName() Identifier { 20 | return e.Name 21 | } 22 | 23 | func (e DeclFunc) DeclOverview() string { 24 | if len(e.Impl.Parameters) == 0 { 25 | return fmt.Sprintf("func %s { => }", e.Name) 26 | } 27 | paramNames := make([]string, len(e.Impl.Parameters)) 28 | for i, param := range e.Impl.Parameters { 29 | paramNames[i] = string(param.Name) 30 | } 31 | return fmt.Sprintf("func %s { %s => }", e.Name, strings.Join(paramNames, ", ")) 32 | } 33 | 34 | func (e DeclFunc) Meta() *MetaDecl { 35 | return e.MetaInfo 36 | } 37 | 38 | func (e DeclFunc) IsExportedDecl() bool { 39 | return true 40 | } 41 | 42 | func MakeDeclFunc(name Identifier, impl *ExprFunc, source *Source) *DeclFunc { 43 | return &DeclFunc{ 44 | Name: name, 45 | Impl: impl, 46 | MetaInfo: &MetaDecl{ 47 | Source: source, 48 | }, 49 | } 50 | } 51 | 52 | func (decl DeclFunc) ProvidedDocs() *Docs { 53 | return decl.Docs 54 | } 55 | 56 | func (decl DeclFunc) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 57 | decl.Impl.EnumerateNestedDecls(enumerate) 58 | } 59 | -------------------------------------------------------------------------------- /ast/expr-func.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | var _ Expr = ExprFunc{} 4 | 5 | type ExprFunc struct { 6 | Name string 7 | Parameters []DeclParameter 8 | Declarations []Decl 9 | Expressions []Expr 10 | 11 | MetaInfo *MetaExpr 12 | } 13 | 14 | func (e ExprFunc) Meta() *MetaExpr { 15 | return e.MetaInfo 16 | } 17 | 18 | func MakeExprFunc(name string, parameters []DeclParameter, source *Source) *ExprFunc { 19 | return &ExprFunc{ 20 | Name: name, 21 | Parameters: parameters, 22 | Declarations: []Decl{}, 23 | Expressions: []Expr{}, 24 | MetaInfo: &MetaExpr{Source: source}, 25 | } 26 | } 27 | 28 | func (e *ExprFunc) AddDecl(decl Decl) { 29 | e.Declarations = append(e.Declarations, decl) 30 | } 31 | 32 | func (e *ExprFunc) AddExpr(expr Expr) { 33 | e.Expressions = append(e.Expressions, expr) 34 | } 35 | 36 | func (e ExprFunc) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 37 | enumeratedDecls := make([]Decl, len(e.Parameters)+len(e.Declarations)) 38 | for i, param := range e.Parameters { 39 | enumeratedDecls[i] = param 40 | } 41 | for i, decl := range e.Declarations { 42 | decl.EnumerateNestedDecls(enumerate) 43 | enumeratedDecls[len(e.Parameters)+i] = decl 44 | } 45 | enumerate(e, enumeratedDecls) 46 | 47 | for _, expr := range e.Expressions { 48 | expr.EnumerateNestedDecls(enumerate) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ast/decl-field.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var _ Decl = DeclField{} 9 | var _ Overviewable = DeclField{} 10 | 11 | type DeclField struct { 12 | Name Identifier 13 | Parameters []DeclParameter 14 | 15 | Docs *Docs 16 | MetaInfo *MetaDecl 17 | } 18 | 19 | func (e DeclField) DeclName() Identifier { 20 | return e.Name 21 | } 22 | 23 | func (e DeclField) DeclOverview() string { 24 | if len(e.Parameters) == 0 { 25 | return string(e.Name) 26 | } 27 | paramNames := make([]string, len(e.Parameters)) 28 | for i, param := range e.Parameters { 29 | paramNames[i] = string(param.Name) 30 | } 31 | return fmt.Sprintf("%s %s", e.Name, strings.Join(paramNames, ", ")) 32 | } 33 | 34 | func (e DeclField) Meta() *MetaDecl { 35 | return e.MetaInfo 36 | } 37 | 38 | func (e DeclField) IsExportedDecl() bool { 39 | return true 40 | } 41 | 42 | func MakeDeclField(name Identifier, params []DeclParameter, source *Source) *DeclField { 43 | return &DeclField{ 44 | Name: name, 45 | Parameters: params, 46 | Docs: MakeDocs([]string{}), 47 | MetaInfo: &MetaDecl{ 48 | Source: source, 49 | }, 50 | } 51 | } 52 | 53 | func (decl DeclField) ProvidedDocs() *Docs { 54 | return decl.Docs 55 | } 56 | 57 | func (DeclField) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 58 | // no nested decls 59 | } 60 | -------------------------------------------------------------------------------- /stdlib/cmp/numeric.lithia: -------------------------------------------------------------------------------- 1 | module cmp 2 | 3 | /// `Comparable` witness for numbers using < and >. 4 | /// Not safe for other types. 5 | let numeric = Comparable { lhs, rhs => 6 | if lhs < rhs, Ascending, ( 7 | if lhs > rhs, Descending, Equal 8 | ) 9 | } 10 | 11 | import tests { test } 12 | 13 | test "cmp.numeric for Equal", { fail => 14 | when (numeric.compare -42, -42) != Equal, fail "should equal" 15 | when (numeric.compare 0, 0) != Equal, fail "should equal" 16 | when (numeric.compare 1, 1) != Equal, fail "should equal" 17 | when (numeric.compare 300, 300) != Equal, fail "should equal" 18 | } 19 | 20 | test "cmp.numeric for Ascending", { fail => 21 | when (numeric.compare -42, -30) != Ascending, fail "should be ascending" 22 | when (numeric.compare 0, 5) != Ascending, fail "should be ascending" 23 | when (numeric.compare 1, 2) != Ascending, fail "should be ascending" 24 | when (numeric.compare 300, 900) != Ascending, fail "should be ascending" 25 | } 26 | 27 | test "cmp.numeric for Descending", { fail => 28 | when (numeric.compare -42, -80) != Descending, fail "should be descending" 29 | when (numeric.compare 0, -5) != Descending, fail "should be descending" 30 | when (numeric.compare 1, 0) != Descending, fail "should be descending" 31 | when (numeric.compare 300, 13) != Descending, fail "should be descending" 32 | } 33 | -------------------------------------------------------------------------------- /parser/parse-expr-dict.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseExprDict() (*ast.ExprDict, []SyntaxError) { 8 | numberOfEntries := int(fp.Node.NamedChildCount()) 9 | entries := make([]ast.ExprDictEntry, 0, numberOfEntries) 10 | for i := 0; i < numberOfEntries; i++ { 11 | entryNode := fp.Node.NamedChild(i) 12 | if entryNode.Type() == TYPE_NODE_COMMENT { 13 | fp.Comments = append(fp.Comments, entryNode.Content(fp.Source)) 14 | continue 15 | } 16 | entry, errs := fp.ChildParserConsumingComments(entryNode).parseExprDictEntry() 17 | if len(errs) > 0 { 18 | return nil, errs 19 | } 20 | if entry != nil { 21 | entries = append(entries, *entry) 22 | } 23 | } 24 | return ast.MakeExprDict(entries, fp.AstSource()), nil 25 | } 26 | 27 | func (fp *FileParser) parseExprDictEntry() (*ast.ExprDictEntry, []SyntaxError) { 28 | keyNode := fp.Node.ChildByFieldName("key") 29 | valueNode := fp.Node.ChildByFieldName("value") 30 | 31 | keyP := fp.ChildParserConsumingComments(keyNode) 32 | key, errs := keyP.ParseExpression() 33 | if len(errs) > 0 { 34 | return nil, errs 35 | } 36 | 37 | valueP := fp.ChildParserConsumingComments(valueNode) 38 | value, errs := valueP.ParseExpression() 39 | if len(errs) > 0 { 40 | return nil, errs 41 | } 42 | return ast.MakeExprDictEntry(key, value, fp.AstSource()), nil 43 | } 44 | -------------------------------------------------------------------------------- /ast/decl-extern-func.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var _ Decl = DeclExternFunc{} 9 | var _ Overviewable = DeclExternFunc{} 10 | 11 | type DeclExternFunc struct { 12 | Name Identifier 13 | Parameters []DeclParameter 14 | 15 | Docs *Docs 16 | MetaInfo *MetaDecl 17 | } 18 | 19 | func (e DeclExternFunc) DeclName() Identifier { 20 | return e.Name 21 | } 22 | 23 | func (e DeclExternFunc) Meta() *MetaDecl { 24 | return e.MetaInfo 25 | } 26 | 27 | func (e DeclExternFunc) IsExportedDecl() bool { 28 | return true 29 | } 30 | 31 | func (e DeclExternFunc) DeclOverview() string { 32 | if len(e.Parameters) == 0 { 33 | return fmt.Sprintf("extern %s { => }", e.Name) 34 | } 35 | paramNames := make([]string, len(e.Parameters)) 36 | for i, param := range e.Parameters { 37 | paramNames[i] = string(param.Name) 38 | } 39 | return fmt.Sprintf("extern %s { %s => }", e.Name, strings.Join(paramNames, ", ")) 40 | } 41 | 42 | func MakeDeclExternFunc(name Identifier, params []DeclParameter, source *Source) *DeclExternFunc { 43 | return &DeclExternFunc{ 44 | Name: name, 45 | Parameters: params, 46 | MetaInfo: &MetaDecl{source}, 47 | } 48 | } 49 | 50 | func (decl DeclExternFunc) ProvidedDocs() *Docs { 51 | return decl.Docs 52 | } 53 | 54 | func (DeclExternFunc) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 55 | // no nested decls 56 | } 57 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/functor_t.lithia: -------------------------------------------------------------------------------- 1 | import tests { test } 2 | import controls { map } 3 | import lists 4 | 5 | test "lists.map", { fail => 6 | lists.map { i => 7 | fail strings.concat ["should not call when empty, but did with ", i] 8 | }, [] 9 | 10 | let incr = { i => i + 1 } 11 | let identity = { i => i } 12 | unless (lists.map incr, []) == [], fail "empty list stays empty" 13 | unless (lists.map incr, [1, 2]) == [2, 3], fail "list with elements return applied results" 14 | unless (lists.map identity, [1, 2]) == [1, 2], fail "identity" 15 | } 16 | 17 | test "lists.functor", { fail => 18 | map { i => 19 | fail strings.concat ["should not call when empty, but did with ", i] 20 | }, lists, [] 21 | 22 | let incr = { i => i + 1 } 23 | let identity = { i => i } 24 | unless (map incr, lists, []) == [], fail "empty list stays empty" 25 | unless (map incr, lists, [1, 2]) == [2, 3], fail "list with elements return applied results" 26 | unless (map identity, lists, [1, 2]) == [1, 2], fail "identity" 27 | } 28 | 29 | test "lists is a functor instance", { fail => 30 | unless (controls.functorFrom lists) != Void, fail "module lists is a functor" 31 | unless (controls.functorFrom lists.functor) != Void, fail "var lists.functor is a functor" 32 | unless (controls.functorFrom lists.map) != Void, fail "func lists.map is a functor" 33 | } 34 | -------------------------------------------------------------------------------- /app/lithia/cmd/run.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/vknabel/lithia" 9 | "github.com/vknabel/lithia/potfile" 10 | "github.com/vknabel/lithia/world" 11 | ) 12 | 13 | func init() { 14 | rootCmd.AddCommand(runCmd) 15 | } 16 | 17 | var runCmd = &cobra.Command{ 18 | Use: "run [script]", 19 | Short: "Runs a Lithia script", 20 | Args: cobra.MinimumNArgs(1), 21 | DisableFlagParsing: true, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | world.Current.Args = args 24 | 25 | firstArg := args[0] 26 | potfileState, err := potfile.ForReferenceFile(firstArg) 27 | if err != nil { 28 | fmt.Fprint(world.Current.Stderr, err) 29 | world.Current.Env.Exit(1) 30 | } 31 | 32 | if potCmd, ok := potfileState.Cmds[firstArg]; ok { 33 | potCmd.RunCmd(args[1:]) 34 | return 35 | } 36 | 37 | runFile(firstArg, args) 38 | }, 39 | } 40 | 41 | func runFile(fileName string, args []string) { 42 | scriptData, err := world.Current.FS.ReadFile(fileName) 43 | if err != nil { 44 | fmt.Fprint(world.Current.Stderr, err) 45 | world.Current.Env.Exit(1) 46 | } 47 | inter := lithia.NewDefaultInterpreter(path.Dir(fileName)) 48 | script := string(scriptData) 49 | _, err = inter.Interpret(fileName, script) 50 | 51 | if err != nil { 52 | fmt.Fprint(world.Current.Stderr, err) 53 | world.Current.Env.Exit(1) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /parser/parse-source-file.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseSourceFile() (*ast.SourceFile, []SyntaxError) { 8 | err := fp.AssertNodeType(TYPE_NODE_SOURCE_FILE) 9 | if err != nil { 10 | return nil, err 11 | } 12 | 13 | sourceFile := ast.MakeSourceFile(fp.File, fp.AstSource()) 14 | parsingErrors := []SyntaxError{} 15 | 16 | for i := 0; i < int(fp.Node.NamedChildCount()); i++ { 17 | child := fp.Node.NamedChild(i) 18 | if child.Type() == TYPE_NODE_COMMENT { 19 | fp.Comments = append(fp.Comments, child.Content(fp.Source)) 20 | continue 21 | } 22 | childFp := fp.ChildParserConsumingComments(child) 23 | 24 | parsedDecls, errs := childFp.ParseDeclsIfGiven() 25 | if len(errs) > 0 { 26 | parsingErrors = append(parsingErrors, errs...) 27 | continue 28 | } 29 | if parsedDecls != nil { 30 | for _, decl := range parsedDecls { 31 | sourceFile.AddDecl(decl) 32 | } 33 | continue 34 | } 35 | expr, errs := childFp.ParseExpressionIfGiven() 36 | if len(errs) > 0 { 37 | parsingErrors = append(parsingErrors, errs...) 38 | continue 39 | } 40 | if expr != nil { 41 | sourceFile.AddExpr(expr) 42 | continue 43 | } 44 | parsingErrors = append(parsingErrors, fp.SyntaxErrorf("unexpected %q, expected module, import, enum, data, func, let or an expression", child.Type())) 45 | } 46 | return sourceFile, parsingErrors 47 | } 48 | -------------------------------------------------------------------------------- /ast/decl-data.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var _ Decl = DeclData{} 9 | var _ Overviewable = DeclData{} 10 | 11 | type DeclData struct { 12 | Name Identifier 13 | Fields []DeclField 14 | 15 | Docs *Docs 16 | MetaInfo *MetaDecl 17 | } 18 | 19 | func (e DeclData) DeclName() Identifier { 20 | return e.Name 21 | } 22 | 23 | func (e DeclData) DeclOverview() string { 24 | if len(e.Fields) == 0 { 25 | return fmt.Sprintf("data %s", e.Name) 26 | } 27 | fieldLines := make([]string, 0) 28 | for _, field := range e.Fields { 29 | fieldLines = append(fieldLines, " "+field.DeclOverview()) 30 | } 31 | return fmt.Sprintf("data %s {\n%s\n}", e.Name, strings.Join(fieldLines, "\n")) 32 | } 33 | 34 | func (e DeclData) Meta() *MetaDecl { 35 | return e.MetaInfo 36 | } 37 | 38 | func (e DeclData) IsExportedDecl() bool { 39 | return true 40 | } 41 | 42 | func MakeDeclData(name Identifier, source *Source) *DeclData { 43 | return &DeclData{ 44 | Name: name, 45 | Fields: []DeclField{}, 46 | Docs: MakeDocs([]string{}), 47 | MetaInfo: &MetaDecl{ 48 | Source: source, 49 | }, 50 | } 51 | } 52 | 53 | func (e *DeclData) AddField(field DeclField) { 54 | e.Fields = append(e.Fields, field) 55 | } 56 | 57 | func (decl DeclData) ProvidedDocs() *Docs { 58 | return decl.Docs 59 | } 60 | 61 | func (DeclData) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 62 | // no nested decls 63 | } 64 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vknabel/lithia 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/smacker/go-tree-sitter v0.0.0-20230720070738-0d0a9f78d8f8 7 | github.com/spf13/cobra v1.7.0 8 | github.com/tliron/glsp v0.1.2-0.20230804172746-dbc79d0f13b7 9 | github.com/tliron/kutil v0.1.68 10 | github.com/vknabel/tree-sitter-lithia v0.2.4-0.20230805155751-08e410d342bd 11 | ) 12 | 13 | require ( 14 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 15 | github.com/gorilla/websocket v1.5.0 // indirect 16 | github.com/iancoleman/strcase v0.2.0 // indirect 17 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 18 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 19 | github.com/mattn/go-isatty v0.0.17 // indirect 20 | github.com/mattn/go-runewidth v0.0.14 // indirect 21 | github.com/muesli/termenv v0.15.1 // indirect 22 | github.com/petermattis/goid v0.0.0-20221215004737-a150e88a970d // indirect 23 | github.com/pkg/errors v0.9.1 // indirect 24 | github.com/rivo/uniseg v0.4.3 // indirect 25 | github.com/sasha-s/go-deadlock v0.3.1 // indirect 26 | github.com/sourcegraph/jsonrpc2 v0.2.0 // indirect 27 | github.com/spf13/pflag v1.0.5 // indirect 28 | github.com/tliron/commonlog v0.1.0 // indirect 29 | github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect 30 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 31 | golang.org/x/crypto v0.17.0 // indirect 32 | golang.org/x/sys v0.15.0 // indirect 33 | golang.org/x/term v0.15.0 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /proposals/LE-003-string-concatenation.md: -------------------------------------------------------------------------------- 1 | # String Concatenation 2 | 3 | - Proposal: LE-003 4 | - Status: **Accepted** 5 | - Author: [**@vknabel**](https://github.com/vknabel) 6 | 7 | ## Introduction 8 | 9 | Adds the ability to concatenate strings using the `..` operator. 10 | 11 | ## Motivation 12 | 13 | Strings need to be concatnated often. The current way to do this is to use the `strings.concat` function, which feels a bit verbose in many cases. 14 | 15 | A simple `..` might improve the readability of the code. 16 | 17 | ## Proposed Solution 18 | 19 | We add a new left-associative operator `..` to the language, which is used to concatenate strings with a low precendence. 20 | 21 | ## Detailed Design 22 | 23 | The `..` operator is left-associative, and has a precedence of 5. 24 | This allows comparisions of concatenated strings. 25 | 26 | ```lithia 27 | "Hello" .. " " .. "World" == "Hello World" 28 | ``` 29 | 30 | ## Changes to the Standard Library 31 | 32 | `strings.concat` will still be available, and will never be deprecated. 33 | 34 | Implementations of string related functions should use `..` internally. 35 | 36 | Deprecation of `String.append`. 37 | 38 | ## Alternatives Considered 39 | 40 | We could overload `+`. But due to the dynamic typing it may be confusing in practice. 41 | `++` is also used in different languages, but we do not want to mix up the meaning of `+` and `++`. 42 | 43 | ## Acknowledgements 44 | 45 | Mimics the `..` operator in Lua. 46 | -------------------------------------------------------------------------------- /stdlib/optionals/equatable.lithia: -------------------------------------------------------------------------------- 1 | module optionals 2 | 3 | import eq 4 | 5 | /// Creates an equal function, that understands optionals and maybes for a given witness. 6 | func equalFor { someWitness, lhs, rhs => 7 | type Maybe { 8 | Some: { lsome => 9 | type Maybe { 10 | Some: { rsome => 11 | someWitness.equal lsome, rsome 12 | }, 13 | None: { _ => False } 14 | } rhs 15 | }, 16 | None: { _ => 17 | type Maybe { 18 | Some: { _ => False }, 19 | None: { _ => True } 20 | } 21 | }, 22 | } optionals.from lhs 23 | } 24 | 25 | /// Creates an Equatable witness for Optionals on top of an existing witness. 26 | func equatableFor { someWitness => 27 | Equatable equalFor someWitness 28 | } 29 | 30 | // describe "optionals are equatable" { it => 31 | // let neverEquatable = Equatable { _ => False } 32 | // let alwaysEquatable = Equatable { _ => True } 33 | 34 | // it "none and none always equal" { expect => 35 | // expect (neverEquatable None, None) 36 | // } 37 | 38 | // it "some and none are never equal", { expect => 39 | // expect (alwaysEquatable Some 1, None) 40 | // expect (alwaysEquatable None, Some 1) 41 | // } 42 | 43 | // it "some and some delegate equality to values" { expect => 44 | // expect numbers, (Some 1), (Some 1) 45 | // expect eq.negated numbers, (Some 1), (Some 2) 46 | // expect eq.negated numbers, (Some 2), (Some 1) 47 | // } 48 | // } 49 | -------------------------------------------------------------------------------- /parser/parse-decl-enum.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseEnumDeclaration() (*ast.DeclEnum, []ast.Decl, []SyntaxError) { 8 | enumName := ast.Identifier(fp.Node.ChildByFieldName("name").Content(fp.Source)) 9 | decl := ast.MakeDeclEnum(enumName, fp.AstSource()) 10 | decl.Docs = fp.ConsumeDocs() 11 | 12 | caseList := fp.Node.ChildByFieldName("cases") 13 | if caseList == nil { 14 | return decl, nil, nil 15 | } 16 | fp.addAllChildComments() 17 | casep := fp.ChildParserConsumingComments(caseList) 18 | 19 | allChildDecls := []ast.Decl{} 20 | errors := []SyntaxError{} 21 | 22 | var caseCount int 23 | if caseList != nil { 24 | caseCount = int(caseList.NamedChildCount()) 25 | } else { 26 | caseCount = 0 27 | } 28 | for i := 0; i < caseCount; i++ { 29 | childNode := caseList.NamedChild(i) 30 | if childNode.Type() == TYPE_NODE_COMMENT { 31 | casep.Comments = append(casep.Comments, childNode.Content(fp.Source)) 32 | continue 33 | } 34 | 35 | docs := ast.MakeDocs(fp.Comments) 36 | caseDecl, childDecls, err := casep.ChildParserConsumingComments(childNode).ParseEnumCaseDeclaration() 37 | if err != nil { 38 | errors = append(errors, err...) 39 | } 40 | if childDecls != nil { 41 | allChildDecls = append(allChildDecls, childDecls...) 42 | } 43 | if caseDecl != nil { 44 | caseDecl.Docs = docs 45 | decl.AddCase(caseDecl) 46 | } 47 | } 48 | return decl, allChildDecls, errors 49 | } 50 | -------------------------------------------------------------------------------- /parser/parse-decl-import.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/vknabel/lithia/ast" 7 | ) 8 | 9 | func (fp *FileParser) ParseImportDeclaration() (*ast.DeclImport, []SyntaxError) { 10 | importModuleNode := fp.Node.ChildByFieldName("name") 11 | aliasNode := fp.Node.ChildByFieldName("alias") 12 | membersNode := fp.Node.ChildByFieldName("members") 13 | var membersCount int 14 | if membersNode == nil { 15 | membersCount = 0 16 | } else { 17 | membersCount = int(membersNode.NamedChildCount()) 18 | } 19 | 20 | modulePath := make([]string, 0, importModuleNode.NamedChildCount()) 21 | for i := 0; i < int(importModuleNode.NamedChildCount()); i++ { 22 | modulePath = append(modulePath, importModuleNode.NamedChild(i).Content(fp.Source)) 23 | } 24 | moduleName := ast.ModuleName(strings.Join(modulePath, ".")) 25 | 26 | var importDecl *ast.DeclImport 27 | if aliasNode == nil { 28 | importDecl = ast.MakeDeclImport(moduleName, fp.AstSource()) 29 | } else { 30 | importDecl = ast.MakeDeclAliasImport(ast.Identifier(aliasNode.Content(fp.Source)), moduleName, fp.AstSource()) 31 | } 32 | 33 | for i := 0; i < membersCount; i++ { 34 | child := membersNode.NamedChild(i) 35 | if child.Type() == TYPE_NODE_IDENTIFIER { 36 | name := ast.Identifier(child.Content(fp.Source)) 37 | member := ast.MakeDeclImportMember(moduleName, name, fp.SameScopeChildParser(child).AstSource()) 38 | importDecl.AddMember(member) 39 | } 40 | } 41 | return importDecl, nil 42 | } 43 | -------------------------------------------------------------------------------- /stdlib/results/monads.lithia: -------------------------------------------------------------------------------- 1 | module results 2 | 3 | import controls 4 | 5 | /// A witness for controls.Monad for results.Result. 6 | /// In contrast to failureMonad, implemented to map Success values. 7 | let successMonad = controls.Monad pureSuccess, flatMapSuccess 8 | 9 | /// Creates a pure succcess value. 10 | func pureSuccess { value => 11 | Success value 12 | } 13 | 14 | /// When successful, attempts another operation by transforming the result. 15 | func flatMapSuccess { transform, result => 16 | with result, type Result { 17 | Success: { success => transform success.value }, 18 | Failure: { failure => failure } 19 | } 20 | } 21 | 22 | 23 | /// A witness for controls.Monad for results.Result. 24 | /// In contrast to successMonad, implemented to map Failure errors. 25 | let failureMonad = controls.Monad pureFailure, flatMapFailure 26 | 27 | /// Creates a pure failure value. 28 | func pureFailure { error => 29 | Failure error 30 | } 31 | 32 | /// When failed, attempts another operation by transforming the error. 33 | func flatMapFailure { transform, result => 34 | with result, type Result { 35 | Success: { success => success }, 36 | Failure: { failure => transform failure.error } 37 | } 38 | } 39 | 40 | /// The default witness for controls.Monad for results.Result. 41 | /// Is equal to successMonad. 42 | let monad = successMonad 43 | /// The default flatMap for results.Result. 44 | /// Is equal to flatMapSuccess. 45 | let flatMap = flatMapSuccess 46 | -------------------------------------------------------------------------------- /ast/context-file.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | type SourceFile struct { 4 | Path string 5 | Imports []ModuleName 6 | Declarations []Decl 7 | Statements []Expr 8 | 9 | *Source 10 | } 11 | 12 | func MakeSourceFile( 13 | Path string, 14 | Source *Source, 15 | ) *SourceFile { 16 | return &SourceFile{ 17 | Path: Path, 18 | Source: Source, 19 | Imports: make([]ModuleName, 0), 20 | Declarations: make([]Decl, 0), 21 | Statements: make([]Expr, 0), 22 | } 23 | } 24 | 25 | func (sf *SourceFile) AddDecl(decl Decl) { 26 | if decl == nil { 27 | return 28 | } 29 | if importDecl, ok := decl.(DeclImport); ok { 30 | sf.Imports = append(sf.Imports, importDecl.ModuleName) 31 | } 32 | sf.Declarations = append(sf.Declarations, decl) 33 | } 34 | 35 | func (sf *SourceFile) AddExpr(expr Expr) { 36 | if expr == nil { 37 | return 38 | } 39 | sf.Statements = append(sf.Statements, expr) 40 | } 41 | 42 | func (sf *SourceFile) ExportedDeclarations() []Decl { 43 | decls := make([]Decl, 0) 44 | for _, decl := range sf.Declarations { 45 | if decl.IsExportedDecl() { 46 | decls = append(decls, decl) 47 | } 48 | } 49 | return decls 50 | } 51 | 52 | // Enumerate all declarations in this source file. 53 | func (sf SourceFile) EnumerateNestedDecls(enumerate func(declaredAt any, decl []Decl)) { 54 | for _, decl := range sf.Declarations { 55 | decl.EnumerateNestedDecls(enumerate) 56 | } 57 | for _, stmt := range sf.Statements { 58 | stmt.EnumerateNestedDecls(enumerate) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ast/decl-extern-type.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var _ Decl = DeclExternType{} 9 | var _ Overviewable = DeclExternType{} 10 | 11 | type DeclExternType struct { 12 | Name Identifier 13 | Fields map[Identifier]DeclField 14 | 15 | Docs *Docs 16 | MetaInfo *MetaDecl 17 | } 18 | 19 | func (e DeclExternType) DeclName() Identifier { 20 | return e.Name 21 | } 22 | 23 | func (e DeclExternType) DeclOverview() string { 24 | if len(e.Fields) == 0 { 25 | return fmt.Sprintf("extern %s", e.Name) 26 | } 27 | fieldLines := make([]string, 0) 28 | for _, field := range e.Fields { 29 | fieldLines = append(fieldLines, " "+field.DeclOverview()) 30 | } 31 | return fmt.Sprintf("extern %s {\n%s\n}", e.Name, strings.Join(fieldLines, "\n")) 32 | } 33 | 34 | func (e DeclExternType) Meta() *MetaDecl { 35 | return e.MetaInfo 36 | } 37 | 38 | func (e DeclExternType) IsExportedDecl() bool { 39 | return true 40 | } 41 | 42 | func MakeDeclExternType(name Identifier, source *Source) *DeclExternType { 43 | return &DeclExternType{ 44 | Name: name, 45 | Fields: make(map[Identifier]DeclField), 46 | Docs: nil, 47 | MetaInfo: &MetaDecl{source}, 48 | } 49 | } 50 | 51 | func (e *DeclExternType) AddField(decl DeclField) { 52 | e.Fields[decl.Name] = decl 53 | } 54 | 55 | func (decl DeclExternType) ProvidedDocs() *Docs { 56 | return decl.Docs 57 | } 58 | 59 | func (DeclExternType) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 60 | // no nested decls 61 | } 62 | -------------------------------------------------------------------------------- /langsrv/handler-text-document-did-change.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | "github.com/tliron/glsp" 5 | protocol "github.com/tliron/glsp/protocol_3_16" 6 | "github.com/vknabel/lithia/parser" 7 | ) 8 | 9 | func textDocumentDidChange(context *glsp.Context, params *protocol.DidChangeTextDocumentParams) error { 10 | mod := ls.resolver.ResolvePackageAndModuleForReferenceFile(params.TextDocument.URI) 11 | entry := ls.documentCache.documents[params.TextDocument.URI] 12 | text := entry.item.Text 13 | for _, event := range params.ContentChanges { 14 | switch e := event.(type) { 15 | case protocol.TextDocumentContentChangeEvent: 16 | text = text[:e.Range.Start.IndexIn(text)] + e.Text + text[e.Range.End.IndexIn(text):] 17 | case protocol.TextDocumentContentChangeEventWhole: 18 | text = e.Text 19 | } 20 | } 21 | entry.item.Text = text 22 | syntaxErrs := make([]parser.SyntaxError, 0) 23 | fileParser, errs := entry.parser.Parse(mod.AbsoluteModuleName(), string(params.TextDocument.URI), text) 24 | syntaxErrs = append(syntaxErrs, errs...) 25 | sourceFile, errs := fileParser.ParseSourceFile() 26 | syntaxErrs = append(syntaxErrs, errs...) 27 | ls.documentCache.documents[params.TextDocument.URI].fileParser = fileParser 28 | ls.documentCache.documents[params.TextDocument.URI].sourceFile = sourceFile 29 | 30 | analyzeErrs := analyzeErrorsForSourceFile(context, mod, *sourceFile) 31 | publishSyntaxErrorDiagnostics(context, params.TextDocument.URI, uint32(params.TextDocument.Version), syntaxErrs, analyzeErrs) 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /parser/parse-expr-type-switch.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseExprTypeSwitch() (*ast.ExprTypeSwitch, []SyntaxError) { 8 | typeNode := fp.Node.ChildByFieldName("type") 9 | bodyNode := fp.Node.ChildByFieldName("body") 10 | 11 | if typeNode == nil || bodyNode == nil { 12 | return nil, []SyntaxError{fp.SyntaxErrorf("expected type and body")} 13 | } 14 | 15 | typeExpr, errs := fp.SameScopeChildParser(typeNode).ParseExpression() 16 | if len(errs) > 0 { 17 | return nil, errs 18 | } 19 | 20 | typeSwitchExpr := ast.MakeExprTypeSwitch(typeExpr, fp.AstSource()) 21 | 22 | caseCount := int(bodyNode.NamedChildCount()) 23 | errs = []SyntaxError{} 24 | for i := 0; i < caseCount; i++ { 25 | typeCaseNode := bodyNode.NamedChild(i) 26 | 27 | if typeCaseNode.Type() == TYPE_NODE_COMMENT { 28 | continue 29 | } 30 | labelNode := typeCaseNode.ChildByFieldName("label") 31 | bodyNode := typeCaseNode.ChildByFieldName("body") 32 | if labelNode == nil || bodyNode == nil { 33 | errs = append(errs, fp.SyntaxErrorf("expected label and body")) 34 | continue 35 | } 36 | bodyExpr, bodyErrs := fp.SameScopeChildParser(bodyNode).ParseExpression() 37 | if len(bodyErrs) > 0 { 38 | errs = append(errs, bodyErrs...) 39 | } 40 | labelIdentifier := ast.Identifier(labelNode.Content(fp.Source)) 41 | typeSwitchExpr.AddCase(labelIdentifier, bodyExpr) 42 | } 43 | if len(errs) > 0 { 44 | return typeSwitchExpr, errs 45 | } else { 46 | return typeSwitchExpr, nil 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /langsrv/handler-definition.go: -------------------------------------------------------------------------------- 1 | package langsrv 2 | 3 | import ( 4 | "github.com/tliron/glsp" 5 | protocol "github.com/tliron/glsp/protocol_3_16" 6 | ) 7 | 8 | func textDocumentDefinition(context *glsp.Context, params *protocol.DefinitionParams) (interface{}, error) { 9 | rc := NewReqContextAtPosition(¶ms.TextDocumentPositionParams) 10 | 11 | token, _, err := rc.findToken() 12 | if err != nil && token == "" { 13 | return nil, nil 14 | } 15 | 16 | for _, imported := range rc.accessibleDeclarations(context) { 17 | decl := imported.decl 18 | if string(decl.DeclName()) != token || decl.Meta().Source == nil { 19 | continue 20 | } 21 | return &[]protocol.LocationLink{ 22 | { 23 | TargetURI: protocol.DocumentUri(decl.Meta().Source.FileName), 24 | TargetRange: protocol.Range{ 25 | Start: protocol.Position{ 26 | Line: uint32(decl.Meta().Source.Start.Line), 27 | Character: uint32(decl.Meta().Source.Start.Column), 28 | }, 29 | End: protocol.Position{ 30 | Line: uint32(decl.Meta().Source.End.Line), 31 | Character: uint32(decl.Meta().Source.End.Column), 32 | }, 33 | }, 34 | TargetSelectionRange: protocol.Range{ 35 | Start: protocol.Position{ 36 | Line: uint32(decl.Meta().Source.Start.Line), 37 | Character: uint32(decl.Meta().Source.Start.Column), 38 | }, 39 | End: protocol.Position{ 40 | Line: uint32(decl.Meta().Source.End.Line), 41 | Character: uint32(decl.Meta().Source.End.Column), 42 | }, 43 | }, 44 | }, 45 | }, nil 46 | } 47 | return nil, nil 48 | } 49 | -------------------------------------------------------------------------------- /stdlib/tests/internal/test-cases.lithia: -------------------------------------------------------------------------------- 1 | import tests 2 | 3 | import lists 4 | import strings 5 | import rx 6 | 7 | let testCases = rx.Variable Nil 8 | 9 | func runTestCase { summary, testCase => 10 | eager summary.failedTests 11 | 12 | let testNumber = summary.ok + summary.notOk + 1 13 | let failure = rx.Variable None 14 | print strings.concat [ 15 | "# ", 16 | testNumber, 17 | " - ", 18 | testCase.title 19 | ] 20 | testCase.impl { message => 21 | let combined = with failure.current, type Optional { 22 | Some: { previous => Some strings.concat [previous.value, "; ", message] }, 23 | None: { _ => Some message } 24 | } 25 | failure.accept combined 26 | } 27 | 28 | with failure.current, type Optional { 29 | Some: { some => 30 | print strings.concat [ 31 | "not ok ", 32 | testNumber, 33 | " - ", 34 | testCase.title 35 | ] 36 | print " ---" 37 | print " message: ".append some.value 38 | print " severity: fail" 39 | tests.TestSummary summary.ok, summary.notOk+1, (lists.append testNumber, summary.failedTests) 40 | }, 41 | None: { _ => 42 | print strings.concat [ 43 | "ok ", 44 | testNumber, 45 | " - ", 46 | testCase.title 47 | ] 48 | tests.TestSummary (summary.ok+1), summary.notOk, summary.failedTests 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /parser/parse-decl-field.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseFieldDeclaration() (*ast.DeclField, []SyntaxError) { 8 | switch fp.Node.Type() { 9 | case TYPE_NODE_DATA_PROPERTY_VALUE: 10 | return fp.parseDataPropertyValue() 11 | case TYPE_NODE_DATA_PROPERTY_FUNCTION: 12 | return fp.parseDataPropertyFunction() 13 | default: 14 | return nil, []SyntaxError{fp.SyntaxErrorf("unexpected node type %s", fp.Node.Type())} 15 | } 16 | } 17 | 18 | func (fp *FileParser) parseDataPropertyValue() (*ast.DeclField, []SyntaxError) { 19 | name := ast.Identifier(fp.Node.ChildByFieldName("name").Content(fp.Source)) 20 | field := ast.MakeDeclField(name, nil, fp.AstSource()) 21 | field.Docs = fp.ConsumeDocs() 22 | return field, nil 23 | } 24 | 25 | func (fp *FileParser) parseDataPropertyFunction() (*ast.DeclField, []SyntaxError) { 26 | name := ast.Identifier(fp.Node.ChildByFieldName("name").Content(fp.Source)) 27 | paramsNode := fp.Node.ChildByFieldName("parameters") 28 | 29 | params := make([]ast.DeclParameter, 0) 30 | errors := []SyntaxError{} 31 | for i := 0; i < int(paramsNode.NamedChildCount()); i++ { 32 | child := paramsNode.NamedChild(i) 33 | param, paramErrors := fp.SameScopeChildParser(child).ParseParameterDeclaration() 34 | if paramErrors != nil { 35 | errors = append(errors, paramErrors...) 36 | } 37 | if param != nil { 38 | params = append(params, *param) 39 | } 40 | } 41 | 42 | field := ast.MakeDeclField(name, params, fp.AstSource()) 43 | field.Docs = fp.ConsumeDocs() 44 | return field, errors 45 | } 46 | -------------------------------------------------------------------------------- /stdlib/controls/monad.lithia: -------------------------------------------------------------------------------- 1 | module functors 2 | 3 | /// Monads apply a function returning wrapped values to a wrapped value. 4 | /// 5 | /// Invariants: 6 | /// 1. Left-Identity: `(pipe [pure, flatMap f], value) == f value` 7 | /// 2. Right-Identity: `(pipe [pure, flatMap { x => x }], value) == pure value` 8 | /// 3. Associative: `(pipe [pure, flatMap f, flatMap g], value) == pipe [pure, flatMap g, flatMap f], value` 9 | data Monad { 10 | /// Wraps a value in a neutral context. 11 | pure value 12 | /// Transforms a wrapped value and merges potential partial results. 13 | flatMap f, instance 14 | } 15 | 16 | /// Valid witnesses for a monad. 17 | /// 18 | /// ``` 19 | /// import lists 20 | /// 21 | /// flatMap repeat 2, lists 22 | /// flatMap repeat 2, lists.monad 23 | /// ``` 24 | enum MonadWitness { 25 | Monad 26 | Module 27 | } 28 | 29 | /// Creates a Monad from a given MonadWitness. 30 | func monadFrom { monadWitness => 31 | with monadWitness, type MonadWitness { 32 | Monad: { witness => witness }, 33 | Module: { module => 34 | Monad module.pure, monad.flatMap 35 | } 36 | } 37 | } 38 | 39 | /// Creates a pure monad value from a yet unknown witness. 40 | /// Can be used in generic contexts, where the witness will be curried. 41 | func pure { value, witness => 42 | (monadFrom witness).pure value 43 | } 44 | 45 | /// Flat map for a yet unknown witness and instance. 46 | /// Can be used in generic contexts, where the witness will be curried. 47 | func flatMap { f, witness, instance => 48 | (monadFrom witness).flatMap f, instance 49 | } 50 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.202.5/containers/go 3 | { 4 | "name": "Go", 5 | "build": { 6 | "dockerfile": "Dockerfile", 7 | "args": { 8 | // Update the VARIANT arg to pick a version of Go: 1, 1.16, 1.17 9 | // Append -bullseye or -buster to pin to an OS version. 10 | // Use -bullseye variants on local arm64/Apple Silicon. 11 | "VARIANT": "1-bullseye", 12 | // Options 13 | "NODE_VERSION": "none" 14 | } 15 | }, 16 | "runArgs": [ 17 | "--init", 18 | "--cap-add=SYS_PTRACE", 19 | "--security-opt", 20 | "seccomp=unconfined" 21 | ], 22 | // Set *default* container specific settings.json values on container create. 23 | "settings": { 24 | "go.toolsManagement.checkForUpdates": "local", 25 | "go.useLanguageServer": true, 26 | "go.gopath": "/go", 27 | "go.goroot": "/usr/local/go", 28 | "lithia.path": ["go", "run", "./app/lithia"] 29 | }, 30 | // Add the IDs of extensions you want installed when the container is created. 31 | "extensions": ["golang.Go", "vknabel.vscode-lithia"], 32 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 33 | // "forwardPorts": [], 34 | // Use 'postCreateCommand' to run commands after the container is created. 35 | // "postCreateCommand": "go version", 36 | // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. 37 | "remoteUser": "vscode" 38 | } 39 | -------------------------------------------------------------------------------- /ast/decl-import.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var _ Decl = DeclImport{} 9 | var _ Overviewable = DeclImport{} 10 | 11 | type DeclImport struct { 12 | Alias Identifier 13 | ModuleName ModuleName 14 | Members []DeclImportMember 15 | 16 | MetaInfo *MetaDecl 17 | } 18 | 19 | func (e DeclImport) DeclName() Identifier { 20 | return e.Alias 21 | } 22 | 23 | func (e DeclImport) DeclOverview() string { 24 | if e.Alias != "" { 25 | return fmt.Sprintf("import %s = %s", e.Alias, e.ModuleName) 26 | } else { 27 | return fmt.Sprintf("import %s", e.ModuleName) 28 | } 29 | } 30 | 31 | func (e DeclImport) Meta() *MetaDecl { 32 | return e.MetaInfo 33 | } 34 | 35 | func (e DeclImport) IsExportedDecl() bool { 36 | return false 37 | } 38 | 39 | func (e *DeclImport) AddMember(member DeclImportMember) { 40 | e.Members = append(e.Members, member) 41 | } 42 | 43 | func MakeDeclImport(name ModuleName, source *Source) *DeclImport { 44 | segments := strings.Split(string(name), ".") 45 | alias := Identifier(segments[len(segments)-1]) 46 | return &DeclImport{ 47 | Alias: alias, 48 | ModuleName: name, 49 | Members: make([]DeclImportMember, 0), 50 | MetaInfo: &MetaDecl{source}, 51 | } 52 | } 53 | 54 | func MakeDeclAliasImport(alias Identifier, name ModuleName, source *Source) *DeclImport { 55 | return &DeclImport{ 56 | Alias: alias, 57 | ModuleName: name, 58 | Members: make([]DeclImportMember, 0), 59 | MetaInfo: &MetaDecl{source}, 60 | } 61 | } 62 | 63 | func (DeclImport) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 64 | // no nested decls 65 | } 66 | -------------------------------------------------------------------------------- /stdlib/tests.md: -------------------------------------------------------------------------------- 1 | # tests 2 | 3 | _module_ 4 | A basic testing framework. Compatible to TAP13. 5 | It doesn't provide any expectations or matchers. 6 | Instead there is only a fail function. 7 | 8 | ``` 9 | import tests 10 | 11 | tests.test "my test case", { fail => 12 | when False, fail "when should not trigger when False" 13 | } 14 | 15 | when tests.enabled, tests.runTests 16 | ``` 17 | 18 | By default, tests are enabled, when `$LITHIA_TESTS` is set. 19 | 20 | ### Discussion 21 | 22 | In case you really need expectations, writing a wrapper around tests should be possible. 23 | 24 | - _data_ [TestCase](#TestCase) 25 | - _data_ [TestSummary](#TestSummary) 26 | - _func_ [runTests](#runTests) 27 | - _func_ [test](#test) case, function 28 | 29 | ## TestCase 30 | 31 | _data_ Represents a buffered test case. 32 | 33 | ### Properties 34 | 35 | - `title` - The title of the test case for the logs. 36 | - `impl fail` - The implementation of the test case. 37 | Calls fail with a String, when failing 38 | 39 | ## TestSummary 40 | 41 | _data_ The prinatble summary of all tests. 42 | 43 | ### Properties 44 | 45 | - `ok` - How many tests have been ok. 46 | - `notOk` - How many tests have been not ok. 47 | - `failedTests` - List of failed test numbers. 48 | 49 | ## runTests 50 | 51 | _func_ `runTests` 52 | 53 | Runs all test cases, that have been buffered by now. 54 | 55 | ## test 56 | 57 | _func_ `test case, function` 58 | 59 | Adds a new test case to the queue and will be executed once `runTests` has been called. 60 | 61 | ``` 62 | import tests 63 | 64 | tests.test "my test case", { fail => 65 | when False, fail "when should not trigger when False" 66 | } 67 | ``` 68 | 69 | 70 | -------------------------------------------------------------------------------- /runtime/prelude-anonymous-function.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/vknabel/lithia/ast" 8 | ) 9 | 10 | var _ RuntimeValue = PreludeAnonymousFunction{} 11 | var _ CallableRuntimeValue = PreludeAnonymousFunction{} 12 | 13 | type PreludeAnonymousFunction struct { 14 | Name string 15 | Params []string 16 | Impl func(args []Evaluatable) (RuntimeValue, *RuntimeError) 17 | } 18 | 19 | func MakeAnonymousFunction( 20 | name string, 21 | params []string, 22 | impl func(args []Evaluatable) (RuntimeValue, *RuntimeError), 23 | ) PreludeAnonymousFunction { 24 | return PreludeAnonymousFunction{ 25 | Name: name, 26 | Params: params, 27 | Impl: impl, 28 | } 29 | } 30 | 31 | func (f PreludeAnonymousFunction) Lookup(member string) (Evaluatable, *RuntimeError) { 32 | switch member { 33 | case "arity": 34 | return NewConstantRuntimeValue(PreludeInt(f.Arity())), nil 35 | default: 36 | return nil, NewRuntimeErrorf("no such member: %s for %s", member, f.RuntimeType().String()) 37 | } 38 | } 39 | 40 | func (PreludeAnonymousFunction) RuntimeType() RuntimeTypeRef { 41 | return PreludeFunctionTypeRef 42 | } 43 | 44 | func (f PreludeAnonymousFunction) String() string { 45 | return fmt.Sprintf("", f.Name, strings.Join(f.Params, ", ")) 46 | } 47 | 48 | func (f PreludeAnonymousFunction) Arity() int { 49 | return len(f.Params) 50 | } 51 | 52 | func (f PreludeAnonymousFunction) Call(args []Evaluatable, fromExpr ast.Expr) (RuntimeValue, *RuntimeError) { 53 | if len(args) != f.Arity() { 54 | panic("use Call to call functions!") 55 | } 56 | return f.Impl(args) 57 | } 58 | 59 | func (f PreludeAnonymousFunction) Source() *ast.Source { 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /runtime/prelude-curried-callable.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/vknabel/lithia/ast" 7 | ) 8 | 9 | var _ RuntimeValue = PreludeCurriedCallable{} 10 | var _ CallableRuntimeValue = PreludeCurriedCallable{} 11 | 12 | type PreludeCurriedCallable struct { 13 | actual CallableRuntimeValue 14 | arguments []Evaluatable 15 | remainingArity int 16 | } 17 | 18 | func MakeCurriedCallable(actual CallableRuntimeValue, arguments []Evaluatable) PreludeCurriedCallable { 19 | return PreludeCurriedCallable{ 20 | actual: actual, 21 | arguments: arguments, 22 | remainingArity: actual.Arity() - len(arguments), 23 | } 24 | } 25 | 26 | func (f PreludeCurriedCallable) Lookup(member string) (Evaluatable, *RuntimeError) { 27 | switch member { 28 | case "arity": 29 | return NewConstantRuntimeValue(PreludeInt(f.Arity())), nil 30 | default: 31 | return nil, NewRuntimeErrorf("no such member: %s for %s", member, f.RuntimeType().String()) 32 | } 33 | } 34 | 35 | func (PreludeCurriedCallable) RuntimeType() RuntimeTypeRef { 36 | return PreludeFunctionTypeRef 37 | } 38 | 39 | func (f PreludeCurriedCallable) String() string { 40 | return fmt.Sprintf("%s curried by %d", f.actual.String(), f.remainingArity) 41 | } 42 | 43 | func (f PreludeCurriedCallable) Arity() int { 44 | return f.remainingArity 45 | } 46 | 47 | func (f PreludeCurriedCallable) Call(args []Evaluatable, fromExpr ast.Expr) (RuntimeValue, *RuntimeError) { 48 | if len(args) != f.Arity() { 49 | panic("use Call to call functions!") 50 | } 51 | allArgs := append(f.arguments, args...) 52 | return Call(f.actual, allArgs, fromExpr) 53 | } 54 | 55 | func (f PreludeCurriedCallable) Source() *ast.Source { 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /stdlib/cmp.md: -------------------------------------------------------------------------------- 1 | # cmp 2 | 3 | _module_ 4 | Defines comparision operations, ascending and descending of values. 5 | 6 | - _data_ [Ascending](#Ascending) 7 | - _data_ [Comparable](#Comparable) 8 | - _data_ [Descending](#Descending) 9 | - _data_ [Equal](#Equal) 10 | - _enum_ [Order](#Order) 11 | - _func_ [equatableFrom](#equatableFrom) comparableWitness 12 | - _func_ [pullback](#pullback) f, witness 13 | 14 | ## Ascending 15 | 16 | _data_ Indicates an ascending order of two values. 17 | For example 1 and 2. 18 | 19 | ## Comparable 20 | 21 | _data_ Instances compare values regarding the order. 22 | Witnesses are typically only defined for specific types. 23 | 24 | ### Properties 25 | 26 | - `compare lhs, rhs` - Compares two values. 27 | @returns Order 28 | 29 | ## Descending 30 | 31 | _data_ Indicates an descending order of two values. 32 | For example 2 and 1. 33 | 34 | ## Equal 35 | 36 | _data_ Both values are ordered equally. 37 | In context of Order, it doesn't necessarily require equality. 38 | 39 | ## Order 40 | 41 | _enum_ 42 | Represents the order of two values. 43 | 44 | ### Cases 45 | 46 | - [Ascending](#Ascending) 47 | - [Equal](#Equal) 48 | - [Descending](#Descending) 49 | 50 | ## equatableFrom 51 | 52 | _func_ `equatableFrom comparableWitness` 53 | 54 | Creates an `eq.Equatable` from a `cmp.Comparable`. 55 | `cmp.Equal` will result in `True`, 56 | `cmp.Ascending` and `cmp.Descending` will be `False`. 57 | 58 | @returns eq.Equatable 59 | 60 | ## pullback 61 | 62 | _func_ `pullback f, witness` 63 | 64 | Lifts an existing `cmp.Comparable` witness to a different type. 65 | Can be used to pick a specific property of complex data. 66 | 67 | ```lithia 68 | let compareUsersById = cmp.pullback { user => user.id }, cmp.numeric 69 | ``` 70 | 71 | -------------------------------------------------------------------------------- /stdlib/controls/contravariant.lithia: -------------------------------------------------------------------------------- 1 | module monads 2 | 3 | /// A contravariant wraps behavior to handle inputs depending on a context. 4 | /// Put simply, a contravariant maps inputs, while a functor maps outputs. 5 | /// 6 | /// ``` 7 | /// import eq 8 | /// 9 | /// let personByNameEquatbale = eq.contravariant.pullback { person => person.name }, strict 10 | /// 11 | /// personByNameEquatbale.equal Person "Alice", Person "Bob" 12 | /// // > False 13 | /// ``` 14 | /// 15 | /// Invariants: 16 | /// 1. Identity: `(pullback { a => a }, value) == value` 17 | /// 2. Associative: `(pipe [pullback f, pullback g], value) == pullback pipe [f, g], value` 18 | data Contravariant { 19 | pullback transform, value 20 | } 21 | 22 | /// Defines all valid witnesses for a contravariant. 23 | /// 24 | /// ``` 25 | /// import cmp 26 | /// 27 | /// pullback { person => person.name }, cmp 28 | /// pullback { person => person.name }, cmp.pullback 29 | /// pullback { person => person.name }, cmp.contravariant 30 | /// ``` 31 | enum ContravariantWitness { 32 | Contravariant 33 | /// Only modules containing all members of a contravariant are valid as convenience. 34 | Module 35 | /// Only pullback functions are valid. 36 | Function 37 | } 38 | 39 | /// Creates a Contravariant from a given ContravariantWitness. 40 | func contravariantFrom { moduleWitness => 41 | with moduleWitness, type ContravariantWitness { 42 | Contravariant: { witness => witness }, 43 | Module: { module => 44 | Contravariant module.pullback 45 | }, 46 | Function: { pullback => 47 | Contravariant pullback 48 | } 49 | } 50 | } 51 | 52 | /// pullback for a yet unknown witness. 53 | func pullback { f, witness => 54 | (contravariantFrom witness).pullback f 55 | } 56 | -------------------------------------------------------------------------------- /runtime/interpreter-context.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | sitter "github.com/smacker/go-tree-sitter" 5 | "github.com/vknabel/lithia/ast" 6 | ) 7 | 8 | type InterpreterContext struct { 9 | interpreter *Interpreter 10 | fileDef *ast.SourceFile 11 | module *RuntimeModule 12 | 13 | path []string 14 | environment *Environment 15 | 16 | evalCache *LazyEvaluationCache 17 | } 18 | 19 | func (inter *Interpreter) NewInterpreterContext(fileDef *ast.SourceFile, module *RuntimeModule, node *sitter.Node, source []byte, environment *Environment) *InterpreterContext { 20 | if environment == nil { 21 | environment = NewEnvironment(module.Environment.Private()) 22 | } 23 | return &InterpreterContext{ 24 | interpreter: inter, 25 | fileDef: fileDef, 26 | module: module, 27 | path: []string{}, 28 | environment: environment, 29 | evalCache: NewLazyEvaluationCache(), 30 | } 31 | } 32 | 33 | func (i *InterpreterContext) NestedInterpreterContext(name string) *InterpreterContext { 34 | return &InterpreterContext{ 35 | interpreter: i.interpreter, 36 | fileDef: i.fileDef, 37 | module: i.module, 38 | path: append(i.path, name), 39 | environment: NewEnvironment(i.environment), 40 | evalCache: NewLazyEvaluationCache(), 41 | } 42 | } 43 | 44 | func (i *InterpreterContext) Evaluate() (RuntimeValue, *RuntimeError) { 45 | return i.evalCache.Evaluate(func() (RuntimeValue, *RuntimeError) { 46 | if len(i.fileDef.Statements) == 0 { 47 | return nil, nil 48 | } 49 | var result RuntimeValue 50 | for _, stmt := range i.fileDef.Statements { 51 | expr := MakeEvaluatableExpr(i, stmt) 52 | value, err := expr.Evaluate() 53 | if err != nil { 54 | return nil, err 55 | } 56 | result = value 57 | } 58 | return result, nil 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /ast/decl-enum.go: -------------------------------------------------------------------------------- 1 | package ast 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var _ Decl = DeclEnum{} 9 | var _ Overviewable = DeclEnum{} 10 | 11 | type DeclEnum struct { 12 | Name Identifier 13 | Cases []*DeclEnumCase 14 | 15 | Docs *Docs 16 | MetaInfo *MetaDecl 17 | } 18 | 19 | func (e DeclEnum) DeclName() Identifier { 20 | return e.Name 21 | } 22 | 23 | func (e DeclEnum) DeclOverview() string { 24 | if len(e.Cases) == 0 { 25 | return fmt.Sprintf("enum %s", e.Name) 26 | } 27 | caseLines := make([]string, 0) 28 | for _, cs := range e.Cases { 29 | caseLines = append(caseLines, " "+string(cs.Name)) 30 | } 31 | return fmt.Sprintf("enum %s {\n%s\n}", e.Name, strings.Join(caseLines, "\n")) 32 | } 33 | 34 | func (e DeclEnum) Meta() *MetaDecl { 35 | return e.MetaInfo 36 | } 37 | 38 | func (e DeclEnum) IsExportedDecl() bool { 39 | return true 40 | } 41 | 42 | func MakeDeclEnum(name Identifier, source *Source) *DeclEnum { 43 | return &DeclEnum{ 44 | Name: name, 45 | Cases: []*DeclEnumCase{}, 46 | Docs: MakeDocs([]string{}), 47 | MetaInfo: &MetaDecl{ 48 | Source: source, 49 | }, 50 | } 51 | } 52 | 53 | func (e *DeclEnum) AddCase(case_ *DeclEnumCase) { 54 | e.Cases = append(e.Cases, case_) 55 | } 56 | 57 | func (e DeclEnum) String() string { 58 | declarationClause := fmt.Sprintf("enum %s", e.Name) 59 | if len(e.Cases) == 0 { 60 | return declarationClause 61 | } 62 | declarationClause += " { " 63 | for _, caseDecl := range e.Cases { 64 | declarationClause += string(caseDecl.Name) + "; " 65 | } 66 | return declarationClause + "}" 67 | } 68 | 69 | func (decl DeclEnum) ProvidedDocs() *Docs { 70 | return decl.Docs 71 | } 72 | 73 | func (decl DeclEnum) EnumerateNestedDecls(enumerate func(interface{}, []Decl)) { 74 | // no nested decls - will be handled by the parser 75 | } 76 | -------------------------------------------------------------------------------- /runtime/runtime-type-ref.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/vknabel/lithia/ast" 7 | ) 8 | 9 | var _ RuntimeType = RuntimeTypeRef{} 10 | 11 | type RuntimeTypeRef struct { 12 | Name ast.Identifier 13 | Module ast.ModuleName 14 | } 15 | 16 | func MakeRuntimeTypeRef(name ast.Identifier, module ast.ModuleName) RuntimeTypeRef { 17 | return RuntimeTypeRef{name, module} 18 | } 19 | 20 | func (r RuntimeTypeRef) String() string { 21 | return fmt.Sprintf("%s.%s", r.Module, r.Name) 22 | } 23 | 24 | func (r RuntimeTypeRef) Declaration(inter *Interpreter) (ast.Decl, *RuntimeError) { 25 | valueType, err := r.ResolveType(inter) 26 | if err != nil { 27 | return nil, err 28 | } 29 | if runtimeType, ok := valueType.(RuntimeType); ok { 30 | return runtimeType.Declaration(inter) 31 | } 32 | panic(fmt.Errorf("TODO: decl runtime value %s has no declaration", valueType)) 33 | } 34 | 35 | func (r RuntimeTypeRef) ResolveType(inter *Interpreter) (DeclRuntimeValue, *RuntimeError) { 36 | module, ok := inter.Modules[r.Module] 37 | if !ok { 38 | return nil, NewRuntimeErrorf("module not found %s", r.Module) 39 | } 40 | // TODO: non-local types! 41 | value, err := module.Environment.GetExportedEvaluatedRuntimeValue(string(r.Name)) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | if typeValue, ok := value.(DeclRuntimeValue); ok { 47 | return typeValue, nil 48 | } else { 49 | return nil, NewRuntimeErrorf("not a valid type %s", r) 50 | } 51 | } 52 | 53 | func (ref RuntimeTypeRef) HasInstance(inter *Interpreter, value RuntimeValue) (bool, *RuntimeError) { 54 | if ref == PreludeAnyTypeRef { 55 | return true, nil 56 | } 57 | runtimeType, err := ref.ResolveType(inter) 58 | if err != nil { 59 | return false, err 60 | } 61 | return runtimeType.HasInstance(inter, value) 62 | } 63 | -------------------------------------------------------------------------------- /stdlib/lists/lists_t/drop_t.lithia: -------------------------------------------------------------------------------- 1 | import lists 2 | import strings 3 | import tests { test } 4 | 5 | test "lists.dropFirst", { fail => 6 | unless (lists.dropFirst []) == [], fail "empty lists stay empty" 7 | unless (lists.dropFirst [0]) == [], fail "single elements will be empty" 8 | unless (lists.dropFirst [1, 2, 3]) == [2, 3], fail "head will be removed" 9 | } 10 | 11 | test "lists.dropN 0", { fail => 12 | unless (lists.dropN 0, []) == [], fail "empty lists should always stay empty" 13 | unless (lists.dropN 0, [0, 1, 2, 3]) == [0, 1, 2, 3], fail "dropping 0 should keep all elements" 14 | } 15 | 16 | test "lists.dropN -", { fail => 17 | unless (lists.dropN -2, []) == [], fail "empty lists should always stay empty" 18 | unless (lists.dropN -3, [0, 1, 2, 3]) == [0, 1, 2, 3], fail "negative counts be treated as 0" 19 | } 20 | 21 | test "lists.dropN +", { fail => 22 | unless (lists.dropN 1, []) == [], fail "empty lists should always stay empty" 23 | unless (lists.dropN 2, [1]) == [], fail "dropping more elements than available should be empty" 24 | unless (lists.dropN 1, [1, 2, 3]) == [2, 3], fail "dropping 1 should only remove first" 25 | } 26 | 27 | test "lists.dropWhile", { fail => 28 | func constTrue { _ => True } 29 | func constFalse { _ => False } 30 | func lessThan { n, i => i < n } 31 | 32 | unless (lists.dropWhile constFalse, []) == [], fail "dropping none when empty is empty" 33 | unless (lists.dropWhile constFalse, [1, 2, 3]) == [1, 2, 3], fail "dropping none keeps all" 34 | unless (lists.dropWhile constTrue, [1, 2, 3]) == [], fail "dropping all is empty" 35 | 36 | unless (lists.dropWhile lessThan 2, [1, 2, 3]) == [2, 3], fail "should drop to [2, 3]" 37 | unless (lists.dropWhile lessThan 2, [3, 2, 1]) == [3, 2, 1], fail "should drop to [3, 2, 1]" 38 | } 39 | -------------------------------------------------------------------------------- /parser/parse-expr-function.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseFunctionExpr(name string) (*ast.ExprFunc, []SyntaxError) { 8 | parametersNode := fp.Node.ChildByFieldName("parameters") 9 | bodyNode := fp.Node.ChildByFieldName("body") 10 | 11 | errors := []SyntaxError{} 12 | var params []ast.DeclParameter 13 | var paramsErrors []SyntaxError 14 | if parametersNode != nil { 15 | params, paramsErrors = fp.SameScopeChildParser(parametersNode).ParseParameterDeclarationList() 16 | } else { 17 | params = []ast.DeclParameter{} 18 | paramsErrors = []SyntaxError{} 19 | } 20 | if paramsErrors != nil { 21 | errors = append(errors, paramsErrors...) 22 | } 23 | if params == nil { 24 | return nil, errors 25 | } 26 | function := ast.MakeExprFunc(name, params, fp.AstSource()) 27 | if bodyNode == nil { 28 | return function, nil 29 | } 30 | bodyParser := fp.NewScopeChildParser(bodyNode) 31 | for i := 0; i < int(bodyNode.NamedChildCount()); i++ { 32 | child := bodyNode.NamedChild(i) 33 | if bodyParser.ParseChildCommentIfNeeded(child) { 34 | continue 35 | } 36 | 37 | childParser := bodyParser.ChildParserConsumingComments(child) 38 | decls, declErrors := childParser.ParseDeclsIfGiven() 39 | if declErrors != nil { 40 | errors = append(errors, declErrors...) 41 | } 42 | if decls != nil { 43 | for _, decl := range decls { 44 | function.AddDecl(decl) 45 | } 46 | continue 47 | } 48 | 49 | expr, exprErrors := childParser.ParseExpressionIfGiven() 50 | if exprErrors != nil { 51 | errors = append(errors, exprErrors...) 52 | } 53 | if expr != nil { 54 | function.AddExpr(expr) 55 | continue 56 | } 57 | 58 | errors = append(errors, fp.SyntaxErrorf("unknown child type")) 59 | } 60 | 61 | return function, errors 62 | } 63 | -------------------------------------------------------------------------------- /parser/parse-decl-extern.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseExternDeclaration() (*ast.Decl, []SyntaxError) { 8 | name := ast.Identifier(fp.Node.ChildByFieldName("name").Content(fp.Source)) 9 | parameters := fp.Node.ChildByFieldName("parameters") 10 | if parameters != nil { 11 | paramsParser := fp.NewScopeChildParser(parameters) 12 | var decl ast.Decl 13 | params, errs := paramsParser.ParseParameterDeclarationList() 14 | if params == nil { 15 | return nil, errs 16 | } 17 | funcDecl := ast.MakeDeclExternFunc(name, params, fp.AstSource()) 18 | funcDecl.Docs = fp.ConsumeDocs() 19 | decl = *funcDecl 20 | return &decl, nil 21 | } 22 | 23 | typeDecl := ast.MakeDeclExternType(name, fp.AstSource()) 24 | typeDecl.Docs = fp.ConsumeDocs() 25 | 26 | propertiesNode := fp.Node.ChildByFieldName("properties") 27 | if propertiesNode == nil { 28 | var decl ast.Decl 29 | decl = *typeDecl 30 | return &decl, nil 31 | } 32 | propsp := fp.NewScopeChildParser(propertiesNode) 33 | 34 | var numberOfFields int 35 | if propertiesNode != nil { 36 | numberOfFields = int(propertiesNode.ChildCount()) 37 | } else { 38 | numberOfFields = 0 39 | } 40 | errors := []SyntaxError{} 41 | for i := 0; i < numberOfFields; i++ { 42 | child := propertiesNode.NamedChild(i) 43 | if child.Type() == TYPE_NODE_COMMENT { 44 | propsp.Comments = append(propsp.Comments, child.Content(fp.Source)) 45 | continue 46 | } 47 | 48 | childp := propsp.ChildParserConsumingComments(child) 49 | field, propErrors := childp.ParseFieldDeclaration() 50 | if len(propErrors) > 0 { 51 | errors = append(errors, propErrors...) 52 | } 53 | if field != nil { 54 | typeDecl.AddField(*field) 55 | } 56 | } 57 | var decl ast.Decl 58 | decl = *typeDecl 59 | return &decl, errors 60 | } 61 | -------------------------------------------------------------------------------- /parser/parse-decl.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | ) 6 | 7 | func (fp *FileParser) ParseDeclsIfGiven() ([]ast.Decl, []SyntaxError) { 8 | switch fp.Node.Type() { 9 | case TYPE_NODE_MODULE_DECLARATION: 10 | stmt, err := fp.ParseModuleDeclaration() 11 | if stmt != nil { 12 | return []ast.Decl{*stmt}, err 13 | } else { 14 | return []ast.Decl{}, err 15 | } 16 | case TYPE_NODE_IMPORT_DECLARATION: 17 | importDecl, err := fp.ParseImportDeclaration() 18 | if importDecl != nil { 19 | memberImportDecls := make([]ast.Decl, len(importDecl.Members)) 20 | for i, member := range importDecl.Members { 21 | memberImportDecls[i] = member 22 | } 23 | return append(memberImportDecls, *importDecl), err 24 | } else { 25 | return []ast.Decl{}, err 26 | } 27 | case TYPE_NODE_LET_DECLARATION: 28 | stmt, err := fp.ParseLetDeclaration() 29 | if stmt != nil { 30 | return []ast.Decl{*stmt}, err 31 | } else { 32 | return []ast.Decl{}, err 33 | } 34 | case TYPE_NODE_ENUM_DECLARATION: 35 | stmt, childDecls, err := fp.ParseEnumDeclaration() 36 | if stmt != nil { 37 | return append(childDecls, *stmt), err 38 | } else { 39 | return []ast.Decl{}, err 40 | } 41 | case TYPE_NODE_DATA_DECLARATION: 42 | stmt, err := fp.ParseDataDeclaration() 43 | if stmt != nil { 44 | return []ast.Decl{*stmt}, err 45 | } else { 46 | return []ast.Decl{}, err 47 | } 48 | case TYPE_NODE_FUNCTION_DECLARATION: 49 | stmt, err := fp.ParseFunctionDeclaration() 50 | if stmt != nil { 51 | return []ast.Decl{*stmt}, err 52 | } else { 53 | return []ast.Decl{}, err 54 | } 55 | case TYPE_NODE_EXTERN_DECLARATION: 56 | stmt, err := fp.ParseExternDeclaration() 57 | if stmt != nil { 58 | return []ast.Decl{*stmt}, err 59 | } else { 60 | return []ast.Decl{}, err 61 | } 62 | 63 | default: 64 | return nil, nil 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /external/rx/extern-rx-variable-type.go: -------------------------------------------------------------------------------- 1 | package rx 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | "github.com/vknabel/lithia/runtime" 6 | ) 7 | 8 | var _ runtime.RuntimeValue = RxVariableType{} 9 | var _ runtime.DeclRuntimeValue = RxVariableType{} 10 | var _ runtime.RuntimeType = RxVariableType{} 11 | var _ runtime.CallableRuntimeValue = RxVariableType{} 12 | 13 | var RxVariableTypeRef = runtime.MakeRuntimeTypeRef("Variable", "rx") 14 | 15 | type RxVariableType struct { 16 | ast.DeclExternType 17 | } 18 | 19 | func (RxVariableType) RuntimeType() runtime.RuntimeTypeRef { 20 | return runtime.PreludeAnyTypeRef 21 | } 22 | 23 | func (RxVariableType) String() string { 24 | return RxVariableTypeRef.String() 25 | } 26 | 27 | func (t RxVariableType) Declaration(inter *runtime.Interpreter) (ast.Decl, *runtime.RuntimeError) { 28 | return t.DeclExternType, nil 29 | } 30 | 31 | func (d RxVariableType) HasInstance(inter *runtime.Interpreter, value runtime.RuntimeValue) (bool, *runtime.RuntimeError) { 32 | if _, ok := value.(RxVariable); ok { 33 | return true, nil 34 | } else { 35 | return false, nil 36 | } 37 | } 38 | 39 | func (RxVariableType) Lookup(member string) (runtime.Evaluatable, *runtime.RuntimeError) { 40 | return nil, runtime.NewRuntimeErrorf("%s is not a member of %s", member, RxVariableTypeRef.String()) 41 | } 42 | 43 | func (RxVariableType) Arity() int { 44 | return 1 45 | } 46 | 47 | func (t RxVariableType) Call(arguments []runtime.Evaluatable, fromExpr ast.Expr) (runtime.RuntimeValue, *runtime.RuntimeError) { 48 | if len(arguments) != 1 { 49 | return nil, runtime.NewRuntimeErrorf("too many arguments for variable type %s", t) 50 | } 51 | value, err := arguments[0].Evaluate() 52 | if err != nil { 53 | return nil, err.CascadeDecl(t.DeclExternType) 54 | } 55 | return MakeRxVariable(&t, value), nil 56 | } 57 | 58 | func (t RxVariableType) Source() *ast.Source { 59 | return t.Meta().Source 60 | } 61 | -------------------------------------------------------------------------------- /runtime/prelude-data-decl.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/vknabel/lithia/ast" 8 | ) 9 | 10 | var _ RuntimeValue = PreludeDataDecl{} 11 | var _ DeclRuntimeValue = PreludeDataDecl{} 12 | var _ CallableRuntimeValue = PreludeDataDecl{} 13 | 14 | type PreludeDataDecl struct { 15 | Decl ast.DeclData 16 | } 17 | 18 | func (d PreludeDataDecl) Lookup(member string) (Evaluatable, *RuntimeError) { 19 | return nil, NewRuntimeErrorf("cannot access member %s of data type %s, see https://github.com/vknabel/lithia/discussions/25", member, d.Decl.Name) 20 | } 21 | 22 | func (PreludeDataDecl) RuntimeType() RuntimeTypeRef { 23 | return PreludeAnyTypeRef 24 | } 25 | 26 | func (d PreludeDataDecl) String() string { 27 | return fmt.Sprintf("data %s", d.Decl.Name) 28 | } 29 | 30 | func (d PreludeDataDecl) HasInstance(inter *Interpreter, value RuntimeValue) (bool, *RuntimeError) { 31 | if dataVal, ok := value.(DataRuntimeValue); ok { 32 | return reflect.DeepEqual(*dataVal.TypeDecl, d), nil 33 | } else { 34 | return false, nil 35 | } 36 | } 37 | 38 | func (d PreludeDataDecl) Arity() int { 39 | return len(d.Decl.Fields) 40 | } 41 | 42 | func (d PreludeDataDecl) Call(args []Evaluatable, fromExpr ast.Expr) (RuntimeValue, *RuntimeError) { 43 | if len(args) != d.Arity() { 44 | panic("use Call to call functions!") 45 | } 46 | if d.Arity() == 0 { 47 | dataVal, err := MakeDataRuntimeValueMemberwise(&d, make(map[string]Evaluatable)) 48 | return dataVal, err.CascadeDecl(d.Decl) 49 | } 50 | members := make(map[string]Evaluatable) 51 | for i, field := range d.Decl.Fields { 52 | members[string(field.DeclName())] = args[i] 53 | } 54 | dataVal, err := MakeDataRuntimeValueMemberwise(&d, members) 55 | if err != nil { 56 | return dataVal, err.CascadeDecl(d.Decl) 57 | } 58 | return dataVal, nil 59 | } 60 | 61 | func (f PreludeDataDecl) Source() *ast.Source { 62 | return f.Decl.Meta().Source 63 | } 64 | -------------------------------------------------------------------------------- /stdlib/cmp/comparables.lithia: -------------------------------------------------------------------------------- 1 | /// Defines comparision operations, ascending and descending of values. 2 | module cmp 3 | 4 | /// Instances compare values regarding the order. 5 | /// Witnesses are typically only defined for specific types. 6 | data Comparable { 7 | /// Compares two values. 8 | /// @returns Order 9 | compare lhs, rhs 10 | } 11 | 12 | /// Represents the order of two values. 13 | enum Order { 14 | /// Indicates an ascending order of two values. 15 | /// For example 1 and 2. 16 | data Ascending 17 | /// Both values are ordered equally. 18 | /// In context of Order, it doesn't necessarily require equality. 19 | data Equal 20 | /// Indicates an descending order of two values. 21 | /// For example 2 and 1. 22 | data Descending 23 | } 24 | 25 | import tests { test } 26 | 27 | test "cmp.Comparable can be constructed", { fail => 28 | let alwaysEqual = Comparable { lhs, rhs => Equal } 29 | when (alwaysEqual.compare 1, 2) != Equal, fail "behavior of comparable changed" 30 | } 31 | 32 | test "cmp.Ascending is cmp.Order", { fail => 33 | with Ascending, type Order { 34 | Ascending: { _ => /* succeed */ }, 35 | Any: { unexpected => fail unexpected } 36 | } 37 | } 38 | 39 | test "cmp.Equal is cmp.Order", { fail => 40 | with Equal, type Order { 41 | Equal: { _ => /* succeed */ }, 42 | Any: { unexpected => fail unexpected } 43 | } 44 | } 45 | 46 | test "cmp.Descending is cmp.Order", { fail => 47 | with Equal, type Order { 48 | Equal: { _ => /* succeed */ }, 49 | Any: { unexpected => fail unexpected } 50 | } 51 | } 52 | 53 | test "cmp.Order is only Equal, Ascending, Descending", { fail => 54 | with Equal, type Order { 55 | Ascending: { _ => /* succeed */ }, 56 | Equal: { _ => /* succeed */ }, 57 | Descending: { _ => /* succeed */ } 58 | // type expression will fail 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /stdlib/prelude/shim.lithia: -------------------------------------------------------------------------------- 1 | /** 2 | * Implements the most basic data types. 3 | * Espcially those needed for built-in functionality and for the compiler. 4 | * Will always be imported implicitly. 5 | */ 6 | module prelude 7 | 8 | /// Eagerly evaluates a given value recursively. 9 | /// All members of lists, dictionaries and data structures will be evaluated. 10 | extern eager value 11 | /// Prints a message to stdout. 12 | extern print message 13 | /// Prints a debug message to stdout. 14 | extern debug message 15 | 16 | /// A base type for non-fractional numbers like -1, 0, 1 and 2. 17 | extern Int 18 | /// A base type for floating point numbers like 13.37. 19 | extern Float 20 | /// Represents text like "hello world". 21 | extern String { 22 | /// The length of the string. 23 | length 24 | /// Allows to append another string. 25 | append str 26 | } 27 | /// A single character of a string. 28 | extern Char 29 | /// A function that may be called. 30 | extern Function { 31 | /// The minimum arity of the function. 32 | /// If it returns another function, the actual arity might be higher. 33 | arity 34 | } 35 | /// A module. Either from an import or by a module-declaration. 36 | extern Module 37 | /// Any value that exists. 38 | extern Any 39 | 40 | /// Stores values for given String-keys. 41 | /// As dicts are immutable, all changing operations return new copies. 42 | extern Dict { 43 | /// The count of all key-value-pairs. 44 | length 45 | /// Returns Some value for a specific key or None. 46 | get key 47 | /// Creates a copy Dict, which includes the given key-value-pair. 48 | /// The current Dict stays at it is. 49 | set key, value 50 | /// Creates a copy Dict, which includes no value for the given key. 51 | /// The current Dict stays at it is. 52 | delete key 53 | /// A List of all entries as Pair. 54 | entries 55 | /// A List of all keys. 56 | keys 57 | /// A List of all values. 58 | values 59 | } -------------------------------------------------------------------------------- /runtime/prelude-extern-function.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/vknabel/lithia/ast" 8 | ) 9 | 10 | var _ RuntimeValue = PreludeExternFunction{} 11 | var _ CallableRuntimeValue = PreludeExternFunction{} 12 | 13 | type PreludeExternFunction struct { 14 | Decl ast.DeclExternFunc 15 | Impl func(args []Evaluatable) (RuntimeValue, *RuntimeError) 16 | } 17 | 18 | func MakeExternFunction( 19 | decl ast.Decl, 20 | impl func(args []Evaluatable) (RuntimeValue, *RuntimeError), 21 | ) PreludeExternFunction { 22 | externDecl, ok := decl.(ast.DeclExternFunc) 23 | if !ok { 24 | panic(fmt.Errorf("extern func declaration requires func definition: %T %s", decl, decl.DeclName())) 25 | } 26 | return PreludeExternFunction{ 27 | Decl: externDecl, 28 | Impl: impl, 29 | } 30 | } 31 | 32 | func (f PreludeExternFunction) Lookup(member string) (Evaluatable, *RuntimeError) { 33 | switch member { 34 | case "arity": 35 | return NewConstantRuntimeValue(PreludeInt(f.Arity())), nil 36 | default: 37 | return nil, NewRuntimeErrorf("no such member: %s for %s", member, f.RuntimeType().String()) 38 | } 39 | } 40 | 41 | func (PreludeExternFunction) RuntimeType() RuntimeTypeRef { 42 | return PreludeFunctionTypeRef 43 | } 44 | 45 | func (f PreludeExternFunction) String() string { 46 | argNames := make([]string, len(f.Decl.Parameters)) 47 | for i, param := range f.Decl.Parameters { 48 | argNames[i] = string(param.Name) 49 | } 50 | return fmt.Sprintf("", f.Decl.Name, strings.Join(argNames, ", ")) 51 | } 52 | 53 | func (f PreludeExternFunction) Arity() int { 54 | return len(f.Decl.Parameters) 55 | } 56 | 57 | func (f PreludeExternFunction) Call(args []Evaluatable, fromExpr ast.Expr) (RuntimeValue, *RuntimeError) { 58 | if len(args) != f.Arity() { 59 | panic("use Call to call functions!") 60 | } 61 | return f.Impl(args) 62 | } 63 | 64 | func (f PreludeExternFunction) Source() *ast.Source { 65 | return f.Decl.Meta().Source 66 | } 67 | -------------------------------------------------------------------------------- /stdlib/cmp/contravariant.lithia: -------------------------------------------------------------------------------- 1 | module cmp 2 | 3 | import controls { Contravariant } 4 | 5 | /// Lifts an existing `cmp.Comparable` witness to a different type. 6 | /// Can be used to pick a specific property of complex data. 7 | /// 8 | /// ```lithia 9 | /// let compareUsersById = cmp.pullback { user => user.id }, cmp.numeric 10 | /// ``` 11 | func pullback { f, witness => 12 | Comparable { lhs, rhs => 13 | witness.compare f lhs, f rhs 14 | } 15 | } 16 | 17 | /// A `controls.Contravariant` witness for `cmp.Comparable`. 18 | /// Allows to lift comparision to different types. 19 | let contravariant = Contravariant pullback 20 | 21 | import tests { test } 22 | 23 | test "cmp.contravariant is a Contravariant", { fail => 24 | with controls.contravariantFrom cmp.contravariant, type controls.ContravariantWitness { 25 | Contravariant: { _ => /* success */ }, 26 | Any: { unexpected => fail unexpected } 27 | } 28 | } 29 | 30 | test "cmp.pullback can act as Contravariant", { fail => 31 | with controls.contravariantFrom cmp.pullback, type controls.ContravariantWitness { 32 | Contravariant: { _ => /* success */ }, 33 | Any: { unexpected => fail unexpected } 34 | } 35 | } 36 | 37 | test "cmp can act as Contravariant", { fail => 38 | with controls.contravariantFrom cmp, type controls.ContravariantWitness { 39 | Contravariant: { _ => /* success */ }, 40 | Any: { unexpected => fail unexpected } 41 | } 42 | } 43 | 44 | test "cmp.pullback is valid", { fail => 45 | func negate { i => i * -1 } 46 | let negatedNumeric = pullback negate, cmp.numeric 47 | when (negatedNumeric.compare 1, 2) == Ascending, fail "-1 > -2" 48 | } 49 | 50 | test "cmp is a valid contraviant", { fail => 51 | func negateNumber { i => i * -1 } 52 | let negateComparable = controls.pullback negateNumber, cmp 53 | let negated = negateComparable cmp.numeric 54 | when (negated.compare 1, 2) == Ascending, fail "-1 > -2" 55 | } 56 | -------------------------------------------------------------------------------- /stdlib/rx.md: -------------------------------------------------------------------------------- 1 | # rx 2 | 3 | _module_ 4 | A very early concept of implementing functional reactive programming. 5 | Currently only used to provide mutability. 6 | 7 | - _enum_ [Async](#Async) 8 | - _extern_ [Future](#Future) 9 | - _extern_ [Variable](#Variable) 10 | - _func_ [await](#await) async 11 | - _func_ [catch](#catch) transform 12 | - _func_ [flatMap](#flatMap) transform 13 | - _func_ [flatten](#flatten) async 14 | - _func_ [map](#map) transform 15 | - _func_ [onSuccess](#onSuccess) sideEffect 16 | 17 | ## Async 18 | 19 | _enum_ 20 | 21 | ### Cases 22 | 23 | - [Result](#Result) 24 | - [Future](#Future) 25 | - [Variable](#Variable) 26 | 27 | ## Future 28 | 29 | _extern_ 30 | Represents a value calculated in background. 31 | It will arrive some time in the future. 32 | 33 | ```lithia 34 | import results 35 | import rx 36 | 37 | let future = rx.Future { receive => 38 | // will be performed in background 39 | receive results.Success 42 40 | } 41 | 42 | // the .await will block and wait for the result 43 | with future.await, type results.Result { 44 | Success: { value => value }, 45 | Failure: { err => 46 | print err 47 | 0 // as default 48 | }, 49 | } 50 | ``` 51 | 52 | ### Properties 53 | 54 | - `await` - Waits for the future to complete. 55 | This will lock the current function until the result has arrived. 56 | At the end, returns the `results.Result`. 57 | 58 | ## Variable 59 | 60 | _extern_ 61 | Holds a value and enables replacing it. 62 | Planned to propagate value changes to observers, but not implemented, yet. 63 | 64 | ### Properties 65 | 66 | - `accept value` - Changes the currently hold value of the variable. 67 | - `current` - Returns the currently hold value. 68 | 69 | ## await 70 | 71 | _func_ `await async` 72 | 73 | ## catch 74 | 75 | _func_ `catch transform` 76 | 77 | ## flatMap 78 | 79 | _func_ `flatMap transform` 80 | 81 | ## flatten 82 | 83 | _func_ `flatten async` 84 | 85 | ## map 86 | 87 | _func_ `map transform` 88 | 89 | ## onSuccess 90 | 91 | _func_ `onSuccess sideEffect` 92 | 93 | -------------------------------------------------------------------------------- /stdlib/controls/functor.lithia: -------------------------------------------------------------------------------- 1 | module functors 2 | 3 | /// A functor wraps values in a context and allows different decisions depending on the context. 4 | /// For example, the types `Optional` and `List` have functors. 5 | /// 6 | /// ``` 7 | /// import lists 8 | /// import optionals 9 | /// 10 | /// let incr = { i => i + 1 } 11 | /// lists.functor.map incr, [1, 2, 3] 12 | /// // > [2, 3, 4] 13 | /// optionals.functor.map incr, Some 41 14 | /// // > Some 42 15 | /// optionals.functor.map incr, None 16 | /// // > None 17 | /// ``` 18 | /// 19 | /// Invariants: 20 | /// 1. Identity: `(map { a => a }, value) == value` 21 | /// 2. Associative: `(pipe [map f, map g], value) == map pipe [f, g], value` 22 | data Functor { 23 | /// Transforms a wrapped value using a function depending context of the functor 24 | map f, value 25 | } 26 | 27 | /// Defines all valid witnesses for a functor. 28 | /// 29 | /// ``` 30 | /// import lists 31 | /// 32 | /// map incr, lists 33 | /// map incr, lists.map 34 | /// map incr, lists.functor 35 | /// ``` 36 | enum FunctorWitness { 37 | Functor 38 | Module 39 | Function 40 | Monad 41 | } 42 | 43 | /// Creates a Functor from a given FunctorWitness. 44 | func functorFrom { moduleWitness => 45 | with moduleWitness, type FunctorWitness { 46 | Functor: { witness => witness }, 47 | Module: { module => 48 | Functor module.map 49 | }, 50 | Function: { fmap => 51 | Functor fmap 52 | }, 53 | Monad: { monad => 54 | Functor { f, instance => monad.pure (monad.flatMap f, instance) } 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Transforms a wrapped value using a yet unknown functor witness and value. 61 | * Essentially just uses the map of the given witness, 62 | * but allows to defer the decision regarding the witness itself. 63 | * 64 | * ``` 65 | * import lists 66 | * 67 | * let incr = { i => i + 1 } 68 | * map incr, lists, [1, 2, 3] 69 | * ``` 70 | */ 71 | func map { f, witness, value => 72 | (functorFrom witness).map f, value 73 | } 74 | -------------------------------------------------------------------------------- /external/rx/extern-rx-future-type.go: -------------------------------------------------------------------------------- 1 | package rx 2 | 3 | import ( 4 | "github.com/vknabel/lithia/ast" 5 | "github.com/vknabel/lithia/runtime" 6 | ) 7 | 8 | var _ runtime.RuntimeValue = RxFutureType{} 9 | var _ runtime.DeclRuntimeValue = RxFutureType{} 10 | var _ runtime.RuntimeType = RxFutureType{} 11 | var _ runtime.CallableRuntimeValue = RxFutureType{} 12 | 13 | var RxFutureTypeRef = runtime.MakeRuntimeTypeRef("Future", "rx") 14 | 15 | type RxFutureType struct { 16 | ast.DeclExternType 17 | exrx ExternalRx 18 | } 19 | 20 | func (RxFutureType) RuntimeType() runtime.RuntimeTypeRef { 21 | return runtime.PreludeAnyTypeRef 22 | } 23 | 24 | func (RxFutureType) String() string { 25 | return RxVariableTypeRef.String() 26 | } 27 | 28 | func (t RxFutureType) Declaration(inter *runtime.Interpreter) (ast.Decl, *runtime.RuntimeError) { 29 | return t.DeclExternType, nil 30 | } 31 | 32 | func (d RxFutureType) HasInstance(inter *runtime.Interpreter, value runtime.RuntimeValue) (bool, *runtime.RuntimeError) { 33 | if _, ok := value.(RxFuture); ok { 34 | return true, nil 35 | } else { 36 | return false, nil 37 | } 38 | } 39 | 40 | func (RxFutureType) Lookup(member string) (runtime.Evaluatable, *runtime.RuntimeError) { 41 | return nil, runtime.NewRuntimeErrorf("%s is not a member of %s", member, RxVariableTypeRef.String()) 42 | } 43 | 44 | func (RxFutureType) Arity() int { 45 | return 1 46 | } 47 | 48 | func (t RxFutureType) Call(arguments []runtime.Evaluatable, fromExpr ast.Expr) (runtime.RuntimeValue, *runtime.RuntimeError) { 49 | if len(arguments) != 1 { 50 | return nil, runtime.NewRuntimeErrorf("too many arguments for variable type %s", t) 51 | } 52 | receive, err := arguments[0].Evaluate() 53 | if err != nil { 54 | return nil, err.CascadeDecl(t.DeclExternType) 55 | } 56 | if receive, ok := receive.(runtime.CallableRuntimeValue); ok { 57 | return t.exrx.MakeRxFuture(&t, receive), nil 58 | } else { 59 | return nil, runtime.NewRuntimeErrorf("%s is not callable", receive) 60 | } 61 | } 62 | 63 | func (t RxFutureType) Source() *ast.Source { 64 | return t.Meta().Source 65 | } 66 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | push: 10 | # Publish semver tags as releases. 11 | tags: [ 'v*.*.*' ] 12 | pull_request: 13 | branches: [ main ] 14 | 15 | env: 16 | # Use docker.io for Docker Hub if empty 17 | REGISTRY: ghcr.io 18 | # github.repository as / 19 | IMAGE_NAME: ${{ github.repository }} 20 | 21 | 22 | jobs: 23 | build: 24 | 25 | runs-on: ubuntu-latest 26 | permissions: 27 | contents: read 28 | packages: write 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v2 33 | 34 | # Login against a Docker registry except on PR 35 | # https://github.com/docker/login-action 36 | - name: Log into registry ${{ env.REGISTRY }} 37 | if: github.event_name != 'pull_request' 38 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 39 | with: 40 | registry: ${{ env.REGISTRY }} 41 | username: ${{ github.actor }} 42 | password: ${{ secrets.GITHUB_TOKEN }} 43 | 44 | # Extract metadata (tags, labels) for Docker 45 | # https://github.com/docker/metadata-action 46 | - name: Extract Docker metadata 47 | id: meta 48 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 49 | with: 50 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 51 | 52 | # Build and push Docker image with Buildx (don't push on PR) 53 | # https://github.com/docker/build-push-action 54 | - name: Build and push Docker image 55 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 56 | with: 57 | context: . 58 | push: ${{ github.event_name != 'pull_request' }} 59 | tags: ${{ steps.meta.outputs.tags }} 60 | labels: ${{ steps.meta.outputs.labels }} 61 | -------------------------------------------------------------------------------- /parser/syntax-parsing-error.go: -------------------------------------------------------------------------------- 1 | package parser 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | sitter "github.com/smacker/go-tree-sitter" 8 | ) 9 | 10 | type SyntaxParsingError struct { 11 | fileName string 12 | source string 13 | tree *sitter.Tree 14 | } 15 | 16 | func MakeSyntaxParsingError(fileName string, source string, tree *sitter.Tree) *SyntaxParsingError { 17 | return &SyntaxParsingError{ 18 | fileName: fileName, 19 | source: source, 20 | tree: tree, 21 | } 22 | } 23 | 24 | func (err SyntaxParsingError) SyntaxErrors() []SyntaxError { 25 | partialErrors := []SyntaxError{} 26 | 27 | for _, errorNode := range err.ErrorNodes(err.tree.RootNode()) { 28 | var message string 29 | if errorNode.ChildCount() > 0 { 30 | message = fmt.Sprintln(errorNode.Child(0).String()) 31 | } else { 32 | message = "" 33 | } 34 | currentError := NewSyntaxError("syntax error", message, err.fileName, err.source, errorNode) 35 | partialErrors = append(partialErrors, currentError) 36 | } 37 | return partialErrors 38 | } 39 | 40 | func (err SyntaxParsingError) Error() string { 41 | partials := []string{} 42 | for _, partialError := range err.SyntaxErrors() { 43 | partials = append(partials, partialError.Error()) 44 | } 45 | if len(partials) > 0 { 46 | return strings.Join(partials, "\n\n") 47 | } else { 48 | return fmt.Sprintf("%s: %s\n\n", err.fileName, err.tree.RootNode().String()) 49 | } 50 | } 51 | 52 | func (e SyntaxParsingError) ErrorNodes(node *sitter.Node) []*sitter.Node { 53 | if node.Type() == TYPE_NODE_ERROR { 54 | return []*sitter.Node{node} 55 | } 56 | 57 | partial := []*sitter.Node{} 58 | if node.IsMissing() { 59 | partial = append(partial, node) 60 | } 61 | if node.IsNull() { 62 | partial = append(partial, node) 63 | } 64 | if node.Type() == TYPE_NODE_UNEXPECTED { 65 | partial = append(partial, node) 66 | } 67 | if node.Type() == TYPE_NODE_MISSING { 68 | partial = append(partial, node) 69 | } 70 | 71 | for i := 0; i < int(node.ChildCount()); i++ { 72 | child := node.Child(i) 73 | partial = append(partial, e.ErrorNodes(child)...) 74 | } 75 | return partial 76 | } 77 | -------------------------------------------------------------------------------- /runtime/prelude-extern-type-method.go: -------------------------------------------------------------------------------- 1 | package runtime 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/vknabel/lithia/ast" 8 | ) 9 | 10 | var _ RuntimeValue = PreludeExternTypeMethod{} 11 | var _ CallableRuntimeValue = PreludeExternTypeMethod{} 12 | 13 | type PreludeExternTypeMethod struct { 14 | Decl ast.DeclField 15 | Impl func(args []Evaluatable) (RuntimeValue, *RuntimeError) 16 | } 17 | 18 | func MakeExternTypeMethod( 19 | decl ast.Decl, 20 | impl func(args []Evaluatable) (RuntimeValue, *RuntimeError), 21 | ) PreludeExternTypeMethod { 22 | externDecl, ok := decl.(ast.DeclField) 23 | if !ok { 24 | panic(fmt.Errorf("extern func declaration requires func definition: %T %s", decl, decl.DeclName())) 25 | } 26 | if len(externDecl.Parameters) == 0 { 27 | panic(fmt.Errorf("extern func declaration requires at least one param: %T %s", decl, decl.DeclName())) 28 | } 29 | return PreludeExternTypeMethod{ 30 | Decl: externDecl, 31 | Impl: impl, 32 | } 33 | } 34 | 35 | func (f PreludeExternTypeMethod) Lookup(member string) (Evaluatable, *RuntimeError) { 36 | switch member { 37 | case "arity": 38 | return NewConstantRuntimeValue(PreludeInt(f.Arity())), nil 39 | default: 40 | return nil, NewRuntimeErrorf("no such member: %s for %s", member, f.RuntimeType().String()) 41 | } 42 | } 43 | 44 | func (PreludeExternTypeMethod) RuntimeType() RuntimeTypeRef { 45 | return PreludeFunctionTypeRef 46 | } 47 | 48 | func (f PreludeExternTypeMethod) String() string { 49 | argNames := make([]string, len(f.Decl.Parameters)) 50 | for i, param := range f.Decl.Parameters { 51 | argNames[i] = string(param.Name) 52 | } 53 | return fmt.Sprintf("", f.Decl.Name, strings.Join(argNames, ", ")) 54 | } 55 | 56 | func (f PreludeExternTypeMethod) Arity() int { 57 | return len(f.Decl.Parameters) 58 | } 59 | 60 | func (f PreludeExternTypeMethod) Call(args []Evaluatable, fromExpr ast.Expr) (RuntimeValue, *RuntimeError) { 61 | if len(args) != f.Arity() { 62 | panic("use Call to call functions!") 63 | } 64 | return f.Impl(args) 65 | } 66 | 67 | func (f PreludeExternTypeMethod) Source() *ast.Source { 68 | return f.Decl.Meta().Source 69 | } 70 | --------------------------------------------------------------------------------