├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── README.md ├── docs ├── AbstractAssembly.md ├── FloatingPoint.md ├── GlasApps.md ├── GlasCLI.md ├── GlasChannels.md ├── GlasDesign.md ├── GlasGUI.md ├── GlasInitLang.md ├── GlasKPN.md ├── GlasLang.md ├── GlasMirror.md ├── GlasNamespaces.md ├── GlasNotebooks.md ├── GlasObject.md ├── GlasProg.md ├── GlasTypes.md ├── GlasZero.md ├── GrammarLogicProg.md ├── InteractionNets.md ├── ProgramSearch.md └── TextTree.md ├── fsrc ├── Makefile ├── glas ├── src │ ├── .gitignore │ ├── Effects.fs │ ├── Glas.fsproj │ ├── GlasZero.fs │ ├── LoadModule.fs │ ├── Main.fs │ ├── ProfStats.fs │ ├── ProgEval.fs │ ├── ProgVal.fs │ ├── README.md │ ├── TextTree.fs │ └── Value.fs └── test │ ├── .gitignore │ ├── Glas.Tests.fsproj │ ├── Main.fs │ ├── RandVal.fs │ ├── TestGlasZero.fs │ ├── TestProgVal.fs │ ├── TestRope.fs │ └── TestVal.fs └── old-glas-src ├── README.md ├── bits-basics ├── README.md └── public.g0 ├── bits ├── README.md └── public.g0 ├── glas-eval ├── README.md ├── public.g0 ├── ref-eval.g0 └── util.g0 ├── gparse └── README.md ├── int ├── README.md └── public.g0 ├── language-g0 ├── README.md ├── compiler.g0 ├── public.g0 └── util.g0 ├── language-tt └── README.md ├── list ├── README.md ├── list-ops.g0 └── public.g0 ├── nat ├── README.md └── public.g0 ├── posit └── README.md ├── prims ├── README.md └── public.g0 ├── record ├── README.md └── public.g0 ├── std ├── README.md └── public.g0 ├── test-loop-perf └── public.g0 └── xyzzy ├── README.md └── public.g0 /.gitignore: -------------------------------------------------------------------------------- 1 | .ionide/ 2 | .vscode/ 3 | bin/ 4 | build/ 5 | .fake 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmbarbour/glas/a8edbf10759a75f9c8180e6caa1e4643839d8a36/.gitmodules -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [] 7 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "command": "dotnet", 9 | "type": "shell", 10 | "args": [ 11 | "build", 12 | "src/", 13 | // Ask dotnet build to generate full paths for file names. 14 | "/property:GenerateFullPaths=true", 15 | // Do not generate summary otherwise it leads to duplicate errors in Problems panel 16 | "/consoleloggerparameters:NoSummary" 17 | ], 18 | "group": "build", 19 | "presentation": { 20 | "reveal": "silent" 21 | }, 22 | "problemMatcher": "$msCompile" 23 | }, 24 | { 25 | "label": "test", 26 | "command": "dotnet", 27 | "type": "shell", 28 | "args": [ 29 | "test", 30 | "--project", "test/" 31 | ], 32 | "group": "build", 33 | "presentation": { 34 | "reveal": "silent" 35 | }, 36 | "problemMatcher": "$msCompile" 37 | }, 38 | { 39 | "label": "watch test", 40 | "command": "dotnet", 41 | "type": "shell", 42 | "args": [ 43 | "watch", 44 | "test", 45 | "--project", "test/" 46 | ], 47 | "group": "build", 48 | "presentation": { 49 | "reveal": "silent" 50 | }, 51 | "problemMatcher": "$msCompile" 52 | } 53 | 54 | ] 55 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Glas 2 | 3 | Glas is a general language system. Glas is designed for scalable, reproducible, and extensible software systems. Glas also reinvisions the application model to simplify concurrency, cache management, and live coding or continuous deployment. 4 | 5 | ## Glas Overview 6 | 7 | Glas has several non-conventional features: 8 | 9 | Glas supports **user-defined syntax**, guided by file extensions. To compute the module value for a file named `foo.xyz`, the compiler will use a program defined by the module named `language-xyz`. It is possible to develop alternative syntax, DSLs, integrate `.json` files as modules, or support projectional editing via specialized syntax. 10 | 11 | Glas supports **user-defined compilers**. When modules compute binary values, those binaries can be extracted. Thus, it is possible to 'compile' meme images or documents. Compiling a program involves processing a homoiconic representation into an executable binary, then extracting. 12 | 13 | Glas supports **large, incremental builds**. Large values support structure sharing across builds by content-addressed storage, i.e. using secure-hashes as value references. Work sharing across similar builds can be supported by explicit memoization. 14 | 15 | Glas will use explicit [**acceleration**](https://en.wikipedia.org/wiki/Hardware_acceleration) for high-performance computing. For example, we could simulate an abstract CPU, then replace by actual CPU to implement compression or cryptography algorithms. Acceleration of Kahn Process Networks could support distributed builds. 16 | 17 | Glas favors a [**transaction machine application model**](docs/GlasApps.md) that is more amenable to live coding and distributed overlay programs than the conventional `int main(string[] args)` app model. This comes with some optimization challenges, but I'm optimistic that it can provide a better basis for applications. 18 | 19 | See the [design doc](docs/GlasDesign.md) for more detail. 20 | 21 | ## Project Goals 22 | 23 | The concrete goal is to bootstrap a command-line utility named `glas` with support for user-defined syntax, compilation of modules, extraction of binaries, content-addressed storage, incremental builds, continuous builds, and usable acceleration (ideally for CPU and KPN). 24 | 25 | A minimal bootstrap implementation will be written in F#, with a motive to leverage JIT compilation for performance. The bootstrap will not support acceleration or stowage unless these prove necessary. 26 | 27 | ## Status of Language 28 | 29 | Glas has been re-envisioned several times, so it's been a slow start. KPNs were dropped from the initial program model because they're too complicated. Backtracking was reintroduced to simplify conditional and loop combinators. 30 | 31 | But at this point I'm ready to start programming. 32 | 33 | -------------------------------------------------------------------------------- /docs/AbstractAssembly.md: -------------------------------------------------------------------------------- 1 | # Abstract Assembly 2 | 3 | Proposed plain-old-data representation for abstract assembly as glas data: 4 | 5 | type AST = (Name, List of AST) # constructor 6 | | d:Data # embedded data 7 | | n:Name # namespace ref 8 | | s:(AST, List of TL) # scoped AST 9 | | z:List of TL # localization 10 | 11 | type Name = prefix-unique Binary, excluding NULL, as bitstring 12 | type Prefix = any prefix of Name (empty to full), byte aligned 13 | type TL = Map of Prefix to (Prefix | NULL) as radix tree dict 14 | # rewrites longest matching prefix, invalidates if NULL 15 | 16 | This encoding uses unlabeled lists for the primary AST node constructor, and a tagged union for everything else. An essential feature is that constructors always start with names. This allows us to leverage the namespace to extend, restrict, and redirect constructors. The system will provide a set of primitive constructor names prefixed with '%', such as '%i.add' for arithmetic and '%seq' for procedural composition. This common prefix simplifies recognition and translation. 17 | 18 | The only computation expressed at the AST layer is scoping 's', which applies a sequence of translations to an AST node. Scoping isn't strictly necessary. It will be eliminated when we apply the translations and evaluate an AST to normal form. However, scoping is convenient when composing large AST fragments, and supports lazy evaluation. A localization essentially records a scope for use in later computations, mostly multi-staged programming. 19 | 20 | Embedded data is the only type that doesn't contain names, and is thus not rewritten based on scope. However, we should wrap most embedded data with a suitable node that can validate its type and represent intentions, e.g. favoring `(%i.const 42)` where an integer expression is expected. Some languages might restrict which data can be embedded. 21 | 22 | Abstract assembly is designed for use in context of the [glas namespace model](GlasNamespaces.md), and I intend to gradually merge this document into that one. 23 | 24 | -------------------------------------------------------------------------------- /docs/FloatingPoint.md: -------------------------------------------------------------------------------- 1 | # Floating Point 2 | 3 | This document describes a tweak to [posits](https://en.wikipedia.org/wiki/Unum_(number_format)#Posit_(Type_III_Unum)) for arbitrary-width bitstrings. 4 | 5 | I'm unlikely to actually use this directly in glas systems. I probably won't even use it indirectly - there are better options for accelerated representations. However, I spent a few days thinking this up, and I'd like to keep it around a while longer. 6 | 7 | ## Encoding 8 | 9 | Every bitstring encodes a unique rational number. Every rational number whose denominator is a power of two can be precisely represented. 10 | 11 | The empty bitstring encodes zero. To interpret any non-empty bitstring, we'll first add logically a `1000...` suffix to a non-empty bitstring (that is a 1 bit followed by infinite 0 bits) then interpret the result as `(sign)(regime)(exponent)(fraction)`. 12 | 13 | Adding 0 bits to a fraction doesn't affect the result, so we don't actually need infinite bits. Just add enough zeroes to reach the fraction. 14 | 15 | The regime is encoded as a sequence of '1' bits followed by a '0', or a sequence of '0' bits followed by a '1'. This determines whether the exponent is positive or negative. Unlike conventional posits, regime also determines exponent size (es) in a simple way. 16 | 17 | regime es exponent 18 | 10 2 0..3 19 | 110 2 4..7 20 | 1110 3 8..15 21 | 11110 4 16..31 22 | 111110 5 32..63 23 | (1*N)0 N (2^N)..(2^(N+1)-1) 24 | 25 | 01 2 -4..-1 26 | 001 2 -8..-5 27 | 0001 3 -16..-9 28 | 00001 4 -32..-17 29 | 000001 5 -64..-33 30 | (0*N)1 N -(2^(N+1))..-((2^N)+1) 31 | 32 | Compared to regular posits, where regime is essentially a unary addition to exponent, this supports compact encoding for much larger exponents. However, it costs one bit for a few intermediate exponents. 33 | 34 | The final number is computed as `(-1)^(sign) * 2^(exponent) * (1.(binary fraction))`, same as normal posits. There is no encoding for not-a-real. 35 | 36 | ## Limitations 37 | 38 | This encoding is exact for addition, subtraction, and multiplication. But division, square roots, etc. would need some additional parameters indicating how much precision to maintain in the result. Further, even encoding decimal `0.3` would require a precision parameter. This is a problem fixed width posits and floating point representations do not have. 39 | 40 | There is no encoding for not-a-number. This isn't a problem for the intended context, glas systems, because we can simply fail operations and backtrack. But it might require some form of signaling failure in other contexts. 41 | 42 | Obviously, there is no hardware support for this encoding. Why bother with floating point when there's no hardware support? Might as well use a bignum representation. 43 | -------------------------------------------------------------------------------- /docs/GlasCLI.md: -------------------------------------------------------------------------------- 1 | # Glas Command Line Interface 2 | 3 | The glas executable generally takes the first command line argument as a switch to interpret the remainder of the arguments. There are a few options for running applications from different sources, and some ad-hoc operations to help users understand and control the glas system. 4 | 5 | glas --run AppName Args To App 6 | glas --script SourceRef Args To App 7 | glas --script.FileExt FilePath Args To App 8 | glas --cmd.FileExt "Source Text" Args To App 9 | glas --shell Args To App 10 | glas --cache CacheOp 11 | glas --conf ConfigOp 12 | ... etc. ... 13 | 14 | A simple syntactic sugar supports user-defined operations: 15 | 16 | glas opname Args 17 | # implicitly rewrites to 18 | glas --run cli.opname Args 19 | 20 | My vision for early use of glas systems is that end users mostly operate through user-defined operations. To avoid cluttering the command line with runtime switches, we push runtime options into the configuration file, application settings, or (rarely) OS environment variables. 21 | 22 | ## Configuration 23 | 24 | The glas executable will read a user configuration based on a `GLAS_CONF` environment variable, loading the specified file as a [namespace](GlasNamespaces.md). If unspecified, the default location is `"~/.config/glas/conf.glas"` in Linux or `"%AppData%\glas\conf.glas"` on Windows. 25 | 26 | A typical user configuration will inherit from a community or company configuration from DVCS, then override some definitions for the user's projects, preferences, and resources. Each DVCS repository becomes a boundary for curation, security, and version control. A community configuration will define hundreds of shared libraries and applications in 'env.\*', relying on lazy loading and shared caching for performance. 27 | 28 | To mitigate risk of naming conflict, the runtime will recognize configuration options under 'conf.\*'. The glas executable may expect definitions for shared heap storage, RPC registries, rooted trust for public key infrastructure, and so on. An extensible executable may support user-defined accelerators, optimizers, and typecheckers via the user configuration. We could maximize portability by asking a configuration to generate an adapter based on application settings and runtime version info. 29 | 30 | ## Running Applications 31 | 32 | An application is expressed within a namespace, using 'app.\*' methods to simplify recognition, access control, extraction, and conflict avoidance. Users can reference and run applications in a few ways: 33 | 34 | * **--run**: To run 'foo', we look for 'env.foo.app.\*' in the user configuration. This application is lazily downloaded and compiled on demand, usually caching to avoid redundant effort. 35 | * *note:* Users may run hidden apps outside of 'env.\*' via '.' prefix, e.g. '.foo => foo.app.\*'. 36 | * **--script**: Compile indicated file, package folder, or URL. The generated namespace must define 'app.\*' at the toplevel. 37 | * **--script.FileExt**: Same as '--script' except we use the given file extension in place of the actual file extension for purpose of user-defined syntax. Mostly for use with shebang scripts in Linux, where file extensions may be elided. 38 | * **--cmd.FileExt**: Treated as '--script.FileExt' for an anonymous, read-only file found in the caller's working directory. The main use case is shell scripts avoiding intermediate files. 39 | 40 | Every application must at least define 'app.settings' to guide integration. The runtime does not observe settings directly. Instead, an 'app.settings' handler is passed when querying the configuration for application-specific options. 41 | 42 | Among the application-specific configuration options, a glas executable may support multiple run modes. For example, a transaction-loop application uses 'app.start' and 'app.step', a threaded application defines 'app.main', a staged application could specifies another namespace procedure 'app.build'. Thus, exactly what happens when we run an application depends on 'app.settings', and is independent of application source. 43 | 44 | See [glas applications](GlasApps.md). 45 | 46 | *Note:* Alternatively, we might model an application namespace as a set of handlers generated within a call graph. This would simplify modeling application state as a set of local vars on the stack. In any case, an application may have more than one associated method. 47 | 48 | ## Installing Applications 49 | 50 | Installing applications - ensuring they're available for low-latency or offline use - can be understood as a form of manual cache management. A user configuration might recommend that a set of definitions is maintained locally. To simplify tooling, we might add a little indirection, perhaps referencing a local file or shared-heap variable. This is easily extended to installing scripts. 51 | 52 | Ideally, 'installing' an application reduces to downloading an executable binary or whatever low-level JIT-compiled representation is cached by the glas executable. Or if not the that, then at least avoiding rework for the more expensive computations. This is feasible using an approach similar to Nix package manager, i.e. downloading from a shared cache based on transitive secure hashes of contributing sources. The user configuration could specify one or more trusted, shared caches. 53 | 54 | ## Initial Namespace 55 | 56 | The glas executable provides an initial namespace containing only a few [program primitives](GlasProg.md) under '%\*'. The space of primitives is marked read-only. Scripts or staged applications are written into the same namespace as the user configuration, albeit in separate volumes for access control. Viable translations: 57 | 58 | # user configuration 59 | move: { "%" => WARN, "" => "u." } 60 | link: { "%" => "%", "%env." => "u.env.", "" => "u." } 61 | 62 | # script or staged app (at addr) 63 | move: { "%" => WARN, "" => "addr." } 64 | link: { "%" => "%", "%env." => "u.env.", "" => "addr." } 65 | 66 | The front-end compiler will further introduce '@\*' compiler dataflow definitions to support automatic integration across module boundaries. This is also the case for a built-in front-end compiler. However, from the runtime's perspective, these are normal definitions and receive no special attention. 67 | 68 | From a regular programmer's perspective, '%\*' and '@\*' are implementation details, and the initial namespace is effectively empty. However, regular users will also inherit from a community configuration 69 | 70 | ## Security 71 | 72 | Not every application should be trusted with full access to FFI, shared state, and other sensitive resources. This is true within a curated community configuration, and even more so for external scripts of ambiguous provenance. What can be done? 73 | 74 | Trust can be tracked for individual definitions based on contributing sources. This can be supported via public key infrastructure, with trusted developers signing manifests and having their own public keys signed in turn. The user configuration can specify the 'root' trust, or how to look it up. The glas executable can heuristically search `".pki/"` subfolders when loading sources for signed manifests and derived certificates. In context of DVCS, those certificates might propagate trust across DVCS repos. 75 | 76 | Trust can be scoped. For example, when signing a manifest, a developer can indicate they trust code with GUI but not full FFI or network access. Unfortunately, we cannot count upon developers precisely scoping trust, nor will regular end-users be security savvy. Instead, communities scope trust of developers or public signatures by serving as certificate authorities. A developer might be trusted with GUI and 'public domain' network access, and this extends to code signed by that developer. 77 | 78 | Trust can be attenuated via annotations. For example, a trusted shared library might implement a GUI using FFI. FFI requires a very high level of trust, but GUI does not. Based on annotations, the library could indicate that a subset of public methods only require the client is trusted with GUI access. A developer can voluntarily sandbox an application by interacting with the system only through such libraries. 79 | 80 | Before running an application, the glas executable can analyze the call graph for trust violations. If there are any concerns, we might warn users and let them abort the application, extend trust, or run in a degraded mode where a subset of transactions is blocked for security reasons. Of course, exactly how this is handled should be configurable. 81 | 82 | *Note:* This trust-based security model can be conveniently combined with security based on abstract data types and access control to definitions or effects handlers. However, the latter techniques are useful only insofar as we trust the toplevel application. 83 | 84 | ## Built-in Tooling 85 | 86 | The glas executable may be extended with useful built-in tools. Some tools that might prove useful: 87 | 88 | * **--conf** - inspect and debug the configuration, perhaps initialize one 89 | * **--cache** - manage installed applications and resources, clean up 90 | * **--db** - query, browse, watch, or edit persistent data in the shared heap 91 | * **--rpc** - inspect RPC registries or issue RPC calls from command line 92 | * **--dbg** - debug or manipulate running apps from the command line 93 | 94 | Heuristics to guide tooling: First, where feasible, every function available via built-in CLI tools should be accessible through applications. This might involve introducing 'sys.refl.conf.\*', 'sys.refl.cache.\*', and similar methods. Even 'sys.refl.cli.help' and 'sys.refl.cli.version' can be included. Second, we should ultimately aim to keep the glas executable small, assigning code bloat a significant weight. 95 | 96 | ## Glas Shell 97 | 98 | A user configuration may define 'app.\*' at the toplevel namespace. In context of [notebook applications](GlasNotebooks.md) this should represent a live-coding projectional editor for the configuration file and its transitive dependencies. Users can run this as a normal application via `"glas --run . Args To App"`. 99 | 100 | A community can feasibly extend this notebook application to serve as a [shell](https://en.wikipedia.org/wiki/Shell_(computing)) for a glas system. Instead of running individual glas applications within the OS, and occasionally managing OS configuration files, users can treat the glas system as one big live-coded environment composed of applications and configurations. This 'shell' application may support multiple user interfaces via CLI, HTTP, and GUI. 101 | 102 | In practice, we'll often favor instanced shells. Instancing might be implemented by copying or logically overlaying the configuration folder. A glas executable could have built-in support for instancing with `"glas --shell ..."`. 103 | 104 | ## Implementation Roadmap 105 | 106 | The initial implementation of the glas executable must be developed outside the glas system. This implementation will lack many features, especially the optimizations that would let transaction loops scale beyond a simple event dispatch loop. Fortunately, simple event dispatch loops are still very useful, and we can fully utilize a system between FFI, accelerators, and sparks. We also have access to conventional threaded applications. 107 | 108 | Ideally, we'll eventually bootstrap the glas executable within the glas system. Early forms of bootstrap could generate C or LLVM, but I hope to swiftly eliminate external dependencies and integrate relevant optimizations into the glas libraries. 109 | 110 | 111 | -------------------------------------------------------------------------------- /docs/GlasChannels.md: -------------------------------------------------------------------------------- 1 | # Glas Object Channels 2 | 3 | The [Glas Apps](GlasApps.md) description includes a preliminary API for data channels within and between Glas systems. This API includes an option for binding to TCP, in which case data will be communicated via [Glas Object](GlasObject.md). 4 | 5 | ## Proposed Channels API 6 | 7 | Summary of API described for Glas Apps, albeit abstracting various reference management issues. 8 | 9 | What we can do with channels: 10 | 11 | * *send* - send data over a channel. 12 | * *recv* - receive data from a channel 13 | * *attach* - send a new subchannel over a channel 14 | * *accept* - receive a subchannel from a channel 15 | * *pipe* - ask system to connect two channels, piping all future communications. This allows some optimizations by removing the program as a middleman. 16 | * *copy* - copy a channel such that inputs (recv/accept) is copied are copied and outputs (send/attach) are merged in non-deterministic order. 17 | * *drop* - tell system you won't be reading or writing from a channel in future. 18 | * *test* - ask system whether channel is active - test fails if remote endpoints are closed and there is no pending input in the buffer. (More than one remote endpoint is possible due to 'copy'.) 19 | * *tune* - inform the system that a channel will be used in some refined manner (read-only, write-only, attach/accept only, buffer size hints, etc.). 20 | 21 | Note that 'accept' and 'recv' must preserve corresponding order with 'attach' and 'send'. 22 | 23 | How we obtain our channels: 24 | 25 | * *create* - create a new, associated pair of channels. These channels are local to the runtime, thus implementation is a lot more ad-hoc. 26 | 27 | * *tcp binding* - create a channel by wrapping a TCP listener or TCP connection. This wrapper will implicitly handle subchannels, data serialization, content-addressed storage, and integration with content distribution networks. 28 | * *listener* - a channel bound to a TCP listener will only receive subchannels corresponding to TCP connections. Close the listener via 'drop'. 29 | * *connection* - a channel bound to a TCP connection will handle serialization and marshalling of data and protocol issues for remote communication. The connection will implicitly break if the associated TCP connection breaks. 30 | 31 | * *global databus* - (idea). I could support a notion of a global databus for glas systems. Binding to the bus would return a channel that receives a copy of future messages sent to the bus. Discovery could use distributed hashtables to find other participants in the bus. Use something like Tahoe-LAFS style bearer tokens for bus names, representing different levels of authority. 32 | 33 | Channel API shouldn't be too difficult within a single runtime. So, this document is primarily about how to implement the TCP bindings for channels. If channels become popular, it should also be feasible to extract these bindings and required runtime components as a library for use within non-glas apps. 34 | 35 | A global databus API could feasibly be implemented indirectly above TCP bindings. But it might be a more convenient and extensible means to glue apps together using publish-subscribe databuses instead of point-to-point connections. 36 | 37 | A runtime may need to keep an extra TCP channel open to handle pipeline optimization. 38 | 39 | ## Desired Features 40 | 41 | ### Overlay Networks and Brokering 42 | 43 | Support for 'pipe' enables the TCP connections to begin forming an overlay network. Ideally, pipes can be optimized, i.e. if we start with connections {(A,B), (B,C)} and B decides to 'pipe' these connections, we'll logically have a new connection {(A,C)}. 44 | 45 | B B B 46 | / \ =pipe=> / \ =detach=> 47 | A C A---C A---C 48 | 49 | Actually implementing this feature will be challenging because it requires initiating a new TCP connection between A and C - a process that may easily fail due to firewalls. This will usually require some negotiation between A and C via B before the pipe is fully detached. 50 | 51 | If B is a broker that repeatedly establishes connections between muliple clients and A, then A might want to delegate some negotiation authority to B then open a TCP listener to receive multiple new connections. Ideally, B can simulate a few non-interactive copy/attach/send operations on the original (A,B) connection to provide context without directly contacting A, instead handing C a single-use, time-sensitive, signed ticket for a new connection with A that specifies initial ops. 52 | 53 | ### Content Distribution Network (CDNs) 54 | 55 | When large values are serialized, we'll use content-addressed references to components of those values, i.e. via SHA3-512 of their binary representation (see [Glas Object](GlasObject.md)). It should be possible to query the TCP connection for the component data if it is not already known. 56 | 57 | However, to ameliorate traffic, it should also be feasible to negotiate an intermediate proxy cache. The data can be stored compressed and encrypted in the proxy. The client would use the first half of that secure hash as a lookup key and the second half as a decryption key. 58 | 59 | However, a proxy cache isn't free. As part of this negotiation, we will need to provide credentials to access 'session' with the proxy cache. Session info would help scope access to data and control charges. It might be useful to support hierarchical subsessions. 60 | 61 | So, there is a lot of fine detail to deal with CDNs that should be designed carefully and integrated with Glas Channels. It should also be extensible with new CDN features. 62 | 63 | ### Remote Evaluation 64 | 65 | Instead of only communicating data over a channel, we could implicitly migrate some code and private state to evaluate nearer to the data source. This would be subject to negotiation and live coding (i.e. updating the remote code between transactions). The remote system might deny service depending on resource constraints, returning to a more conventional data channel. Some communications would report updates to the private state. 66 | 67 | Support for code distribution, and resource constraints (such as quotas), could be expressed as an extra option when binding a channel over a TCP connection or TCP listener. This would directly support my vision of glas applications as distributed, live-coded overlay networks. 68 | 69 | ### Transaction Alignment 70 | 71 | As a convenient feature, messages written to a channel within a single transaction should also be readable within a single transaction. This might require tracking transaction frames within the TCP communications. 72 | 73 | ## Subchannels 74 | 75 | Every TCP connection will host multiple subchannels. The names for those subchannels will be local to the TCP connection, and perhaps even local to each endpoint of the connection. Need to consider the details here. 76 | 77 | New ones can be created via 'attach'. Subchannels can be re-routed to another TCP connection (or to a local in-memory channel) via 'pipe'. Every message we send must include some metadata abou 78 | 79 | Whenever we send a message, it must be obvious which subchannel is receiving that message. Thus, we need some channel metadata as part of the send. 80 | 81 | 82 | ## TCP Messages 83 | 84 | ## Message Frame 85 | 86 | 87 | -------------------------------------------------------------------------------- /docs/GlasGUI.md: -------------------------------------------------------------------------------- 1 | # Graphical User Interface for Glas 2 | 3 | It is feasible to support a conventional GUI interface, but that isn't a good fit for my vision of glas systems. My big idea is *users participate in transactions*. 4 | 5 | ## Transactional User Interaction 6 | 7 | It isn't easy for human users to participate in transactions! The biggest problems are that humans are slow to respond, slow transactions are disrupted by background events, disrupted transactions are repeated, and humans also don't like repeating themselves. 8 | 9 | To solve this, we introduce a user agent to handle fast response and repetition. But the user must be provided tools to see what the user agent sees, such as data and queries, and control how the user agent responds to queries on the user's behalf. 10 | 11 | This involves *reflection* on the user agent, together with manipulation of user variables. Reflection allows users to observe aborted transactions. This provides a basis for read-only views or to withhold approval until the user has time to understand the information on display. User variables might be rendered as knobs, sliders, toggles, and text boxes. 12 | 13 | Reasonable modes for user participation: 14 | 15 | * *read-only view* - The user agent continuously or infrequently renders the GUI then aborts. 16 | * *live action* - The user agent continuously renders the GUI and commits when possible. 17 | * *approved action* - The transaction is aborted unless the user explicitly submits. The GUI system tracks user observations and presents a summary of relevant changes for approval in proximity to a submit button. 18 | 19 | The *approved action* mode gives users the most stake in each transaction. Approving a summary of relevant changes even simulates a read-write conflict analysis. However, it's slow and deliberate, not suitable for every context. The *live action* mode is close to [immediate mode GUI](https://en.wikipedia.org/wiki/Immediate_mode_GUI), while the *read-only view* is suitable for maintaining user awareness. 20 | 21 | In context of *live action* mode, we may need to buffer or latch user inputs. For example, pushing a button sets a 'button-pushed' variable to 'true' until it is read and reset. The button would continue to render in a depressed state while 'button-pushed' remains true. 22 | 23 | ### Mitigating Glitches 24 | 25 | If users observe all transactions in which a user agent participates, they will certainly observe some transactions that are ultimately aborted due to concurrent read-write conflicts. A subset of these may exhibit 'glitches' where rendered values are inconsistent (e.g. due to reading cached values from multiple remote systems). 26 | 27 | A transactional GUI system can easily skip rendering of transactions that might be inconsistent, but there is a small cost to latency (to wait for consistency checks) and a small to large cost to frame-rate (because skipping bad 'frames' due to inconistency) depending on level of concurrent interference. This can be mitigated through app design (buffers and queues, history for views) or runtime support (rendering older snapshots for read-only views, precise conflict analysis). 28 | 29 | Alternatively, we can modify applications to reduce severity of known glitches. This would be closer to convention with non-transactional GUI today. 30 | 31 | An adjacent issue is that *asynchronous* interactions - where feedback is not logically 'instantaneous' within a transaction - may appear to be glitchy if presented as synchronous to the user. In this case, I think the problem is more about managing user expectations (e.g. report actual status of pending requests) or meeting them (e.g. use RPC to complete actions synchronously in GUI transaction). 32 | 33 | ## Integration 34 | 35 | gui : FrameRef? -> [user, system] unit 36 | 37 | An application's 'gui' method is repeatedly called in separate transactions. On each call, it queries the user agent and renders some outputs. 38 | 39 | In general, the queries and rendered outputs may be stable, subject to incremental computing. However, some 'frames' may be less stable than others. To support these cases (TBD) 40 | 41 | In some cases, we may 'fork' the GUI with non-deterministic choice, which a user agent might render in terms of multiple windows. We render without commit; the final 'commit' decision is left to the user through the user agent. 42 | 43 | A user agent can help users systematically explore different outcomes. This involves heuristically maintaining history, checkpoints and bookmarks based on which values are observed. An application can help, perhaps suggesting alternatives to a query or using naming conventions to guide heuristics (e.g. distinguishing navigation and control). 44 | 45 | 46 | 47 | *Note:* It is feasible to introduce a notion of user-local state or session-local state. However, it is not clear to me how such state would be integrated between sessions, other than as queries. A few exceptions include passing files and such over the GUI, e.g. drag and drop, which may require special attention. 48 | 49 | ## Navigation Variables 50 | 51 | UserAgents might broadly distinguish a few 'roles' for variables. Navigation variables would serve a role similar to HTTP URLs, with the user agent maintaining a history and providing a 'back' button. Writing to navigation variables would essentially represent navigating to a new location upon commit, albeit limited to the same 'gui' interface. 52 | 53 | Other potential roles would be inventory or equipment, influencing how the user interacts with the world. In any case, I think we could and should develop a more coherent metaphor than clipboards and cookies. 54 | 55 | ## Rendering Temporal Media and Large Objects 56 | 57 | An application may ask a user agent to 'render' a video for user consumption. As a participant in the transaction, a user should have the tools to comprehend this video before committing to anything. 58 | 59 | One of the best ways to understand a video is to play it. Of course, other very useful tools would include the ability to search it (find people or particular objects), read dialogues, present video frames side by side, apply filters, slow motion, fast forward, reverse, etc.. Ideally, the GUI system provides a whole gamut of tools that can be applied to any video. 60 | 61 | The same idea should apply to any large 'object' presented to the user within a transaction. For example, if the user agent is asked to render an entire 'database' as a value the user should have suitable tools to browse, query, and graph database values to obtain some comprehension of them. Rendering of very large objects is feasible between content-addressed references and content distribution networks. 62 | 63 | Ideally, user agents are extensible such that, if they lack the necessary tools, users can easily download the tools they need. We could develop some conventions for recommending certain tools to understand a large object. Further, an application can also support users in understanding large objects. 64 | 65 | ## Non-deterministic Choice and GUI 66 | 67 | For isolated transactions, repetition is equivalent to replication. Fair non-deterministic choice can be replicated to evaluate both forks in parallel. Assuming the transactions do not have a read-write conflict, they can both commit. This optimization is leveraged for task-based concurrency of transaction loops. 68 | 69 | This will impact GUI. If an application makes a non-deterministic choice, it will potentially affect what is rendered to the user. Assuming the user agent is aware of the choice, this could be rendered using multiple frames (tabs, windows, etc.) or more adventurously rendered as an overlay or branching conversation. 70 | 71 | Ideally, the user should have some control over the non-deterministic choice. This allows a *read-only view* to focus on frames that receive user attention, and *approved action* to efficiently approve a specific branch instead of waiting for it to cycle around. 72 | 73 | This can be understood as a form of participation: users can ignore and abort forks that aren't of interest to them, or explore the options in a controlled manner instead of randomly. Control over non-deterministic choice must be integrated with both the runtime and distributed transactions. Fortunately, this is a feature we'll also want for many other reasons: scheduling transactions, debugging, loop fusion, etc.. 74 | 75 | ## Multi-User Transactions 76 | 77 | The API directly supports multi-user systems where each user is performing independent transactions. That should be sufficient for most use cases. However, what if we want a 'multi-user transaction' in the sense of multiple users participating in one transaction? 78 | 79 | To support a multi-user transaction, we could model a 'multi-user agent'. If the users do not share a physical room, the multi-user agent could be placed into a virtual room created for the task. If the application is not multi-user aware, we could use a normal user agent and the virtual room could instead implement handoff protocols. 80 | -------------------------------------------------------------------------------- /docs/GlasInitLang.md: -------------------------------------------------------------------------------- 1 | # Glas Initialization, Integration, Input, and Configuration Language 2 | 3 | This is a language for modular configurations. Preferred file extension: ".gin" or maybe ".g" if I unify layers. 4 | 5 | An unusual feature of glas system configuration is that we'll represent the entire 'package system' within the configuration. This results in very large configurations, thus we require ample support for modularity. Notable features: 6 | 7 | * *Modularity and Abstraction.* The language is designed to support *very large* configurations with multiple files and abstraction via late binding and override. 8 | * *Laziness and Caching.* To support the expected use cases, the configuration language must support lazy loading of imports and persistent caching of computations. 9 | * *Grammar Inspired Functions* Functions are expressed as deterministic grammar rules, based loosely on [parsing expression grammars (PEGs)](https://en.wikipedia.org/wiki/Parsing_expression_grammar). 10 | * *Termination Guarantee.* Computation is ideally restricted to [primitive recursive functions](https://en.wikipedia.org/wiki/Primitive_recursive_function). This should be enforced by analysis of mutually recursive definitions. 11 | * *Simple Syntax.* The toplevel syntax is inspired from [toml](https://toml.io/en/), with minimal extensions for modularity and functions. 12 | 13 | In general, user configurations will inherit from much larger community or company configurations, which include base distributions of modules and conventions for integrating application settings and host resources. In glas systems, the configuration will abstract the host layer such module locations and authorization, OS environment variables, foreign function interfaces, filesystem folders, and network interfaces 14 | 15 | *Note:* The ".g" application language should be syntactically very similar to ".gin", albeit with a few differences in how modules are referenced, support for metaprogramming, and access to effects. 16 | 17 | ## Data 18 | 19 | We'll support only plain old glas data. Syntactic support will focus on lists, numbers, and labeled data. Computation may also involve some tacit parameters for abstract locations, localizations, or higher-order functions. But these won't be presented as first-class values. 20 | 21 | ## Config Namespace 22 | 23 | A configuration file defines one [namespace](GlasNamespaces.md) of functions and data expressions. This can inherit and override other configuration files. It is possible to develop template-like abstract configuration files where overrides are expected. For example, by convention definitions under `sys.*` are left abstract for later system-provided overrides, such as access to OS environment variables. 24 | 25 | Regarding dependencies when loading a file as a module, we might apply a call context to represent the logical environment for the compiler. 26 | 27 | ## Import Expressions? 28 | 29 | A configuration file may reference other configuration files or remote resources. The question is how much computation is needed to represent these files or resources. We could permit arbitrary expressions, to be evaluated at parse time. This would allow for late binding and overrides, but it greatly complicates processing of the configuration. Alternatively, we could restrict to inline expressions, e.g. relative file paths or URLs. This simplifies processing but is a bit less flexible. 30 | 31 | I'm leaning towards the static inline expressions. It isn't clear to me where flexibility of locations would be useful for configurations, instead I suspect it would be confusing if we used it at all. 32 | 33 | To simplify lazy imports, we'll ensure in the syntax layer that we never have more than one definition for a word, at most one 'open' dependency for implicit imports, and that we only use one definition for a word within a configuration file. 34 | 35 | ## Toplevel Syntax 36 | 37 | I'm aiming for a syntax similar to [toml](https://toml.io/en/), though not an exact match. This is complicated a little by support for import expressions and functions. 38 | 39 | 40 | ## Toplevel Syntax 41 | 42 | A configuration file consists of a header of import and export statements, followed by multiple block-structured namespace definitions. 43 | 44 | # sample config 45 | import locations.math-file as m 46 | from "./oldconfig.g0" import locations, config as old-config 47 | from "../foobar.g0" import foo, bar 48 | export config, locations 49 | 50 | @ns config 51 | # syntax tbd 52 | include old-config 53 | :server.foo 54 | include foo # include into server.foo 55 | set address = "192.168.1.2" # server.foo.address 56 | :server.bar 57 | include bar # include into server.bar 58 | set address = "192.168.1.3" # server.bar.address 59 | 60 | The relative order of imports and definitions is ignored. Instead, we'll enforce that definitions are unambiguous and that dependencies are acyclic. This gives a 'declarative' feel to the configuration language. 61 | 62 | The use of block structure at multiple layers ('@' blocks in toplevel, ':' blocks in namespace definition) is intended to reduce need for indentation. We'll still use a little indentation in some larger data expressions (pattern matching, loops, multi-line texts, etc.) but it should be kept shallow. 63 | 64 | Line comments start with '#' and are generally permitted where whitespace is insignificant, which is most places whitespace is accepted outside of text literals. In addition to line comments, there is explicit support for ad-hoc annotations within namespaces. 65 | 66 | ## Imports and Exports 67 | 68 | Imports and exports must be placed at the head the configuration file, prior to the first '@' block separator. Proposed syntax: 69 | 70 | # statements (commutative, logical lines, '#' line comments) 71 | open Source # implicit defs (limit one) 72 | from Source import Aliases # explicit defs 73 | import Source as Word # hierarchical defs 74 | export Aliases # export control (limit one) 75 | 76 | # grammars 77 | Aliases <= (Word|(Path 'as' Word))(',' Aliases)? 78 | Word <= ([a-z][a-z0-9]*)('-'Word)? 79 | Path <= Word('.'Path)? 80 | Source <= 'file' InlineText | 'loc' Path 81 | 82 | Explicit imports forbid name shadowing and are always prioritized over implicit imports using 'open'. We can always determine *where* every import is coming from without searching outside the configuration file. This supports lazy loading and processing of imports. 83 | 84 | The Source is currently limited to files or a dotted path that should evaluate to a Location. The Location type may specify computed file paths, DVCS resources with access tokens and version tags, and so on. 85 | 86 | *Note:* When importing definitions, we might want the option to override instead of shadow definitions. This might need to be represented explicitly in the import list, and is ideally consistent with how we distinguish override versus shadowing outside the list. Of course, this is a non-issue if we omit 'open' imports. 87 | 88 | ## Implicit Parameters and Algebraic Effects 89 | 90 | 91 | 92 | ## Limited Higher Order Programming 93 | 94 | The language will support limited higher order programming in terms of overriding functions within a namespace, and in terms of algebraic effects. These are structurally restricted to simplify termination analysis. 95 | 96 | These are always static, thus won't interfere with termination analysis. Some higher order loops may be built-in to the syntax for convenience. 97 | 98 | The Localization type isn't used for higher order programming in this language because dynamic . It is used only for translating global module names back into the configuration namespace. 99 | 100 | 101 | ## Function Definitions 102 | 103 | 104 | ## Namespace Blocks 105 | 106 | The namespace will define data and functions. We might override definitions by default, providing access to the 'prior' definition, but we could support explicit introduction where we want to assume a name is previously unused. 107 | 108 | We can support some local shadowing of implicit definitions in context, so long as we don't refer to those implicit definitions. 109 | 110 | 111 | 112 | 113 | 114 | 115 | Data can be modeled as a function that doesn't match any input. 116 | 117 | Name shadowing is only permitted for implicit definitions, and a namespace block must 118 | 119 | In general, a 'complete match' of input is required for a function, meanin 120 | 121 | 122 | 123 | ## Explicit Introduction 124 | 125 | By default, definitions will be overrides. If we want to assume we 'introduce' a definition for the first time, we might specify 'intro ListOfNames'. I think this will better fit the normal use case. 126 | 127 | 128 | ## Data Expression Language 129 | 130 | Definitions within each namespace allow for limited computation of data. The language is not Turing complete, but is capable of simple arithmetic, pattern matching, and [primitive recursion](https://en.wikipedia.org/wiki/Primitive_recursive_function). Data definitions must be acyclic, forming a directed acyclic graph. 131 | 132 | There is a sublanguage for 133 | 134 | I want to keep this language very simple. There are no user-defined functions at this layer, but we might, only user-defined data. We'll freely use keywords and dedicated syntax for arithmetic, conditions, etc. 135 | 136 | ### Multi-Line Texts 137 | 138 | ### Importing File Data 139 | 140 | ### Conditional Expression 141 | 142 | Might be based mostly on pattern matching. 143 | 144 | ### Arithmetic 145 | 146 | I'm torn a bit on how much support for arithmetic in configurations should be provided. Integers? Rationals? Vectors and matrices? I'm leaning towards support for ad-hoc polymorphism based on the shape of data. 147 | 148 | ### Lists and Tables 149 | 150 | I don't plan to support full loops, but it might be convenient to support some operations to filter, join, zip, and summarize lists similar to a relational algebra. 151 | 152 | ### Structured Data 153 | 154 | Support for pairs, lists, and and labeled variants or dictionaries is essential. We could also make it feasible to lift a configuration namespace into data. 155 | 156 | -------------------------------------------------------------------------------- /docs/GlasKPN.md: -------------------------------------------------------------------------------- 1 | # Kahn Process Networks for Glas 2 | 3 | [Kahn Process Networks](https://en.wikipedia.org/wiki/Kahn_process_networks) (KPNs) express deterministic computation in terms of concurrent processes that communicate entirely by reading and writing channels. KPNs are able to leverage distributed processors by partitioning the process network. Their main weakness is that, insofar as they are deterministic, they do not directly handle network disruption or partitioning. 4 | 5 | Also, KPNs do not support backtracking - it is feasible to model distributed transactions, but it would require explicit support from the processes. 6 | 7 | I'm interested in developing KPNs as a program model for glas systems. This could be provided as an accelerated model, but also as a basic model for language modules and applications. KPNs can make it easier for glas systems to model 'large scale' compile tasks, such as rendering video. However, even without KPNs, it is still feasible to use annotations to guide parallel evaluation if we avoid effects. 8 | 9 | ## Static vs. Dynamic KPNs 10 | 11 | It is not difficult to model 'dynamic' process networks, where the set of processes and network links may vary over time. Dynamic KPNs have a simpler API and better flexibility and scalability. OTOH, static KPNs are easier to debug, analyze, optimize. 12 | 13 | I lean towards dynamic KPNs, at least initially, for their flexibility, avoidance of names, and convenient integration of concurrent effects. 14 | 15 | ### Modeling Static KPNs 16 | 17 | With static process networks, all subchannels and subprocesses are labeled statically. We can construct these declaratively: every process has a labeled set of subprocesses, a set of labeled input ports, a set of labeled output ports, a list of messages pending on each port, an anonymous set of internal 'wires', and a procedural main task. 18 | 19 | For consistency, the external boundary of a process might be referenced as a special 'io' subprocess. For example, to reference port 'x' on subprocess 'foo' we use 'foo:x'. To reference port 'x' on the outer process, we could use 'io:x'. Input and output ports with the same name can be distinguished contextually (i.e. if you're reading from it, it must be an input port). 20 | 21 | Wires bypass the main task, implicitly forwarding data from one channel to another. It is possible to wire input channels directly to output channels, or to a subprocess. Wires between subprocesses are more efficient than using the procedural main task to read from one subchannel then write to another. 22 | 23 | The main task would be representing using a procedural sublanguage. This could be represented using the 'prog' model or something more specialized. There is no support for concurrency at this layer - any concurrent operations must be represented by subprocesses. 24 | 25 | The main issue with static KPNs is probably binding them to effects in an application. In practice, we might want multiple concurrent bindings to the filesystem, for example. It is feasible to represent this binding as a separate declarative configuration. 26 | 27 | ### Modeling Dynamic KPNs 28 | 29 | A port represents one endpoint of a duplex channel. It is possible to send and receive data and ports through a channel. Spawning a subprocess immediately returns a port connected to that subprocess. The subprocess starts with one port, representing the other end of that channel, and nothing on the data stack. The initial connection can be used directly, or leveraged to establish fine-grained channels for concurrent subtasks. In many cases, initialization can be partially evaluated to build the initial process network. 30 | 31 | I propose to separate ports and data onto two separate stacks. This simplifies many problems for the runtime and semantics, especially tracking of ports and copying data, though will require using subprocesses to model nodes in a 'routing' structure (distinct from data structures). 32 | 33 | Dynamic KPNs are vastly more flexible than static KPNs, but they're also more complicated to optimize and distribute, requiring runtime dataflow analysis and distributed garbage collection. Static KPNs probably align better with my simplicity goals for glas systems. 34 | 35 | ## Temporal KPNs 36 | 37 | Temporal KPNs extend the KPN with logical time, enabling a process to wait on multiple input channels and process the 'first' message. Temporal KPNs greatly simplify modeling of real-time systems, clocks, and routing... and they better align with intuitions about how a process can work with multiple asynchronous event channel. 38 | 39 | Normally, a KPN must wait indefinitely upon 'read' for data. This is how determinism is guaranteed. A temporal KPN effectively adds time step messages, such that a read can return early with a 'no data yet, wait for later' result. This enables simulation of polling multiple channels. A process may separately 'wait', which implicitly sends a time step to all output channels. 40 | 41 | In practice, it's convenient if those 'time step' messages are very fine-grained. Perhaps corresponding to real-time nanoseconds. In any case, we don't want to run the process loop at the same frequency as the time step. Instead, we want an effect to 'wait' on data from multiple input channels or a timeout, whichever comes first. 42 | 43 | So, in terms of API, temporal KPNs only need a couple extensions: 44 | 45 | * the ability for reads to fail with 'try again later' 46 | * the ability to wait on multiple input channels and a timeout 47 | 48 | Wait messages implicitly propagate to all the output channels, but can also be implicitly combined in-buffer, e.g. `[time-step:1, time-step:2, ...]` combines to `[time-step:3, ...]`. Conversely, waiting removes time-step messages from input channels. Every input channel could add waits to a local time register, then cancel time-step messages while reducing the time register. 49 | 50 | When binding to external effects, we'll need to inject appropriate time-steps. For example, to bind a 40kHz input stream, we might insert a `time-step:25000` message immediately after each data value, assuming time-steps correspond to real-time nanoseconds. (Though, in practice, we might prefer to model a buffered stream with lower frequency, larger blocks of data.) 51 | 52 | In any case, temporal KPNs add a lot of value to KPNs without adding too much complexity or overhead. They make a lot of problem domains more accessible. They better match intuitions about time. I think they're a worthy default feature. 53 | 54 | ## Acceleration of Processes 55 | 56 | It is possible to extend the process model with some specialized processes such as map or filter that are easily optimized by a compiler. Alternatively, we could try to model these as a form of 'acceleration' of processes without extending semantics. I currently favor acceleration. 57 | 58 | This requires at least one annotation header for processes. 59 | 60 | ## Proposed Program Model 61 | 62 | ### Basic Data Manipulation 63 | 64 | * *copy* - duplicate top item on data stack, adds one item to data stack 65 | * *drop* - remove top item from data stack 66 | * *dip:P* - temporarily hide top item on data stack while evaluating P 67 | * *swap* - switch top items on data stack 68 | * *data:V* - add copy of value V to top of data stack 69 | * *seq:\[List, Of, P\]* - 70 | 71 | * *put* 72 | * *del* 73 | * *get:(then:P1, else:P2)* - (R|K?V) K, if K is present evaluates P1 with V on stack, otherwise evaluates P2 with R on stack. (Either way K is dropped.) 74 | * *eq:(then:P1, else:P2)* - V1 V2, if V1=V2 then eval P1, otherwise eval P2. Does not remove data from stack. 75 | 76 | * *halt:ErrorType* - divergence. Same as entering an infinite do-nothing loop, except more efficient. No 'fail' for KPNs. 77 | 78 | * *proc* - annotations as needed. 79 | 80 | ... 81 | 82 | ### Extensions for Dynamic KPNs 83 | 84 | Channels represent a communication 'endpoint' are duplex by default, representing the ability to interact with a remote process. The remote endpoint is 85 | 86 | * copyc 87 | * dropc 88 | * swapc 89 | * dipc 90 | 91 | * send 92 | * sendc 93 | * recv:(data:P1, chan:P2, time:P3) 94 | * poll:[P1, P2, ..., PK] - wait for data to become available on at least one of the top K channels on the stack. Behavior is specified per channel. If two channels have data at the same time, always selects topmost. Use with 'timeout' to limit wait time. 95 | 96 | * pulse - create a timer that will trigger with some constant data every given number of time steps (at least 1). Use with 'poll' for timeouts. 97 | * spawn 98 | * wire - creates a pair of connected channels 99 | 100 | * bind - connect top two channels from stack, such that reads from each are written to the other. Both channels are removed from the stack. 101 | 102 | Reads from C become reads from C2. Reads from C1 are implicitly written to C2. 103 | 104 | top two channels on stack channels permanently. Returns a single channel, such that writes, i.e. so reads from one become inputs to the other and vice versa. Returns a single channel, such that writes become 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /docs/GlasLang.md: -------------------------------------------------------------------------------- 1 | # Initial Language for Glas (TODO!) 2 | 3 | The glas system can support multiple programming languages, but there 4 | 5 | ## Convenient Access to Implicit Environment 6 | 7 | Perhaps something like this: 8 | 9 | foo.x # local foo.x 10 | :foo.x # %env.foo.x 11 | 12 | ## Overrides and Hyperstatic Env 13 | 14 | When we import from a source, we might want to hide some definitions and override others. This could be expressed as part of the import alias list (e.g. 'overriding' clauses on load), or alternatively by syntactically discriminating '=' (let, hide prior) vs. ':=' (assign, update in place) or similar. Need to find a solution that makes both comfortable. 15 | 16 | ## Outcome 17 | 18 | The [namespace model](GlasNamespaces.md) describes a process of iteratively writing definitions into an abstract, monotonic, partial namespace. The namespace is never expressed as a concrete value. 19 | 20 | 21 | containing [abstract assembly](AbstractAssembly.md) definitions. This compiles 22 | To simplify extensibility, dictionary definitions are initially limited to 'ns' and 'mx' headers, and the 'g' header can help integrate ".g" modules into other languages. 23 | 24 | 25 | 26 | ## Desiderata 27 | 28 | This is a procedural language without a separate heap. Objects are second-class, in the sense they can be allocated on the stack but not directly returned from a subroutine. This is mitigated by a macro-like system leveraging [abstract assembly](AbstractAssembly.md). A basic procedure is compiled into two parts: a procedure body, plus a simple wrapper macro that evaluates arguments in the caller then calls the procedure body. Other macros may abstract over declaration of local vars, closures, and construction of stack objects. 29 | 30 | The language should also support static eval. Users can express assumptions about which parameters and results should be determined at compile time. It should be feasible to support lightweight DSLs, e.g. a syntactic sugar around different monads or GADTs (though true syntax extensions are left to language modules). 31 | 32 | I would like to support *units* on numbers in some sensible way. Phantom types? Associated metadata? Ideally something that doesn't cost too much for serialization and makes sense for basic arithmetic. 33 | 34 | And perhaps similar on other values. These units should be subject to compile-time analysis, and could provide a lightweight basis as a type system. 35 | 36 | . Some lightweight DSLs might be supported in terms of compile-time p of data that represents subprograms. Of course, the glas system fully supports user-defined syntax (by defining language modules), 37 | 38 | , including true front-end extensions by defining lang-g. 39 | 40 | 41 | The glas system also supports staging via 42 | although ".g" syntax is not directly extensible (indirectly, users could extend lang-g or de 43 | 44 | Some lightweight DSLs may be feasible based on this (i.e. a macro could parse a const value representing a program)., though glas also allows users to define other file extensions with ad-hoc syntax. 45 | 46 | Ideally, the ".g" language should also support lightweight user-defined DSLs. However, this is inherently limited to structures we can parse without 47 | 48 | I hope to also support lightweight DSLs based on macros. 49 | 50 | ## Macros For All 51 | 52 | 53 | ## Caching 54 | 55 | ## Types 56 | 57 | ## Logging 58 | 59 | ## Profiling 60 | 61 | ## Proof Carrying Code 62 | 63 | 64 | ## Process Networks 65 | 66 | 67 | 68 | it isn't clear how to make a process networks integrate nicely with effects and transactions. 69 | 70 | To support distributed computations at larger scales, it might be convenient to support some model of process networks within glas programs. However, 71 | 72 | 73 | 74 | I believe Glas systems would benefit heavily from good support for Kahn Process Networks, especially temporal KPNs (where processes, channels, and messages have abstract time). 75 | 76 | I would like the KPN language to produce KPN representations without any specific assumption about the target platform, i.e. so it could be compiled to multiple targets - including, but not limited to, Glas processes. 77 | 78 | Instead of dynamic channels, might aim for labeled ports with external wiring. This would be a closer fit for the static-after-metaprogramming structure of glas systems. 79 | 80 | ## Soft Constraint Search 81 | 82 | This is a big one. I'd like to support search for flexible models, especially including metaprogramming. The main thing here would be to express and compose search models, and also develop the optimizers and evaluators for the search. 83 | 84 | ## Logic Language 85 | 86 | 87 | ## Glas Lisp 88 | 89 | Lightweight support for recursion might be convenient. 90 | 91 | 92 | -------------------------------------------------------------------------------- /docs/GlasMirror.md: -------------------------------------------------------------------------------- 1 | # Mirroring in Glas 2 | 3 | Mirroring is replicating an application on a network while maintaining consistency between replicas. 4 | 5 | Mirroring is useful for performance and partitioning tolerance. Regarding performance, mirrors can be closer to the resources they manipulate, reducing latency and bandwidth costs. Mirrors also provide access to remote processors and specialized processors, supporting scalability and acceleration. Regarding partitioning tolerance, mirrors can provide degraded service when the network is disrupted, then automatically recover as network is restored. 6 | 7 | The [transaction loop application model](GlasApps.md) supports near perfect mirroring with miniml attention from developers. The relative ease of mirroring can contribute greatly to scalability and resilience of glas systems. This document details my vision for mirroring in glas systems. 8 | 9 | ## Configuration 10 | 11 | The details for mirrors should be expressed within the configuration file, and the configuration may be application specific (i.e. has access to `settings.*`). 12 | 13 | In general, the configuration would need to include a list of mirrors. And for each mirror we would need enough information to activate a remote node, authorize and authenticate, cross-compile, push code, and so on. 14 | 15 | Details TBD. 16 | 17 | ## Distributed Runtime 18 | 19 | I find it useful to understand mirroring as implmenting a distributed runtime for the application. That is, we have one distributed application that happens to shares a database, has access to multiple network interfaces, and where the runtime itself is partitioning tolerant. Communication between mirrors may be specialized to the runtime version or even to the application (with compiler support). 20 | 21 | Some 'effects' may be supported on multiple mirrors. Notably, access to `sys.time.*` could use the mirror-local clock, and the network API could support binding to mirror-local network interfaces. Other effects, such as filesystem access, would implicitly bind to origin. 22 | 23 | Every mirror would publish to the same RPC registries, but glas runtimes should be mirroring-aware, able to merge the same RPC object from multiple mirrors into one. Which RPC instance is favored can be based on heuristics such as latency and load balancing. 24 | 25 | ## Performance 26 | 27 | Every mirror logically runs the same 'step' transactions, but we might heuristically abort any transaction whose first location-specific effect would require a distributed transaction with another mirror. The premise is that it's better to initiate that transaction on the other mirror, where it *might* run locally, avoiding the expensive distributed transaction. 28 | 29 | For the database, 'ownership' of some variables may also be migrated. This would influence which steps require distributed transactions. 30 | 31 | I expect a whole mess of math would be required to optimize the distribution of variables to maximize performance. Rather than relying entirely on math, we might express a programmer's assumptions about proximity and partitioning behavior via annotations within the program. 32 | 33 | ## Long Term Partition Failure and Recovery 34 | 35 | The notion of 'ownership' of state is acceptable for short-term partitioning, but becomes awkward for long-term partition failures. To support long-term partitioning, we should instead favor state types that are ownerless, i.e. where each partition can continue performing reads and writes locally on its own instance, and where mirrored instances interact in a simple way while connected. 36 | 37 | The 'bag' type is one case of this: every partition can have its own local instance of the bag, and while partitions are connected we're free to heuristically migrate data between instances. [CRDTs](https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type) are another viable class of types. 38 | 39 | In any case, this is mostly a problem to solve via database APIs. 40 | 41 | ## Live Coding and Mirrors 42 | 43 | The 'switch' transaction only commits once for a given code change, so it's probably more convenient if a specific mirror - usually origin - 'owns' that responsibility. All code updates would propagate from that mirror. Similarly, the origin would be responsible for 'start'. 44 | 45 | ## HTTP and Mirrors 46 | 47 | Usefully, every mirror could provide the HTTP service locally, and we could integrate with conventional load balancers and such. 48 | 49 | ## Multi-Application Mirrors 50 | 51 | Similar to how we might run multiple glas applications concurrently on 'origin', it would be convenient if we can easily support multiple applications concurrently on the mirrors. This might be supported by modeling mirroring as a remote service, perhaps based on virtual machines, that can run multiple processes. 52 | 53 | -------------------------------------------------------------------------------- /docs/GlasNotebooks.md: -------------------------------------------------------------------------------- 1 | # Interactive Development 2 | 3 | Extending a read-eval-print loop (REPL) to support graphical inputs and outputs, live coding, and fancy comments essentially results in a [notebook interface](https://en.wikipedia.org/wiki/Notebook_interface). In my vision for glas systems, this sort of interactive development experience should be the default. Even for applications that present a conventional front-end, we might provide access to the notebook view. 4 | 5 | In the notebook metaphor, a module might represent a page or chapter or widget. Pages could be inlined or hyperlinked into a view. Ideally, we can automatically construct a table of contents, or mark some content for latent access as an appendix. Not every syntax is suitable for presenting source alongside runtime output. However, even a ".txt" file benefits from a good editable projection, and a larger notebook page could let users immediately observe the outcome of changing the text. 6 | 7 | ## Implementation Thoughts 8 | 9 | ### Source Setters 10 | 11 | How shall we route proposed updates back to original source files? In context of user-defined syntax and ".zip" files, this must be abstracted and aligned with 'ns.read'. But it is feasible for a compiler to systematically support '@src.set(FilePath, Data)' or similar. The intermediate '@src.set' would rewrite SrcRef then pass each request onwards. A rooted '@src.set' will instead engage the filesystem or network. 12 | 13 | ### Auxilliary Output 14 | 15 | Tables of contents, table of illustrations or interactions, navigation support (e.g. 'prev' and 'next' buttons), etc.. 16 | 17 | ### Cooperative Work 18 | 19 | Use of '@src.set' may be a little simplistic in context of communities, concurrent users, and DVCS. We could extend this API with functions to support cooperative work, e.g. tracking attention, proposed edits, comments, curation, pull requests. But what should this API look like? '@src.op(MethodName, SrcRef, Args)' or similar? 20 | 21 | ### Avoiding Bloat 22 | 23 | We'll need to rely heavily on shared libraries for the editable projections. It's probably also a good idea to bottleneck the source setter/getter/etc. so we can easily modify source references and further delegate without a lot of extra code in the namespace. 24 | 25 | Ideally, the notebook is also designed such that unused features are subject to lazy loading and dead-code elimination. 26 | 27 | ### Extension to AR or VR 28 | 29 | We can move from 2D into 3D with augmented reality (AR) or virtual reality (VR) devices. Use of AR would require some extra index binding views to visual fingerprints. What exactly would this entail? We'll probably need to extend '.gui' to 3D, or use some form of VRML via 'http'? 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/GlasObject.md: -------------------------------------------------------------------------------- 1 | # Glas Object 2 | 3 | Glas Object, or 'glob', is a compact, indexed binary representation for tree-structured data. Primary use cases are data storage, communication, and caching. The focus is representation of dictionaries (radix trees) and lists (arrays, binaries, finger tree ropes), and structure sharing. 4 | 5 | Glas Object is intended to work well with content-addressed storage. A glob can reference other globs by secure hash (SHA3-512), and can work together with proxy caching via content delivery networks. However, it is also feasible to use globs within the glas module system, with access to other modules and procedural generation. This is supported by abstracting the *external reference* type. 6 | 7 | ## Desiderata 8 | 9 | * *indexed* - Data access to specific elements of lists and dicts does not need to parse or scan unrelated regions of the glob file. Dictionaries can be organized as radix trees. Lists can be organized as finger trees or ropes. 10 | * *compact* - Common data and binaries can be efficiently embedded. Can easily share common structure within a glob. 11 | * *scalable* - Larger-than-memory values can be loaded partially, leaving most data in content-addressed storage. All sizes use variable-sized numbers. Content-addressed storage is persistent, can support databases. 12 | * *simple* - Should not have a complicated parser. Composition and decomposition should be easy. A basic writer (i.e. without optimization passes) should be easy. 13 | * *extensible* - Space for new data types or representations to meet future needs. 14 | 15 | ## Encoding 16 | 17 | A parser for Glas Object will first read a header byte that indicates how to read the remainder of a node. The first byte of a glob binary is the 'root value' for the glob, and all relevant data in the glob must be accessible from the root. 18 | 19 | ### Basic Data 20 | 21 | Glas data is binary trees. Glas Object distinguishes leaves, stems, and branches. 22 | 23 | * *leaf* - a terminal node in the tree. 24 | * *stem* - a tree node with one child. 25 | * *branch* - a tree node with two children. 26 | 27 | Glas Object uses stems heavily to encode bitstrings. Numbers, symbols, etc. are encoded into as bitstrings. Thus, compaction of stems is essential. Additionally, we could save some bytes (one byte per symbol, number, or branch within a radix tree) by merging stem-leaf and stem-branch. 28 | 29 | The proposed encoding for Basic Nodes consumes 96 header types, and supports flexible encoding of large bitstring fragments. 30 | 31 | ttt0 abc1 - 3 stem bits in header 32 | ttt0 ab10 - 2 stem bits in header 33 | ttt0 a100 - 1 stem bits in header 34 | ttt0 1000 - 0 stem bits in header 35 | ttt0 0000 - reserved 36 | 37 | ttt1 fnnn . (bytes) - stems of 4 to 64 bits 38 | f - full or partial first byte (0 - partial) 39 | nnn - 1-8 bytes, msb to lsb 40 | 41 | partial first byte - encodes 1 to 7 bits 42 | abcdefg1 - 7 bits 43 | abcdef10 - 6 bits 44 | ... 45 | a1000000 - 1 bits 46 | 10000000 - 0 bits (unused in practice) 47 | 00000000 - unused 48 | 49 | ttt values: 50 | 001 - Stem-Leaf and Leaf (0x28) Nodes 51 | 010 - Stem Nodes and Nop (0x48) 52 | header . (child value) 53 | 011 - Stem-Branch and Branch (0x68) Nodes 54 | header . (offset to right child) . (left child) 55 | 56 | This compactly encodes symbols, numbers, composite variants, radix trees, etc.. 57 | 58 | *Note:* I've dropped support for shared stems via `ttt0 0000` because it was messy and inefficient. Currently there is no option to share common stems. But templated data could possibly fill this role (see *Potential Future Extensions*). 59 | 60 | ### Lists 61 | 62 | In glas systems, lists are conventionally encoded in a binary tree as a right-spine of branch nodes (pairs), terminating in a leaf node (unit value). This is a Lisp-like encoding of lists. 63 | 64 | /\ type List = (Value * List) | () 65 | a /\ 66 | b /\ a list of 5 elements 67 | c /\ [a, b, c, d, e] 68 | d /\ 69 | e () 70 | 71 | However, direct representation of lists is inefficient for many use-cases. Thus, glas runtimes support specialized representations for lists: binaries, arrays, and [finger-tree](https://en.wikipedia.org/wiki/Finger_tree) [ropes](https://en.wikipedia.org/wiki/Rope_(data_structure)). To protect performance, Glas Object also offers specialized list nodes: 72 | 73 | * *array* - header (0x0A) . (length - 1) . (array of offsets); represents a list of values of given length. Offsets are varnats all relative to the end of the array, denormalized to the same width. Width is determined by looking at the first varnat. 74 | * *binary* - header (0x0B) . (length - 1) . (bytes); represents a list of bytes. Each byte represents a small positive integer, 0..255. 75 | * *concat* - header (0x0C) . (offset to right value) . (left list); represents logical concatenation, substituting left list terminal with given right value (usually another list). 76 | * *drop* and *take* - see *Accessors*, support sharing slices of a list 77 | 78 | See *Encoding Finger Tree Ropes* for a pattern to leverage concat effectively. Of course, the system is free to convert ropes into larger arrays or binaries if it doesn't need fine-grained structure sharing of list fragments within or between globs. 79 | 80 | ### External References 81 | 82 | External references are primarily intended for references between globs. 83 | 84 | * *external ref* - header (0x02) followed by a reference value. A reference value must be recognized as representing another value in context. We can logically substitute the external reference with the referenced value. 85 | 86 | Reference values in context of content-addressed storage: 87 | * *glob:SecureHash* - reference to content-addressed glob. SecureHash is usually a 64-byte binary representing the SHA3-512 of an external binary. 88 | * *bin:SecureHash* - reference to content-addressed binary data. Same SecureHash as for globs, but the referent is loaded as a binary instead of parsed as a glob. 89 | 90 | External references generalize as a *contextual extension* mechanism for Glas Object. For example, in context of a module system, we might use *local:ModuleName* and *global:ModuleName* instead of content-addressed *glob* and *bin* references. In context of streaming data or templates, we might introduce *var:Nat* to represent data that will arrive later in the stream or perhaps upon demand. 91 | 92 | *Note:* Establishing and maintaining the context is rarely free. Effective support for external references may involve access tokens for a [CDN](https://en.wikipedia.org/wiki/Content_delivery_network), protocols for content negotiation (analogous to HTTP Accept header), reference validation overheads, and so on. 93 | 94 | ### Internal References 95 | 96 | We can forward reference within a glob file. 97 | 98 | * *internal ref* - header (0x88) . (offset); i.e. the whole-value *accessor*. 99 | 100 | Internal references are mostly useful to improve structure sharing or compression of data. Also useful for the *Glob Headers* pattern, where a glob binary starts with an internal ref. 101 | 102 | ### Accessors 103 | 104 | Accessors support fine-grained structure sharing that preserves indexability and works in context of content-addressed storage. Essentially, we support slicing lists and indexing into records. 105 | 106 | * *path* - headers (0x80-0x9F) . (offset); uses stem-bit header (ttt=100) to encode a bitstring path. Equivalent to following that path into the target value as a radix tree. 107 | * *drop* - header (0x0D) . (length) . (offset to list); equivalent to path of length '1' bits. 108 | * *take* - header (0x0E) . (length) . (inline list value); equivalent to sublist of first length items from list. Although useful to slice lists, this is heavily used to cache list lengths for ropes. 109 | 110 | Indexing a list is possible via composition of path and drop, but it shouldn't be needed frequently, so it isn't optimized. 111 | 112 | ### Annotations 113 | 114 | Support for ad-hoc comments within Glas Object. 115 | 116 | * *annotation* - header (0x01) . (offset to data) . (metadata); the metadata may be an arbitrary value. 117 | 118 | In practice, annotations at the glas object layer are written by a runtime when it's storing data then read by the runtime when it's loading data. Potential use cases include hints for accelerated runtime representations and tracking dataflow while debugging. User programs can potentially access these annotations via runtime reflection APIs. However, it's usually wiser to model annotations in the data layer if possible. 119 | 120 | ### Accelerated Representations 121 | 122 | We can extend *external references* to support logical representations of data. In this case, the reference contains all the information we need, but not in canonical form. For example, an unboxed floating point matrix might be represented as: 123 | 124 | (0x02) . matrix:(dim:[200,300], type:f32, data:Binary) 125 | 126 | When translated to canonical form, this might translate to a list of lists of 32-bit bitstrings. But a runtime could potentially use the unboxed representation directly. 127 | 128 | We can potentially introduce many more variants to support graphs, sets, etc.. And even matrices might benefit from logical transposition, lazy multiplication, etc.. This complicates content negotiation and the runtime. If parties fail to agree to an accelerated representation, they can still construct the canonical representation and add *annotations* they know to read the data back as a matrix. Of course, if conversion is very expensive, the transaction might be aborted on quota constraints. 129 | 130 | Eventually, as accelerated representations achieve status as de-facto standards, we can contemplate assigning dedicated headers in Glas Object to save a few bytes. 131 | 132 | ## Varnats, Lengths, and Offsets 133 | 134 | A varnat is encoded with a prefix '1*0' encoding a length in bytes, followed by 7 data bits per prefix bit, msb to lsb order. For example: 135 | 136 | 0nnnnnnn 137 | 10nnnnnn nnnnnnnn 138 | 110nnnnn nnnnnnnn nnnnnnnn 139 | ... 140 | 141 | In normal form, varnats use the smallest number of bytes to encode a value. It isn't an error to use more bytes, just not very useful within an immutable binary. In some where the minimum value is one instead of zero, we'll encode one less such that a single byte can encode 1 to 128. 142 | 143 | ## Summary of Node Headers 144 | 145 | 0x00 (never used) 146 | 0x01 Annotation 147 | 0x02 External Ref 148 | 149 | 0x0A Array 150 | 0x0B Binary 151 | 0x0C Concat 152 | 0x0D Drop 153 | 0x0E Take 154 | 155 | 0x20-0x3F Stem-Leaf and Leaf (0x28) 156 | 0x40-0x5F Stem Nodes (and Nop - 0x48) 157 | 0x60-0x7F Stem-Branch and Branch (0x68) 158 | 0x80-0x9F Index Path and Internal Ref (0x88) 159 | 160 | CURRENTLY UNUSED: 161 | 0x05-0x09 162 | 0x0F-0x1F 163 | 0xA0-0xFF 164 | 165 | ## Conventions and Patterns 166 | 167 | Some ideas about how we might leverage Glas Object for more use cases. 168 | 169 | ### Encoding Finger Tree Ropes 170 | 171 | It is feasible to combine list-take (Size) and concatenation nodes in a way that provides hints of finger-tree structure to support tree-balancing manipulations. 172 | 173 | Concat (L1 ++ L2) 174 | Take (Size . List) 175 | 176 | Digit(k) 177 | k = 0 178 | Array 179 | Binary 180 | k > 0 181 | Larger Array or Binary (heuristic) 182 | Size . Node(k-1) 183 | Node(k) - two or three concatenated Digit(k) 184 | Digits(k) - one to four concatenated Digit(k) 185 | LDigits(k) - right assoc, e.g. (A ++ (B ++ C)) 186 | RDigits(k) - left assoc, e.g. ((A ++ B) ++ C) 187 | Rope(k) 188 | Empty Leaf 189 | Single Array | Binary | Node(k-1) 190 | Many Size . (LDigits(k) ++ (Rope(k+1) ++ RDigits(k))) 191 | 192 | This structure represents a 2-3 finger-tree rope, where '2-3' refers to the size of internal nodes. It is possible that wider nodes and more digits would offer superior performance, but most gains will likely be due to favoring larger binary or array fragments. 193 | 194 | ### Glob Headers 195 | 196 | As a simple convention, a glob binary that starts with an internal reference (0x88) is considered to have a header. The header should also be glas data, typically a record of form `(field1:Value1, field2:Value2, ...)`. 197 | 198 | 0x88 (offset to data) (header) (data) 199 | 200 | A header can be considered an annotation for the glob binary as a whole. Potential use cases include adding provenance metadata, glob extension or version information, or entropy for a convergence secret. 201 | 202 | ### Data Validation 203 | 204 | Validation of glob binaries can be expensive in context of very large data or accelerated representations. Nonetheless, it should be performed before we commit potentially invalid data into a database. That's our last good opportunity to abort a transaction without risk of long-term corruption. 205 | 206 | To mitigate validation overheads, a runtime might implicitly trust hashes it learns about from a trusted database or CDN. This trust would be expressed in the runtime configuration. Additionally, we can leverage glob headers or annotations to include proof hints or cryptographic signatures. Proof hints can reduce the cost to re-validate, while signatures might indicate a party you trust already performed the validation. 207 | 208 | ### Deduplication and Convergence Secret 209 | 210 | It is possible for a glas system to 'compress' data by generating the same glob binaries, with the same secure hash. This is mostly a good thing, but there are subtle attacks and side-channels. These attacks can be greatly mitigated via controlled introduction of entropy, e.g. [Tahoe's convergence secret](https://tahoe-lafs.readthedocs.io/en/latest/convergence-secret.html). 211 | 212 | -------------------------------------------------------------------------------- /docs/GlasTypes.md: -------------------------------------------------------------------------------- 1 | # Type Annotations for Glas Programs 2 | 3 | Glas doesn't have a built-in type system, but supports ad-hoc annotations. So, what sort of annotations would be most useful for static analysis or typechecking? This document explores this a bit and sketches out some preliminary designs. 4 | 5 | ## Stack Arity 6 | 7 | Perhaps the simplest type. Just count the number of stack inputs and stack outputs. Sometimes written as `2 -- 3` to mean 2 stack inputs, 3 stack outputs. As a program annotation: 8 | 9 | * *arity:(i:Nat, o:Nat)* - indicate how many values from the data stack are input and output (with outputs counted as if inputs were first removed). 10 | 11 | ## Value Type Descriptions 12 | 13 | To do much more than describe numbers of things, we'll need the ability to describe data and program behavior program types effectively. I'd prefer to have something more declarative and decidable than running an arbitrary program. However, the ability to include some sizes and computations in types is still useful. 14 | 15 | 16 | 17 | 18 | ## Pre and Post Conditions 19 | 20 | This model is very expressive but also very difficult to verify statically. 21 | 22 | Viable annotations: 23 | 24 | * *preconds:[List of prog:(do:Program, ...)]* - set of pass/fail programs that should be able to pass before we evaluate the annotated program. 25 | * *postconds:[List of prog:(do:Program, ...)]* - set of pass/fail programs that should be able to pass after we evaluate the annotated program. 26 | 27 | Eventually, we might extend precond and postcond to accept descriptions other than 'prog'. The conditions would ideally be verified statically, but in practice it's more likely we'll only check them in debug modes, and further might only spot-check them. 28 | 29 | The weakness of conditions like this is that the postconditions cannot depend on the preconditions. Use of *Behavioral Equivalence* is even more expressive. 30 | 31 | ## Behavioral Equivalence 32 | 33 | We could annotate one program as being behaviorally equivalent to another, or perhaps to a list of other programs. For example, we could specify that a program is equivalent to the same program integrated with a bunch of runtime checks. 34 | 35 | extra precondition and postcondition checks, in which case the wrapper might carry some state. Or we could say that the program is equivalent to a low-performance reference implementation of the same program. 36 | 37 | 38 | 39 | 40 | 41 | # OLD STUFF 42 | 43 | ## Type System 44 | 45 | Glas doesn't specify a type system, but a type system is naturally influenced by the data and program models. Ideally, they type system should support partial type annotations, i.e. such that programmers can express just their local intentions without capturing the entire type at one location. 46 | 47 | Types should capture both representation and meaning (e.g. units for numbers). 48 | 49 | Representation types can include: 50 | 51 | * symbols, enums 52 | * records (open or closed) 53 | * lists and tuples 54 | * non-homogeneous, grammar patterned lists? 55 | * natural numbers 56 | * other numbers (floating point? complex/vectors?) 57 | * 58 | 59 | 60 | All data types are essentially refinements of binary trees. A type system might specialize list, record, and variant types explicitly. Programs will have stack types and effect types, both of which have significant integration with data types. 61 | 62 | Glas programs can support abstract types by asserting that certain data is only indirectly observed via external 'eff' handlers. Abstract types can be extended to linear types by also restricting a subprogram's ability to drop or copy the data. 63 | 64 | Interaction between data and subprograms can potentially be expressed by tracking 'static' vs. 'dynamic' data types, refinement types, and dependent data types. It is also feasible to support associated types (aka phantom types) based on annotations and some abstract interpretation. For example, a given number might have an associated unit type so we don't accidentally add meters to kilograms. 65 | 66 | Glas also supports staged programming. We might desire to analyze whether the staged program will produce a valid program based on reasonable or verifiable assumptions about its inputs. 67 | 68 | Above the program is the application layer. We might want session types or state-machine types to describe asynchronous interactions, to check whether the application is meta-stable or has other nice properties. 69 | 70 | Types are a very deep subject. Glas barely touches the surface with static stack arity checks. Everything else must be supported via program annotations and user-defined analysis. For this reason, Glas programs provide a feature for extensible annotations. But 71 | 72 | ### Overloads and Generics 73 | 74 | A popular use of type systems is to automate selection of functions, so we can use the same syntax `a + b` for adding ints vs. floats vs. vectors. 75 | 76 | Glas doesn't have any built-in support for this feature, but it is feasible to build it in at the user-defined syntax layer together with some staged programming. Support for overloads is low priority, left as an exercise for the future. 77 | 78 | ### User-Defined Analysis 79 | 80 | Glas system supports user-defined static analysis. Language modules can analyze local code and the value of loaded dependencies. Where a module doesn't adequately analyze itself, developers can introduce an intermediate module to perform the analysis, or analyze at point of use. 81 | 82 | Compilers are also user-defined in Glas - simply a language module whose output is binary. Compilers will often perform static analysis to check assumptions and improve optimizations. 83 | 84 | ### Provenance and Error Reporting 85 | 86 | Error reporting is a little troublesome in Glas because analysis mostly operates on an intermediate program model instead of original source code. 87 | 88 | This could be mitigated by having language modules add some provenance annotations to program outputs. However, it is awkward to preserve provenance explicitly. An alternative is to design a Glas intepreter that can maintain provenance implicitly. 89 | 90 | ### Gradual Typing vs. Package Systems 91 | 92 | A goal of Glas design is to simplify gradual typing of the codebase and evolution of the type system. Naturally, this results in old modules and applications failing under new analyses. 93 | 94 | Those broken modules should be readily discovered and repaired or replaced. This influences design of the package system, e.g. favoring community and company distributions like artifactory or docker instead of a central server like nuget or hackage. 95 | 96 | We might identify broken modules and estimate the health of a distribution by identifying which modules fail and which emit 'error' or 'warn' log messages. With this feedback, we could develop a safe approach to gradually integrate breaking changes, or directly fix the broken modules when there are only a few. 97 | 98 | ## Techniques 99 | 100 | ### SHErrLoc 101 | 102 | The [SHErrLoc project](https://www.cs.cornell.edu/projects/SHErrLoc/) of Cornell is an inspiring approach to static analysis based on producing a constraint set that is subsequently analyzed by a general constraint solver, with some probabilistic inference of blame. 103 | 104 | -------------------------------------------------------------------------------- /docs/GlasZero.md: -------------------------------------------------------------------------------- 1 | # The g0 Language 2 | 3 | See the [language-g0 module README](../glas-src/language-g0/README.md). Content moved there to avoid maintaining the description in two locations. 4 | 5 | -------------------------------------------------------------------------------- /docs/InteractionNets.md: -------------------------------------------------------------------------------- 1 | # Interaction Nets 2 | 3 | Interaction nets are a graph rewrite model where each node has exactly one 'active' port, and rewrite rules exist for pairs of nodes connected on active ports. Multiple rewrites can occur in parallel, and evaluation is deterministic if each rewrite rule is deterministic. Link: [a good introduction to interaction nets](https://zicklag.github.io/blog/interaction-nets-combinators-calculus/). 4 | 5 | We can optimally model a useful subset of lambda calculus with interaction calculus. But I'll probably need to develop node types that are very suitable for what I want to express, including the glas data model. 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/ProgramSearch.md: -------------------------------------------------------------------------------- 1 | # Program Search 2 | 3 | This document explores how program search might be supported in Glas. 4 | 5 | I'm interested in program search as a long-term goal for glas systems. But my vision for this also has some nuance, such as the importance of modularity, incremental computation, and iterative refinement. 6 | 7 | ## Some Ideas 8 | 9 | Something similar to prototype object-orientation is suitable, in the sense that we 'instantiate' objects that have only one final value, but we may have multiple independent instances within a larger program, yet those instances might not be fully independent due to sharing some decisions or context. 10 | 11 | Hard constraints need clear, preferably modular dataflow dependencies, such that we can check them ASAP and filter results. This is the case even if the constraints are scattered throughout the codebase. 12 | 13 | Soft constraints need to be monotonic, such that searches are more incremental in nature. This could be based on scoring 'costs', and perhaps by providing some vector of weights for different kinds of costs. 14 | 15 | It should be feasible to extract search paths and apply machine learning to improve search performance. 16 | 17 | ## Design Goals 18 | 19 | * Avoid named variables, at least for the lower level representation, i.e. favor a tacit program model. This simplifies composition, decomposition, and metaprogramming. This might be achieved via stack of anonymous constraint/unification variables or graph structure, for example. 20 | * Modular partial evaluation and memoization. This likely requires monotonic heuristics that can be evaluated locally without full knowledge of context. 21 | * Handles ambiguity. A word in a program could refer to a range of meanings that only become obvious given context of other words. 22 | * Handles vagueness. Meanings can be missing some details that can be filled in later, preferably with reference to context. 23 | * Flexible output model. Doesn't need to be a Glas program, but can be one. Output could also be a DSL or something else entirely. 24 | * Supports extension. There is a mechanism to effectively compose subprograms that can grow or refine the search space. This likely requires a structured search space. 25 | 26 | -------------------------------------------------------------------------------- /docs/TextTree.md: -------------------------------------------------------------------------------- 1 | # Text Tree Language 2 | 3 | Text tree is intended as a lightweight language for configuration or structured data. Compared to XML and JSON, text tree uses less punctuation and fewer escapes, is better at isolating errors, and has similar extensibility. Essentially, it records a recursive list of labeled elements where each element may have a primary text and a sublist of elements. (Unlike markup languages, there is no direct mixing of text and structure.) 4 | 5 | A typical text tree might look like this: 6 | 7 | person 8 | name David 9 | alias Dave 10 | e-mail david@example.com 11 | 12 | person 13 | ... 14 | 15 | A text tree is represented by a list of entries. Every entry consists of a label, a main text (inline or multi-line), and a subtree for attributes. The subtree is distinguished by indentation. Order of entries is preserved. The text should be ASCII or UTF-8. Line separators may be CR, LF, or CRLF. Indentation should use spaces. 16 | 17 | The text tree may parse into a list of triples: 18 | 19 | type TT = List of { Text, Text, TT } 20 | 21 | For example, the example text tree might parse to: 22 | 23 | [{"person", "", 24 | [{"name", "David", 25 | [{"alias", "Dave", []} 26 | ]} 27 | {"e-mail", "david@example.com", []} 28 | ]} 29 | {"person", "", [...]} 30 | ] 31 | 32 | Spaces are trimmed with a special exception for multi-line texts (see below). Similar to XML or JSON, text tree may benefit from a schema for validation in many contexts, but that won't be developed immediately. 33 | 34 | ## Escape Sequences 35 | 36 | All entries that start with backslash ('\', codepoint 92) are reserved for escapes and extensions. Escapes are scoped to their containing entry to simplify local reasoning. Currently used for multi-line texts, potential use for inclusions and transclusions. 37 | 38 | ## Multi-Line Texts 39 | 40 | Multi-line text may follow an entry with an empty inline text. 41 | 42 | para 43 | \ text starts here 44 | \ and continues on 45 | \ yet another line 46 | 47 | Unlike inline text, multi-line text is not trimmed. The indentation, '\' label, and a single space following the label is removed from each line, but further prefix or trailing spaces are preserved. In the parsed result, lines are separated by LF even if the text tree file uses CR or CRLF. 48 | 49 | ## Comments 50 | 51 | The text tree format proposes a lightweight convention for comments: any entry whose label starts with '#' should be interpreted as a comment or annotation or metadata. This includes '#author', '#date', and so on. These entries are left accessible for post-processing, but should be understood as providing or supporting context without influence on meaning. 52 | 53 | ## Thoughts 54 | 55 | ### Inclusion? Defer. 56 | 57 | Text tree could easily be extended to support logical inclusion of other text-tree and text file, e.g. `\include FilePath` within an entry, or `\text FilePath` within a multi-line text. This is much more limited than a programmable configuration. In theory, this is useful for representing large configurations. But I propose to defer them until I have a clear use case in practice, e.g. in context of post-processing and dhall. I would not be surprised to discover that some form of selection and transclusion is needed to effectively leverage inclusions. 58 | 59 | ### Programmable Configurations 60 | 61 | I would have liked a lightweight, generic approach to inheritance and mixin-like abstractions of text trees. But, even with escape sequences, I don't see this happening easily with text tree. Too much code is required, and text tree is designed as a structured ".txt" rather than a programmable configuration language. Users could adapt [dhall](https://dhall-lang.org/) or similar to mitigate these limitations. Of course, we can also support configuration-specific solutions via post-processing. 62 | 63 | -------------------------------------------------------------------------------- /fsrc/Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: all glas install test watch-test clean 3 | 4 | all: glas test install 5 | 6 | glas: 7 | dotnet publish -c release -r linux-x64 --self-contained true -p:PublishTrimmed=true -o bin/ src/ 8 | 9 | test: 10 | dotnet test test/ 11 | 12 | watch-test: 13 | dotnet watch test --project test/ 14 | 15 | clean: 16 | rm -rf bin src/bin src/obj test/bin test/obj 17 | -------------------------------------------------------------------------------- /fsrc/glas: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | make glas 3 | ./bin/Glas "$@" -------------------------------------------------------------------------------- /fsrc/src/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | 4 | 5 | -------------------------------------------------------------------------------- /fsrc/src/Glas.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net7.0 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /fsrc/src/LoadModule.fs: -------------------------------------------------------------------------------- 1 | namespace Glas 2 | 3 | /// This module describes the FileSystem search algorithms and effects 4 | /// for loading Glas modules. 5 | /// 6 | /// Global module search is configured via "sources.txt" in GLAS_HOME. 7 | /// The GLAS_HOME is assigned a default location in Main.fs if needed. 8 | module LoadModule = 9 | open System 10 | open System.IO 11 | open Effects 12 | open ProgVal 13 | open ProgEval 14 | 15 | // type aliases for documentation 16 | type FolderPath = string 17 | type FilePath = string 18 | type ModuleName = string 19 | 20 | // load global search path from GLAS_HOME/sources.txt 21 | // 22 | // currently limited to 'dir' entries with no attributes. 23 | // Relative 'dir' paths are relative to GLAS_HOME. 24 | // 25 | // Eventually "sources.txt" should support networked resources, 26 | // which would be downloaded then cached locally. But that can 27 | // wait for after bootstrap. 28 | let globalSearchPath (ll : IEffHandler) : FolderPath list = 29 | let sources_name = "sources.tt" 30 | try 31 | let home = Path.GetFullPath(Environment.GetEnvironmentVariable("GLAS_HOME")) 32 | let sourcesFile = Path.Combine(home, sources_name) 33 | let sourcesText = File.ReadAllText(sourcesFile) 34 | let parseDirEnts (ent : TextTree.TTEnt) = 35 | if ent.Label.StartsWith("#") then [] else // ignore comments 36 | let isBasicDirEnt = (ent.Label = "dir") && (List.isEmpty ent.Attrib) 37 | if isBasicDirEnt then [Path.Combine(home, ent.Data)] else 38 | logWarn ll (sprintf "unhandled entry in %s: %A" sources_name ent) 39 | [] 40 | sourcesText |> TextTree.parseEnts |> List.collect parseDirEnts 41 | with 42 | | e -> 43 | logError ll (sprintf "failed to load %s: %A" sources_name e) 44 | [] 45 | 46 | let private findModuleAsFile (m:ModuleName) (dir:FolderPath) : FilePath list = 47 | let matching_name (fp : FilePath) : bool = 48 | Path.GetFileName(fp).Split('.').[0] = m 49 | Directory.EnumerateFiles(dir) |> Seq.filter matching_name |> List.ofSeq 50 | 51 | let private findModuleAsFolder (m:ModuleName) (dir:FolderPath) : FilePath list = 52 | let subdir = Path.Combine(dir,m) 53 | if not (Directory.Exists(subdir)) then [] else 54 | match findModuleAsFile "public" subdir with 55 | | [] -> [Path.Combine(subdir, "public.g0")] // default missing file 56 | | files -> files // should be list of one file 57 | 58 | let private findLocalModule (m:ModuleName) (localDir:FolderPath): FilePath list = 59 | List.append (findModuleAsFile m localDir) (findModuleAsFolder m localDir) 60 | 61 | let rec private findGlobalModule (m:ModuleName) (searchPath : FolderPath list): FilePath list = 62 | match searchPath with 63 | | (p :: searchPath') -> 64 | match findModuleAsFolder m p with 65 | | [] -> findGlobalModule m searchPath' 66 | | findings -> findings 67 | | [] -> [] 68 | 69 | // wrap a compiler function for arity 1--1 70 | let private _compilerFn (p:Program) (ll:IEffHandler) = 71 | let preLinkedEval = eval p ll 72 | fun v -> 73 | match preLinkedEval [v] with 74 | | Some [r] -> ValueSome r 75 | | _ -> ValueNone 76 | 77 | // factored out some error handling 78 | let private _expectCompiler (ll:IEffHandler) (src:string) (vOpt:Value voption) = 79 | match vOpt with 80 | | ValueSome (Value.FullRec ["compile"] ([(Value.Variant "prog" _) as pCompile], _)) -> 81 | match stackArity pCompile with 82 | | Arity (a,b) when ((a = b) && (1 >= a)) -> 83 | ValueSome pCompile 84 | | ar -> 85 | logError ll (sprintf "%s.compile has incorrect arity %A" src ar) 86 | ValueNone 87 | | ValueSome _ -> 88 | logError ll (sprintf "%s does not define 'compile'" src) 89 | ValueNone 90 | | ValueNone -> ValueNone 91 | 92 | type Loader = 93 | // Effects other than 'load'. Logging is assumed. 94 | val private NonLoadEff : IEffHandler 95 | 96 | // cache global module search path 97 | val private GlobalSearchPath : FolderPath list 98 | 99 | // To resist cyclic dependencies, track which files we are loading. 100 | val mutable private Loading : FilePath list 101 | 102 | // Cache results per file. 103 | val mutable private Cache : Map 104 | 105 | // Cached compiler functions. The "g0" compiler is added at construction. 106 | val mutable private Compilers : Map Value voption) voption)> 107 | 108 | new (linkG0,eff0) as ll = 109 | { NonLoadEff = eff0 110 | ; GlobalSearchPath = globalSearchPath eff0 111 | ; Loading = [] 112 | ; Cache = Map.empty 113 | ; Compilers = Map.empty 114 | } then // link the g0 compiler 115 | let g0c = linkG0 (ll :> IEffHandler) 116 | ll.Compilers <- Map.add "g0" (ValueSome g0c) (ll.Compilers) 117 | 118 | member private ll.GetCompiler (fileExt : string) : (Value -> Value voption) voption = 119 | if String.IsNullOrEmpty(fileExt) then ValueNone else 120 | match Map.tryFind fileExt ll.Compilers with 121 | | Some result -> result // cached result 122 | | None -> 123 | let m = "language-" + fileExt 124 | let result = 125 | match _expectCompiler ll m (ll.LoadGlobalModule m) with 126 | | ValueSome pCompile -> ValueSome (_compilerFn pCompile ll) 127 | | ValueNone -> ValueNone 128 | ll.Compilers <- Map.add fileExt result ll.Compilers 129 | result 130 | 131 | member private ll.Compile fileExt (v0 : Value) : Value voption = 132 | match ll.GetCompiler fileExt with 133 | | ValueSome p -> p v0 134 | | ValueNone -> ValueNone 135 | 136 | member private ll.LoadFileBasic (fp : FilePath) : Value voption = 137 | let appLang fileSuffix vOpt = 138 | match vOpt with 139 | | ValueSome v -> ll.Compile fileSuffix v 140 | | ValueNone -> ValueNone 141 | let langs = Path.GetFileName(fp).Split('.') |> Array.toList |> List.tail 142 | let v0 = 143 | try fp |> File.ReadAllBytes |> Value.ofBinary |> ValueSome 144 | with 145 | | e -> 146 | logError ll (sprintf "error loading file %s: %A" fp e) 147 | ValueNone 148 | // extensions apply from outer to inner 149 | List.foldBack appLang langs v0 150 | 151 | /// Load a specified file as a module. 152 | member ll.LoadFile (fp : FilePath) : Value voption = 153 | match Map.tryFind fp (ll.Cache) with 154 | | Some r -> // use cached value 155 | //logInfo ll (sprintf "using cached result for file %s" fp) 156 | r 157 | | None -> 158 | if List.contains fp (ll.Loading) then 159 | // report cyclic dependency, leave to programmers to solve. 160 | let cycle = List.rev <| fp :: List.takeWhile ((<>) fp) ll.Loading 161 | logError ll (sprintf "dependency cycle detected! %s" (String.concat ", " cycle)) 162 | ValueNone 163 | elif File.Exists(fp) then 164 | //logInfo ll (sprintf "loading file %s" fp) 165 | let ld0 = ll.Loading 166 | ll.Loading <- fp :: ld0 167 | try 168 | let r = ll.LoadFileBasic fp 169 | ll.Cache <- Map.add fp r ll.Cache 170 | r 171 | finally 172 | ll.Loading <- ld0 173 | else 174 | logError ll (sprintf "file does not exist: %s" fp) 175 | ValueNone 176 | 177 | member ll.LoadLocalModule (m : ModuleName) : Value voption = 178 | let localDir = 179 | match ll.Loading with 180 | | (fp::_) -> Path.GetDirectoryName(fp) 181 | | [] -> Directory.GetCurrentDirectory() 182 | match findLocalModule m localDir with 183 | | [fp] -> ll.LoadFile fp 184 | | [] -> 185 | logWarn ll (sprintf "local module %s not found in %s" m localDir) 186 | ValueNone 187 | | ps -> 188 | logWarn ll (sprintf "local module %s ambiguous in %s" m localDir) 189 | ValueNone 190 | 191 | member ll.LoadGlobalModule (m : ModuleName) : Value voption = 192 | match findGlobalModule m (ll.GlobalSearchPath) with 193 | | [fp] -> ll.LoadFile fp 194 | | [] -> 195 | logWarn ll (sprintf "global module %s not found" m) 196 | ValueNone 197 | | fps -> 198 | // most likely cause is more than one 'public' file. 199 | logWarn ll (sprintf "global module %s ambiguous [%s]" m (String.concat "; " fps)) 200 | ValueNone 201 | 202 | interface IEffHandler with 203 | // Handle 'load' effects. Forward everything else. 204 | member ll.Eff v = 205 | match v with 206 | | Value.Variant "load" vLoad -> 207 | match vLoad with 208 | | Value.Variant "local" (Value.String m) -> 209 | ll.LoadLocalModule m 210 | | Value.Variant "global" (Value.String m) -> 211 | ll.LoadGlobalModule m 212 | | _ -> 213 | logWarn ll (sprintf "unrecognized ModuleRef %s" (Value.prettyPrint vLoad)) 214 | ValueNone 215 | | Value.Variant "log" vMsg -> 216 | // add filepath to log messages 217 | let vMsg' = 218 | match ll.Loading with 219 | | (f::_) -> Value.record_insert (Value.label "file") (Value.ofString f) vMsg 220 | | _ -> vMsg 221 | ll.NonLoadEff.Eff (Value.variant "log" vMsg') 222 | | _ -> ll.NonLoadEff.Eff v 223 | interface ITransactional with 224 | // Loader assumes external modules are constant during its lifespan. 225 | // The cache is thus valid across transaction boundaries. But we do 226 | // pass transactions onwards to the logger or other effects. 227 | member ll.Try () = 228 | ll.NonLoadEff.Try () 229 | member ll.Commit () = 230 | ll.NonLoadEff.Commit () 231 | member ll.Abort () = 232 | ll.NonLoadEff.Abort () 233 | 234 | let private _builtInG0 ll v = 235 | match v with 236 | | Value.String s -> Zero.compile ll s 237 | | _ -> 238 | logError ll "built-in g0 requires string input" 239 | ValueNone 240 | 241 | /// Loader without bootstrapping. Simply use the built-in g0. 242 | let nonBootStrapLoader (nle : IEffHandler) : Loader = 243 | Loader(_builtInG0, nle) 244 | 245 | /// Attempt to bootstrap the g0 language, then use the language-g0 246 | /// module for the loader. 247 | let tryBootStrapLoader (nle : IEffHandler) : Loader voption = 248 | let ll0 = nonBootStrapLoader nle 249 | let src0 = "(as compiled by built-in g0) language-g0" 250 | match _expectCompiler ll0 src0 (ll0.LoadGlobalModule "language-g0") with 251 | | ValueNone -> ValueNone 252 | | ValueSome p0 -> 253 | // logInfo nle "bootstrap: language-g0 compiled using built-in g0" 254 | let ll1 = Loader(_compilerFn p0, nle) 255 | let src1 = "(as compiled by language-g0) language-g0" 256 | match _expectCompiler ll1 src1 (ll1.LoadGlobalModule "language-g0") with 257 | | ValueNone -> ValueNone 258 | | ValueSome p1 -> 259 | // logInfo nle "bootstrap: language-g0 compiled using language-g0" 260 | let ll2 = Loader(_compilerFn p1, nle) 261 | match ll2.LoadGlobalModule "language-g0" with 262 | | ValueSome (Value.FullRec ["compile"] ([p2],_)) when (Value.eq p2 p1) -> 263 | // logInfo nle "language-g0 bootstrap successful!" 264 | ValueSome ll2 265 | | _ -> 266 | logError nle "language-g0.compile fails to exactly rebuild itself" 267 | ValueNone 268 | -------------------------------------------------------------------------------- /fsrc/src/Main.fs: -------------------------------------------------------------------------------- 1 | 2 | // The Glas command line utility API has a proper design now. 3 | // 4 | // glas --run ProgramRef Args 5 | // glas --help 6 | // glas --version 7 | // glas --extract BinaryRef 8 | // 9 | // Additionally, we support user-defined verbs: 10 | // 11 | // glas verb parameters 12 | // rewrites to 13 | // glas --run glas-cli-verb.main -- parameters 14 | // 15 | // However, this is a pre-bootstrap implementation that does not 16 | // support --run. This considerably simplifies the scope. 17 | // 18 | 19 | open Glas 20 | open FParsec 21 | open Glas.Effects 22 | open Glas.LoadModule 23 | open Glas.ProgVal 24 | open Glas.ProgEval 25 | 26 | 27 | let helpMsg = String.concat "\n" [ 28 | "A pre-bootstrap implementation of Glas command line interface." 29 | "" 30 | "Built-in Commands:" 31 | "" 32 | " glas --run ValueRef Args" 33 | " Evaluate an application process, which represents" 34 | " a transactional step function. Limited effects:" 35 | " sys.tty.read/write " 36 | " 'http' interface " 37 | "" 38 | " glas --version" 39 | " Print a version string." 40 | "" 41 | " glas --help" 42 | " Print this message." 43 | "" 44 | " glas --print ValueRef" 45 | " Write arbitrary value to standard output using an" 46 | " ad-hoc pretty printer. Intended for debugging." 47 | "" 48 | " glas --check ValueRef" 49 | " Check that module compiles and value is defined." 50 | " Combines nicely with assertions for debugging." 51 | "" 52 | "User-Defined Commands (no '-' prefix):" 53 | "" 54 | " glas opname Args" 55 | " rewrites to" 56 | " glas --run glas-cli-opname.main -- Args" 57 | "" 58 | " Users may freely define glas-cli-* modules " 59 | "" 60 | "A ValueRef is essentially a dotted path starting with a module ref. A" 61 | "module ref can be a module-name (global) or ./module-name (local)." 62 | "" 63 | ] 64 | 65 | let ver = "glas pre-bootstrap 0.2 (dotnet)" 66 | 67 | let EXIT_OKAY = 0 68 | let EXIT_FAIL = 1 69 | 70 | // a short script to ensure GLAS_HOME is defined and exists. 71 | let GLAS_HOME = "GLAS_HOME" 72 | let select_GLAS_HOME () : string = 73 | let home = System.Environment.GetEnvironmentVariable(GLAS_HOME) 74 | if not (isNull home) then home else 75 | // default GLAS_HOME 76 | let appDir = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData) 77 | System.IO.Path.Combine(appDir, "glas") 78 | let prepare_GLAS_HOME () : unit = 79 | let home = System.IO.Path.GetFullPath(select_GLAS_HOME ()) 80 | // printfn "GLAS_HOME=%A" home 81 | ignore <| System.IO.Directory.CreateDirectory(home) 82 | System.Environment.SetEnvironmentVariable(GLAS_HOME, home) 83 | 84 | // parser for value ref. 85 | module ValueRef = 86 | open FParsec 87 | type P<'T> = Parser<'T,unit> 88 | 89 | type Word = string 90 | 91 | type ModuleRef = 92 | | Local of Word 93 | | Global of Word 94 | 95 | let parseWord : P = 96 | let wf = many1Satisfy2 isAsciiLower (fun c -> isAsciiLower c || isDigit c) 97 | stringsSepBy1 wf (pstring "-") 98 | 99 | let parseModuleRef : P = 100 | choice [ 101 | pstring "./" >>. parseWord |>> Local 102 | parseWord |>> Global 103 | ] 104 | 105 | let parse : P<(ModuleRef * Word list)> = 106 | (parseModuleRef .>>. many (pchar '.' >>. parseWord)) .>> eof 107 | 108 | let mRefStr m = 109 | match m with 110 | | ValueRef.Local p -> "./" + p 111 | | ValueRef.Global p -> p 112 | 113 | 114 | let getValue (ll:Loader) (vref : string): Value voption = 115 | match FParsec.CharParsers.run (ValueRef.parse) vref with 116 | | FParsec.CharParsers.Success ((m,idx),_,_) -> 117 | let mv = 118 | match m with 119 | | ValueRef.Local m' -> ll.LoadLocalModule m' 120 | | ValueRef.Global m' -> ll.LoadGlobalModule m' 121 | if ValueOption.isNone mv then ValueNone else // error already printed 122 | let fn vOpt s = 123 | match vOpt with 124 | | ValueNone -> ValueNone 125 | | ValueSome v -> Value.record_lookup (Value.label s) v 126 | let result = List.fold fn mv idx 127 | if ValueOption.isNone result then 128 | logError ll (sprintf "value of module %s does not have path .%s" (mRefStr m) (String.concat "." idx)) 129 | result 130 | | FParsec.CharParsers.Failure (msg, _, _) -> 131 | logError ll (sprintf "reference %s fails to parse: %s" vref msg) 132 | ValueNone 133 | 134 | let inline sepListOn sep l = 135 | match List.tryFindIndex ((=) sep) l with 136 | | None -> (l, []) 137 | | Some ix -> (List.take ix l, List.skip (1 + ix) l) 138 | 139 | // get app value will also apply application macros, consuming args. 140 | let getAppValue (ll:Loader) (vref : string) (args0 : string list) : (Value * string list) voption = 141 | match getValue ll vref with 142 | | ValueSome v0 -> 143 | // might need to expand application macros to interpret some args 144 | let rec macroLoop v args = 145 | match v with 146 | | (Value.Variant "macro" ((Value.Variant "prog" _) as p)) -> 147 | let (staticArgs,dynamicArgs) = sepListOn "--" args 148 | let sa = staticArgs |> List.map Value.ofString |> Value.ofList 149 | match eval p ll [sa] with 150 | | Some [p'] -> 151 | macroLoop p' dynamicArgs 152 | | _ -> 153 | logError ll (sprintf "application macro expansion failure in %s" vref) 154 | ValueNone 155 | | Value.Variant "prog" _ -> 156 | match stackArity v with 157 | | Arity(1,1) -> 158 | ValueSome(v, args) 159 | | _ -> 160 | logError ll (sprintf "%s has invalid arity" vref) 161 | ValueNone 162 | | _ -> 163 | logError ll (sprintf "%s not recognized as an application" vref) 164 | ValueNone 165 | macroLoop v0 args0 166 | | ValueNone -> ValueNone // error reported by getValue 167 | 168 | let getLoader (logger:IEffHandler) = 169 | match tryBootStrapLoader logger with 170 | | ValueSome ll -> ll 171 | | ValueNone -> 172 | logWarn logger "failed to bootstrap language-g0; using built-in" 173 | nonBootStrapLoader logger 174 | 175 | let extract (vref:string) : int = 176 | let ll = getLoader <| consoleErrLogger () 177 | match getValue ll vref with 178 | | ValueNone -> EXIT_FAIL 179 | | ValueSome v -> 180 | match v with 181 | | Value.Variant "data" (Value.BinaryArray b) -> 182 | let stdout = System.Console.OpenStandardOutput() 183 | stdout.Write(b,0,b.Length) 184 | EXIT_OKAY 185 | | Value.Variant "prog" _ -> 186 | match stackArity v with 187 | | Arity(0,0) -> 188 | let llw = extractionEffects ll 189 | match eval v llw [] with 190 | | Some _ -> 191 | EXIT_OKAY 192 | | None -> 193 | logError llw ("stream extraction terminated early") 194 | EXIT_FAIL 195 | | _ -> 196 | logError ll (sprintf "value %s has wrong arity for extract" vref) 197 | EXIT_FAIL 198 | | _ -> 199 | logError ll (sprintf "value %s cannot be extracted as binary" vref) 200 | EXIT_FAIL 201 | 202 | let print (vref:string) : int = 203 | let ll = getLoader <| consoleErrLogger () 204 | match getValue ll vref with 205 | | ValueSome v -> 206 | let s = Value.prettyPrint v 207 | System.Console.WriteLine(s) 208 | EXIT_OKAY 209 | | ValueNone -> 210 | EXIT_FAIL 211 | 212 | let check (vref:string) : int = 213 | let ll = getLoader <| consoleErrLogger() 214 | match getValue ll vref with 215 | | ValueSome _ -> EXIT_OKAY 216 | | ValueNone -> EXIT_FAIL 217 | 218 | let run (vref:string) (args : string list) : int = 219 | let ll = getLoader <| consoleErrLogger () 220 | match getAppValue ll vref args with 221 | | ValueNone -> EXIT_FAIL 222 | | ValueSome(p, args') -> 223 | let eff = runtimeEffects ll 224 | let pfn = eval p eff 225 | let rec loop st = 226 | let tx = withTX eff 227 | match pfn [st] with 228 | | None -> 229 | tx.Abort() 230 | // ideally, we'd track effects to know when to retry. 231 | // but this implementation is blind, so just wait and hope. 232 | System.Threading.Thread.Sleep(20) 233 | loop st 234 | | Some [st'] -> 235 | tx.Commit() 236 | match st' with 237 | | Value.Variant "step" _ -> 238 | loop st' 239 | | Value.Variant "halt" (Value.Int32 exit_code) -> 240 | exit_code 241 | | _ -> 242 | logErrorV ll (sprintf "program %s reached unrecognized state" vref) st' 243 | EXIT_FAIL 244 | | _ -> 245 | tx.Abort() 246 | logError ll (sprintf "program %s halted on arity error" vref) 247 | EXIT_FAIL 248 | try 249 | let v0 = args' |> List.map Value.ofString |> Value.ofList |> Value.variant "init" 250 | loop v0 251 | with 252 | | e -> 253 | logError ll (sprintf "program %s halted on exception %A" vref e) 254 | EXIT_FAIL 255 | 256 | let main' (args : string list) : int = 257 | match args with 258 | | ["--extract"; b] -> 259 | extract b 260 | | ("--run" :: p :: "--" :: args') -> 261 | run p args' 262 | | ["--run"; p] -> // no args to app 263 | run p [] 264 | | ["--version"] -> 265 | System.Console.WriteLine(ver) 266 | EXIT_OKAY 267 | | ["--help"] -> 268 | System.Console.WriteLine(helpMsg) 269 | EXIT_OKAY 270 | | ["--print"; v] -> 271 | print v 272 | | ["--check"; v] -> 273 | check v 274 | | args -> 275 | eprintfn "unrecognized command: %s" (String.concat " " args) 276 | eprintfn "try 'glas --help'" 277 | EXIT_FAIL 278 | 279 | let rewriteArgs (args : string list) : string list = 280 | match args with 281 | | (verb::args') when not (verb.StartsWith("-")) -> 282 | // this rewrite supports user-defined behavior 283 | // by making it easier to access and prettier 284 | let p = "glas-cli-" + verb + ".run" 285 | "--run" :: p :: "--" :: args' 286 | | _ -> args 287 | 288 | [] 289 | let main args = 290 | try 291 | prepare_GLAS_HOME () 292 | args |> Array.toList 293 | |> rewriteArgs 294 | |> main' 295 | with 296 | | e -> 297 | eprintfn "Unhandled exception: %A" e 298 | EXIT_FAIL 299 | 300 | -------------------------------------------------------------------------------- /fsrc/src/ProfStats.fs: -------------------------------------------------------------------------------- 1 | namespace Glas 2 | 3 | module Stats = 4 | 5 | type S = 6 | { 7 | Cnt : uint64 8 | Sum : float 9 | SSQ : float 10 | Min : float 11 | Max : float 12 | } 13 | 14 | let inline private sq x = (x * x) 15 | 16 | let s0 = 17 | { Cnt = 0UL 18 | ; Sum = 0.0 19 | ; SSQ = 0.0 20 | ; Min = System.Double.PositiveInfinity 21 | ; Max = System.Double.NegativeInfinity 22 | } 23 | 24 | let s1 f = 25 | { Cnt = 1UL 26 | ; Sum = f 27 | ; SSQ = sq f 28 | ; Min = f 29 | ; Max = f 30 | } 31 | 32 | let add s f = 33 | { Cnt = 1UL + s.Cnt 34 | ; Sum = s.Sum + f 35 | ; SSQ = s.SSQ + sq f 36 | ; Min = min (s.Min) f 37 | ; Max = max (s.Max) f 38 | } 39 | 40 | let join a b = 41 | { Cnt = (a.Cnt + b.Cnt) 42 | ; Sum = (a.Sum + b.Sum) 43 | ; SSQ = (a.SSQ + b.SSQ) 44 | ; Min = min (a.Min) (b.Min) 45 | ; Max = max (a.Max) (b.Max) 46 | } 47 | 48 | let average s = 49 | assert(s.Cnt > 0UL) 50 | (s.Sum / float(s.Cnt)) 51 | 52 | let variability s = 53 | // (SSQ - N*(AVG^2)) / (N - 1) 54 | // N*(Avg^2) = N*((Sum/N)^2) = N*Sum^2 / N^2 = Sum^2 / N 55 | // (SSQ - (Sum^2/N)) / (N - 1) 56 | assert(s.Cnt > 1UL) 57 | (s.SSQ - ((sq s.Sum) / float(s.Cnt))) / float(s.Cnt - 1UL) 58 | 59 | let sdev s = 60 | sqrt (variability s) 61 | 62 | // I'm thinking about keeping some extra detail on stats 63 | // via MultiStats. In this case, we'll aggregate a set of 64 | // stat 'buckets' then occasionally reduce the set to keep 65 | // memory use under control. This would make it easier to 66 | // produce bar graphs of some form. 67 | // 68 | // However, it also adds a lot of overhead when profiling. 69 | // 70 | 71 | [] 72 | type MultiStat = 73 | { BCT : int 74 | ; BS : S list 75 | } 76 | 77 | let private heuristicJoinCost a b = 78 | // weighted cost of moving each box to joined value. 79 | let joinAvg = (a.Sum + b.Sum) / float(a.Cnt + b.Cnt) 80 | let joinMax = max (a.Max) (b.Max) 81 | let joinMin = min (a.Min) (b.Min) 82 | let inline dist s = 83 | abs(joinAvg - (average s)) + 84 | (joinMax - s.Max) + 85 | (s.Min - joinMin) 86 | let inline moveCost s = 87 | float(s.Cnt) * (dist s) 88 | moveCost a + moveCost b 89 | 90 | // will reduce every sequence of 5 items to 4 items. 91 | let rec private reduceFifths acc rs = 92 | match rs with 93 | | (r1::r2::r3::r4::r5::rs') -> // 5 or more items, reduce to 4 items 94 | let c12 = heuristicJoinCost r1 r2 95 | let c23 = heuristicJoinCost r2 r3 96 | let c34 = heuristicJoinCost r3 r4 97 | let c45 = heuristicJoinCost r4 r5 98 | let cMin = min (min c12 c23) (min c34 c45) 99 | let acc' = 100 | if (cMin = c12) then (r5::r4::r3::(join r2 r1)::acc) else 101 | if (cMin = c23) then (r5::r4::(join r3 r2)::r1::acc) else 102 | if (cMin = c34) then (r5::(join r4 r3)::r2::r1::acc) else 103 | assert(cMin = c45); ((join r5 r4)::r3::r2::r1::acc) 104 | reduceFifths acc' rs' 105 | | (r::rs') -> // fewer than 5 items, is not reduced 106 | reduceFifths (r::acc) rs' 107 | | [] -> acc // preserve reverse order. 108 | 109 | // heuristic exponential reduction of stats buckets. 110 | // This reduces ~36% of items (9 per 25). 111 | let reduce l = 112 | l |> List.filter (fun s -> (s.Cnt > 0UL)) 113 | // sort so items with similar averages are merged. 114 | |> List.sortBy average 115 | // use two 5=>4 reduction passes to avoid aliasing. 116 | |> reduceFifths [] 117 | |> reduceFifths [] 118 | 119 | // reduce to a specific number of buckets 120 | let rec reduceTo n l = 121 | if (List.length l > n) 122 | then reduceTo n (reduce l) 123 | else l 124 | 125 | let autoReduce ms = 126 | if (100 > ms.BCT) then ms else 127 | let bs' = reduce ms.BS 128 | { BS = bs' 129 | ; BCT = List.length bs' 130 | } 131 | 132 | let addVal ms v = 133 | autoReduce { BCT = 1 + (ms.BCT); BS = (s1 v)::(ms.BS) } 134 | -------------------------------------------------------------------------------- /fsrc/src/README.md: -------------------------------------------------------------------------------- 1 | # Glas Bootstrap 2 | 3 | This implementation of the `glas` command-line tool is written in F#, using dotnet core with minimal external dependencies. This is intended as a bootstrap implementation and experimentation platform for early development of Glas, until Glas is ready to fully self host. 4 | 5 | Most relevant material: 6 | 7 | * [Glas CLI](../docs/GlasCLI.md) - design of the command line executable 8 | * [Glas Object](../docs/GlasObject.md) - a serialized data representation, guides in-memory data representation and describes a useful pattern for finger-tree ropes. 9 | * [The g0 language](../glas-src/language-g0/README.md) - the bootstrap language, requires implementation within 10 | 11 | This bootstrap implementation currently doesn't bother with stowage systems or most effects for command line apps. 12 | -------------------------------------------------------------------------------- /fsrc/src/TextTree.fs: -------------------------------------------------------------------------------- 1 | namespace Glas 2 | 3 | 4 | // Lightweight structured data format used in sources.txt. 5 | // 6 | // hdr1 data is remainder of line 7 | // attrib1 data is remainder of line 8 | // attrib2 data is remainder of line 9 | // subattrib data is remainder of line 10 | // hdr2 data is remainder of line 11 | // ... 12 | // 13 | // Both \r and \n are recognized as line terminals. 14 | // Blank lines or lines starting with '#' are skipped. 15 | // Indentation via spaces. Data is trimmed of spaces. 16 | // 17 | // Note: this has limited support for multi-line text and 18 | // \rem remark escapes. 19 | module TextTree = 20 | 21 | type TTEnt = 22 | { Label : string 23 | ; Data : string 24 | ; Attrib : TT 25 | } 26 | and TT = TTEnt list 27 | 28 | type Line = (struct(int * string * string)) // indent, header, data 29 | 30 | let tryParseLine (s : string) : Line voption = 31 | let mutable ix = 0 32 | while((ix < s.Length) && (s[ix] = ' ')) do 33 | ix <- ix + 1 34 | if(ix = s.Length) then ValueNone else 35 | let indent = ix 36 | while((ix < s.Length) && (s[ix] <> ' ')) do 37 | ix <- ix + 1 38 | let label = s.Substring(indent, ix-indent) 39 | let data1 = if (ix = s.Length) then "" else s.Substring(ix + 1) 40 | let data = if (label = "\\") then data1 else data1.Trim(' ') 41 | ValueSome(struct(indent, label, data)) 42 | 43 | let toLines (s : string) : Line list = 44 | let sepBy = [| '\r'; '\n' |] 45 | s.Split(sepBy, System.StringSplitOptions.None) 46 | |> Seq.map tryParseLine 47 | |> Seq.collect ValueOption.toList 48 | |> Seq.toList 49 | 50 | let rec private collectMultiLineText (acc : string list) (indent : int) (ll : Line list) : struct(string * Line list) = 51 | match ll with 52 | | (struct(ix, "\\", data)::ll') when (ix = indent) -> 53 | collectMultiLineText (data::acc) indent ll' 54 | | _ -> 55 | let result = acc |> List.rev |> String.concat "\n" 56 | struct(result, ll) 57 | 58 | let private tryMultiLineText (data0 : string) (indent : int) (ll : Line list) : struct(string * Line list) = 59 | match ll with 60 | | (struct(ix, "\\", data)::ll') when ((ix > indent) && (data0 = "")) -> 61 | // all lines must have same indentation 62 | collectMultiLineText [data] ix ll' 63 | | _ -> struct(data0, ll) 64 | 65 | let rec private parseEntList (acc : TTEnt list) (indent : int) (ll : Line list) : struct(TTEnt list * Line list) = 66 | match ll with 67 | | (struct(ix, lbl, data0)::ll0) when (ix >= indent) -> 68 | let struct(data, llAttrib) = tryMultiLineText data0 ix ll0 69 | let struct(attribs, ll') = parseEntList [] (ix + 1) llAttrib 70 | let ent = { Label = lbl; Data = data; Attrib = attribs } 71 | parseEntList (ent::acc) indent ll' 72 | | _ -> struct((List.rev acc), ll) 73 | 74 | let parseEnts (s : string) : TTEnt list = 75 | let struct(ents, llRem) = parseEntList [] 0 (toLines s) 76 | assert(List.isEmpty llRem) 77 | ents 78 | 79 | -------------------------------------------------------------------------------- /fsrc/test/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ 3 | 4 | 5 | -------------------------------------------------------------------------------- /fsrc/test/Glas.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net7.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /fsrc/test/Main.fs: -------------------------------------------------------------------------------- 1 | module Glas.Tests 2 | open Expecto 3 | 4 | [] 5 | let main argv = 6 | Tests.runTestsInAssembly defaultConfig argv 7 | -------------------------------------------------------------------------------- /fsrc/test/RandVal.fs: -------------------------------------------------------------------------------- 1 | module RandVal 2 | 3 | open Glas 4 | open Value 5 | 6 | let valDepth = 8 7 | 8 | // global rng is fine for testing 9 | let rng = System.Random() 10 | 11 | let randomBytes ct = 12 | let arr = Array.zeroCreate ct 13 | rng.NextBytes(arr) 14 | arr 15 | 16 | let randomRange m n = 17 | (min m n) + (rng.Next() % (1 + (abs (m - n)))) 18 | 19 | let labels = Array.ofList [ 20 | // grabbed from randomwordgenerator.com 21 | "banana"; "market"; "entry"; "empire"; "feast"; "watch"; 22 | "ride"; "quote"; "article"; "snake"; "flatware"; "particle"; 23 | "humanity"; "means"; "sunrise"; "construct"; "burn"; "branch"; 24 | "instrument"; "candle"; "fear"; "introduction"; "beautiful"; "record"; 25 | "boot"; "subject"; "embrace"; "roar"; "designer"; "give"; 26 | "man"; "hope"; "applied"; "jurisdiction"; "tender"; "variable"; 27 | "snatch"; "final"; "dialogue"; "trait"; "echo"; "presence"; 28 | "ticket"; "stake"; "mountain"; "hide"; "steep"; "beef"; 29 | "soldier"; "sympathetic"; "salon"; "operational"; "overwhelm"; "mosque"; 30 | "treatment"; "rubbish"; "reservoir"; "appeal"; "eye"; "war"; 31 | "monkey"; "classify"; "expansion"; "outside"; "food"; "training"; 32 | "freedom"; "seal"; "outfit"; "banish"; "pioneer"; "chalk"; 33 | "treatment"; "reception"; "voice"; "recording"; "veteran"; "different"; 34 | "patient"; "swim"; "snub"; "professor"; "lease"; "digress"; 35 | "rotation"; "color-blind"; "cabin"; "depression"; "even"; "regulation"; 36 | "adjust"; "dorm"; "counter"; "free"; "improvement"; "accept"; 37 | "audience"; "determine"; "solution"; "organization"; "celebration"; "order"; 38 | ] 39 | 40 | let randomLabel () = 41 | let ix = rng.Next() % labels.Length 42 | labels[ix] 43 | 44 | let mkRandomInt () = 45 | let buffer = Array.zeroCreate 8 46 | rng.NextBytes(buffer) 47 | System.BitConverter.ToInt64(buffer) 48 | 49 | let mkRandomIntVal () = 50 | Value.ofInt (mkRandomInt()) 51 | 52 | let rec mkRandomVal (d : int) = 53 | let sel = rng.Next () % 100 54 | if ((sel < 20) || (d < 1)) then 55 | mkRandomIntVal() 56 | elif (sel < 30) then 57 | Value.unit 58 | elif (sel < 40) then 59 | let a = mkRandomVal (d - 1) 60 | let b = mkRandomVal (d - 1) 61 | Value.pair a b 62 | elif (sel < 45) then 63 | Value.left (mkRandomVal (d - 1)) 64 | elif (sel < 50) then 65 | Value.right (mkRandomVal (d - 1)) 66 | elif (sel < 60) then 67 | Value.variant (randomLabel()) (mkRandomVal (d - 1)) 68 | elif (sel < 80) then 69 | Value.ofTerm (mkRandomRope (d - 1)) 70 | else 71 | mkRandomRecord (d - 1) (randomRange 3 9) 72 | 73 | and mkRandomRope (d : int) : Term = 74 | let sel = rng.Next() % 40 75 | if((sel < 10) || (d < 1)) then 76 | Binary(ImmArray.UnsafeOfArray(randomBytes (randomRange 12 24))) 77 | elif(sel < 20) then 78 | let arr = Array.init (randomRange 6 12) (fun _ -> mkRandomVal (d - 1)) 79 | Array(ImmArray.UnsafeOfArray(arr)) 80 | else 81 | let ct = randomRange 3 9 82 | let lFrags = List.init ct (fun _ -> mkRandomRope (d - 1)) 83 | List.fold (Rope.append) Leaf lFrags 84 | 85 | and mkRandomRecord d ct = 86 | let ks = List.init ct (fun _ -> randomLabel ()) 87 | let vs = List.init ct (fun _ -> mkRandomVal d) 88 | Value.asRecord ks vs 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /fsrc/test/TestRope.fs: -------------------------------------------------------------------------------- 1 | 2 | module Glas.FTList 3 | 4 | open Expecto 5 | open Glas 6 | open RandVal 7 | 8 | let randomList m n = 9 | List.init (randomRange m n) (fun _ -> mkRandomIntVal ()) 10 | 11 | let ropeOfList l = 12 | Value.Rope.ofSeq l 13 | 14 | let ropeToList t = 15 | Value.Rope.foldBack (fun v l -> (v::l)) t [] 16 | 17 | let ropeToArray t = 18 | match Value.ofTerm t with 19 | | Value.ValueArray a -> a 20 | | _ -> failtest "could not convert term to array" 21 | 22 | // goal here is to produce a messy rope value 23 | let randomRope m n = 24 | let mutable t = Value.Leaf 25 | while((Value.Rope.len t) <= uint64 n) do 26 | // add content 27 | let l = randomList 10 20 28 | let r = randomList 10 20 29 | t <- Value.Rope.append (ropeOfList l) (Value.Rope.append t (ropeOfList r)) 30 | for _ in 1 .. 4 do 31 | // cut and switch 32 | let ixCut = uint64 <| randomRange 0 (int (Value.Rope.len t)) 33 | let tl = Value.Rope.take ixCut t 34 | let tr = Value.Rope.drop ixCut t 35 | t <- Value.Rope.append tr tl 36 | Value.Rope.take (uint64 (randomRange m n)) t 37 | 38 | 39 | [] 40 | let tests = 41 | testList "rope tests" [ 42 | testCase "empty" <| fun () -> 43 | Expect.equal (ropeToList Value.Rope.empty) (List.empty) "empty is as empty does" 44 | 45 | testCase "list conversions" <| fun () -> 46 | let l = randomList 200 400 47 | Expect.equal (ropeToList (ropeOfList l)) l "ofList toList" 48 | let f = randomRope 200 400 49 | Expect.equal f (ropeOfList (ropeToList f)) "toList ofList" 50 | 51 | testCase "map" <| fun () -> 52 | let l = randomList 200 400 53 | let fn v = Value.pair Value.unit (Value.pair v Value.unit) 54 | Expect.equal (ropeToList (Value.Rope.map fn (ropeOfList l))) (List.map fn l) "equality over map" 55 | 56 | 57 | testCase "length" <| fun () -> 58 | let l = List.map (fun i -> randomList i (2 * i)) [0; 5; 10; 20; 100; 200; 400; 800; 1600] 59 | let lz = List.map (List.length) l 60 | let tlz = List.map (ropeOfList >> Value.Rope.len >> int) l 61 | Expect.equal tlz lz "equal list lengths" 62 | 63 | let f = randomRope 2000 4000 64 | Expect.equal (List.length (ropeToList f)) (int (Value.Rope.len f)) "equal lengths irregular" 65 | 66 | testCase "split" <| fun () -> 67 | let len = 1000 68 | let f = randomRope len len 69 | let l = ropeToList f 70 | for _ in 1 .. 100 do 71 | let xz = randomRange 0 len 72 | let (ll, lr) = List.splitAt xz l 73 | let (fl, fr) = (Value.Rope.take (uint64 xz) f, Value.Rope.drop (uint64 xz) f) 74 | Expect.equal (ropeToList fl) ll "left equal" 75 | Expect.equal (ropeToList fr) lr "right equal" 76 | 77 | testCase "append" <| fun () -> 78 | for _ in 1 .. 100 do 79 | let l1 = randomRope 0 1000 80 | let l2 = randomRope 0 1000 81 | Expect.equal (ropeToList (Value.Rope.append l1 l2)) 82 | (List.append (ropeToList l1) (ropeToList l2)) "equal append" 83 | 84 | testCase "arrays" <| fun () -> 85 | let t = randomRope 1000 2000 86 | let a = ropeToArray t 87 | Expect.equal (a.Length) (int (Value.Rope.len t)) "length preserved" 88 | Expect.equal (Value.Rope.ofSeq a) t "round trip through array" 89 | Expect.equal (List.ofArray a) (ropeToList t) "indirect through array" 90 | 91 | testCase "seqs" <| fun () -> 92 | let l = randomList 1000 2000 93 | Expect.equal (List.ofSeq (Value.Rope.toSeq (ropeOfList l))) l "equal via toSeq" 94 | Expect.equal (ropeToList (Value.Rope.ofSeq (List.toSeq l))) l "equal via ofSeq" 95 | 96 | testCase "equal" <| fun () -> 97 | let t = randomRope 1000 2000 98 | Expect.equal t t "eq" 99 | Expect.equal t (Value.Rope.drop 0UL t) "equal drop nothing" 100 | Expect.equal t (Value.Rope.take (Value.Rope.len t) t) "equal take everything" 101 | Expect.notEqual t (Value.Rope.drop 1UL t) "not eq drop" 102 | Expect.notEqual t (Value.Rope.cons (Value.unit) t) "not eq cons" 103 | Expect.notEqual t (Value.Rope.snoc t (Value.unit)) "not eq snoc" 104 | 105 | testCase "index" <| fun () -> 106 | let t = randomRope 1000 2000 107 | let a = ropeToArray t 108 | Expect.equal (int (Value.Rope.len t)) (a.Length) "length preserved" 109 | for ix in 0 .. (a.Length - 1) do 110 | Expect.equal (Value.Rope.item (uint64 ix) t) (Array.item ix a) "equal list item" 111 | ] 112 | -------------------------------------------------------------------------------- /fsrc/test/TestVal.fs: -------------------------------------------------------------------------------- 1 | 2 | module Glas.TestVal 3 | 4 | open Expecto 5 | open RandVal 6 | open Glas.Value 7 | 8 | let ofBitList l = List.foldBack consStemBit l unit 9 | 10 | 11 | [] 12 | let tests = 13 | testList "Glas values" [ 14 | testCase "unit is unit" <| fun () -> 15 | Expect.isTrue (isUnit unit) "isUnit unit" 16 | 17 | testCase "unit equality" <| fun () -> 18 | Expect.equal unit unit "unit eq unit" 19 | Expect.notEqual unit (left unit) "unit not-eq false" 20 | Expect.notEqual unit (right unit) "unit not-eq true" 21 | Expect.notEqual unit (pair unit unit) "unit not-eq pair" 22 | 23 | testCase "stem equality" <| fun () -> 24 | let t = right unit 25 | let f = left unit 26 | Expect.equal t t "eq t t" 27 | Expect.equal f f "eq f f" 28 | Expect.notEqual t f "eq t f" 29 | Expect.notEqual f t "eq f t" 30 | 31 | testCase "pair equality" <| fun () -> 32 | let t = right unit 33 | let f = left unit 34 | Expect.equal (pair t t) (pair t t) "eq pair 1" 35 | Expect.equal (pair f t) (pair f t) "eq pair 2" 36 | Expect.equal (pair t f) (pair t f) "eq pair 3" 37 | Expect.equal (pair f f) (pair f f) "eq pair 4" 38 | Expect.notEqual (pair t t) (pair t f) "neq pair 1" 39 | Expect.notEqual (pair t t) (pair f t) "neq pair 2" 40 | Expect.notEqual (pair t t) (pair f f) "neq pair 3" 41 | 42 | testCase "integer round trip" <| fun () -> 43 | let testVal n = 44 | match ofInt n with 45 | | Int64(n') -> 46 | Expect.equal n n' "equal round trop int through value" 47 | | _ -> failtestf "int round trip failed for %A" n 48 | let lForceTests = [System.Int64.MinValue; -4L; -3L; -2L; -1L; 49 | 0L; 1L; 2L; 3L; 4L; System.Int64.MaxValue] 50 | for n in lForceTests do 51 | testVal n 52 | for _ in 1 .. 1000 do 53 | testVal (mkRandomInt ()) 54 | 55 | testCase "deep match pair" <| fun () -> 56 | let a = pair (ofNat 1234UL) (ofNat 2345UL) 57 | let b = pair (ofNat 4567UL) (ofNat 5678UL) 58 | match (pair a b) with 59 | | P (P (Nat64 w, Nat64 x), P (Nat64 y, Nat64 z)) -> 60 | Expect.equal w 1234UL "match pair ll" 61 | Expect.equal x 2345UL "match pair lr" 62 | Expect.equal y 4567UL "match pair rl" 63 | Expect.equal z 5678UL "match pair rr" 64 | | _ -> failtest "failed to match" 65 | 66 | testCase "record lookup" <| fun () -> 67 | // singleton lookup 68 | match record_lookup (ofBitList [false; true]) (left (right (ofByte 27uy))) with 69 | | ValueSome (Byte 27uy) -> () 70 | | x -> failtest (sprintf "singleton record lookup -> %A" x) 71 | 72 | match record_lookup (ofBitList [false; true]) (left (left (ofByte 27uy))) with 73 | | ValueNone -> () 74 | | x -> failtest "singleton lookup should fail" 75 | 76 | 77 | // multi-value lookup 78 | let r = pair (pair (ofByte 27uy) (ofByte 42uy)) (pair (ofByte 108uy) (ofByte 22uy)) 79 | match record_lookup (ofBitList [false; false]) r with 80 | | ValueSome (Byte 27uy) -> () 81 | | x -> failtest (sprintf "saturated lookup 00 -> %A" x) 82 | match record_lookup (ofBitList [false; true]) r with 83 | | ValueSome (Byte 42uy) -> () 84 | | x -> failtest (sprintf "saturated lookup 01 -> %A" x) 85 | match record_lookup (ofBitList [true; false]) r with 86 | | ValueSome (Byte 108uy) -> () 87 | | x -> failtest (sprintf "saturated lookup 10 -> %A" x) 88 | match record_lookup (ofBitList [true; true]) r with 89 | | ValueSome (Byte 22uy) -> () 90 | | x -> failtest (sprintf "saturated lookup 11 -> %A" x) 91 | 92 | testCase "record delete" <| fun () -> 93 | let r = left (right (pair unit unit)) 94 | Expect.equal r (record_delete (ofBitList [true]) r) "delete non-present field" 95 | Expect.isTrue (isUnit (record_delete (ofBitList []) r)) "delete empty path is unit" 96 | Expect.isTrue (isUnit (record_delete (ofBitList [false]) r)) "delete l is unit" 97 | Expect.isTrue (isUnit (record_delete (ofBitList [false; true]) r)) "delete lr is unit" 98 | Expect.equal (left (right (right unit))) (record_delete (ofBitList [false; true; false]) r) "delete lrl is lrr" 99 | Expect.equal (left (right (left unit))) (record_delete (ofBitList [false; true; true]) r) "delete lrr is lrl" 100 | Expect.equal (left (right (right unit))) (record_delete (ofBitList [false; true; false; true]) r) "delete lrlr is lrr" 101 | 102 | testCase "record insert" <| fun () -> 103 | let checkElem k m r = 104 | match (record_lookup (ofByte k) r), (Map.tryFind k m) with 105 | | ValueSome (Nat64 n), Some n' -> Expect.equal n n' "equal lookup" 106 | | ValueNone, None -> () 107 | | _ -> failtest "mismatch" 108 | let mutable m = Map.empty 109 | let mutable r = unit 110 | for x in 1UL .. 4000UL do 111 | let k = uint8 (rng.Next()) 112 | checkElem k m r 113 | m <- Map.add k x m 114 | r <- record_insert (ofByte k) (ofNat x) r 115 | checkElem k m r 116 | 117 | testCase "record insert delete" <| fun () -> 118 | let checkElem k m r = 119 | match (record_lookup (ofByte k) r), (Map.tryFind k m) with 120 | | ValueSome (Nat64 n), Some n' -> Expect.equal n n' "equal lookup" 121 | | ValueNone, None -> () 122 | | _ -> failtest "mismatch" 123 | let mutable m = Map.empty 124 | let mutable r = unit 125 | for x in 1UL .. 3000UL do 126 | // add a few items. 127 | let addCt = randomRange 5 10 128 | for _ in 1 .. addCt do 129 | let k = uint8 (rng.Next()) 130 | checkElem k m r 131 | m <- Map.add k x m 132 | r <- record_insert (ofByte k) (ofNat x) r 133 | checkElem k m r 134 | // remove a few items. 135 | let delCt = randomRange 10 20 136 | for _ in 1 .. delCt do 137 | let k = uint8 (rng.Next()) 138 | checkElem k m r 139 | m <- Map.remove k m 140 | r <- record_delete (ofByte k) r 141 | checkElem k m r 142 | 143 | testCase "asRecord" <| fun () -> 144 | let v = asRecord ["fe"; "fi"; "fo"; "fum"] (List.map ofByte [1uy .. 4uy]) 145 | match v with 146 | | Record ["fe"; "fi"; "fo"; "foo"] 147 | ([ValueSome (Byte 1uy); ValueSome (Byte 2uy); ValueSome (Byte 3uy); ValueNone] 148 | ,Variant "fum" (Byte 4uy)) -> () 149 | | _ -> failwith "record match failed" 150 | Expect.isTrue (isRecord v) "v is a labeled record" 151 | 152 | 153 | testCase "recordSeq" <| fun () -> 154 | for _ in 1 .. 100 do 155 | let mutable m = Map.empty // ground truth rep 156 | let mutable v = unit // tested representation 157 | for ix in 1uy .. 100uy do 158 | let k = randomLabel () 159 | m <- Map.add k ix m 160 | v <- record_insert (label k) (ofByte ix) v 161 | let convert kvp = 162 | match kvp with 163 | | (k, Byte ix) -> (k,ix) 164 | | _ -> invalidArg (nameof kvp) "not a valid kvp" 165 | //printf "kvps=%A\n" m 166 | Expect.equal (v |> recordSeq |> Seq.map convert |> List.ofSeq) (Map.toList m) "equal maps" 167 | 168 | testCase "string round trip" <| fun () -> 169 | for _ in 1 .. 100 do 170 | let s = randomLabel () 171 | let v = ofString s 172 | let s' = toString v 173 | Expect.equal s s' "equal strings" 174 | 175 | // testing the pretty printer... 176 | testCase "printing ugly multi-line string" <| fun () -> 177 | let s0 = "\n\"Hello, world!\"\a\t\v\r\x1b\x7f" 178 | let v = ofString s0 179 | let spp = prettyPrint v 180 | let se = "\"\"\"\n \n \"Hello, world!\"\a\t\v\r\x1b\x7f\n\"\"\"" 181 | Expect.equal spp se "pretty printing multi-line string" 182 | 183 | testCase "printing numbers" <| fun () -> 184 | Expect.equal "23" (prettyPrint (ofNat 23UL)) "print numbers" 185 | Expect.equal "42" (prettyPrint (ofNat 42UL)) "printing more nats" 186 | Expect.equal "-108" (prettyPrint (ofInt -108L)) "printing integers" 187 | 188 | testCase "printing basic pairs and sums data" <| fun () -> 189 | Expect.equal "((), -1)" (prettyPrint (pair unit (left unit))) "pulu" 190 | Expect.equal "~1[()]" (prettyPrint (right (pair unit unit))) "rpuu" 191 | Expect.equal "~10((), 1)" (prettyPrint (right (left (pair unit (right unit))))) "rlpuru" 192 | 193 | testCase "printing variants" <| fun () -> 194 | Expect.equal "foo" (prettyPrint (symbol "foo")) "foo" 195 | Expect.equal "text:msg:\"hello, world!\"" (prettyPrint (variant "text" (variant "msg" (ofString "hello, world!")))) "text" 196 | 197 | testCase "printing lists" <| fun () -> 198 | let l = [1;2;127;128;254;255] |> List.map (uint64 >> Value.ofNat) |> Value.Rope.ofSeq |> Value.ofTerm 199 | Expect.equal "[1, 2, 127, 128, 254, 255]" (prettyPrint l) "list of numbers" 200 | Expect.equal "\"Ok\"" (prettyPrint (ofBinary [|byte 'O'; byte 'k'|])) "looks like a string" 201 | 202 | testCase "printing records" <| fun () -> 203 | let v = asRecord ["foo"; "bar"; "baz"; "qux"; "gort"] (List.map (uint64 >> Value.ofNat) [1 .. 5]) 204 | let ppv = "(bar:2, baz:3, foo:1, gort:5, qux:4)" 205 | Expect.equal ppv (prettyPrint v) "record" 206 | ] 207 | 208 | -------------------------------------------------------------------------------- /old-glas-src/README.md: -------------------------------------------------------------------------------- 1 | # Glas Modules 2 | 3 | This folder contains an initial set of global glas mdoules intended to support bootstrap and early system development. The global module search path for glas must be configured to include this folder. 4 | 5 | To configure the global module search path, set the GLAS_HOME environment variable (or use the default such as `~/.config/glas`) then edit GLAS_HOME/sources.txt to contain this folder. For example: 6 | 7 | # initial sources.txt 8 | # edit path based on where you're developing 9 | dir /home/dmbarbour/projects/glas/glas-src 10 | 11 | I hope to eventually shift glas systems to mostly depend on networked repositories. But for now, the focus is filesystem-local module representation. 12 | -------------------------------------------------------------------------------- /old-glas-src/bits-basics/README.md: -------------------------------------------------------------------------------- 1 | # Basic Increment, Decrement, and Counting Loops 2 | 3 | These are separated from other modules to avoid cyclic module dependencies between integer and bitstring operations. -------------------------------------------------------------------------------- /old-glas-src/bits-basics/public.g0: -------------------------------------------------------------------------------- 1 | # Separated to avoid a cyclic dependency. 2 | # Re-exported from 'bits' or 'int' modules, 3 | # albeit with localized names. 4 | 5 | open prims 6 | 7 | # type annotation that top argument on stack is a bitstring. 8 | # currently is placeholder awaiting development of type system 9 | prog type-bits [] # todo 10 | prog type-int [type-bits] 11 | prog type-nat [ 12 | type-int 13 | # todo: also express int is positive 14 | [0b0 get][halt-type-error]try-then 15 | ] 16 | 17 | prog bits-prefix [[type-bits] dip 0 swap put] 18 | prog bits-suffix [swap bits-prefix] 19 | prog bits-pop [type-bits [0b0 get 0b0][0b1 get 0b1]try-else swap] 20 | data bits-empty [0b] 21 | prog p-bits-each [[bits-pop] swap p-dip p-while-do [bits-empty eq] p-suffix] 22 | macro bits-each [p-bits-each] 23 | 24 | prog bits-reverse-append-impl [[bits-prefix] bits-each] 25 | prog bits-reverse-append [[bits-reverse-append-impl] 'bits-reverse-append p-accel-opt apply] 26 | prog bits-reverse [[bits-empty] dip bits-reverse-append] 27 | 28 | prog test-bits-reverse-append [ 29 | copy3 [bits-reverse-append-impl] dip eq 30 | [bits-reverse-append] dip eq 31 | ] 32 | assert [0b1100 0b0101 0b10101100 test-bits-reverse-append] 33 | assert [0b1101 0b0101001101 0b10110010101101 test-bits-reverse-append] 34 | 35 | assert [ 36 | # 0=>0, 1=>8, 2=>4, 3=>C, 4=>2, 5=>A, 6=>6, 7=>E, 37 | # 8=>1, 9=>9, A=>5, B=>D, C=>3, D=>B, E=>7, F=>F 38 | 39 | 0xCDE 0x0123456789ABCDEF0123456789ABCDEF01234 40 | 0x2C480F7B3D591E6A2C480F7B3D591E6A2C480CDE 41 | test-bits-reverse-append 42 | ] 43 | 44 | prog bits-negate-impl [ 45 | type-bits 46 | bits-empty swap 47 | [bits-empty eq] 48 | [ [0b0 get 0b1] [0b1 get 0b0] try-else 49 | swap [bits-prefix] dip 50 | ] until-do 51 | bits-reverse 52 | ] 53 | prog bits-negate [[bits-negate-impl] 'bits-negate p-accel-opt apply] 54 | 55 | prog test-bits-negate [ 56 | copy2 [bits-negate-impl] dip eq 57 | [bits-negate] dip eq 58 | ] 59 | assert [0b0011010 0b1100101 test-bits-negate] 60 | assert [0b011000 0b100111 test-bits-negate] 61 | 62 | assert [ 63 | # 0<=>F, 1<=>E, 2<=>D, 3<=>C, 4<=>B, 5<=>A, 6<=>9, 7<=>8 64 | 0x0123456789ABCDEF0123456789ABCDEF01234 65 | 0xFEDCBA9876543210FEDCBA9876543210FEDCB 66 | test-bits-negate 67 | ] 68 | 69 | 70 | # increment a bitstring reversed integer 71 | prog revint-increment [ 72 | bits-empty swap 73 | [0b1 get][[0b0 bits-prefix] dip] while-do 74 | # now we have either: 0b, 0b0, 0b0xx 75 | [0b eq 0b1] # 0 => 1 76 | [0b0 get 77 | [0b eq 0b] # -1 => 0 78 | [0b1 bits-prefix] # add without carry 79 | try-else 80 | ] 81 | try-else 82 | bits-suffix 83 | ] 84 | 85 | prog int-increment-impl [type-int bits-reverse revint-increment bits-reverse] 86 | 87 | # int-increment Int -- Int 88 | # implements increment via bitstring manipulation 89 | prog int-increment [[int-increment-impl] 'int-increment p-accel-opt apply] 90 | 91 | prog test-int-increment [ 92 | copy2 [int-increment] dip eq 93 | [int-increment-impl] dip eq 94 | ] 95 | assert [ 4 5 test-int-increment] 96 | assert [ 3 4 test-int-increment] 97 | assert [ 2 3 test-int-increment] 98 | assert [ 1 2 test-int-increment] 99 | assert [ 0 1 test-int-increment] 100 | assert [-1 0 test-int-increment] 101 | assert [-2 -1 test-int-increment] 102 | assert [-3 -2 test-int-increment] 103 | assert [-4 -3 test-int-increment] 104 | assert [0x0123456789ABCDEF0123456789ABCDEF 105 | 0x0123456789ABCDEF0123456789ABCDF0 106 | test-int-increment] 107 | assert [ 0xEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 108 | 0xF000000000000000000000000000000000 109 | test-int-increment] 110 | assert [ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 111 | 0x0000000000000000000000000000000000 0b1 bits-prefix 112 | test-int-increment] 113 | assert [ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 0b0 bits-prefix 114 | 0x0000000000000000000000000000000000 115 | test-int-increment] 116 | 117 | prog revint-decrement [ 118 | [bits-empty] dip 119 | [0b0 get] [[0b1 bits-prefix] dip] while-do 120 | [0b eq 0b0] # 0 => -1 121 | [0b1 get 122 | [0b eq 0b] # 1 => 0 123 | [0b0 bits-prefix] # decrement no carry 124 | try-else 125 | ] 126 | try-else 127 | bits-suffix 128 | ] 129 | 130 | prog int-decrement-impl [type-int bits-reverse revint-decrement bits-reverse] 131 | 132 | # int-decrement Int -- Int 133 | # implements decrement via bitstring manipulation 134 | prog int-decrement [[int-decrement-impl] 'int-decrement p-accel-opt apply] 135 | 136 | prog test-int-decrement [ 137 | copy2 [int-decrement-impl] dip eq 138 | [int-decrement] dip eq 139 | ] 140 | assert [ 4 3 test-int-decrement] 141 | assert [ 3 2 test-int-decrement] 142 | assert [ 2 1 test-int-decrement] 143 | assert [ 1 0 test-int-decrement] 144 | assert [ 0 -1 test-int-decrement] 145 | assert [-1 -2 test-int-decrement] 146 | assert [-2 -3 test-int-decrement] 147 | assert [-3 -4 test-int-decrement] 148 | assert [-4 -5 test-int-decrement] 149 | 150 | assert [0x0123456789ABCDEF0123456789ABCDF0 151 | 0x0123456789ABCDEF0123456789ABCDEF 152 | test-int-decrement] 153 | assert [ 0xF000000000000000000000000000000000 154 | 0xEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 155 | test-int-decrement] 156 | assert [ 0x0000000000000000000000000000000000 0b1 bits-prefix 157 | 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 158 | test-int-decrement] 159 | assert [ 0x0000000000000000000000000000000000 160 | 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 0b0 bits-prefix 161 | test-int-decrement] 162 | 163 | prog nat-decrement [type-nat [0 eq][fail][int-decrement] try-then-else] 164 | prog nat-increment [type-nat int-increment] 165 | 166 | # N [Op] repeat 167 | # will repeat [Op] N times. 168 | # N may be dynamic and is required to be a natural number. 169 | prog p-repeat [ 170 | # until N is zero, decrement then run Op 171 | [0 eq] swap p-dip [int-decrement] p-prefix p-until-do 172 | 173 | # annotate or assert that N is a natural number 174 | [type-nat] p-prefix 175 | ] 176 | macro repeat [p-repeat] 177 | assert [-9 20 [int-increment] repeat 11 eq] 178 | 179 | # N [Op] static-repeat 180 | # Equivalent to writing subprogram `Op` N times 181 | # e.g. 3 [Bork] static-repeat => Bork Bork Bork 182 | prog p-static-repeat [ swap [p-nop] dip [[copy] dip p-seq] repeat [drop] dip ] 183 | macro static-repeat [p-static-repeat] 184 | assert [7 3 [copy int-increment] static-repeat 10 eq 9 eq 8 eq 7 eq] 185 | 186 | -------------------------------------------------------------------------------- /old-glas-src/bits/README.md: -------------------------------------------------------------------------------- 1 | # Bitstring Manipulations 2 | 3 | A bitstring in glas is encoded as a binary tree where every node has one or zero children. Each edge is labeled 0 or 1, representing a left or right child. A bitstring is encoded into the path through these edges. 4 | 5 | / 0 Tree represents bitstring 6 | \ 1 0b011010001 7 | \ 1 8 | / 0 9 | \ 1 10 | / 0 11 | / 0 12 | / 0 13 | \ 1 14 | 15 | Bitstrings are useful for representing integers, bytes, symbols, and other simple data. However, bitstrings in glas are not optimized for random access and should be relatively short. A binary (a list of bytes) is favored for large texts or binaries. 16 | 17 | This module defines many useful operations on bitstrings. 18 | 19 | ## Compact Encoding Assumption 20 | 21 | One assumption for glas systems is that non-branching bitstring fragments are encoded in a compact manner. One viable encoding that I've used in a few implementations: 22 | 23 | type Stem = Word64 // encodes 0..63 bits 24 | type Node = Leaf | Branch of Val * Val | Stem64 of Word64 * Node 25 | type Val = (Stem * Node) // struct 26 | 27 | With a K-bit stem word we can encode up to K-1 bits and stem length. For example, with a 4-bit word, we can encode up to 3 stem bits: 28 | 29 | 1000 0 bits 30 | a100 1 bit 31 | ab10 2 bits 32 | abc1 3 bits 33 | 0000 unused 34 | 35 | In this case, short bitstring values (unit, small integers or symbols, etc. with a final Leaf node) will be non-allocating, which is very convenient. 36 | 37 | ## Operations Performance 38 | 39 | My vision for glas systems does not emphasize bitstring manipulations. Common operations can be accelerated, but we shouldn't depend on it. Binaries (lists of bytes) should be favored over large bitstrings for serialization, and will be compacted by another mechanism. 40 | 41 | Eventually, to enter domains such as compression and cryptography, glas systems will accelerate virtual machines that support low-level 'bit banging' operations. However, this is a task for another module. 42 | -------------------------------------------------------------------------------- /old-glas-src/bits/public.g0: -------------------------------------------------------------------------------- 1 | # Bitstring Manipulation 2 | 3 | open prims 4 | import bits-basics as bb 5 | from [bb] import type-bits 6 | 7 | # the empty bitstring 8 | data empty [bb/bits-empty] 9 | 10 | # pop Bits -- Bool Bits 11 | # return head bit and remainder of bits 12 | prog pop [bb/bits-pop] 13 | prog head [pop drop] 14 | prog tail [pop swap drop] 15 | 16 | assert [ 0b100 head 0b1 eq ] 17 | assert [ 0b011 head 0b0 eq ] 18 | assert [ 0b101100 tail 0b01100 eq ] 19 | 20 | prog is-bits-impl [ 21 | [[empty eq] # until empty 22 | [[unpair] reject tail] # verify head is not a pair, take tail. 23 | until-do 24 | ] verify # also, don't modify the input 25 | ] 26 | assert [0b1011 is-bits-impl 0b1011 eq] 27 | assert [[1 2 pair is-bits-impl] reject] 28 | 29 | # is-bits Value -- Bits | FAILURE 30 | # fails if input is not a bitstring. 31 | prog is-bits [[is-bits-impl] 'bits-verify p-accel-opt apply] 32 | 33 | assert [0b1011 is-bits 0b1011 eq] 34 | assert [[1 2 pair 'test d1 is-bits] reject] 35 | 36 | # TODO: type support 37 | 38 | 39 | # prefix/suffix - append forms 40 | # DEF ABC prefix => ABCDEF 41 | # ABC DEF suffix => ABCDEF 42 | prog prefix [bb/bits-prefix] 43 | prog suffix [bb/bits-suffix] 44 | 45 | # append ABC DEF -- ABCDEF 46 | # Append two bitstrings. 47 | prog append [suffix] 48 | assert [0b110100 0b011110 append 0b110100011110 eq] 49 | 50 | prog p-each [p-dip [pop] swap p-while-do [empty eq] p-suffix] 51 | prog p-each-while [p-dip [pop] p-prefix p-loop] 52 | 53 | # each Bits [Bit -- ] -- 54 | # process each bit sequentially with a given function. 55 | # entire operation fails if given function fails. This 56 | # can also serve as a left-fold over bitstrings. 57 | macro each [p-each] 58 | 59 | # each-while Bits [Bit -- | FAIL] -- Bits 60 | # same as 'each' but will short-circuit the loop if the 61 | # operation fails. Returns the unprocessed bits. 62 | macro each-while [p-each-while] 63 | 64 | # reverse ABCDEF -- FEDCBA 65 | # Reverse a bitstring. 66 | prog reverse [bb/bits-reverse] 67 | assert [0b00101101001 reverse 0b10010110100 eq] 68 | 69 | # map Bits [Bit -- Bit] -- Bits 70 | # apply a function to each bit in a bitstring, 71 | # produces a new bitstring. 72 | prog p-map [ 73 | p-dip [swap] p-prefix [append] p-suffix 74 | p-each 75 | [empty swap] p-prefix [reverse] p-suffix 76 | ] 77 | macro map [p-map] 78 | 79 | 80 | # negate Bits -- Bits 81 | # Negate every bit in the bitstring. 82 | # Also equivalent to negating a number. 83 | prog negate [bb/bits-negate] 84 | assert [0b00101101001 negate 0b11010010110 eq] 85 | 86 | # map2 Bits Bits [Bit Bit -- Bit] -- Bits 87 | # apply a function to every pair of bits from two bitstrings. 88 | # fails if bitstrings are of different lengths. 89 | prog p-map2 [ 90 | p-dip [[pop] dip swap] p-prefix [swap] p-suffix 91 | p-map 92 | [empty eq] p-dip p-suffix 93 | ] 94 | macro map2 [p-map2] 95 | 96 | # bit-max Bit Bit -- Bit 97 | # return maximum of two bits (bit 'or') 98 | prog bit-max [[0b0 eq] [drop2 0b1] try-else] 99 | assert [0b0 0b0 bit-max 0b0 eq] 100 | assert [0b1 0b0 bit-max 0b1 eq] 101 | assert [0b0 0b1 bit-max 0b1 eq] 102 | assert [0b1 0b1 bit-max 0b1 eq] 103 | 104 | prog or-impl [ [bit-max] map2 ] 105 | 106 | # or Bits Bits -- Bits 107 | # bitwise maximum (or bitwise 'or') of two bitstrings 108 | # requires bitstrings of equal length 109 | prog or [[or-impl] 'bits-or p-accel-opt apply] 110 | 111 | prog test-or [ 112 | copy3 [or-impl] dip eq 113 | copy3 [or] dip eq 114 | copy3 [swap or-impl] dip eq 115 | [swap or] dip eq 116 | ] 117 | assert [0b0011 0b0110 0b0111 test-or] 118 | 119 | # bit-min Bit Bit -- Bit 120 | # return minimum of two bits (bit 'and') 121 | prog bit-min [[0b1 eq] [drop2 0b0] try-else] 122 | assert [0b0 0b0 bit-min 0b0 eq] 123 | assert [0b1 0b0 bit-min 0b0 eq] 124 | assert [0b0 0b1 bit-min 0b0 eq] 125 | assert [0b1 0b1 bit-min 0b1 eq] 126 | 127 | prog and-impl [ [bit-min] map2 ] 128 | 129 | # and Bits Bits -- Bits 130 | # bitwise minimum (or bitwise 'and') of two bitstrings 131 | # requires bitstrings of equal length. 132 | prog and [[and-impl] 'bits-and p-accel-opt apply] 133 | 134 | prog test-and [ 135 | copy3 [and-impl] dip eq 136 | copy3 [and] dip eq 137 | copy3 [swap and-impl] dip eq 138 | [swap and] dip eq 139 | ] 140 | assert [0b0011 0b0110 0b0010 test-and] 141 | 142 | # bit-neq Bit Bit -- Bit 143 | # value is 0b1 if bits are the same, 0b1 otherwise 144 | prog bit-neq [[eq 0b0] [drop2 0b1] try-else] 145 | assert [0b0 0b0 bit-neq 0b0 eq] 146 | assert [0b1 0b0 bit-neq 0b1 eq] 147 | assert [0b0 0b1 bit-neq 0b1 eq] 148 | assert [0b1 0b1 bit-neq 0b0 eq] 149 | 150 | prog xor-impl [ [bit-neq] map2 ] 151 | 152 | # xor Bits Bits -- Bits 153 | # bitwise not-equal (or bitwise 'xor') of two bitstrings 154 | # requires bitstrings of equal length 155 | prog xor [[xor-impl] 'bits-xor p-accel-opt apply] 156 | 157 | prog test-xor [ 158 | copy3 [xor-impl] dip eq 159 | copy3 [xor] dip eq 160 | copy3 [swap xor-impl] dip eq 161 | [swap xor] dip eq 162 | ] 163 | assert [0b0011 0b0110 0b0101 test-xor] 164 | 165 | prog erase-leading-zeroes [ [0b0 get] loop ] 166 | prog erase-leading-ones [ [0b1 get] loop ] 167 | 168 | assert [0b00101 erase-leading-zeroes 0b101 eq] 169 | assert [0b11010 erase-leading-zeroes 0b11010 eq] 170 | assert [0b00101 erase-leading-ones 0b00101 eq] 171 | assert [0b11010 erase-leading-ones 0b010 eq] 172 | 173 | prog length-impl [0 swap [tail [bb/int-increment] dip] loop 0 eq] 174 | 175 | # length Bits -- Nat 176 | prog length [[length-impl] 'bits-length p-accel-opt apply] 177 | 178 | prog test-length [ 179 | copy2 [length-impl] dip eq 180 | [length] dip eq 181 | ] 182 | 183 | assert [empty 0 test-length] 184 | assert [0b001101001 9 test-length] 185 | assert ['length 56 test-length] 186 | 187 | 188 | prog take-impl [ 189 | type-bits 190 | [empty] dip2 191 | [pop [prefix] dip] bb/repeat 192 | drop reverse 193 | ] 194 | prog skip-impl [bb/type-bits [tail] bb/repeat ] 195 | 196 | # take Bits N -- Bits 197 | prog take [[take-impl] 'bits-take p-accel-opt apply] 198 | 199 | prog test-take [ 200 | copy3 [take-impl] dip eq 201 | [take] dip eq 202 | ] 203 | assert [ 0b1101001 4 0b1101 test-take ] 204 | 205 | # skip Bits N -- Bits 206 | # drop first N bits from bitstring 207 | prog skip [[skip-impl] 'bits-skip p-accel-opt apply] 208 | 209 | prog test-skip [ 210 | copy3 [skip-impl] dip eq 211 | [skip] dip eq 212 | ] 213 | assert [ 0b0101100010 4 0b100010 test-skip ] 214 | 215 | # Note: I could add some boolean stuff here, such as 216 | # defining true (0b1), false (0b0), and if-then-else. 217 | # 218 | # I'm uncertain this is the best place to do so, but 219 | # it does make sense. 220 | # 221 | 222 | export type-bits as type, is-bits 223 | , empty, head, tail, pop 224 | , p-each, p-each-while, p-map, p-map2 225 | , each, each-while, map, map2 226 | , append, prefix, suffix 227 | , reverse, negate, xor, or, and 228 | , length, take, skip 229 | , true, false, if-then-else, p-if-then-else 230 | -------------------------------------------------------------------------------- /old-glas-src/glas-eval/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmbarbour/glas/a8edbf10759a75f9c8180e6caa1e4643839d8a36/old-glas-src/glas-eval/README.md -------------------------------------------------------------------------------- /old-glas-src/glas-eval/public.g0: -------------------------------------------------------------------------------- 1 | # from ref-eval import eval, is-program 2 | 3 | open list 4 | 5 | data test ["Hello, World!" 0x0a pushr] 6 | 7 | export [test] 8 | -------------------------------------------------------------------------------- /old-glas-src/glas-eval/ref-eval.g0: -------------------------------------------------------------------------------- 1 | open prims 2 | import list as l 3 | 4 | ; list of all single-symbol ops 5 | data symbolic-ops-list [ 6 | l0 'copy 7 | li 'drop 8 | li 'swap 9 | li 'eq 10 | li 'fail 11 | li 'eff 12 | li 'get 13 | li 'put 14 | li 'del 15 | li # end of list 16 | ] 17 | 18 | prog is-program-op [ ; Program -- | FAILURE 19 | symbolic-ops-list value-in-list 20 | ] 21 | 22 | ; split-program : Program -- List | FAILURE 23 | prog split-program [ 24 | try [is-program-op] then [] else [ 25 | 26 | ] 27 | 28 | ] 29 | 30 | ; is-program: Program -- | FAILURE 31 | ; This accepts the input if it represents a program. 32 | prog is-program [ 33 | ; use list of programs as queue 34 | list-singleton 35 | 36 | ; for each program in stack, split into fragments, add to stack 37 | while [popr] do [split-program join] 38 | 39 | ; delete empty stack 40 | drop-empty-list 41 | ] 42 | 43 | 44 | ; eval: List Program -- List 45 | ; This is a simple reference implementation, via direct interpretation. 46 | 47 | 48 | ; eval-k: List Program -- List Continuation 49 | -------------------------------------------------------------------------------- /old-glas-src/glas-eval/util.g0: -------------------------------------------------------------------------------- 1 | prog unit [0] 2 | prog empty-list [unit] 3 | prog empty-record [unit] 4 | prog list-singleton [dip[empty-list] pushl] 5 | 6 | prog drop-unit [unit eq] 7 | prog drop-empty-list [drop-unit] 8 | prog neq [ try [eq] then [fail] else [] ] 9 | 10 | prog variant [dip[dip[empty-record]] put] 11 | 12 | ; As 'get' but fails if there are any branches. This provides some dynamic 13 | ; type-safety, though it would be better to have static types. 14 | prog from-variant [ 15 | dip [copy] copy 16 | dip [del drop-unit] ; check for singleton 17 | get 18 | ] 19 | 20 | ; Value List -- | FAILURE 21 | prog value-in-list [ 22 | ; loop through list, halting on match 23 | while [popl swap dip[neq drop]] do [] 24 | ; succeed if we halt before end of list 25 | empty-list neq drop2 26 | ] 27 | 28 | -------------------------------------------------------------------------------- /old-glas-src/gparse/README.md: -------------------------------------------------------------------------------- 1 | # Goals 2 | 3 | This is intended to support parsing of texts into structured data. 4 | 5 | One approach is parser combinators, and another is compiling 'grammars' into parser functions. The latter approach has potential to be much more efficient insofar as we can recognize common structure between grammars (e.g. that two options share a prefix). 6 | 7 | Later, I'd like to develop a DSL for representing grammars. So, the grammar approach should align nicely with a grammar we can easily produce. 8 | 9 | -------------------------------------------------------------------------------- /old-glas-src/int/README.md: -------------------------------------------------------------------------------- 1 | # Basic Arithmetic 2 | 3 | ## Integer Representation 4 | 5 | In Glas, integers are normally represented by variable-length bitstrings, msb to lsb, with negative integers using negated bits (aka one's complement). Every bitstring uniquely represents an integer. 6 | 7 | 5 0b101 8 | 4 0b100 9 | 3 0b11 10 | 2 0b10 11 | 1 0b1 12 | 0 0b 13 | -1 0b0 14 | -2 0b01 15 | -3 0b00 16 | -4 0b011 17 | -5 0b010 18 | 19 | This representation has some nice properties such as symmetry and having no upper bound on numbers. Of course, performance will take a hit when operating on larger integers. 20 | 21 | ## Performance 22 | 23 | At the moment, the implementation of these functions doesn't take much advantage of CPU built-in operations. Essentially, it's bitwise primary school algorithms. 24 | 25 | essentially primary school algorithms. I might end up marking several of these functions for optional acceleration, but I'd prefer to avoid relying on acceleration. 26 | 27 | ## Arithmetic by Bitstring Manipulations 28 | 29 | ### Increment/Decrement 30 | 31 | We can increment an integer by rewriting the low '1' bits to '0' bits, then handling the upper bits specially: 32 | 33 | 0b => 0b1 (0 to 1) 34 | 0b0 => 0b (-1 to 0) 35 | 0bxx0 => 0bxx1 (e.g. 2 to 3, or -3 to -2) 36 | 37 | This can be viewed as a simplified add-with-carry that happens to work with negative numbers. It's easiest to operate on low bits if we first reverse the bitstring. Decrement can be implemented by inverting all the 0 and 1 bits. 38 | 39 | *Note:* Increment and decrement are currently implemented in the 'bits' module and re-exported here to avoid a dependency cycle for implementation of functions such as bitstring split and length. 40 | 41 | ### Add 42 | 43 | It's easiest to think about adding two positive numbers. This can be achieved using the normal add-carry algorithm on individual bits, lsb to msb. Adding a negative number can become subtraction, though I'll need to properly handle negative outcomes. And subtracting from a negative number can be implemented via addition again. 44 | 45 | ### Subtract 46 | 47 | I need to figure out the fine rules for this. 48 | 49 | 0b111 - 0b110 => 0b1 50 | 0b110 - 0b110 => 0b 51 | 0b101 - 0b110 => 0b0 52 | 0b100 - 0b110 => 0b01 53 | 54 | The transition to negatives is a tad awkward to express. 55 | 56 | 57 | whether I can use simple subtract with carry in this context, where subtracting a final '1' via carry becomes a 0b0 prefix. 58 | 59 | 60 | ### Multiply 61 | 62 | We can multiply two positive numbers by adding with shifting. This is perhaps the most straightforward reasonably efficient version. It does benefit from preparing the shifts and add-carry onto those. Negative numbers can be handled by negating the number and the result. 63 | 64 | ### Divmod 65 | 66 | Uh oh. I can't even remember how to do elementary school long division. 67 | 68 | -------------------------------------------------------------------------------- /old-glas-src/int/public.g0: -------------------------------------------------------------------------------- 1 | open prims 2 | import bits as b 3 | import bits-basics as bb 4 | from [bb] import 5 | int-increment as increment, 6 | int-decrement as decrement, 7 | bits-negate as negate, 8 | p-repeat, repeat, 9 | type-int, type-nat 10 | import nat as n 11 | 12 | prog is-int [b/is-bits] 13 | 14 | prog is-pos [type-int [0b1 get] verify] 15 | prog is-neg [type-int [0b0 get] verify] 16 | prog is-nat [type-int [0b0 get] reject] 17 | prog is-zero [type-int 0 eq1] 18 | 19 | 20 | # subtract nat from nat, resulting in integer 21 | prog sub-nn [ 22 | [n/sub] # fails for negative result 23 | [swap n/sub negate] # (A-B) = -(B-A) 24 | try-else 25 | ] 26 | 27 | prog add-impl [ 28 | [[is-neg negate] dip] 29 | [[is-neg negate] 30 | [n/add negate] # neg + neg 31 | [swap sub-nn] # neg + pos 32 | try-then-else 33 | ] 34 | [[is-neg negate] 35 | [sub-nn] # pos + neg 36 | [n/add] # pos + pos 37 | try-then-else 38 | ] 39 | try-then-else 40 | ] 41 | 42 | # add two integers 43 | prog add [[add-impl] 'int-add p-accel-opt apply] 44 | 45 | prog test-add [ 46 | copy3 [add-impl] dip eq 47 | copy3 [swap add-impl] dip eq 48 | copy3 [add] dip eq 49 | [swap add] dip eq 50 | ] 51 | 52 | assert [-9 19 10 test-add] 53 | assert [-9 -19 -28 test-add] 54 | assert [9 19 28 test-add] 55 | assert [9 -19 -10 test-add] 56 | 57 | prog sub-impl [negate add] 58 | 59 | # subtract two integers 60 | prog sub [[sub-impl] 'int-sub p-accel-opt apply] 61 | 62 | prog test-sub [ 63 | copy3 [sub-impl] dip eq 64 | copy3 [sub] dip eq 65 | copy3 [swap sub-impl] dip negate eq 66 | [swap sub] dip negate eq 67 | ] 68 | 69 | assert [19 9 10 test-sub] 70 | assert [19 -9 28 test-sub] 71 | assert [-19 9 -28 test-sub] 72 | assert [-19 -9 -10 test-sub] 73 | 74 | prog mul-impl [ 75 | [[is-neg negate] dip] 76 | [ 77 | [is-neg negate] 78 | [n/mul] # neg * neg 79 | [n/mul negate] # neg * pos 80 | try-then-else 81 | ] 82 | [ 83 | [is-neg negate] 84 | [n/mul negate] # pos * neg 85 | [n/mul] # pos * pos 86 | try-then-else 87 | ] 88 | try-then-else 89 | ] 90 | 91 | # multiply two integers 92 | prog mul [[mul-impl] 'int-mul p-accel-opt apply] 93 | 94 | prog test-mul [ 95 | copy3 [mul-impl] dip eq 96 | copy3 [swap mul-impl] dip eq 97 | copy3 [mul] dip eq 98 | [swap mul] dip eq 99 | ] 100 | assert [-3 4 -12 test-mul] 101 | assert [-7 -4 28 test-mul] 102 | assert [9 -7 -63 test-mul] 103 | assert [7 6 42 test-mul] 104 | 105 | prog divmod-impl [ 106 | # I currently don't have a good idea how to simplify this. 107 | # for now, just handling each signs case separately. 108 | [[is-neg negate] dip] 109 | [ 110 | [is-neg negate] 111 | [n/divmod negate] # -17 -5 => 3 -2 112 | [ # -17 5 => -4 3 113 | copy 114 | [n/divmod [negate] dip] dip 115 | 116 | swap 117 | [0 eq] 118 | [drop 0] 119 | [n/sub [decrement] dip] 120 | try-then-else 121 | ] 122 | try-then-else 123 | ] 124 | [ 125 | [is-neg negate] 126 | [ # 17 -5 => -4 -3 127 | copy 128 | [n/divmod [negate] dip] dip 129 | 130 | swap 131 | [0 eq] 132 | [drop 0] 133 | [n/sub negate [decrement] dip] 134 | try-then-else 135 | ] 136 | [n/divmod] 137 | try-then-else 138 | ] 139 | try-then-else 140 | ] 141 | 142 | # Dividend Divisor -- Quotient Remainder 143 | # Quotient * Divisor + Remainder = Dividend 144 | # Remainder in [0,Divisor) or (Divisor,0]. 145 | # A non-zero Remainder has sign of Divisor. 146 | # 147 | # For example: 148 | # 149 | # -17 -5 => 3 -2 150 | # -17 5 => -4 3 151 | # 17 -5 => -4 -3 152 | # 17 5 => 3 2 153 | # 154 | prog divmod [[divmod-impl] 'int-divmod p-accel-opt apply] 155 | 156 | # need a bit more confidence in divmod... 157 | 158 | prog test-divmod [ 159 | pair 160 | copy3 [divmod-impl pair] dip eq 161 | [divmod pair] dip eq 162 | ] 163 | 164 | assert [ 17 5 3 2 test-divmod] 165 | assert [ 17 -5 -4 -3 test-divmod] 166 | assert [-17 5 -4 3 test-divmod] 167 | assert [-17 -5 3 -2 test-divmod] 168 | 169 | assert [-4 3 -2 2 test-divmod] 170 | assert [-3 3 -1 0 test-divmod] 171 | assert [-2 3 -1 1 test-divmod] 172 | assert [-1 3 -1 2 test-divmod] 173 | assert [ 0 3 0 0 test-divmod] 174 | assert [ 1 3 0 1 test-divmod] 175 | assert [ 2 3 0 2 test-divmod] 176 | assert [ 3 3 1 0 test-divmod] 177 | assert [ 4 3 1 1 test-divmod] 178 | 179 | assert [-4 -3 1 -1 test-divmod] 180 | assert [-3 -3 1 0 test-divmod] 181 | assert [-2 -3 0 -2 test-divmod] 182 | assert [-1 -3 0 -1 test-divmod] 183 | assert [ 0 -3 0 0 test-divmod] 184 | assert [ 1 -3 -1 -2 test-divmod] 185 | assert [ 2 -3 -1 -1 test-divmod] 186 | assert [ 3 -3 -1 0 test-divmod] 187 | assert [ 4 -3 -2 -2 test-divmod] 188 | 189 | assert [-100 -1 100 0 test-divmod] 190 | assert [100 -1 -100 0 test-divmod] 191 | assert [-100 1 -100 0 test-divmod] 192 | 193 | prog div [ divmod drop ] 194 | prog mod [ divmod swap drop ] 195 | 196 | prog gt-impl [sub 0b1 get drop] 197 | prog gte-impl [sub is-nat drop] 198 | 199 | prog gte [[gte-impl] 'int-gte p-accel-opt apply] 200 | prog gt [[gt-impl] 'int-gt p-accel-opt apply] 201 | prog lte [swap gte] 202 | prog lt [swap gt] 203 | 204 | # convert to/from 'words' - bitstrings of fixed size. 205 | # 206 | # NOTE: Negative words use 2's complement instead of 1's 207 | # complement. 208 | 209 | # Convert a word to an integer. 210 | # 211 | # The word in this case will be in 2's complement if negative, e.g. 212 | # 0xFF is -1, 0x80 is -128, and 0x7F is 127. 213 | prog of-word [ 214 | bb/type-bits 215 | [0b1 get] 216 | [[0b1 get] loop decrement] 217 | [[0b0 get] loop] 218 | try-then-else 219 | ] 220 | assert [0x7F of-word 127 eq] 221 | assert [0x30 of-word 48 eq] 222 | assert [0xFF of-word -1 eq] 223 | assert [0x80 of-word -128 eq] 224 | 225 | # Convert an integer to a signed word of a given size. 226 | # 227 | # Int Size to-word -- SWord 228 | # 229 | # This will convert a negative integer to 2's complement then 230 | # add a prefix of '1' bits. That is, this is a conventional 231 | # representation of integers within fixed size registers. 232 | prog to-word [ 233 | [[is-nat] dip] 234 | [ n/to-word [0b0 get] verify ] 235 | [ 236 | [increment copy b/length] dip 237 | swap n/sub 238 | [0b1 b/prefix] repeat 239 | [0b1 get] verify 240 | ] 241 | try-then-else 242 | ] 243 | 244 | assert [[128 8 to-word] reject] 245 | assert [127 8 to-word 0x7F eq] 246 | assert [-128 8 to-word 0x80 eq] 247 | assert [-1 8 to-word 0xFF eq] 248 | assert [-2 8 to-word 0xFE eq] 249 | assert [-3 8 to-word 0xFD eq] 250 | assert [[-129 8 to-word] reject] 251 | 252 | export 253 | add, sub, mul, 254 | divmod, div, mod, 255 | increment, decrement, negate, 256 | repeat, p-repeat, 257 | type-int as type, 258 | is-pos, is-neg, is-nat, is-zero, 259 | of-word, to-word 260 | -------------------------------------------------------------------------------- /old-glas-src/language-g0/compiler.g0: -------------------------------------------------------------------------------- 1 | open prims 2 | 3 | # Binary -- WordFragment Binary | FAILURE 4 | prog parse-word-fragment [ 5 | 6 | ] 7 | 8 | # 9 | prog parse-word [ 10 | 11 | ] 12 | 13 | prog compile [ fail ] 14 | -------------------------------------------------------------------------------- /old-glas-src/language-g0/public.g0: -------------------------------------------------------------------------------- 1 | 2 | # from ./compiler import compile 3 | -------------------------------------------------------------------------------- /old-glas-src/language-g0/util.g0: -------------------------------------------------------------------------------- 1 | 2 | prog unit [0] 3 | prog drop-unit [unit eq] 4 | 5 | prog empty-list [unit] 6 | prog empty-record [unit] 7 | prog empty-bitstring [unit] 8 | 9 | ; Binary BitString -- BitString 10 | prog concat-bits-onto [swap while [popr] do [swap dip [swap bjoin]]] 11 | 12 | ; BitString - List 13 | prog bits-to-bytes [ 14 | empty-list swap 15 | while [8 bsplit] do [dip[pushr]] 16 | drop-unit 17 | ] 18 | 19 | prog bytes-to-symbol [ 0x00 concat-bits-onto ] 20 | prog symbol-to-bytes [ bits-to-bytes popr 0x00 eq1 drop ] -------------------------------------------------------------------------------- /old-glas-src/language-tt/README.md: -------------------------------------------------------------------------------- 1 | # Text Tree Language 2 | 3 | Text tree (".tt" file extension) is intended as a lightweight alternative to XML or JSON for configuration or structured data entry. Compared to XML and JSON, text tree uses less punctuation and fewer escapes, is better at isolating errors, and has similar extensibility. 4 | 5 | A typical text tree might look like this: 6 | 7 | person 8 | name David 9 | alias Dave 10 | e-mail david@example.com 11 | 12 | person 13 | ... 14 | 15 | A text tree is represented by a list of entries. Every entry consists of a label, a main text (inline or multi-line), and a subtree for attributes. The subtree is distinguished by indentation. Order of entries is preserved. The text should be ASCII or UTF-8. Line separators may be CR, LF, or CRLF. Indentation should use spaces. 16 | 17 | The text tree may parse into a list of triples: 18 | 19 | type TT = List of { String, String, TT } 20 | 21 | For example, the example text tree might parse to: 22 | 23 | [["person", "", 24 | [["name", "David", 25 | [["alias", "Dave", []]]] 26 | ["e-mail", "david@example.com", []] 27 | ]] 28 | ["person", "", [...]] 29 | ] 30 | 31 | Spaces are trimmed, with a special exception for multi-line texts (see below). Similar to XML or JSON, text tree may benefit from a schema for validation in a given context. 32 | 33 | ## Escape Sequences 34 | 35 | Entries that start with backslash ('\', codepoint 92) are reserved by the text tree parser for escapes and extensions. This can potentially support modularity (e.g. via '\include' or similar), macro calls, inline schema definitions, and other ad-hoc features. Currently, escapes are used only for multi-line texts. 36 | 37 | *Aside:* An important design constraint for escapes is that their effect must be scoped to the entry they are embedded within. This simplifies local reasoning. 38 | 39 | ## Multi-Line Texts 40 | 41 | Multi-line text may immediately follow an entry with an empty inline text. 42 | 43 | para 44 | \ text starts here 45 | \ and continues on 46 | \ yet another line 47 | 48 | Unlike inline text, multi-line text is not trimmed. The indentation, '\' label, and a single space following the label is removed from each line, but further prefix or trailing spaces are preserved. In the parsed result, lines are separated by LF even if the text tree file uses CR or CRLF. 49 | 50 | ## Comments 51 | 52 | The text tree format proposes a lightweight convention for comments: any entry whose label starts with '#' should be interpreted as a comment or annotation or metadata. This includes '#author', '#date', and so on. These entries are left accessible for post-processing, but should be understood as providing or supporting context without influence on meaning. 53 | 54 | *Aside:* It is feasible to use escapes to indicate comments, perhaps labels starting with '\\'. But I'm currently avoiding this because it's often useful to process comments in some way. 55 | -------------------------------------------------------------------------------- /old-glas-src/list/README.md: -------------------------------------------------------------------------------- 1 | # Lists 2 | 3 | Lists in glas systems are logically constructed from tree branch nodes (pairs) extending to the right, terminating in a leaf node (unit), with elements as the left branch of each pair. 4 | 5 | type List a = (a * List a) | () 6 | 7 | /\ list of four items 8 | a /\ [a,b,c,d] 9 | b /\ 10 | c /\ 11 | d () 12 | 13 | Direct representation of lists only provides efficient access to the first few elements. This is inadequate for many use cases of sequential data. To mitigate this, glas systems will represent lists under-the-hood using arrays, binaries, and [finger-tree](https://en.wikipedia.org/wiki/Finger_tree) [ropes](https://en.wikipedia.org/wiki/Rope_%28data_structure%29). These representations are accessible via accelerated list functions. 14 | 15 | ## Related Types 16 | 17 | The list representation is also used for tuples and vectors. 18 | 19 | ### Tuples 20 | 21 | A tuple is a fixed-sized list of heterogeneous types. 22 | 23 | type Tuple [a,b,c] = (a * (b * (c * ()))) 24 | 25 | However, glas systems usually favor records over tuples. Records are much more extensible and self-documenting, and are still reasonably efficient. 26 | 27 | ### Vectors 28 | 29 | A vector is a fixed-sized list of homogeneous type. 30 | 31 | type Vector n a = List a of length n 32 | 33 | Vectors are very useful in certain maths. 34 | 35 | -------------------------------------------------------------------------------- /old-glas-src/list/list-ops.g0: -------------------------------------------------------------------------------- 1 | open prims 2 | import nat as n 3 | 4 | data empty [0] 5 | 6 | # list each ([Op] => (ABC... -- A Op B Op C Op ...)) 7 | # As each-sc, but fails if not every element is processed. 8 | prog p-each [ p-dip [unpair] swap p-while-do [empty eq] p-suffix ] 9 | macro each [p-each] 10 | 11 | # thoughts: I could implement p-each in terms of p-each-sc, but I 12 | # prefer to lift the operation out of the hierarchical transaction 13 | # such that failure is handled more directly. 14 | 15 | prog is-list-impl [[[drop] each] verify] 16 | assert ["abc" is-list-impl "abc" eq] 17 | assert [['abc is-list-impl] reject] 18 | 19 | # is-list Value -- List | FAIL 20 | # 21 | # Verify that input is a list. Does not modify data, 22 | # but may adjust list representation under the hood. 23 | # 24 | prog is-list [[is-list-impl] 'list-verify p-accel apply] 25 | 26 | assert ["abc" copy is-list eq] 27 | assert [['abc is-list] reject] 28 | 29 | # insist that top item on the data stack is a valid list 30 | # and optimize runtime representation for list operators 31 | prog list-type [ 32 | # todo: checkable type annotation 33 | [is-list] [halt-type-error] try-else 34 | ] 35 | 36 | # pushl (V L -- V:L) 37 | prog pushl-impl [pair list-type] 38 | prog pushl [[pushl-impl] 'list-pushl p-accel-opt apply] 39 | 40 | # popl (V:L -- V L) 41 | prog popl-impl [unpair list-type] 42 | prog popl [[popl-impl] 'list-popl p-accel-opt apply] 43 | 44 | assert [0x61 "bcdef" pushl "abcdef" eq] 45 | assert ["abcdef" popl "bcdef" eq 0x61 eq] 46 | 47 | # list reverse-append (DEF CBA -- ABCDEF) 48 | # O(N) - not accelerated 49 | prog reverse-append [[swap pair] each list-type] 50 | assert ["def" "cba" reverse-append "abcdef" eq] 51 | 52 | # list reverse (ABCDEF -- FEDCBA) 53 | # O(N) even if accelerated, but acceleration might make this lazy. 54 | prog reverse [[empty] dip reverse-append] 55 | assert ["hello" reverse "olleh" eq] 56 | 57 | prog append-impl [swap reverse reverse-append] 58 | assert ["abc" "def" append-impl "abcdef" eq] 59 | 60 | # append (ABC DEF -- ABCDEF) 61 | # 62 | # Concatenate two lists. This operation is accelerated and should be 63 | # O(log(min(N,M))) on the smaller of the two lists, assuming runtime 64 | # representation of larger lists is based on finger tree ropes. 65 | # 66 | prog append [[append-impl] 'list-append p-accel apply] 67 | 68 | prog test-append [ 69 | copy3 70 | [append-impl] dip eq 71 | [append] dip eq 72 | ] 73 | assert ["" "abc" "abc" test-append] 74 | assert ["abc" "" "abc" test-append] 75 | assert ["abc" "def" "abcdef" test-append] 76 | 77 | prog length-impl [[0] dip [drop n/increment] each] 78 | assert ["abcdef" length-impl 6 eq] 79 | 80 | # useful aliases 81 | prog suffix [append] 82 | prog prefix [swap suffix] 83 | 84 | # length (List -- N) 85 | # returns length of input list 86 | # 87 | # This function is accelerated, should be O(1) in cases where 88 | # a list runtime representation (e.g. array or rope) is used. 89 | prog length [[length-impl] 'list-length p-accel apply] 90 | 91 | prog test-length [ 92 | copy2 93 | [length-impl] dip eq 94 | [length] dip eq 95 | ] 96 | 97 | assert ["" 0 test-length] 98 | assert ["omn-nom" 7 test-length] 99 | assert ["omn " 10 [copy append] n/repeat 4096 test-length] 100 | 101 | prog take-impl [ 102 | [empty] dip2 103 | [unpair [swap pair] dip] n/repeat 104 | drop reverse 105 | ] 106 | prog skip-impl [ 107 | [unpair swap drop] n/repeat 108 | list-type 109 | ] 110 | 111 | assert ["ping" 3 take-impl "pin" eq] 112 | assert ["ping" 3 skip-impl "g" eq] 113 | 114 | # list take List N -- List 115 | # 116 | # Return initial sublist of length N. Fails if list is shorter than N. 117 | # This operation is accelerated. Should be O(log(N)) on the runtime 118 | # representation of a large list. 119 | # 120 | prog take [[take-impl] 'list-take p-accel apply] 121 | 122 | # list skip List N -- List 123 | # 124 | # Return remaining list after removing first N elements. Fails if list 125 | # is shorter than N elements. This operation is accelerated and should 126 | # be O(log(N)) on the runtime representation of a large list. 127 | # 128 | prog skip [[skip-impl] 'list-skip p-accel apply] 129 | 130 | prog test-take [ 131 | copy3 132 | [take-impl] dip eq 133 | [take] dip eq 134 | ] 135 | prog test-skip [ 136 | copy3 137 | [skip-impl] dip eq 138 | [skip] dip eq 139 | ] 140 | 141 | assert ["" 0 "" test-take] 142 | assert ["profound" 3 "pro" test-take] 143 | assert ["" 0 "" test-skip] 144 | assert ["profound" 3 "found" test-skip] 145 | 146 | # cut List N -- List List 147 | # same as applying both take and skip 148 | prog cut [copy2 skip [take] dip] 149 | assert [ 150 | "nowhere" 3 cut pair 151 | "now" "here" pair 152 | eq 153 | ] 154 | 155 | # item (List A) N -- A 156 | # 157 | # access the Nth item in a list. This is the 158 | # same as skipping N items, taking the head. 159 | prog item-impl [skip popl drop] 160 | prog item [[item-impl] 'list-item p-accel-opt apply] 161 | 162 | prog test-item [ 163 | copy3 [item-impl] dip eq 164 | [item] dip eq 165 | ] 166 | assert ["hello" 0 0x68 test-item] 167 | assert ["hello" 1 0x65 test-item] 168 | assert ["hello" 4 0x6F test-item] 169 | assert ["hello" 5 [item] reject [item-impl] reject drop2] 170 | 171 | prog pushr-impl [l1 append] 172 | 173 | # pushr (L V -- L:V) 174 | # 175 | # Add an element to the right end of a list. This operation is accelerated 176 | # and should be O(1) on the accelerated runtime representation of lists. 177 | prog pushr [[pushr-impl] 'list-pushr p-accel-opt apply] 178 | 179 | prog test-pushr [ 180 | copy3 181 | [pushr-impl] dip eq 182 | [pushr] dip eq 183 | ] 184 | assert ["pin" 0x67 "ping" test-pushr] 185 | 186 | prog popr-impl [ 187 | copy length n/decrement 188 | cut unpair 0 eq 189 | ] 190 | assert ["pint" popr-impl 0x74 eq "pin" eq] 191 | 192 | # popr (L:V -- L V) 193 | # 194 | # Separate rightmost element from a list. This operation is accelerated 195 | # and should be O(1) on the accelerated runtime reprsentation of lists. 196 | # 197 | prog popr [[popr-impl] 'list-popr p-accel-opt apply] 198 | 199 | prog test-popr [ 200 | pair copy2 201 | [popr-impl pair] dip eq 202 | [popr pair] dip eq 203 | ] 204 | assert ["ping" "pin" 0x67 test-popr] 205 | 206 | # li alias for 'pushr' 207 | # 208 | # Intended to support pseudo-literal construction of lists. 209 | # 210 | # l0 "this was a triumph" 211 | # li "making a note here," 212 | # li " huge success!" 213 | # li " -- GlaDOS" 214 | # li unlines 215 | # 216 | # Though, it would be better to design a data language rather 217 | # than attempt to embed significant volumes of data into g0. 218 | # 219 | prog li [pushr] 220 | 221 | # MORE LOOPS!!! 222 | # 223 | # short-circuiting loops 224 | # reverse ordered loops 225 | # mapping over a list 226 | # filter/flatmap over a list 227 | # zips combining items from two or more lists 228 | # sorting of lists 229 | # 230 | 231 | # list each-sc ((S * List A) * [(S * A) -> S | FAIL]) -> (S * List A) 232 | # 233 | # Short-circuiting loop over a list that processes each element in order, 234 | # returns the unprocessed remainder of the list in case the operation fails. 235 | prog p-each-sc [p-dip [unpair] p-prefix p-loop] 236 | macro each-sc [p-each-sc] 237 | 238 | assert [ 239 | 0 240 | 1 2 3 -4 5 6 l6 241 | [[0b1 get] verify swap pair] each-sc 242 | pair 243 | 3 2 1 l3 244 | -4 5 6 l3 245 | pair 246 | eq 247 | ] 248 | 249 | # p-each-rev-sc 250 | # each-rev-sc 251 | # 252 | # A reverse-order each-sc. Returns the remaining prefix of the list at 253 | # the top of the stack upon operation failure. If the remaining prefix 254 | # is empty, then there were no failures. 255 | prog p-each-rev-sc [ 256 | p-dip [popr swap] p-prefix p-loop 257 | [list-type] p-prefix 258 | ] 259 | macro each-rev-sc [p-each-rev-sc] 260 | assert [ 261 | 0 262 | 1 2 3 -4 5 6 l6 263 | [[0b1 get] verify n/add] each-rev-sc 264 | pair 265 | 11 266 | 1 2 3 -4 l4 267 | pair 268 | eq 269 | ] 270 | 271 | # p-each-rev [(S * A) -> S] -> [(S * List A) -> S] 272 | # each-rev ABC [Op] => C Op B Op A Op 273 | # 274 | # A reverse-order 'each' operation. Operates from end of list. 275 | prog p-each-rev [ 276 | p-dip [popr swap] swap p-while-do 277 | [list-type] p-prefix [empty eq] p-suffix 278 | ] 279 | macro each-rev [p-each-rev] 280 | assert [0 "hello" [swap pair] each-rev "hello" eq] 281 | 282 | # p-map [(S * A) -> (S * B)] -- [(S * List A) -> (S * List B)] 283 | # map ((S * List A) * [(S * A) -> (S * B)]) -> (S * List B) 284 | # 285 | # Apply an operation to each element of a list. Failures are not permitted. 286 | prog p-map [ 287 | p-dip [swap] p-prefix [swap pushr] p-suffix 288 | p-each 289 | [[empty] dip] p-prefix 290 | ] 291 | macro map [p-map] 292 | 293 | assert [ 294 | 1 2 3 4 l4 295 | [3 n/mul] map 296 | 3 6 9 12 l4 eq 297 | ] 298 | 299 | # p-map-sc [(S * A) -> (S * B) | FAIL] -- [(S * List A) -> ((S * List B) * List A)] 300 | # map-sc ((S * List A) * [(S * A) -> (S * B) | FAIL]) -> ((S * List B) * List A) 301 | # 302 | # Map over a list until a failure is reached. Returns the mapped prefix and the unprocessed 303 | # suffix of the list. 304 | prog p-map-sc [ 305 | p-dip [swap] p-prefix [swap pushr] p-suffix 306 | p-each-sc 307 | [[empty] dip] p-prefix 308 | ] 309 | macro map-sc [p-map-sc] 310 | 311 | assert [ 312 | 1 2 3 4 -5 6 l6 313 | [[0b1 get] verify 3 n/mul] map-sc 314 | pair 315 | 3 6 9 12 l4 316 | -5 6 l2 317 | pair eq 318 | ] 319 | 320 | # p-map-rev 321 | # map-rev 322 | prog p-map-rev [ 323 | p-dip [swap] p-prefix [pushl] p-suffix 324 | p-each-rev 325 | [[empty] dip] p-prefix 326 | ] 327 | macro map-rev [p-map-rev] 328 | 329 | # p-map-rev-sc 330 | # map-rev-sc 331 | prog p-map-rev-sc [ 332 | p-dip [swap] p-prefix [pushl] p-suffix 333 | p-each-rev-sc 334 | [[empty] dip] p-prefix 335 | ] 336 | macro map-rev-sc [p-map-rev-sc] 337 | 338 | 339 | # p-mapf [(S * A) -> (S * List B)] -- [(S * List A) -> (S * List B)] 340 | # mapf 341 | # 342 | # Map over a list while filtering and flattening the result. The operation 343 | # should produce a list for each element in the input list. These lists are 344 | # concatenated. Failure for any operation causes the entire mapf to fail. 345 | # 346 | prog p-mapf [ 347 | p-dip [swap] p-prefix [prefix] p-suffix 348 | p-each 349 | [[empty] dip] p-prefix 350 | ] 351 | macro mapf [p-mapf] 352 | 353 | assert [ 354 | "hello" 355 | [l1 "." append] mapf 356 | "h.e.l.l.o." eq 357 | ] 358 | 359 | # p-mapf-sc 360 | # mapf-sc 361 | # 362 | # Short-circuiting implementations of mapf. When the operation fails, the 363 | # remaining list is returned at the top of the list. If the remaining list 364 | # is empty, then there were no operation failures. 365 | # p-mapf-sc 366 | prog p-mapf-sc [ 367 | p-dip [swap] p-prefix [prefix] p-suffix 368 | p-each-sc 369 | [[empty] dip] p-prefix 370 | ] 371 | macro mapf-sc [p-mapf-sc] 372 | 373 | # p-mapf-rev 374 | # mapf-rev 375 | prog p-mapf-rev [ 376 | p-dip [swap] p-prefix [suffix] p-suffix 377 | p-each-rev 378 | [[empty] dip] p-prefix 379 | ] 380 | macro mapf-rev [p-mapf-rev] 381 | 382 | # p-mapf-rev-sc 383 | # mapf-rev-sc 384 | prog p-mapf-rev-sc [ 385 | p-dip [swap] p-prefix [suffix] p-suffix 386 | p-each-rev-sc 387 | [[empty] dip] p-prefix 388 | ] 389 | macro mapf-rev-sc [p-mapf-rev-sc] 390 | 391 | 392 | 393 | # ZIPS (map over two or more lists) 394 | # 395 | # Easiest to model as operating on a list of lists. 396 | 397 | # popl-many ListOfLists -- Firsts Suffixes 398 | # take first item from each item in a list of lists to form a 399 | # new list and a remaining list-of-lists. 400 | prog popl-many [ 401 | [empty empty] dip # accumulators 402 | [popl [swap [swap pushl] dip] dip swap pushl] each-rev 403 | ] 404 | assert [ 405 | "foo" "bar" "baz" l3 406 | popl-many 407 | pair 408 | "fbb" 409 | "oo" "ar" "az" l3 410 | pair eq 411 | ] 412 | 413 | # popr-many ListOfLists -- Prefixes Lasts 414 | prog popr-many [ 415 | [empty empty] dip # accumulators 416 | [popr [swap [swap pushl] dip] dip swap pushl] each-rev 417 | ] 418 | assert [ 419 | "foo" "bar" "baz" l3 420 | popr-many 421 | pair 422 | "fo" "ba" "ba" l3 423 | "orz" 424 | pair eq 425 | ] 426 | 427 | # p-zip-each-rem 428 | # 429 | # Process columns from a row-major list of lists. Returns the 430 | # remaining lists after at least one input list is empty. This 431 | # is mostly relevant in case not all lists are the same length. 432 | prog p-zip-each-rem [ 433 | # process first elements of each list 434 | [popl-many] swap p-dip p-while-do 435 | ] 436 | 437 | # p-zip-each 438 | # 439 | # Applies given operation to columns in a row-major list of lists. 440 | # fails if not all lists have the same length. Must operate on stack 441 | # or use effects to produce an output. 442 | prog p-zip-each [ 443 | p-zip-each-rem 444 | # verify remainder lists are is empty. 445 | [empty eq] p-each p-suffix 446 | ] 447 | macro zip-each [p-zip-each] 448 | 449 | # p-zip 450 | # 451 | # Apply operation that returns a new value for each column in a 452 | # row-major list of lists. Returns a list of returned items. 453 | prog p-zip [ 454 | p-dip [swap] p-prefix [swap pushr] p-suffix 455 | p-zip-each 456 | [[empty] dip] p-prefix 457 | ] 458 | macro zip [p-zip] 459 | 460 | assert [ 461 | "foo" "bar" "baz" l3 462 | [] zip 463 | "fbb" "oaa" "orz" l3 464 | eq 465 | ] 466 | 467 | # p-zip-each-sc 468 | prog p-zip-each-sc [ p-dip [popl-many] p-prefix p-loop ] 469 | macro zip-each-sc [p-zip-each-sc] 470 | 471 | # p-zip-sc 472 | # 473 | # Short-circuiting zip. If this fails at any step, returns the 474 | # partially zipped result and the remaining list of lists. 475 | # 476 | # Remainder is at top of stack. If at least one list in the 477 | # remainder is empty, then loop halted due to reaching end 478 | # of input. 479 | prog p-zip-sc [ 480 | p-dip [swap] p-prefix [swap pushr] p-suffix 481 | p-zip-each-sc 482 | [[empty] dip] p-prefix 483 | ] 484 | macro zip-sc [p-zip-sc] 485 | 486 | assert [ 487 | "lettuce" "tomato" "bacon" l3 488 | [] zip-sc 489 | pair 490 | "ltb" "eoa" "tmc" "tao" "utn" l5 491 | "ce" "o" empty l3 492 | pair eq 493 | ] 494 | 495 | # sort List [LessThan] -- List 496 | # 497 | # The LessThan operator must work on pairs of values from the 498 | # list and return a boolean value. This should probably be a 499 | # pure function, though that isn't checked here. 500 | # 501 | # This is a 'stable' sort meaning that partial order is preserved. 502 | # The sort algorithm is currently a merge sort. 503 | # 504 | 505 | 506 | # sorting lists 507 | # searching lists 508 | # generating lists 509 | # comparing lists (lexicographic) 510 | # matching prefixes or suffixes of lists 511 | 512 | 513 | 514 | -------------------------------------------------------------------------------- /old-glas-src/list/public.g0: -------------------------------------------------------------------------------- 1 | from ./list-ops import 2 | li, l0, l1, l2, l3, l4, l5, l6, l7, 3 | unl0, unl1, unl2, unl3, unl4, unl5, unl6, unl7, 4 | pushl, popl, pushr, popr, 5 | reverse, 6 | append, prefix, suffix, 7 | length, skip, take, cut, 8 | item, 9 | 10 | 11 | each, each-rev, each-sc, each-rev-sc, 12 | p-each, p-each-rev, p-each-sc, p-each-rev-sc, 13 | 14 | map, map-rev, map-sc, map-rev-sc, 15 | p-map, p-map-rev, p-map-sc, p-map-rev-sc, 16 | 17 | mapf, mapf-rev, mapf-sc, mapf-rev-sc, 18 | p-mapf, p-mapf-rev, p-mapf-sc, p-mapf-rev-sc, 19 | 20 | list-type as type 21 | -------------------------------------------------------------------------------- /old-glas-src/nat/README.md: -------------------------------------------------------------------------------- 1 | # Natural Numbers 2 | 3 | Natural numbers are represented the same as integers zero or above. This module provides a few arithmetic functions for operating on natural numbers. These are primarily used as piecewise components to implement arithmetic on integers. 4 | 5 | 6 | -------------------------------------------------------------------------------- /old-glas-src/nat/public.g0: -------------------------------------------------------------------------------- 1 | # The glas program model doesn't have any built-in ops for numbers. 2 | # However, most of these operations will be optionally accelerated. 3 | 4 | open prims 5 | import bits-basics as bb 6 | import bits as b 7 | from [bb] import type-nat 8 | , repeat 9 | , nat-increment as increment 10 | , nat-decrement as decrement 11 | 12 | prog is-nat [b/is-bits [0b0 get] reject] 13 | prog is-pos [type-nat [0b1 get] verify] 14 | prog is-zero [type-nat 0 eq1] 15 | 16 | # clarity functions 17 | prog revnat-increment [bb/revint-increment] 18 | prog revnat-lsb-pop [[bb/bits-pop][0b0 swap]try-else] 19 | prog revnat-mul2-add1 [0b1 bb/bits-prefix] 20 | prog revnat-mul2 [[0 eq1][0b0 bb/bits-prefix]try-else] 21 | 22 | prog add-rev [ 23 | [0] dip2 # introduce accumulator 24 | # stack: Acc A B 25 | # reversed A,B so lsb is first bit. 26 | 27 | # repeat until either number is zero 28 | [[0 eq] [swap 0 eq] try-else] 29 | [ 30 | [0b0 get] 31 | [ # xaaa + 0bbb => x(aaa + bbb) 32 | [ 33 | bb/bits-pop 34 | [bb/bits-prefix] dip 35 | ] dip 36 | ] 37 | [0b1 get 38 | [ 39 | [0b0 get] 40 | [ # 0aaa + 1bbb => 1(aaa + bbb) 41 | [0b1 bb/bits-prefix] dip 42 | ] 43 | [ # 1aaa + 1bbb => (1aaa + 1) + (1bbb - 1)) 44 | # => (0(aaa+1)) + (0bbb) 45 | # => 0((aaa+1) + bbb) 46 | 0b1 get revnat-increment # 1aaa => (aaa+1) 47 | [0b0 bb/bits-prefix] dip 48 | ] 49 | try-then-else 50 | ] dip 51 | ] 52 | try-then-else 53 | ] 54 | until-do 55 | 56 | # add acc bits to unmodified high bits. 57 | # (preserves reversed bit order.) 58 | swap 59 | bb/bits-reverse-append 60 | ] 61 | 62 | # test add, commutative of add 63 | prog test-add-rev [ 64 | copy3 65 | [add-rev] dip eq 66 | [swap add-rev] dip eq 67 | ] 68 | 69 | assert [0b101 0b0011 0b10001 test-add-rev] 70 | 71 | prog add-impl [ 72 | # type declarations 73 | [type-nat] dip type-nat 74 | 75 | # setup - reverse both ints 76 | [bb/bits-reverse] dip 77 | bb/bits-reverse 78 | 79 | # add reversed 80 | add-rev 81 | 82 | # reverse result to a normal int 83 | bb/bits-reverse 84 | ] 85 | 86 | # add two natural numbers (possibly accelerated) 87 | prog add [[add-impl] 'nat-add p-accel-opt apply] 88 | 89 | prog test-add [ 90 | copy3 [add-impl] dip eq 91 | copy3 [swap add-impl] dip eq 92 | copy3 [add] dip eq 93 | [swap add] dip eq 94 | ] 95 | 96 | assert [12345 87654 99999 test-add] 97 | assert [1000 9000 10000 test-add] 98 | assert [1234 5678 6912 test-add] 99 | assert [314 710 1024 test-add] 100 | assert [ 101 | 0x876543210FEDCBA876543210FEDCBA876543210FEDCBA 102 | 0x89ABCDEF012345789ABCDEF012345789ABCDEF012345 0b111 bb/bits-prefix 103 | 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 104 | test-add 105 | ] 106 | 107 | 108 | prog sub-rev [ 109 | [0] dip2 110 | [0 eq] 111 | [ 112 | [0b0 get] 113 | [ # xaaa - 0bbb => x(aaa - bbb) 114 | [ bb/bits-pop 115 | [bb/bits-prefix] dip 116 | ] dip 117 | ] 118 | [0b1 get 119 | [[0b0 get] dip] 120 | [ # 0aaa - 1bbb => (1+0aaa) - (1+1bbb) 121 | # => 1aaa - 0(1+bbb) 122 | # => 1(aaa - (1+bbb)) 123 | revnat-increment 124 | [0b1 bb/bits-prefix] dip2 125 | ] 126 | [ # 1aaa - 1bbb => 0(aaa - bbb) 127 | [0b1 get 128 | [0b0 bb/bits-prefix] dip 129 | ] dip 130 | ] 131 | try-then-else 132 | ] 133 | try-then-else 134 | ] 135 | until-do 136 | 137 | # special case: may need to shrink result 138 | # if high bit was reached. 139 | [0 eq][[0b0 get] loop 0] try-then 140 | 141 | # reverse the accumulator 142 | swap 143 | bb/bits-reverse-append 144 | ] 145 | 146 | assert [0b001 0b1 sub-rev 0b11 eq] 147 | assert [0b001 0b001 sub-rev 0 eq] 148 | assert [[0b1 0b01 sub-rev] reject] 149 | 150 | prog sub-impl [ 151 | [type-nat] dip type-nat 152 | [bb/bits-reverse] dip bb/bits-reverse 153 | sub-rev 154 | bb/bits-reverse 155 | ] 156 | 157 | # subtract natural numbers. 158 | # This fails if difference would be negative. 159 | prog sub [[sub-impl] 'nat-sub p-accel-opt apply] 160 | 161 | prog test-sub [ 162 | copy3 [sub-impl] dip eq 163 | copy3 [sub] dip eq 164 | 165 | # A - B = C implies A - C = B; swap B, C then test 166 | copy3 swap [sub-impl] dip eq 167 | copy3 swap [sub] dip eq 168 | 169 | [0 eq eq] # if expect zero, check that args are equal 170 | [ # otherwise reject swapped subtractions 171 | drop swap 172 | [sub-impl] reject 173 | [sub] reject 174 | drop2 175 | ] try-else 176 | ] 177 | 178 | assert [8 5 3 test-sub] 179 | assert [1024 314 710 test-sub] 180 | assert [99999 12345 87654 test-sub] 181 | assert [10000 1001 8999 test-sub] 182 | assert [1234 1234 0 test-sub] 183 | 184 | assert [ 185 | 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 186 | 0x876543210FEDCBA876543210FEDCBA876543210FEDCBA 187 | 0x89ABCDEF012345789ABCDEF012345789ABCDEF012345 0b111 bb/bits-prefix 188 | test-sub 189 | ] 190 | 191 | 192 | prog mul-nz-rev [ 193 | [0 0] dip2 # introduce Shift and Sum accumulators 194 | # stack: Shift Sum X Y(rem) 195 | 196 | [0 eq] # stop when Y(rem) is zero. 197 | [ 198 | # add X to Sum if low bit of Y(rem) is 1 199 | # then remove low bit of Y(rem) 200 | [0b0 get] 201 | [0b1 get [copy [add-rev] dip] dip] 202 | try-else 203 | 204 | # move lsb from Sum to Shift (logically multiply X by 2) 205 | [revnat-lsb-pop [bb/bits-prefix] dip] dip2 206 | ] 207 | until-do 208 | 209 | # final step is to move Shift bits back onto tail of Sum. 210 | drop swap bb/bits-reverse-append 211 | ] 212 | 213 | assert [0b011 0b101 mul-nz-rev 0b01111 eq] 214 | 215 | prog mul-impl [ 216 | [type-nat] dip type-nat 217 | [[0 eq][swap 0 eq]try-else] 218 | [drop 0] 219 | [ 220 | [bb/bits-reverse] dip bb/bits-reverse 221 | mul-nz-rev 222 | bb/bits-reverse 223 | ] 224 | try-then-else 225 | ] 226 | 227 | prog mul [[mul-impl] 'nat-mul p-accel-opt apply] 228 | 229 | prog test-mul [ 230 | copy3 [mul-impl] dip eq 231 | copy3 [swap mul-impl] dip eq 232 | copy3 [mul] dip eq 233 | [swap mul] dip eq 234 | ] 235 | 236 | assert [42 0 0 test-mul] 237 | assert [67 83 5561 test-mul] 238 | assert [7757 3373 26164361 test-mul] 239 | 240 | # binary long division algorithm 241 | prog divmod-impl [ 242 | [type-nat] dip type-nat 243 | [0 eq] reject # fail on divide-by-zero 244 | bb/bits-reverse swap 245 | [0 0] dip2 # stack: Q(rev) R(rev) D(rev) N(normal) 246 | # N is not reversed; we'll be taking bits from msb. 247 | 248 | [0 eq] 249 | [ 250 | # move msb of N to lsb of R 251 | [0b0 get] 252 | [[revnat-mul2] dip2] 253 | [0b1 get [revnat-mul2-add1] dip2] 254 | try-then-else 255 | 256 | [ # if R is greater or equal to D: 257 | # subtract D from R 258 | # shift '1' bit into lsb of Q 259 | # otherwise: 260 | # multiply Q by 2 (shift '0' bit into lsb if Q not zero) 261 | [copy [sub-rev] dip] 262 | [[revnat-mul2-add1] dip2] 263 | [[revnat-mul2] dip2] 264 | try-then-else 265 | ] dip # hide N 266 | ] 267 | until-do 268 | drop [bb/bits-reverse] dip bb/bits-reverse 269 | ] 270 | 271 | # divide two natural numbers N D -- Q R 272 | # such that (Q*D)+R = N 273 | prog divmod [[divmod-impl] 'nat-divmod p-accel-opt apply] 274 | 275 | prog test-divmod [ 276 | pair 277 | copy3 [divmod-impl pair] dip eq 278 | [divmod pair] dip eq 279 | ] 280 | 281 | assert [17 5 3 2 test-divmod] 282 | assert [63 6 10 3 test-divmod] 283 | assert [63 7 9 0 test-divmod] 284 | assert [63 8 7 7 test-divmod] 285 | assert [10003087 3373 2965 2142 test-divmod] 286 | assert [10003087 10003087 1 0 test-divmod] 287 | assert [10003087 1 10003087 0 test-divmod] 288 | assert [9 10 0 9 test-divmod] 289 | assert [0 3 0 0 test-divmod] 290 | 291 | prog div-impl [divmod drop] 292 | prog mod-impl [divmod swap drop] 293 | 294 | prog div [[div-impl] 'nat-div p-accel-opt apply] 295 | prog mod [[mod-impl] 'nat-mod p-accel-opt apply] 296 | 297 | prog gt-impl [sub 0b1 get drop] 298 | prog gte-impl [sub drop] 299 | 300 | # number comparisons 301 | # 302 | # gte - greater than or equal to 303 | # gt - greater than 304 | # lte - less than or equal to 305 | # lt - less than 306 | # 307 | # These operations fail if the comparison is not true, otherwise 308 | # drop two items from the data stack and continue. Consider 309 | # use of copy2 or `[gt] verify` or similar to keep the input. 310 | # 311 | prog gte [[gte-impl] 'nat-gte p-accel-opt apply] 312 | prog gt [[gt-impl] 'nat-gt p-accel-opt apply] 313 | prog lte [swap gte] 314 | prog lt [swap gt] 315 | 316 | prog test-gte [ 317 | [gte] verify 318 | [gte-impl] verify 319 | [lt] reject 320 | 321 | [[eq] reject] 322 | [ 323 | [gt] verify 324 | [gt-impl] verify 325 | [lte] reject 326 | ] 327 | [ 328 | [gt] reject 329 | [gt-impl] reject 330 | [lte] verify 331 | ] 332 | try-then-else 333 | drop2 334 | ] 335 | assert [9 8 test-gte] 336 | assert [8 8 test-gte] 337 | assert [12345 0 test-gte] 338 | assert [0x8000000000000000000000000000000000 339 | 0x8000000000000000000000000000000000 340 | test-gte 341 | ] 342 | assert [0x8000000000000000000010000000000000 343 | 0x8000000000000000000000000000000000 344 | test-gte 345 | ] 346 | 347 | 348 | # convert a fixed-width 'word' to a nat by removing 349 | # the leading zeroes prefix. This works for words of 350 | # any length, such as bytes or 32-bit words. 351 | prog of-word [bb/type-bits [0b0 get] loop type-nat] 352 | assert [0b0011 of-word 3 eq] 353 | 354 | # Nat Size to-word => UWord | Fail 355 | # 356 | # Convert a natural number to an unsigned word of a given size. 357 | # Fails if the natural number would be too large for the word. 358 | prog to-word [ 359 | [type-nat copy b/length] dip 360 | type-nat 361 | swap sub [0b0 b/prefix] repeat 362 | ] 363 | assert [10 8 to-word 0b00001010 eq] 364 | assert [128 8 to-word 0x80 eq] 365 | assert [255 8 to-word 0xFF eq] 366 | assert [[256 8 to-word] reject] 367 | 368 | export type-nat as type 369 | , repeat 370 | , increment, decrement 371 | , add, sub, mul, divmod, div, mod 372 | , gt, gte, lt, lte 373 | , of-word, to-word 374 | -------------------------------------------------------------------------------- /old-glas-src/posit/README.md: -------------------------------------------------------------------------------- 1 | # Posits 2 | 3 | The [posit (aka type III unum)](https://en.wikipedia.org/wiki/Unum_%28number_format%29#Unum_III) is a new format for floating point arithmetic, which is much better behaved than IEEE floating point in certain respects, albeit with much less hardware support at the moment. 4 | 5 | ## Posit Types 6 | 7 | Posit representations are essentially typed by two numbers: their bit length (n) and their maximum exponent size (es). I'll represent this type-level metadata as `posit:(n:Nat, es:Nat)`. Some typical values for these: 8 | 9 | posit:(n:8, es:0) 10 | posit:(n:16, es:1) 11 | posit:(n:32, es:2) 12 | posit:(n:64, es:3) 13 | 14 | Operations provided by this module may be parameterized by the `posit:(...)` descriptor, perhaps as a static parameter. However, I haven't entirely decided how I'm going to approach this, e.g. it might be preferable to implement posits within an accelerated virtual machine instead. 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /old-glas-src/prims/README.md: -------------------------------------------------------------------------------- 1 | # Primitives 2 | 3 | This module provides some basic functions for implementing other modules. However, it's much smaller than a proper standard library would be. No support for looping over lists or bitstrings or record fields, for example. 4 | 5 | -------------------------------------------------------------------------------- /old-glas-src/record/README.md: -------------------------------------------------------------------------------- 1 | # Record Utilities 2 | 3 | Records are a common data type in glas systems and are represented as radix trees. Labels are UTF-8 null-terminated text (or null-terminated binary) encoded into the bitstring path to each value. 4 | 5 | 'c' 0x63 6 | / 0 7 | \ 1 8 | \ 1 9 | / 0 10 | / 0 11 | / 0 12 | \ 1 13 | \ 1 14 | 15 | 16 | A null byte (0x00, or 8 sequential zero bits) separates the label from the embedded data. 17 | 18 | This module provides a few utilities for working with records, such as obtaining the list of labels, or converting between records and key-value lists. It also supports working with labels as values. 19 | 20 | Some related types: 21 | 22 | * *variant* - essentially a record with only a single label from a known set. This serves as the labeled sum type, where records are the labeled product type. 23 | * *dictionary* - represented as a record, but with dynamic labels and homogeneous data 24 | * *wordmaps* - uses fixed-length labels (e.g. 32-bit words) instead of null terminators; useful as a basis for hashmaps 25 | * *symbols* - just the utf-8 null-terminated bitstring representing a single label 26 | 27 | ## Runtime Encoding 28 | 29 | Due to compact encoding of bitstring fragments (see [bits](../bits/README.md)), records are radix trees by default. This representation is reasonably efficient but still involves significant allocation and access overheads. 30 | 31 | A compiler can potentially do much better, translating static record types into a 'struct' representation with a single allocation and efficient, offset-based access. Further, in-place updates are possible if the compiler knows a record is uniquely referenced. These performance features can be guided by annotations. 32 | 33 | Annotations can potentially elevate records into first-class data types in glas runtimes. 34 | 35 | ## Regarding Key-Value Maps 36 | 37 | It is feasible to support arbitrary key-value maps by encoding arbitrary glas values into bitstrings, but I'm not convinced this is a good idea. Viable alternatives include constructing a hashmap or a balanced binary search tree. 38 | -------------------------------------------------------------------------------- /old-glas-src/record/public.g0: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmbarbour/glas/a8edbf10759a75f9c8180e6caa1e4643839d8a36/old-glas-src/record/public.g0 -------------------------------------------------------------------------------- /old-glas-src/std/README.md: -------------------------------------------------------------------------------- 1 | # Glas Module 'std' 2 | 3 | This module should be a curated aggregator for a standard dictionary that is adequate for many uses of Glas. Avoid defining words directly within 'std', but don't hesitate to add static assertions for consistency checks. 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /old-glas-src/std/public.g0: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /old-glas-src/test-loop-perf/public.g0: -------------------------------------------------------------------------------- 1 | 2 | open prims 3 | import int as i 4 | import nat as n 5 | import list as l 6 | import bits as b 7 | 8 | # convert ascii byte '0' - '9' to natural number. 9 | # fails if byte is not in appropriate range. 10 | prog ascii-digit [ 11 | [0b0 get] loop # remove leading zero bits 12 | 48 n/sub # subtract 48 (ascii '0') 13 | [10 n/lt] verify # max result is 9 14 | ] 15 | 16 | prog to-ascii-digit [48 n/add 0b00 b/prefix] 17 | 18 | # convert a string such as "12345" to number 12345 19 | # (incomplete; doesn't support negatives) 20 | prog atoi [ 21 | [0] dip 22 | [ascii-digit [10 n/mul] dip n/add] l/each 23 | ] 24 | assert [ "1234567890" atoi 1234567890 eq ] 25 | assert [ ["123abc" atoi] reject ] 26 | 27 | # convert a non-negative integer to a string. 28 | prog itoa [ 29 | [ 0 eq ] 30 | [ "0" ] 31 | [ "" 32 | [[0 eq] dip] 33 | [[10 n/divmod to-ascii-digit] dip l/pushl] 34 | until-do 35 | ] try-then-else 36 | ] 37 | assert [1234567890 itoa "1234567890" eq] 38 | 39 | macro log-every-nth [ 40 | [n/mod 0 eq] p-curry p-verify 41 | [copy "step" log-info-v] p-try-then 42 | ] 43 | 44 | prog main [ 45 | ['init get] 46 | [ # on init, build a step:[Sum,Counter] pair. 47 | l/popl drop atoi # initial Counter from arg[0] 48 | [0] dip l2 # initial Sum is zero 49 | 'step d1 # prepared for next step 50 | ] 51 | [ # on each step:[Sum, Counter] 52 | # if Counter is 0, then print Sum and halt. 53 | # otherwise, print every 1000000th Counter, 54 | # add Counter to Sum 55 | # decrement Counter 56 | # continue to next step 57 | 'step get unl2 58 | [ 0 eq 59 | "sum" log-info-v 60 | 'halt 61 | ] 62 | [ 63 | 100000 log-every-nth 64 | copy 65 | [n/add] dip 66 | n/decrement 67 | l2 'step d1 68 | ] 69 | try-else 70 | ] 71 | try-then-else 72 | ] 73 | 74 | # variation that loops all at once 75 | prog interior [[['halt get] verify][main]until-do] 76 | 77 | prog fibonacci [ 78 | [0 eq] 79 | [0] 80 | [ n/decrement 81 | [0 1] dip 82 | [copy [swap] dip n/add] n/repeat 83 | [drop] dip 84 | ] try-then-else 85 | ] 86 | 87 | prog try-fib [ 88 | 'init get 89 | l/popl drop 90 | atoi 91 | fibonacci 92 | itoa 93 | "result" log-info-v 94 | 'halt 95 | ] 96 | 97 | export main, interior, try-fib 98 | -------------------------------------------------------------------------------- /old-glas-src/xyzzy/README.md: -------------------------------------------------------------------------------- 1 | Just some junk for testing the glas command line tool manually. -------------------------------------------------------------------------------- /old-glas-src/xyzzy/public.g0: -------------------------------------------------------------------------------- 1 | open prims 2 | import nat as n 3 | import int as i 4 | 5 | assert [ 6 | 1 10000 eff-nop [[i/decrement] n/repeat] 'tst1 p-prof-chan apply 7 | -9999 eq 8 | ] 9 | 10 | data hello ["Hello, World!"] 11 | 12 | prog write [ 'write d1 eff drop ] 13 | prog eol [ 0x0A l1 write ] 14 | 15 | prog test-hello [ "Hello" write ", " write "World!" write eol ] 16 | 17 | --------------------------------------------------------------------------------