Add some compiler features and documentation (#147)

* update doc

* add disassemble and type checking

* improve compiler error messages
This commit is contained in:
water111 2020-12-01 21:39:46 -05:00 committed by GitHub
parent 21fbdce7aa
commit 71dda76e2b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1533 additions and 351 deletions

View file

@ -108,16 +108,25 @@ void TextDb::link(const Object& o, std::shared_ptr<SourceText> frag, int offset)
/*!
* Given an object, get a string representing where it's from. Or "?" if we can't find it.
*/
std::string TextDb::get_info_for(const Object& o) {
std::string TextDb::get_info_for(const Object& o, bool* terminate_compiler_error) {
if (o.is_pair()) {
auto kv = map.find(o.heap_obj);
if (kv != map.end()) {
if (terminate_compiler_error) {
*terminate_compiler_error = kv->second.frag->terminate_compiler_error();
}
return get_info_for(kv->second.frag, kv->second.offset);
} else {
return "?";
if (terminate_compiler_error) {
*terminate_compiler_error = false;
}
return "?\n";
}
} else {
return "?";
if (terminate_compiler_error) {
*terminate_compiler_error = false;
}
return "?\n";
}
}

View file

@ -34,6 +34,10 @@ class SourceText {
virtual std::string get_description() = 0;
std::string get_line_containing_offset(int offset);
int get_line_idx(int offset);
// should the compiler keep looking up the stack when printing errors on this, or not?
// this should return true if the text source is specific enough so that they can find what they
// want
virtual bool terminate_compiler_error() { return true; }
virtual ~SourceText(){};
@ -87,7 +91,7 @@ class TextDb {
public:
void insert(const std::shared_ptr<SourceText>& frag);
void link(const Object& o, std::shared_ptr<SourceText> frag, int offset);
std::string get_info_for(const Object& o);
std::string get_info_for(const Object& o, bool* terminate_compiler_error = nullptr);
std::string get_info_for(const std::shared_ptr<SourceText>& frag, int offset);
private:

View file

@ -13,7 +13,7 @@
namespace versions {
// language version
constexpr s32 GOAL_VERSION_MAJOR = 0;
constexpr s32 GOAL_VERSION_MINOR = 2;
constexpr s32 GOAL_VERSION_MINOR = 3;
constexpr u32 ART_FILE_VERSION = 6;
constexpr u32 LEVEL_FILE_VERSION = 30;
constexpr u32 DGO_FILE_VERSION = 1;

View file

@ -173,7 +173,7 @@
(require-for-run uint32 :offset-assert 8)
(allow-to-run uint32 :offset-assert 12)
(next-pid int32 :offset-assert 16)
(fast-stack-top uint32 :offset-assert 20)
(fast-stack-top pointer :offset-assert 20)
(current-process basic :offset-assert 24)
(relocating-process basic :offset-assert 28)
(relocating-min int32 :offset-assert 32)
@ -10885,7 +10885,7 @@
(deftype load-chunk-msg (structure)
((rsvd uint16 :offset-assert 0)
(result uint16 :offset-assert 2)
(address uint32 :offset-assert 4)
(address pointer :offset-assert 4)
(section uint32 :offset-assert 8)
(maxlen uint32 :offset-assert 12)
(id uint32 :offset 4)

View file

@ -58,3 +58,7 @@
- Added bitfield types to the type system
- Added the ability to cast integers to bitfield types
- Fixed a bug where casting between integer types with `the` that did not involve emitting code would permanently change the type of the variable.
- Added a `:disassemble` option to `asm-file` to disassemble functions for debugging purposes.
## V0.3
- Added typechecking when setting fields of a type.

View file

@ -28,13 +28,15 @@ Needs child types of integer
## vu-macros.gc
Empty.
# Math Section
## math.gc
Has a unit test for a lot of functions.
rand-vu-init, rand-vu, rand-vu-nostep, rand-vu-float-range, rand-vu-percent?, rand-vu-int-range, rand-vu-int-count aren't implemented
rand-uint31-gen might be wrong.
## vector.gc
## vector-h.gc
Partially done
## gravity-h.gc
@ -63,3 +65,17 @@ Empty File.
## transformq-h.gc
Not done.
## bounding-box.gc
## matrix.gc
## transform.gc
## quaternion.gc
## euler.gc
## geometry.gc
## trigonometry.gc

View file

@ -1,12 +1,5 @@
# OpenGOAL Document
This is the main documentation for the OpenGOAL language. It explains the syntax of OpenGOAL programs and also how OpenGOAL can be decompiled from original GOAL. It's broken up into three sections:
1. Compiler forms, which are things built-in to the compiler and have names. Like `+` or `deftype`.
2. Important Syntax Macros, which are really important features of the languages that are implemented as macros, like `if` and `defun`.
3. Compiler features, which are things that are built-in to the compiler but don't have explicit names. Like calling functions or the rules for how names are scoped.
4. Built-in types
Each feature will have a description, explanation of the syntax, a guess at how often it's used, an example or two in OpenGOAL, and an example or two in MIPS GOAL. There will also be details on the order of evaluation that is useful for the decompiler but can mostly be ignored for normal programming in OpenGAL.
This is the main documentation for the OpenGOAL language. It's designed to be read in order to learn OpenGOAL. It does not explain the OpenGOAL kernel or state system.
The syntax description uses these rules:
- Something `[in-brackets]` is optional and can be left out.
@ -14,7 +7,507 @@ The syntax description uses these rules:
- When there are multiple choices, they are separated by `|`. Example: `#t|#f` is either `#t` or `#f`.
- A `...` means more of the thing before can be included. Example `(f arg...)` can have multiple arguments.
When talking about ordering things, GOAL code fragments can be `compile`d and `flush`ed as two separate things. For the most part, everything is done during compilation, like calling functions. But consider `(set! (-> my-object value) x)`. We don't actually want the value of `(-> my-object value)`, we want to set its value. The `compile` stage gets us description of how to read/write a thing (if possible), and the `flush` stage gets us an actual value in a register. This can basically be ignored outside of the decompiler.
# All Forms
Documented forms are crossed out.
- ~~asm-file~~
- ~~m~~
- ~~ml~~
- ~~md~~
- ~~build-game~~
- ~~build-data~~
- ~~blg~~
- tc
- ~~e~~
- db (debug mode)
- #when
- #unless
- ~~lt~~
- ~~r~~
- ~~shutdown-target~~
- db (disassemble byte)
- dh
- dw
- dd
- df
- segfault
- fpe
- let
- let*
- ~~defun~~
- while
- until
- dotimes
- protect
- +!
- ~~if~~
- ~~when~~
- ~~unless~~
- and
- or
- +1
- +!
- -!
- *!
- 1-
- zero?
- &+!
- &-
- &->
- basic?
- pair?
- binteger?
- rtype-of
- cons
- list
- null?
- caar
- object-new
- expect-eq
- expect-true
- expect-false
- start-test
- finish-test
- top-level
- ~~begin~~
- ~~block~~
- ~~return-from~~
- ~~label~~
- ~~goto~~
- gs
- :exit
- ~~asm-file~~
- ~~asm-data-file~~
- listen-to-target
- reset-target
- :status
- ~~in-package~~
- ~~#cond~~
- ~defglobalconstant~
- ~~seval~~
- ~~cond~~
- ~~when-goto~~
- ~~define~~
- ~~define-extern~~
- ~~set!~~
- dbs
- dbg
- :cont
- :break
- :dump-all-mem
- :pm
- :di
- :disasm
- :bp
- :ubp
- deftype
- ~~defmethod~~
- ->
- &
- the-as
- the
- print-type
- new
- car
- cdr
- method
- declare-type
- none
- ~~lambda~~
- ~~declare~~
- ~~inline~~
- ~~quote~~
- ~~mlet~~
- defconstant
- ~~+~~
- ~~-~~
- ~~*~~
- ~~/~~
- ~~shlv~~
- ~~shrv~~
- ~~sarv~~
- ~~mod~~
- ~~logior~~
- ~~logxor~~
- ~~logand~~
- ~~lognot~~
- ~~=~~
- ~~!=~~
- ~~eq?~~
- ~~neq?~~
- ~~not~~
- ~~<=~~
- ~~>=~~
- ~~<~~
- ~~>~~
- &+
- ~~build-dgos~~
- ~~set-config!~~
- set-config!
# Language Basics
OpenGOAL is a compiled language. Source code is stored in `.gc` files. Each `.gc` file is compiled into a `.o` file. These `.o` files are then loaded by the game. When they are loaded, it has the effect of running every "top level" expression in the file. Usually these are function, type, and method declarations, but you can also use this for initialization code. For example, it is common to first define types, functions, and methods, then set up global instances.
There are effectively three different "languages":
1. OpenGOAL - the normal compiled language.
2. OpenGOAL compiler commands - simple commands to run the compiler, listener, and debugger. These run in the compiler only.
3. GOOS macro language. This is used in OpenGOAL macros and runs at compile-time. These macros generate OpenGOAL compiler commands or OpenGOAL source which is then processed. These run in the compiler only.
The OpenGOAL language uses a LISP syntax, but on the inside is closer to C or C++. There is no protection against use-after-free or other common pointer bugs.
Unlike a C/C++ compiler, the OpenGOAL compiler has a state. It remembers functions/methods/types/macros/constants/enums/etc defined in previous files.
# Compiler REPL
When you start the OpenGOAL compiler, you'll see a prompt like this:
```
OpenGOAL Compiler 0.2
g >
```
The `g` indicates that you can input OpenGOAL compiler commands. For example:
***
### `(e)` - Compiler Command
Exit Compiler
```lisp
(e)
```
Exit the compiler once the current REPL command is finished. Takes no arguments. If we are connected to a target through the listener, attempts to reset the target.
***
### `(:exit)` - Compiler Command
Exit Compiler
```lisp
(:exit)
```
Same as `(e)`, just requires more typing. `(e)` is actually a macro for `(:exit)`. Takes no arguments.
***
### `(lt)` - Compiler Command
Listen to Target
```lisp
(lt ["ip address"] [port-number])
```
Connect the listener to a running target. The IP address defaults to `"127.0.0.1"` and the port to `8112` (`DECI2_PORT` in `listener_common.h`). These defaults are usually what you want, so you can just run `(lt)` to connect.
Example:
```lisp
g > (lt)
[Listener] Socket connected established! (took 0 tries). Waiting for version...
Got version 0.2 OK!
[Debugger] Context: valid = true, s7 = 0x147d24, base = 0x2000000000, tid = 1296302
gc >
```
***
### `r`
Reset the target.
```lisp
(r ["ip address"] [port-number])
```
Regardless of the current state, attempt to reset the target and reconnect. After this, the target will have nothing loaded. Like with `(lt)`, the default IP and port are probably what you want.
Note: `r` is actually a macro.
***
### `shutdown-target`
If the target is connected, make it exit.
```lisp
(shutdown-target)
```
The target will print
```
GOAL Runtime Shutdown (code 2)
```
before it exits.
***
### `:status`
Ping the target.
```lisp
(:status)
```
Send a ping-like message to the target. Requires the target to be connected. If successful, prints nothing. Will time-out and display and error message if the GOAL kernel or code dispatched by the kernel is stuck in an infinite loop. Unlikely to be used often.
***
## Connecting To Target Example
```lisp
;; we cannot execute OpenGOAL code unless we connect the listener
g > (+ 1 2 3)
REPL Error: Compilation generated code, but wasn't supposed to
;; connect to the target
g > (lt)
[Listener] Socket connected established! (took 0 tries). Waiting for version...
Got version 0.2 OK!
[Debugger] Context: valid = true, s7 = 0x147d24, base = 0x2000000000, tid = 1297692
;; execute OpenGOAL code
gc > (+ 1 2 3)
6
;; quit the compiler and reset the target for next time.
gc > (e)
[Listener] Closed connection to target
```
Once we are connected, we see that there is a `gc` prompt. This indicates that the listener has an open socket connection. Now the REPL will accept both compiler commands and OpenGOAL source code. All `(format #t ...` debugging prints (like `printf`) will show up in this REPL. Each time you run something in the REPL, the result is printed as a decimal number. If the result doesn't make sense to print as a decimal, or there is no result, you will get some random number.
In the future there will be a fancier printer here.
# OpenGOAL Type System
OpenGOAL has a type system. Every expression and object in OpenGOAL has a type. With the exception of three special types (`none`, `_varags_`, and `_types_`), every type has a parent type, and the root of all types is `object`. Types themselves are objects in the runtime that contain some basic information and their method table.
One annoying detail of OpenGOAL is that the type system has some slightly different types for variables and places in memory, and automatic conversions between them.
Another annoying detail is that there are a totally separate set of rules for 128-bit integer types (or children of these). Nothing in this section applies to these.
Some types are boxed vs. unboxed. If you have an object of boxed type, it is possible to figure out its type at runtime. If you have an object of unboxed type, you can't. If you have an unboxed type, you can't tell if it's a boxed or unboxed object.
Some types are value or reference. A value type means it has value semantics, it is passed by value everywhere. A reference type is like a C/C++ pointer or reference, where there is memory allocated for the data somewhere, and the you just pass around a reference to this memory.
## Built-in Types
There are a number of built-in types. I use "abstract" type to refer to a type that is only a parent type .
### `none`
This is a special type that represents "no information". This is the return type of a function which returns nothing, and also the return type of an expression that doesn't return anything. For example, the expression `(goto x)` does not produce a value, so its type is `none`.
### `object`
This is the parent type of all types. This is an abstract class. In a variable, this is always `object`, and can hold any `object`. In memory, this is either `object32` or `object64`. The `object32` can hold everything except for `binteger` and 64-bit integers. This type is neither boxed nor unboxed and is neither value nor reference.
### `structure` (child of `object`)
This is the parent type of all types with fields. This is an abstract class and a reference class. A `structure` can hold any `structure`, both in memory and in a variable. It is unboxed.
### `basic` (child of `structure`)
This is the "boxed" version of `structure`. The first field of a basic is `type`, which contains the `type` of the object. It is boxed and a reference. A `basic` can hold any `basic`, both in memory and in a variable.
### `symbol` (child of `basic`)
A symbol has a name and a value. The name is a string, and the value is an `object32`. Note that the value is an `object32` so you cannot store a 64-bit integer in a symbol. It is considered "bad" to store unboxed objects in symbols, though you can get away with it sometimes.
All `symbol`s are stored in the global symbol table, which is a hash table. As a result, you cannot have multiple symbols with the same name. A name is enough to uniquely determine the symbol. To get a symbol, use the syntax `'symbol-name`. To get the value, use `symbol-name`.
Each global variable, type, and named global function has a symbol for it which has the variable, type, or function as its value. The linker is able to perform symbol table lookups at link time and patch the code so you don't have to do a hash table lookup every time you access a global variable, function, or type.
You can also use symbols as a efficient way to represent a enum. For example, a function may return `'error` or `'complete` as a status. The compiler is able to compare symbols for equality very efficiently (just a pointer comparison, as symbols are a reference type).
### `type` (child of `basic`)
A `type` stores information about an OpenGOAL type, including its size, parent, and name (stored as a `symbol`). It also stores the method table. Some OpenGOAL types (children of integers, bitfield types, enums, compounds types) do not have runtime types, and instead become the parent/base type. But these types cannot have runtime type information or methods and are pretty rare. It is a reference type, is boxed, and is dynamically sized (the method table's size is not fixed).
### `string` (child of `basic`)
A string. The string is null terminated and also stores the buffer size. This type is a reference type, is boxed, and is also dynamically sized.
### `function` (child of `basic`)
A function. Boxed and reference. It is a reference to a function, so it's like a C/C++ function pointer type.
### `kheap` (child of `structure`)
A simple bump-allocated heap. Doesn't store the heap memory, just metadata. Supports allocating from either the top or the bottom. This is used as the memory allocation strategy for the global, debug, and level heaps. Unboxed, reference, not dynamic.
### `array` (child of `basic`)
A "fancy" array. It is not yet implemented in OpenGOAL.
### `pair` (child of `object`)
A pair. It is boxed. You should not make child types of `pair`. The two objects stored by the pair are `object32`s.
### `pointer` (child of `object`)
It is a 32-bit value type containing a pointer. Not boxed, value type. See section on compound types for more information.
### `number` (child of `object`)
Abstract type for all numbers. Value type. 64-bits.
### `float` (child of `number`)
4-byte, single precision floating point number. Value type.
### `integer` (child of `number`)
Abstract class for integer numbers. Child classes are `sinteger` (signed integer), `uinteger` (unsigned integer), and `binteger` (boxed-integer, always signed). These are all 64-bit types.
Children of `sinteger` and `uinteger` are `int8`, `int16`, `int32`, `int64`, `uint8`, `uint16`, `uint32`, `uint64`. These are the size you expect, value types, and not boxed. These only exist as memory types. In a variable, there is only `int` and `uint`. These are both 64-bit types. All integer operations (math, logical, shifts) are 64-bit.
The `binteger` is a boxed integer. It is a 61 bit signed integer (the other three bits are lost due to the number being boxed). There may actually be a `buinteger` (or `ubinteger`) but it doesn't exist in OpenGOAL at this point.
### Weird Built-in types that aren't supported yet.
- `vu-function`
- `link-block`
- `connectable`
- `file-stream`
- `inline-array` (class)
## Compound Types
A compound type is a type like "a pointer to an int64" or "a function which takes int as an argument and returns a string". These exist only at compile time, and get simplified at runtime. For example, all pointers become `pointer` and all functions become `function`. (The one exception to this seems to be `inline-array-class` stuff, but this is not yet supported in OpenGOAL).
### Pointer
Pointers work like you would expect. They can only point to memory types - you can't have a `(pointer int)`, instead you must have a `(pointer int32)` (for example). Note that a `(pointer basic)` is like a C++ `basic**` as `basic` is already like a C++ pointer to struct. You can nest these, like `(pointer (pointer int64))`. If you want a pointer with no type, (like C++ `void*`) just use a plain `pointer`. The `(pointer none)` type is invalid.
Like in C/C++, you can use array indexing with a pointer. One thing to note is that a `(pointer basic)` (or pointer to any reference type) is like a C++ "array of pointers to structs". To get the C++ "array of structs", you need an `inline-array`.
### Inline Array
These are only valid for reference types. They refer to an array of the actual data (like C array of structs) rather than an array of reference (like C array of pointers to structs, or GOAL `(pointer structure)`). At runtime, `inline-array` becomes pointer.
For an inline array of basics, elements are 16-byte aligned. For `structure`s that aren't `basic`, the alignment is usually the minimum alignment of all members of the structure, but there is an option to make it 16-byte aligned if needed.
For information about how to create these arrays, see `deftype` (fields in a type) and `new` (create just an array) sections.
### Function
Function compound types look like this `(function arg0-type arg1-type... return-type)`. There can be no arguments. The `return-type` must always be specified, and should be `none` if there is no return value. The argument types themselves can be compound types. In order to call a function, you must have a compound function type - a `function` by itself cannot be called.
## Field Definitions
GOAL field definitions look like this:
`(name type-name [optional stuff])`
where optional stuff can include these, in any order:
- `:inline #t` (default is false), to mark field as inline. This can only be done for a reference type, and indicates that the data should be stored inline, in the type, rather than just storing a reference to data stored elsewhere.
- `:dynamic #t` (default is false), to mark field as dynamically-sized array (must be the last field in the type)
- a number, to give an array size.
- `:offset x` where x is a number, to manually specify where the field is located
There are many combinations of reference/value, dynamic/not-dynamic, inline/not-inline, array-size/no-array-size, and it can be confusing. This list explains all that are valid.
- Value type, no modifiers: a single value is stored in the field. The field type is the value type.
- Value type, `:dynamic #t`: the field marks the beginning of an array (of unknown size). Field type is `(pointer your-type)`
- Value type, with array size: the field marks the beginning of an array (of known size). Field type is `(pointer your-type)`
- Value type, with `:inline #t`: invalid in all cases.
- Reference type, no modifiers: a single reference is stored in the type. Type of field is `your-type` (a C++ pointer).
- Reference type, `:inline #t`: a single object is stored inside the type. Type of field is `your-type` still (a C++ pointer). The access logic is different to make this work.
- Reference type, `:dynamic #t` or array size: the field marks the beginning of an **array of references**. Field type is `(pointer your-type)`. Like C array of pointers.
- Reference type, `:inline #t` and (`:dynamic #t` or array size): the field marks the beginning of an **array of inline objects**. Field type is `(inline-array your-type)`. Like C array of structs.
Bonus ones, for where the array is stored _outside_ of the type:
- A dynamically typed GOAL array, stored outside your type (think `std::vector`): use `(name (array your-type))`
- A dynamically type GOAL array, stored inside your type: Not allowed, `array` is dynamic!
- An array of value types, stored outside your type: use `(name (pointer your-type))`
- An array of references (C++ array of pointers), stored outside your type: use `(name (pointer your-ref-type))`
- An array of objects of reference type (C++ array of structs), stored outside your type: use `(name (inline-array your-ref-type))`
Of course, you can combine these, to get even more confusing types! But this seems uncommon.
## Dynamic Size Types
Any type which ends with a dynamic array as the last field is dynamic. For these, it's a good idea to implement the `asize-of` method.
# OpenGOAL Method System
OpenGOAL has a virtual method system. This means that child types can override parent methods. The first argument to a method is always the object the method is being called on, except for `new`.
## Special Method Type: `_type_`
Methods have the same type as `function`. But they are allowed to use the special type `_type_`, which means "the compile-time type of the object the method is being called on". The type system is flexible with allowing you to use `_type_` in the method declaration in `deftype`, but not using `_type_` in the actual `defmethod`.
## Built in Methods
All types have these 9 methods. They have reasonable defaults if you don't provide anything.
### `new`
The new method is a very special method used to construct a new object, like a constructor. Note that some usages of the `new` keyword __do not__ end up calling the new method. See the `new` section for more details. Unlike C++, fields of a type and elements in an array are not constructed either.
The first argument is an "allocation", indicating where the object should be constructed. It can be
- The symbol `'global` or `'debug`, indicating the global or debug heaps
- The symbols `'process-level-heap` or `'loading-level`, indicating whatever heaps are stored in those symbols.
- `'process`, indicating the allocation should occur on the current process heap.
- `'scratch`, for allocating on the scratchpad. This is unused.
- Otherwise it's treated as a 16-byte aligned address and used for in place construction (it zeros the memory first)
The second argument is the "type to make". It might seem stupid at first, but it allows child classes to use the same `new` method as the parent class.
The remaining arguments can be used for whatever you want.
When writing your own `new` methods, you should ignore the `allocation` argument and use the `object-new` macro to actually do the allocation. This takes care of all the details for getting the memory (and setting up runtime type information if its a basic). See the section on `object-new` for more details.
### `delete`
This method isn't really used very much. Unlike a C++ destructor it's never called automatically. In some cases, it's repurposed as a "clean up" type function but it doesn't actually free any memory. It takes no arguments. The default implementations call `kfree` on what the allocation, but there are two issues:
1. The implementation is sometimes wrong, likely confusing doing pointer math (steps by array stride) with address math (steps by one byte).
2. The `kfree` function does nothing.
The `kheap` system doesn't really support freeing objects unless you free in the opposite order you allocate, so it makes sense that `delete` doesn't really work.
### `print`
This method should print out a short description of the object (with no newlines) and return the object. The printing should be done with `(format #t ...)` (see the section on `format`) for more information. If you call `print` by itself, it'll make this description show up in the REPL. (Note that there is some magic involved to add a newline here... there's actually a function named `print` that calls the `print` method and adds a newline)
The default short description looks like this: `#<test-type @ #x173e54>` for printing an object of type `test-type`. Of course, you can override it with a better version. Built-in types like string, type, boxed integer, pair, have reasonable overrides.
This method is also used to print out the object with `format`'s `~A` format option.
### `inspect`
This method should print out a detailed, multi-line description. By default, `structure`s and `basic`s will have an auto-generated method that prints out the name and value of all fields. For example:
```
gc > (inspect *kernel-context*)
[00164b44] kernel-context
prevent-from-run: 65
require-for-run: 0
allow-to-run: 0
next-pid: 2
fast-stack-top: 1879064576
current-process: #f
relocating-process: #f
relocating-min: 0
relocating-max: 0
relocating-offset: 0
low-memory-message: #t
```
In some cases this method is overridden to provide nicer formatting.
### `length`
This method should return a "length". The default method for this just returns 0, but for things like strings or buffers, it could be used to return the number of characters or elements in use. It's usually used to refer to how many are used, rather than the capacity.
### `asize-of`
This method should return the size of the object. Including the 4 bytes of type info for a `basic`.
By default this grabs the value from the object's `type`, which is only correct for non-dynamic types. For types like `string` or other dynamic types, this method should be overridden. If you intend to store dynamically sized objects of a given type on a process heap, you __must__ implement this method accurately.
### `copy`
Creates a copy of the object. I don't think this used very much. Just does a `memcpy` to duplicate by default.
### `relocate`
The exact details are still unknown, but is used to update internal data structures after an object is moved in memory. This must be support for objects allocated in process heaps of processes allocated on the actor heap or debug actor heap.
It's also called on objects loaded from a GOAL data object file.
### `mem-usage`
Not much is known yet, but used for computing memory usage statistics.
## Details on the Order of Overrides
The order in which you `defmethod` and `deftype` matters.
When you `deftype`, you copy all methods from the parent. When you `defmethod`, you always set a method in that type. You may also override methods in a child if: the child hasn't modified that method already, and if you are in a certain mode. This is a somewhat slow process that involves iterating over the entire symbol table and every type in the runtime, so I believe it was disabled when loading level code, and you just had to make sure to `deftype` and `defmethod` in order.
Assume you have the type hierarchy where `a` is the parent of `b`, which is the parent of `c`.
If you first define the three types using `deftype`, then override a method from `a` on `c`, then override that same method on `b`, then `c` won't use the override from `b`.
If you first define the three types using `deftype`, then override a method on `b`, it will _sometimes_ do the override on `c`. This depends on the value of the global variable `*enable-method-set*`, and some other confusing options. It may also print a warning but still do the override in certain cases.
# Syntax Basics
An "atom" in Lisp is a form that can't be broken down into smaller forms. For example `1234` is an atom, but `(1234 5678)` is not. OpenGOAL supports the following atoms:
## Integers
All integers are by default `int`, a signed 64-bit integer. You can use:
- decimal: Like `123` or `-232`. The allowable range is `INT64_MIN` to `INT64_MAX`.
- hex: Like `#x123`. The allowable range is `0` to `UINT64_MAX`. Values over `INT64_MAX` will wrap around.
- binary: Like `#b10101010`. The range is the same as hex.
- character:
- Most can be written like `#\c` for the character `c`.
- Space is `#\\s`
- New Line is `#\\n`
- Tab is `#\\t`
## String
A string generates a static string constant. Currently the "const" of this string "constant" isn't enforced. Creating two identical string constants creates two different string objects, which is different from GOAL and should be fixed at some point.
The string data is in quotes, like in C. The following escapes are supported:
- Newline: `\n`
- Tab: `\t`
- The `\` character: `\\`
- The `"` character: `\"`
- Any character: `\cXX` where `XX` is the hex number for the character.
## Float
Any number constant with a decimal in it. The trailing and leading zeros and negative sign is flexible, so you can do any of these:
- `1.`, `1.0`, `01.`, `01.0`
- `.1`, `0.1`, `.10`, `0.10`
- `-.1`, `-0.1`, `-.10`, `-0.10`
Like string, it creates a static floating point constant. In later games the float was inlined instead of being a static constant.
## Symbol
Use `symbol-name` to get the value of a symbol and `'symbol-name` to get the symbol object.
## Comments
Use `;` for line comments and `#|` and `|#` for block comments.
# Compiler Forms - Block Related
@ -36,8 +529,6 @@ Example:
```
will print `hello world!` and the value of the entire form is `7`.
In `begin` and similar "do everything in the list" forms, each form is `compile`d then `flush`ed.
The `begin` form is used a lot in macros, but not that much in code. It's generally used when you want to execute multiple things, but fit it into a single form.
@ -112,17 +603,8 @@ The `goto` form used very rarely outside of macros and inline assembly. Try to a
## `top-level`
This form is reserved by the compiler. Internally all forms in a file are grouped under a `top-level` form, so you may see this in error messages. Do not name anything `top-level`.
# Compiler Forms - Compiler Control
These forms are used to control the GOAL compiler, and are usually entered at the GOAL REPL, or as part of a macro that's executed at the GOAL REPL. These shouldn't really be used in GOAL source code.
## `:exit`
This causes the compiler to exit after the current REPL command is over.
```lisp
(:exit)
```
If the listener is connected, it sends a reset command to reboot the target so it is ready for the next compiler connection.
There's a useful macro `(e)` to exit with less typing. Obviously this shouldn't be used in game code.
# Compiler Forms - Compiler Commands
These forms are used to control the GOAL compiler, and are usually entered at the GOAL REPL, or as part of a macro that's executed at the GOAL REPL. These shouldn't be used in GOAL source code.
## `seval`
Execute GOOS code.
@ -138,56 +620,26 @@ Compile a file.
```
This runs the compiler on a given file. The file path is relative to the `jak-project` folder. These are the options:
- `:color`: run register allocation and code generation. Can be omitted if you don't want actually generate code. Usually you want this option.
- `:write`: write the object file to the `data` folder. You must also have `:color` on. You must do this to include this file in a DGO.
- `:load`: send the object file to the target with the listener. Requires `:color` but not `:write`. There may be issues with `:load`ing very large object files.
- `:write`: write the object file to the `out/obj` folder. You must also have `:color` on. You must do this to include this file in a DGO.
- `:load`: send the object file to the target with the listener. Requires `:color` but not `:write`. There may be issues with `:load`ing very large object files (believed fixed).
- `:disassemble`: prints a disassembly of the code by function. Currently data is not disassebmled. This code is not linked so references to symbols will have placeholder values like `0xDEADBEEF`. The IR is printed next to each instruction so you can see what symbol is supposed to be linked. Requires `:color`.
- `:no-code`: checks that the result of processing the file generates no code or data. This will be true if your file contains only macros / constant definition. The `goal-lib.gc` file that is loaded by the compiler automatically when it starts must generate no code. You can use `(asm-file "goal_src/goal-lib.gc" :no-code)` to reload this file and double check that it doesn't generate code.
To reduce typing, there are some useful macros:
- `(m "filename")` is "make" and does a `:color` and `:write`.
- `(ml "filename")` is "make and load" and does a `:color` and `:write` and `:load`. This effectively replaces the previous version of file in the currently running game with the one you just compiled, and is a super useful tool for quick debugging/iterating.
- `(md "filename")` is "make debug" and does a `:color`, `:write`, and `:disassemble`. It is quite useful for working on the compiler and seeing what code is output.
- `(build-game)` does `m` on all game files and rebuilds DGOs
- `(blg)` (build and load game) does `build-game` then sends commands to load KERNEL and GAME CGOs. The load is done through DGO loading, not `:load`ing individual object files.
## `lt`
Listen to target.
## `asm-data-file`
Build a data file.
```lisp
(lt ["ip address"] [port-number])
(asm-data-file tool-name "file-name")
```
This causes the compiler to connect to the target/runtime. Usually it's just run as `(lt)`, as the default IP is `127.0.0.1` and the default port is the right one. If it works, you should see something like this:
```
[Listener] Socket connected established! (took 0 tries). Waiting for version...
Got version 2.6 OK!
[OUTPUT] reset #x147d24
```
The `OUTPUT` message is a pending message from the runtime saying that it has reset and the location of the symbol table.
The `tool-name` refers to which data building tool should be used. For example, this should be `game-text` when building the game text data files.
Note: `lt` is actually a macro. Use these target control macros over the direct compiler forms (currently undocumented) whenever possible, as running the compiler forms in the wrong order can leave the target/compiler in a strange state.
## `r`
Reset the target.
```lisp
(r)
```
Regardless of the current state, attempt to reset the target and reconnect.
Note: `r` is actually a macro. Use it over the (currently undocumented) compiler forms.
## `shutdown-target`
If the target is connected, shut it down.
```lisp
(shutdown-target)
```
The target will print
```
GOAL Runtime Shutdown (code 2)
```
when it shuts down.
## `:status`
Ping the target.
```lisp
(:status)
```
Send a ping-like message to the target. Requires the target to be connected. If successful, prints nothing. Will time-out if the GOAL kernel or code dispatched by the kernel is stuck in an infinite loop. Unlikely to be used often.
There's a macro `(build-data)` which rebuilds everything.
## `gs`
Enter a GOOS REPL.
@ -222,6 +674,8 @@ The compiler ignores this. GOAL files evidently start with this for some reason
```
Builds all the DGO files described in the DGO description file. See `goal_src/builds/dgos.txt` for an example. This just packs existing things into DGOs - you must have already built all the dependencies.
In the future, this may become part of `asm-data-file`.
# Compiler Forms - Control Flow
## GOAL "Two Element" Conditions
@ -1038,3 +1492,8 @@ Any other codes will result in an error message being printed.
## Load Stuff
# GOOS
# Built-in Functions
# Compiler Start Procedure
# Inline Functions

View file

@ -34,7 +34,7 @@ struct RPC_Dgo_Cmd {
(deftype load-chunk-msg (structure)
((rsvd uint16 :offset-assert 0)
(result uint16 :offset-assert 2)
(address uint32 :offset-assert 4)
(address pointer :offset-assert 4)
(section uint32 :offset-assert 8)
(maxlen uint32 :offset-assert 12)
(id uint32 :offset 4)

View file

@ -191,7 +191,7 @@
;; method 14
(defmethod pop-last-received rpc-buffer-pair ((obj rpc-buffer-pair))
(let ((result (-> obj last-recv-buffer)))
(set! (-> obj last-recv-buffer) (the rpc-buffer '#f))
(set! (-> obj last-recv-buffer) (the pointer '#f))
result
)
)

View file

@ -17,6 +17,12 @@
`(asm-file ,file :color :write)
)
;; compile, color, save, and disassemble a file.
;; make "debug".
(defmacro md (file)
`(asm-file ,file :color :write :disassemble)
)
;; compile, color, load and save a file
(defmacro ml (file)
`(asm-file ,file :color :load :write)

View file

@ -89,7 +89,7 @@
(require-for-run uint32 :offset-assert 8)
(allow-to-run uint32 :offset-assert 12)
(next-pid int32 :offset-assert 16)
(fast-stack-top uint32 :offset-assert 20)
(fast-stack-top pointer :offset-assert 20)
(current-process basic :offset-assert 24)
(relocating-process basic :offset-assert 28)
(relocating-min int32 :offset-assert 32)

View file

@ -4,6 +4,7 @@
#include "IR.h"
#include "goalc/regalloc/allocate.h"
#include "third-party/fmt/core.h"
#include "CompilerException.h"
#include <chrono>
#include <thread>
@ -129,27 +130,45 @@ std::unique_ptr<FunctionEnv> Compiler::compile_top_level_function(const std::str
Val* Compiler::compile_error_guard(const goos::Object& code, Env* env) {
try {
return compile(code, env);
} catch (std::runtime_error& e) {
printf(
"------------------------------------------------------------------------------------------"
"-\n");
} catch (CompilerException& ce) {
if (ce.print_err_stack) {
auto obj_print = code.print();
if (obj_print.length() > 80) {
obj_print = obj_print.substr(0, 80);
obj_print += "...";
}
bool term;
fmt::print(fg(fmt::color::yellow) | fmt::emphasis::bold, "Location:\n");
fmt::print(m_goos.reader.db.get_info_for(code, &term));
fmt::print(fg(fmt::color::yellow) | fmt::emphasis::bold, "Code:\n");
fmt::print("{}\n", obj_print);
if (term) {
ce.print_err_stack = false;
}
std::string line(80, '-');
line.push_back('\n');
fmt::print(line);
}
throw ce;
}
catch (std::runtime_error& e) {
auto obj_print = code.print();
if (obj_print.length() > 80) {
obj_print = obj_print.substr(0, 80);
obj_print += "...";
}
printf("object: %s\nfrom : %s\n", obj_print.c_str(),
m_goos.reader.db.get_info_for(code).c_str());
fmt::print(fg(fmt::color::yellow) | fmt::emphasis::bold, "Location:\n");
fmt::print(m_goos.reader.db.get_info_for(code));
fmt::print(fg(fmt::color::yellow) | fmt::emphasis::bold, "Code:\n");
fmt::print("{}\n", obj_print);
std::string line(80, '-');
line.push_back('\n');
fmt::print(line);
throw e;
}
}
void Compiler::throw_compile_error(const goos::Object& o, const std::string& err) {
gLogger.log(MSG_ERR, "[Error] Could not compile %s!\nReason: %s\n", o.print().c_str(),
err.c_str());
throw std::runtime_error(err);
}
void Compiler::ice(const std::string& error) {
gLogger.log(MSG_ICE, "[ICE] %s\n", error.c_str());
throw std::runtime_error("ICE");
@ -185,6 +204,18 @@ std::vector<u8> Compiler::codegen_object_file(FileEnv* env) {
return gen.run();
}
bool Compiler::codegen_and_disassemble_object_file(FileEnv* env,
std::vector<u8>* data_out,
std::string* asm_out) {
auto debug_info = &m_debugger.get_debug_info_for_object(env->name());
debug_info->clear();
CodeGenerator gen(env, debug_info);
*data_out = gen.run();
bool ok = false;
*asm_out = debug_info->disassemble_all_functions(&ok);
return ok;
}
std::vector<std::string> Compiler::run_test_from_file(const std::string& source_code) {
try {
if (!connect_to_target()) {

View file

@ -12,6 +12,9 @@
#include "goalc/compiler/IR.h"
#include "goalc/debugger/Debugger.h"
#include "CompilerSettings.h"
#include "third-party/fmt/core.h"
#include "third-party/fmt/color.h"
#include "CompilerException.h"
enum MathMode { MATH_INT, MATH_BINT, MATH_FLOAT, MATH_INVALID };
@ -27,7 +30,6 @@ class Compiler {
Env* env);
Val* compile(const goos::Object& code, Env* env);
Val* compile_error_guard(const goos::Object& code, Env* env);
void throw_compile_error(const goos::Object& o, const std::string& err);
void ice(const std::string& err);
None* get_none() { return m_none.get(); }
@ -64,7 +66,7 @@ class Compiler {
Val* compile_symbol(const goos::Object& form, Env* env);
Val* compile_string(const goos::Object& form, Env* env);
Val* compile_string(const std::string& str, Env* env, int seg = MAIN_SEGMENT);
Val* compile_get_symbol_value(const std::string& name, Env* env);
Val* compile_get_symbol_value(const goos::Object& form, const std::string& name, Env* env);
Val* compile_function_or_method_call(const goos::Object& form, Env* env);
Val* get_field_of_structure(const StructureType* type,
@ -75,6 +77,9 @@ class Compiler {
SymbolVal* compile_get_sym_obj(const std::string& name, Env* env);
void color_object_file(FileEnv* env);
std::vector<u8> codegen_object_file(FileEnv* env);
bool codegen_and_disassemble_object_file(FileEnv* env,
std::vector<u8>* data_out,
std::string* asm_out);
void for_each_in_list(const goos::Object& list,
const std::function<void(const goos::Object&)>& f);
@ -130,14 +135,22 @@ class Compiler {
bool is_integer(const TypeSpec& ts);
bool is_binteger(const TypeSpec& ts);
bool is_singed_integer_or_binteger(const TypeSpec& ts);
Val* number_to_integer(Val* in, Env* env);
Val* number_to_float(Val* in, Env* env);
Val* number_to_binteger(Val* in, Env* env);
Val* to_math_type(Val* in, MathMode mode, Env* env);
Val* number_to_integer(const goos::Object& form, Val* in, Env* env);
Val* number_to_float(const goos::Object& form, Val* in, Env* env);
Val* number_to_binteger(const goos::Object& form, Val* in, Env* env);
Val* to_math_type(const goos::Object& form, Val* in, MathMode mode, Env* env);
bool is_none(Val* in);
Val* compile_variable_shift(const RegVal* in, const RegVal* sa, Env* env, IntegerMathKind kind);
Val* compile_fixed_shift(const RegVal* in, u8 sa, Env* env, IntegerMathKind kind);
Val* compile_variable_shift(const goos::Object& form,
const RegVal* in,
const RegVal* sa,
Env* env,
IntegerMathKind kind);
Val* compile_fixed_shift(const goos::Object& form,
const RegVal* in,
u8 sa,
Env* env,
IntegerMathKind kind);
Val* compile_format_string(const goos::Object& form,
Env* env,
@ -150,10 +163,14 @@ class Compiler {
RegVal* reg,
const Field& f);
Val* generate_inspector_for_type(const goos::Object& form, Env* env, Type* type);
RegVal* compile_get_method_of_type(const TypeSpec& type,
RegVal* compile_get_method_of_type(const goos::Object& form,
const TypeSpec& type,
const std::string& method_name,
Env* env);
RegVal* compile_get_method_of_object(RegVal* object, const std::string& method_name, Env* env);
RegVal* compile_get_method_of_object(const goos::Object& form,
RegVal* object,
const std::string& method_name,
Env* env);
Val* compile_define_constant(const goos::Object& form,
const goos::Object& rest,
Env* env,
@ -169,6 +186,20 @@ class Compiler {
const goos::Object& field_defs,
Env* env);
template <typename... Args>
void throw_compiler_error(const goos::Object& code, const std::string& str, Args&&... args) {
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, "-- Compilation Error! --\n");
if (!str.empty() && str.back() == '\n') {
fmt::print(str, std::forward<Args>(args)...);
} else {
fmt::print(str + '\n', std::forward<Args>(args)...);
}
fmt::print(fg(fmt::color::yellow) | fmt::emphasis::bold, "Form:\n");
fmt::print("{}\n", code.print());
throw CompilerException("Compilation Error");
}
public:
// Atoms

View file

@ -0,0 +1,9 @@
#pragma once
#include <stdexcept>
class CompilerException : public std::runtime_error {
public:
CompilerException(const std::string& err) : std::runtime_error(err) {}
bool print_err_stack = true;
};

View file

@ -7,7 +7,7 @@ goos::Arguments Compiler::get_va(const goos::Object& form, const goos::Object& r
std::string err;
if (!goos::get_va(rest, &err, &args)) {
throw_compile_error(form, err);
throw_compiler_error(form, err);
}
return args;
}
@ -20,7 +20,7 @@ void Compiler::va_check(
named) {
std::string err;
if (!goos::va_check(args, unnamed, named, &err)) {
throw_compile_error(form, err);
throw_compiler_error(form, err);
}
}
@ -34,7 +34,7 @@ void Compiler::for_each_in_list(const goos::Object& list,
}
if (!iter->is_empty_list()) {
throw_compile_error(list, "invalid list in for_each_in_list");
throw_compiler_error(list, "Invalid list: {}", list.print());
}
}
@ -50,7 +50,7 @@ std::string Compiler::quoted_sym_as_string(const goos::Object& o) {
auto args = get_va(o, o);
va_check(o, args, {{goos::ObjectType::SYMBOL}, {goos::ObjectType::SYMBOL}}, {});
if (symbol_string(args.unnamed.at(0)) != "quote") {
throw_compile_error(o, "invalid quoted symbol " + o.print());
throw_compiler_error(o, "Invalid quoted symbol: {}.", o.print());
}
return symbol_string(args.unnamed.at(1));
}
@ -83,7 +83,7 @@ const goos::Object& Compiler::pair_cdr(const goos::Object& o) {
void Compiler::expect_empty_list(const goos::Object& o) {
if (!o.is_empty_list()) {
throw_compile_error(o, "expected to be an empty list");
throw_compiler_error(o, "expected to be an empty list");
}
}
@ -98,7 +98,7 @@ TypeSpec Compiler::parse_typespec(const goos::Object& src) {
return ts;
} else {
throw_compile_error(src, "invalid typespec");
throw_compiler_error(src, "Invalid typespec.");
}
assert(false);
return {};

View file

@ -229,10 +229,13 @@ SymbolVal* Compiler::compile_get_sym_obj(const std::string& name, Env* env) {
* Will throw a compilation error if the symbol wasn't previously defined.
* TODO - determine sign extension behavior when loading symbol values.
*/
Val* Compiler::compile_get_symbol_value(const std::string& name, Env* env) {
Val* Compiler::compile_get_symbol_value(const goos::Object& form,
const std::string& name,
Env* env) {
auto existing_symbol = m_symbol_types.find(name);
if (existing_symbol == m_symbol_types.end()) {
throw std::runtime_error("The symbol " + name + " was not defined");
throw_compiler_error(
form, "The symbol {} was looked up as a global variable, but it does not exist.", name);
}
auto ts = existing_symbol->second;
@ -272,9 +275,9 @@ Val* Compiler::compile_symbol(const goos::Object& form, Env* env) {
if (global_constant != m_global_constants.end()) {
// check there is no symbol with the same name
if (existing_symbol != m_symbol_types.end()) {
throw_compile_error(form,
"symbol is both a runtime symbol and a global constant. Something is "
"likely very wrong.");
throw_compiler_error(form,
"Ambiguous symbol: {} is both a global variable and a constant and it "
"is not clear which should be used here.");
}
// got a global constant
@ -282,7 +285,7 @@ Val* Compiler::compile_symbol(const goos::Object& form, Env* env) {
}
// none of those, so get a global symbol.
return compile_get_symbol_value(name, env);
return compile_get_symbol_value(form, name, env);
}
/*!
@ -333,7 +336,7 @@ Val* Compiler::compile_float(float value, Env* env, int seg) {
Val* Compiler::compile_pointer_add(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
if (args.unnamed.size() < 2 || !args.named.empty()) {
throw_compile_error(form, "&+ takes at least two arguments");
throw_compiler_error(form, "&+ must be used with at least two arguments.");
}
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
@ -346,7 +349,9 @@ Val* Compiler::compile_pointer_add(const goos::Object& form, const goos::Object&
}
if (!ok_type) {
throw_compile_error(form, "&+'s first argument must be a pointer, structure, or inline-array");
throw_compiler_error(
form, "&+ was used with a {}, which is not a pointer, structure, or inline-array.",
first->type().print());
}
auto result = env->make_gpr(first->type());

View file

@ -44,7 +44,7 @@ Val* Compiler::compile_block(const goos::Object& form, const goos::Object& _rest
rest = &pair_cdr(*rest);
if (!rest->is_pair()) {
throw_compile_error(form, "Block form has an empty or invalid body");
throw_compiler_error(form, "Block form has an empty or invalid body");
}
auto fe = get_parent_env_of_type<FunctionEnv>(env);
@ -121,8 +121,8 @@ Val* Compiler::compile_return_from(const goos::Object& form, const goos::Object&
// find block to return from
auto block = dynamic_cast<BlockEnv*>(env->find_block(block_name));
if (!block) {
throw_compile_error(form,
"The return-from form was unable to find a block named " + block_name);
throw_compiler_error(form, "The return-from form was unable to find a block named {}.",
block_name);
}
// move result into return register
@ -157,8 +157,8 @@ Val* Compiler::compile_label(const goos::Object& form, const goos::Object& rest,
auto& labels = env->get_label_map();
auto kv = labels.find(label_name);
if (kv != labels.end()) {
throw_compile_error(
form, "There are two labels named " + label_name + " in the same label environment");
throw_compiler_error(form, "There are two labels named \"{}\" in the same label environment",
label_name);
}
// make a label pointing to the end of the current function env. safe because we'll always add

View file

@ -44,7 +44,7 @@ Val* Compiler::compile_seval(const goos::Object& form, const goos::Object& rest,
m_goos.eval_with_rewind(o, m_goos.global_environment.as_env());
});
} catch (std::runtime_error& e) {
throw_compile_error(form, std::string("seval error: ") + e.what());
throw_compiler_error(form, "Error while evaluating GOOS: ", e.what());
}
return get_none();
}
@ -62,7 +62,7 @@ Val* Compiler::compile_asm_data_file(const goos::Object& form, const goos::Objec
} else if (kind == "game-count") {
compile_game_count(as_string(args.unnamed.at(1)));
} else {
throw_compile_error(form, "Unknown asm data file mode");
throw_compiler_error(form, "The option {} was not recognized for asm-data-file.", kind);
}
return get_none();
}
@ -79,6 +79,7 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
bool color = false;
bool write = false;
bool no_code = false;
bool disassemble = false;
std::vector<std::pair<std::string, float>> timing;
Timer total_timer;
@ -97,8 +98,10 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
write = true;
} else if (setting == ":no-code") {
no_code = true;
} else if (setting == ":disassemble") {
disassemble = true;
} else {
throw_compile_error(form, "invalid option " + setting + " in asm-file form");
throw_compiler_error(form, "The option {} was not recognized for asm-file.", setting);
}
}
i++;
@ -133,7 +136,14 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
// code/object file generation
Timer codegen_timer;
auto data = codegen_object_file(obj_file);
std::vector<u8> data;
std::string disasm;
if (disassemble) {
codegen_and_disassemble_object_file(obj_file, &data, &disasm);
printf("%s\n", disasm.c_str());
} else {
data = codegen_object_file(obj_file);
}
timing.emplace_back("codegen", codegen_timer.getMs());
// send to target
@ -159,6 +169,10 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re
if (write) {
printf("WARNING - couldn't write because coloring is not enabled\n");
}
if (disassemble) {
printf("WARNING - couldn't disassemble because coloring is not enabled\n");
}
}
if (m_settings.print_timing) {
@ -189,18 +203,18 @@ Val* Compiler::compile_listen_to_target(const goos::Object& form,
for_each_in_list(rest, [&](const goos::Object& o) {
if (o.is_string()) {
if (got_ip) {
throw_compile_error(form, "got multiple strings!");
throw_compiler_error(form, "listen-to-target can only use 1 IP address");
}
got_ip = true;
ip = o.as_string()->data;
} else if (o.is_int()) {
if (got_port) {
throw_compile_error(form, "got multiple ports!");
throw_compiler_error(form, "listen-to-target can only use 1 port number");
}
got_port = true;
port = o.integer_obj.value;
} else {
throw_compile_error(form, "invalid argument to listen-to-target");
throw_compiler_error(form, "invalid argument to listen-to-target: \"{}\"", o.print());
}
});
@ -220,7 +234,7 @@ Val* Compiler::compile_reset_target(const goos::Object& form, const goos::Object
if (o.is_symbol() && symbol_string(o) == ":shutdown") {
shutdown = true;
} else {
throw_compile_error(form, "invalid argument to reset-target");
throw_compiler_error(form, "invalid argument to reset-target: \"{}\"", o.print());
}
});
m_listener.send_reset(shutdown);

View file

@ -50,7 +50,7 @@ Condition Compiler::compile_condition(const goos::Object& condition, Env* env, b
if (fas->name == "not") {
auto arg = pair_car(rest);
if (!pair_cdr(rest).is_empty_list()) {
throw_compile_error(condition, "A condition with \"not\" can have only one argument");
throw_compiler_error(condition, "A condition with \"not\" can have only one argument");
}
return compile_condition(arg, env, !invert);
}
@ -75,12 +75,12 @@ Condition Compiler::compile_condition(const goos::Object& condition, Env* env, b
// there is no support for comparing bintegers, so we turn the binteger comparison into an
// integer.
if (is_binteger(first_arg->type())) {
first_arg = number_to_integer(first_arg, env);
first_arg = number_to_integer(condition, first_arg, env);
}
// convert second one to appropriate type as needed
if (is_number(second_arg->type())) {
second_arg = to_math_type(second_arg, math_mode, env);
second_arg = to_math_type(condition, second_arg, math_mode, env);
}
}
@ -182,7 +182,7 @@ Val* Compiler::compile_cond(const goos::Object& form, const goos::Object& rest,
auto clauses = pair_cdr(o);
if (got_else) {
throw_compile_error(form, "cannot have anything after an else in a cond");
throw_compiler_error(form, "Cond from cannot have any cases after else.");
}
if (test.is_symbol() && symbol_string(test) == "else") {

View file

@ -15,32 +15,36 @@ u32 Compiler::parse_address_spec(const goos::Object& form) {
if (rest.is_pair() && rest.as_pair()->car.is_symbol()) {
u32 addr = m_debugger.get_symbol_address(symbol_string(rest.as_pair()->car));
if (!addr) {
throw_compile_error(form, "debugger doesn't know where the symbol is");
throw_compiler_error(form,
"Could not find symbol {} in the target to create an addr-spec",
symbol_string(rest.as_pair()->car));
}
return addr;
} else {
throw_compile_error(form, "invalid sym form");
throw_compiler_error(form, "addr-spec sym form must receive exactly one symbol argument.");
return 0;
}
} else if (first.is_symbol() && symbol_string(first) == "sym-val") {
if (rest.is_pair() && rest.as_pair()->car.is_symbol()) {
u32 addr = 0;
if (!m_debugger.get_symbol_value(symbol_string(rest.as_pair()->car), &addr)) {
throw_compile_error(form, "debugger doesn't know where the symbol is");
throw_compiler_error(form,
"Could not find symbol {} in the target to create an addr-spec",
symbol_string(rest.as_pair()->car));
}
return addr;
} else {
throw_compile_error(form, "invalid sym-val form");
throw_compiler_error(form,
"addr-spec sym-val form must receive exactly one symbol argument.");
return 0;
}
}
else {
throw_compile_error(form, "can't parse this address spec");
} else {
throw_compiler_error(form, "Can't parse this address spec: option {} was not recognized.",
first.print());
return 0;
}
} else {
throw_compile_error(form, "can't parse this address spec");
throw_compiler_error(form, "Invalid address spec.");
return 0;
}
}
@ -219,27 +223,28 @@ Val* Compiler::compile_pm(const goos::Object& form, const goos::Object& rest, En
} else if (mode_name == "float") {
mode = FLOAT;
} else {
throw_compile_error(form, "Unknown print-mode for :pm " + mode_name);
throw_compiler_error(form, "Unknown print-mode {} for :pm.", mode_name);
}
}
if (!m_debugger.is_halted()) {
throw_compile_error(
throw_compiler_error(
form, "Cannot print memory, the debugger must be connected and the target must be halted.");
}
auto mem_size = elts * elt_size;
if (mem_size > 1024 * 1024) {
throw_compile_error(
form,
fmt::format(":pm used on over 1 MB of memory, this probably isn't what you meant to do."));
throw_compiler_error(form, fmt::format(":pm used on over 1 MB of memory. Not printing."));
}
std::vector<u8> mem;
mem.resize(mem_size);
if (addr < EE_MAIN_MEM_LOW_PROTECT || (addr + mem_size) > EE_MAIN_MEM_SIZE) {
throw_compile_error(form, ":pm memory out of range");
throw_compiler_error(form,
":pm memory out of range. Wanted to print 0x{:x} to 0x{:x}, but valid "
"memory is 0x{:x} to 0x{:x}.",
addr, addr + mem_size, EE_MAIN_MEM_LOW_PROTECT, EE_MAIN_MEM_SIZE);
}
m_debugger.read_memory(mem.data(), mem_size, addr);
@ -261,7 +266,7 @@ Val* Compiler::compile_pm(const goos::Object& form, const goos::Object& rest, En
mem_print((u64*)mem.data(), elts, addr, mode);
break;
default:
throw_compile_error(form, ":pm bad element size");
throw_compiler_error(form, ":pm {} is an invalid element size for unsigned", elt_size);
}
break;
case SIGNED_DEC:
@ -279,7 +284,7 @@ Val* Compiler::compile_pm(const goos::Object& form, const goos::Object& rest, En
mem_print((s64*)mem.data(), elts, addr, mode);
break;
default:
throw_compile_error(form, ":pm bad element size");
throw_compiler_error(form, ":pm {} is a bad element size for signed", elt_size);
}
break;
case FLOAT:
@ -288,7 +293,8 @@ Val* Compiler::compile_pm(const goos::Object& form, const goos::Object& rest, En
mem_print((float*)mem.data(), elts, addr, mode);
break;
default:
throw_compile_error(form, ":pm bad element size");
throw_compiler_error(form, ":pm float can only be printed with size 4, but got size {}",
elt_size);
}
break;
default:
@ -303,7 +309,7 @@ Val* Compiler::compile_di(const goos::Object& form, const goos::Object& rest, En
(void)rest;
(void)env;
if (!m_debugger.is_halted()) {
throw_compile_error(
throw_compiler_error(
form,
"Cannot get debug info, the debugger must be connected and the target must be halted.");
}
@ -320,13 +326,13 @@ Val* Compiler::compile_disasm(const goos::Object& form, const goos::Object& rest
u32 size = args.unnamed.at(1).as_int();
if (!m_debugger.is_halted()) {
throw_compile_error(
throw_compiler_error(
form,
"Cannot disassemble memory, the debugger must be connected and the target must be halted.");
}
if (size > 1024 * 1024) {
throw_compile_error(
throw_compiler_error(
form,
fmt::format(
":disasm used on over 1 MB of memory, this probably isn't what you meant to do."));
@ -336,7 +342,10 @@ Val* Compiler::compile_disasm(const goos::Object& form, const goos::Object& rest
mem.resize(size);
if (addr < EE_MAIN_MEM_LOW_PROTECT || (addr + size) > EE_MAIN_MEM_SIZE) {
throw_compile_error(form, ":disasm memory out of range");
throw_compiler_error(form,
":disasm memory out of range. Wanted to print 0x{:x} to 0x{:x}, but valid "
"memory is 0x{:x} to 0x{:x}.",
addr, addr + size, EE_MAIN_MEM_LOW_PROTECT, EE_MAIN_MEM_SIZE);
}
m_debugger.read_memory(mem.data(), size, addr);
@ -353,7 +362,7 @@ Val* Compiler::compile_bp(const goos::Object& form, const goos::Object& rest, En
va_check(form, args, {{}}, {});
if (!m_debugger.is_halted()) {
throw_compile_error(
throw_compiler_error(
form,
"Cannot add breakpoint, the debugger must be connected and the target must be halted.");
}
@ -370,7 +379,7 @@ Val* Compiler::compile_ubp(const goos::Object& form, const goos::Object& rest, E
va_check(form, args, {{}}, {});
if (!m_debugger.is_halted()) {
throw_compile_error(
throw_compiler_error(
form,
"Cannot remove breakpoint, the debugger must be connected and the target must be halted.");
}

View file

@ -19,8 +19,9 @@ Val* Compiler::compile_define(const goos::Object& form, const goos::Object& rest
// check we aren't duplicated a name as both a symbol and global constant
auto global_constant = m_global_constants.find(sym.as_symbol());
if (global_constant != m_global_constants.end()) {
throw_compile_error(
form, "it is illegal to define a GOAL symbol with the same name as a GOAL global constant");
throw_compiler_error(
form, "Cannot define a symbol named {}, it already exists as a global constant (value {}).",
sym.print(), global_constant->second.print());
}
auto fe = get_parent_env_of_type<FunctionEnv>(env);
@ -47,8 +48,7 @@ Val* Compiler::compile_define(const goos::Object& form, const goos::Object& rest
}
if (!sym_val->settable()) {
throw_compile_error(
form, "Tried to use define on something that wasn't settable: " + sym_val->print());
throw_compiler_error(form, "Cannot define {} because it cannot be set.", sym_val->print());
}
fe->emit(std::make_unique<IR_SetSymbolValue>(sym_val, in_gpr));
return in_gpr;
@ -68,15 +68,17 @@ Val* Compiler::compile_define_extern(const goos::Object& form, const goos::Objec
auto existing_type = m_symbol_types.find(symbol_string(sym));
if (existing_type != m_symbol_types.end() && existing_type->second != new_type) {
// todo spdlog
gLogger.log(
MSG_WARN,
"[Warning] define-extern has redefined the type of symbol %s\npreviously: %s\nnow: %s\n",
symbol_string(sym).c_str(), existing_type->second.print().c_str(),
new_type.print().c_str());
if (m_throw_on_define_extern_redefinition) {
throw_compile_error(form, "define-extern redefinition");
throw_compiler_error(form,
"define-extern would redefine the type of symbol {} from {} to {}.",
symbol_string(sym), existing_type->second.print(), new_type.print());
} else {
// todo nicer warning message.
gLogger.log(
MSG_WARN,
"[Warning] define-extern has redefined the type of symbol %s\npreviously: %s\nnow: %s\n",
symbol_string(sym).c_str(), existing_type->second.print().c_str(),
new_type.print().c_str());
}
}
@ -89,7 +91,6 @@ Val* Compiler::compile_define_extern(const goos::Object& form, const goos::Objec
}
void Compiler::set_bitfield(const goos::Object& form, BitFieldVal* dst, RegVal* src, Env* env) {
assert(!dst->sext());
auto fe = get_parent_env_of_type<FunctionEnv>(env);
// first, get the value we want to modify:
@ -132,8 +133,7 @@ void Compiler::set_bitfield(const goos::Object& form, BitFieldVal* dst, RegVal*
*/
Val* Compiler::do_set(const goos::Object& form, Val* dest, RegVal* source, Env* env) {
if (!dest->settable()) {
throw_compile_error(form,
"Tried to use set! on something that wasn't settable: " + dest->print());
throw_compiler_error(form, "Cannot set! {} because it is not settable.", dest->print());
}
auto as_mem_deref = dynamic_cast<MemoryDerefVal*>(dest);
auto as_pair = dynamic_cast<PairEntryVal*>(dest);
@ -142,6 +142,11 @@ Val* Compiler::do_set(const goos::Object& form, Val* dest, RegVal* source, Env*
auto as_bitfield = dynamic_cast<BitFieldVal*>(dest);
if (as_mem_deref) {
auto dest_type = coerce_to_reg_type(as_mem_deref->type());
if (dest_type != TypeSpec("uint") || source->type() != TypeSpec("int")) {
typecheck(form, dest_type, source->type(), "set! memory");
}
// setting somewhere in memory
auto base = as_mem_deref->base;
auto base_as_mco = dynamic_cast<MemoryOffsetConstantVal*>(base);
@ -177,7 +182,7 @@ Val* Compiler::do_set(const goos::Object& form, Val* dest, RegVal* source, Env*
return get_none();
}
throw_compile_error(form, "Set not implemented for: " + dest->print());
throw_compiler_error(form, "There is no implementation of set! for {}.", dest->print());
return get_none();
}

View file

@ -53,11 +53,13 @@ Val* Compiler::compile_inline(const goos::Object& form, const goos::Object& rest
auto kv = m_inlineable_functions.find(args.unnamed.at(0).as_symbol());
if (kv == m_inlineable_functions.end()) {
throw_compile_error(form, "Couldn't find function to inline!");
throw_compiler_error(form, "Cannot inline {} because the function's code could not be found.",
args.unnamed.at(0).print());
}
if (kv->second->func && !kv->second->func->settings.allow_inline) {
throw_compile_error(form, "Found function to inline, but it isn't allowed.");
throw_compiler_error(form,
"Cannot inline {} because inlining of this function was disallowed.");
}
auto fe = get_parent_env_of_type<FunctionEnv>(env);
return fe->alloc_val<InlinedLambdaVal>(kv->second->type(), kv->second);
@ -73,7 +75,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
auto args = get_va(form, rest);
if (args.unnamed.empty() || !args.unnamed.front().is_list() ||
!args.only_contains_named({"name", "inline-only", "segment"})) {
throw_compile_error(form, "Invalid lambda form");
throw_compiler_error(form, "Invalid lambda form");
}
auto place = fe->alloc_val<LambdaVal>(get_none()->type());
@ -126,7 +128,7 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
} else if (segment_name == "debug") {
segment = DEBUG_SEGMENT;
} else {
throw_compile_error(form, "invalid segment override in lambda");
throw_compiler_error(form, "Segment {} was not recognized in lambda option.", segment_name);
}
}
@ -141,7 +143,10 @@ Val* Compiler::compile_lambda(const goos::Object& form, const goos::Object& rest
// set up arguments
if (lambda.params.size() >= 8) {
throw_compile_error(form, "lambda generating code has too many parameters!");
throw_compiler_error(form,
"Cannot generate an x86-64 function for a lambda with {} parameters. "
"The current limit is 8.",
lambda.params.size());
}
// set up argument register constraints.
@ -314,7 +319,8 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
// check args are ok
if (head_as_lambda->lambda.params.size() != eval_args.size()) {
throw_compile_error(form, "invalid argument count");
throw_compiler_error(form, "Expected {} arguments but got {} for inlined lambda.",
head_as_lambda->lambda.params.size(), eval_args.size());
}
// construct a lexical environment
@ -332,9 +338,9 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
// check arg types
if (!head->type().arg_count()) {
if (head->type().arg_count() - 1 != eval_args.size()) {
throw_compile_error(form,
"invalid number of arguments to function call (inline or immediate "
"lambda application)");
throw_compiler_error(form,
"Expected {} arguments for an inlined lambda with type {} but got {}.",
head->type().arg_count() - 1, head->type().print(), eval_args.size());
}
// immediate lambdas (lets) will have all types as the most general object by default
// inlined functions will have real types that are checked...
@ -411,11 +417,10 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
if (is_method_call) {
// method needs at least one argument to tell what we're calling the method on.
if (eval_args.empty()) {
throw_compile_error(form,
"Unrecognized symbol " + uneval_head.print() + " as head of form");
throw_compiler_error(form, "Unrecognized symbol {} as head of form.", uneval_head.print());
}
// get the method function pointer
head = compile_get_method_of_object(eval_args.front(), symbol_string(uneval_head), env);
head = compile_get_method_of_object(form, eval_args.front(), symbol_string(uneval_head), env);
fmt::format("method of object {} {}\n", head->print(), head->type().print());
}
@ -431,11 +436,11 @@ Val* Compiler::compile_function_or_method_call(const goos::Object& form, Env* en
}
} else {
throw_compile_error(form, "can't figure out this function call!");
throw_compiler_error(form, "Invalid function call! Possibly a compiler bug.");
}
}
throw_compile_error(form, "call_function_or_method unreachable");
assert(false);
return get_none();
}
@ -461,8 +466,8 @@ Val* Compiler::compile_real_function_call(const goos::Object& form,
TypeSpec return_ts;
if (function->type().arg_count() == 0) {
// if the type system doesn't know what the function will return, don't allow it to be called
throw_compile_error(
form, "This function call has unknown argument and return types and cannot be called");
throw_compiler_error(
form, "This function call has unknown argument and return types and cannot be called.");
} else {
return_ts = function->type().last_arg();
}
@ -472,10 +477,9 @@ Val* Compiler::compile_real_function_call(const goos::Object& form,
// check arg count:
if (function->type().arg_count() && !is_varargs_function(function->type())) {
if (function->type().arg_count() - 1 != args.size()) {
throw_compile_error(form, "invalid number of arguments to function call: got " +
std::to_string(args.size()) + " and expected " +
std::to_string(function->type().arg_count() - 1) + " for " +
function->type().print());
throw_compiler_error(form,
"Expected {} arguments but got {} for a real function call on type {}.",
function->type().arg_count() - 1, args.size(), function->type().print());
}
for (uint32_t i = 0; i < args.size(); i++) {
if (method_type_name.empty()) {
@ -489,7 +493,7 @@ Val* Compiler::compile_real_function_call(const goos::Object& form,
}
if (args.size() > 8) {
throw_compile_error(form, "Function call cannot use more than 8 parameters");
throw_compiler_error(form, "Function call cannot use more than 8 parameters.");
}
// set args (introducing a move here makes coloring more likely to be possible)
@ -522,42 +526,42 @@ Val* Compiler::compile_declare(const goos::Object& form, const goos::Object& res
auto& settings = get_parent_env_of_type<DeclareEnv>(env)->settings;
if (settings.is_set) {
throw_compile_error(form, "function has multiple declares");
throw_compiler_error(form, "Function cannot have multiple declares");
}
settings.is_set = true;
for_each_in_list(rest, [&](const goos::Object& o) {
if (!o.is_pair()) {
throw_compile_error(o, "invalid declare specification");
throw_compiler_error(o, "Invalid declare specification.");
}
auto first = o.as_pair()->car;
auto rrest = o.as_pair()->cdr;
if (!first.is_symbol()) {
throw_compile_error(first, "invalid declare specification, expected a symbol");
throw_compiler_error(
first, "Invalid declare option specification, expected a symbol, but got {} instead.",
first.print());
}
if (first.as_symbol()->name == "inline") {
if (!rrest.is_empty_list()) {
throw_compile_error(first, "invalid inline declare");
throw_compiler_error(first, "Invalid inline declare, no options were expected.");
}
settings.allow_inline = true;
settings.inline_by_default = true;
settings.save_code = true;
} else if (first.as_symbol()->name == "allow-inline") {
if (!rrest.is_empty_list()) {
throw_compile_error(first, "invalid allow-inline declare");
throw_compiler_error(first, "Invalid allow-inline declare");
}
settings.allow_inline = true;
settings.inline_by_default = false;
settings.save_code = true;
} else if (first.as_symbol()->name == "asm-func") {
get_parent_env_of_type<FunctionEnv>(env)->is_asm_func = true;
}
else {
throw_compile_error(first, "unrecognized declare statement");
} else {
throw_compiler_error(first, "Unrecognized declare option {}.", first.print());
}
});
return get_none();

View file

@ -49,7 +49,7 @@ Val* Compiler::compile_goos_macro(const goos::Object& o,
*/
Val* Compiler::compile_gscond(const goos::Object& form, const goos::Object& rest, Env* env) {
if (!rest.is_pair()) {
throw_compile_error(form, "#cond must have at least one clause, which must be a form");
throw_compiler_error(form, "#cond must have at least one clause, which must be a form");
}
Val* result = nullptr;
@ -58,7 +58,7 @@ Val* Compiler::compile_gscond(const goos::Object& form, const goos::Object& rest
if (lst.is_pair()) {
Object current_case = lst.as_pair()->car;
if (!current_case.is_pair()) {
throw_compile_error(lst, "Bad case in #cond");
throw_compiler_error(lst, "Bad case in #cond");
}
// check condition:
@ -85,7 +85,7 @@ Val* Compiler::compile_gscond(const goos::Object& form, const goos::Object& rest
} else if (lst.is_empty_list()) {
return get_none();
} else {
throw_compile_error(form, "malformed #cond");
throw_compiler_error(form, "malformed #cond");
}
}
}
@ -108,7 +108,7 @@ Val* Compiler::compile_quote(const goos::Object& form, const goos::Object& rest,
}
// todo...
default:
throw_compile_error(form, "Can't quote this");
throw_compiler_error(form, "Quote is not yet implemented for {}.", thing.print());
}
return get_none();
}
@ -121,7 +121,7 @@ Val* Compiler::compile_define_constant(const goos::Object& form,
auto rest = &_rest;
(void)env;
if (!rest->is_pair()) {
throw_compile_error(form, "invalid constant definition");
throw_compiler_error(form, "invalid constant definition");
}
auto sym = pair_car(*rest).as_symbol();
@ -130,15 +130,16 @@ Val* Compiler::compile_define_constant(const goos::Object& form,
rest = &rest->as_pair()->cdr;
if (!rest->is_empty_list()) {
throw_compile_error(form, "invalid constant definition");
throw_compiler_error(form, "invalid constant definition");
}
// GOAL constant
if (goal) {
if (m_symbol_types.find(sym->name) != m_symbol_types.end()) {
throw_compile_error(form, fmt::format("The name {} cannot be defined as a constant because "
"it is already the name of a symbol of type {}",
sym->name, m_symbol_types.at(sym->name).print()));
throw_compiler_error(form,
"Cannot define {} as a constant because "
"it is already the name of a symbol of type {}",
sym->name, m_symbol_types.at(sym->name).print());
}
m_global_constants[sym] = value;
}

View file

@ -38,11 +38,11 @@ bool Compiler::is_singed_integer_or_binteger(const TypeSpec& ts) {
!m_ts.typecheck(m_ts.make_typespec("uinteger"), ts, "", false, false);
}
Val* Compiler::number_to_integer(Val* in, Env* env) {
Val* Compiler::number_to_integer(const goos::Object& form, Val* in, Env* env) {
(void)env;
auto ts = in->type();
if (is_binteger(ts)) {
throw std::runtime_error("Can't convert " + in->print() + " (a binteger) to an integer.");
throw_compiler_error(form, "Cannot convert {} (a binteger) to an integer yet.", in->print());
} else if (is_float(ts)) {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto result = fe->make_gpr(m_ts.make_typespec("int"));
@ -51,31 +51,33 @@ Val* Compiler::number_to_integer(Val* in, Env* env) {
} else if (is_integer(ts)) {
return in;
}
throw std::runtime_error("Can't convert " + in->print() + " to an integer.");
throw_compiler_error(form, "Cannot convert a {} to an integer.", in->type().print());
return nullptr;
}
Val* Compiler::number_to_binteger(Val* in, Env* env) {
Val* Compiler::number_to_binteger(const goos::Object& form, Val* in, Env* env) {
(void)env;
auto ts = in->type();
if (is_binteger(ts)) {
return in;
} else if (is_float(ts)) {
throw std::runtime_error("Can't convert " + in->print() + " (a float) to a binteger.");
throw_compiler_error(form, "Cannot convert {} (a float) to an integer yet.", in->print());
} else if (is_integer(ts)) {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
RegVal* input = in->to_reg(env);
auto sa = fe->make_gpr(m_ts.make_typespec("int"));
env->emit(std::make_unique<IR_LoadConstant64>(sa, 3));
return compile_variable_shift(input, sa, env, IntegerMathKind::SHLV_64);
return compile_variable_shift(form, input, sa, env, IntegerMathKind::SHLV_64);
}
throw std::runtime_error("Can't convert " + in->print() + " to a binteger.");
throw_compiler_error(form, "Cannot convert a {} to a binteger.", in->type().print());
return nullptr;
}
Val* Compiler::number_to_float(Val* in, Env* env) {
Val* Compiler::number_to_float(const goos::Object& form, Val* in, Env* env) {
(void)env;
auto ts = in->type();
if (is_binteger(ts)) {
throw std::runtime_error("Can't convert " + in->print() + " (a binteger) to a float.");
throw_compiler_error(form, "Cannot convert {} (a binteger) to an float yet.", in->print());
} else if (is_float(ts)) {
return in;
} else if (is_integer(ts)) {
@ -83,28 +85,29 @@ Val* Compiler::number_to_float(Val* in, Env* env) {
auto result = fe->make_xmm(m_ts.make_typespec("float"));
env->emit(std::make_unique<IR_IntToFloat>(result, in->to_gpr(env)));
return result;
} else {
throw std::runtime_error("Can't convert " + in->print() + " a float.");
}
throw_compiler_error(form, "Cannot convert a {} to a float.", in->type().print());
return nullptr;
}
Val* Compiler::to_math_type(Val* in, MathMode mode, Env* env) {
Val* Compiler::to_math_type(const goos::Object& form, Val* in, MathMode mode, Env* env) {
switch (mode) {
case MATH_BINT:
return number_to_binteger(in, env);
return number_to_binteger(form, in, env);
case MATH_INT:
return number_to_integer(in, env);
return number_to_integer(form, in, env);
case MATH_FLOAT:
return number_to_float(in, env);
return number_to_float(form, in, env);
default:
throw std::runtime_error("Unknown math type: " + in->print());
throw_compiler_error(form, "Cannot do math on a {}.", in->type().print());
}
return nullptr;
}
Val* Compiler::compile_add(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
if (!args.named.empty() || args.unnamed.empty()) {
throw_compile_error(form, "Invalid + form");
throw_compiler_error(form, "Invalid + form");
}
// look at the first value to determine the math mode
@ -119,7 +122,7 @@ Val* Compiler::compile_add(const goos::Object& form, const goos::Object& rest, E
for (size_t i = 1; i < args.unnamed.size(); i++) {
env->emit(std::make_unique<IR_IntegerMath>(
IntegerMathKind::ADD_64, result,
to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env)
to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
->to_gpr(env)));
}
return result;
@ -132,14 +135,13 @@ Val* Compiler::compile_add(const goos::Object& form, const goos::Object& rest, E
for (size_t i = 1; i < args.unnamed.size(); i++) {
env->emit(std::make_unique<IR_FloatMath>(
FloatMathKind::ADD_SS, result,
to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env)
to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
->to_xmm(env)));
}
return result;
}
case MATH_INVALID:
throw_compile_error(
form, "Cannot determine the math mode for object of type " + first_type.print());
throw_compiler_error(form, "Cannot do math on a {}.", first_type.print());
break;
default:
assert(false);
@ -151,7 +153,7 @@ Val* Compiler::compile_add(const goos::Object& form, const goos::Object& rest, E
Val* Compiler::compile_mul(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
if (!args.named.empty() || args.unnamed.empty()) {
throw_compile_error(form, "Invalid * form");
throw_compiler_error(form, "Invalid * form");
}
// look at the first value to determine the math mode
@ -167,7 +169,7 @@ Val* Compiler::compile_mul(const goos::Object& form, const goos::Object& rest, E
for (size_t i = 1; i < args.unnamed.size(); i++) {
env->emit(std::make_unique<IR_IntegerMath>(
IntegerMathKind::IMUL_32, result,
to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env)
to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
->to_gpr(env)));
}
return result;
@ -179,14 +181,13 @@ Val* Compiler::compile_mul(const goos::Object& form, const goos::Object& rest, E
for (size_t i = 1; i < args.unnamed.size(); i++) {
env->emit(std::make_unique<IR_FloatMath>(
FloatMathKind::MUL_SS, result,
to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env)
to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
->to_xmm(env)));
}
return result;
}
case MATH_INVALID:
throw_compile_error(
form, "Cannot determine the math mode for object of type " + first_type.print());
throw_compiler_error(form, "Cannot do math on a {}.", first_type.print());
break;
default:
assert(false);
@ -198,20 +199,20 @@ Val* Compiler::compile_mul(const goos::Object& form, const goos::Object& rest, E
Val* Compiler::compile_fmin(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
if (!args.named.empty() || args.unnamed.empty()) {
throw_compile_error(form, "Invalid fmin form");
throw_compiler_error(form, "Invalid fmin form");
}
// look at the first value to determine the math mode
auto first_val = compile_error_guard(args.unnamed.at(0), env);
if (get_math_mode(first_val->type()) != MATH_FLOAT) {
throw_compile_error(form, "Must use floats in fmin");
throw_compiler_error(form, "Must use floats in fmin");
}
auto result = env->make_xmm(first_val->type());
env->emit(std::make_unique<IR_RegSet>(result, first_val->to_xmm(env)));
for (size_t i = 1; i < args.unnamed.size(); i++) {
auto val = compile_error_guard(args.unnamed.at(i), env);
if (get_math_mode(val->type()) != MATH_FLOAT) {
throw_compile_error(form, "Must use floats in fmin");
throw_compiler_error(form, "Must use floats in fmin");
}
env->emit(std::make_unique<IR_FloatMath>(FloatMathKind::MIN_SS, result, val->to_xmm(env)));
}
@ -221,20 +222,20 @@ Val* Compiler::compile_fmin(const goos::Object& form, const goos::Object& rest,
Val* Compiler::compile_fmax(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
if (!args.named.empty() || args.unnamed.empty()) {
throw_compile_error(form, "Invalid fmax form");
throw_compiler_error(form, "Invalid fmax form");
}
// look at the first value to determine the math mode
auto first_val = compile_error_guard(args.unnamed.at(0), env);
if (get_math_mode(first_val->type()) != MATH_FLOAT) {
throw_compile_error(form, "Must use floats in fmax");
throw_compiler_error(form, "Must use floats in fmax");
}
auto result = env->make_xmm(first_val->type());
env->emit(std::make_unique<IR_RegSet>(result, first_val->to_xmm(env)));
for (size_t i = 1; i < args.unnamed.size(); i++) {
auto val = compile_error_guard(args.unnamed.at(i), env);
if (get_math_mode(val->type()) != MATH_FLOAT) {
throw_compile_error(form, "Must use floats in fmax");
throw_compiler_error(form, "Must use floats in fmax");
}
env->emit(std::make_unique<IR_FloatMath>(FloatMathKind::MAX_SS, result, val->to_xmm(env)));
}
@ -244,7 +245,7 @@ Val* Compiler::compile_fmax(const goos::Object& form, const goos::Object& rest,
Val* Compiler::compile_imul64(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
if (!args.named.empty() || args.unnamed.empty()) {
throw_compile_error(form, "Invalid * form");
throw_compiler_error(form, "Invalid imul64 form");
}
// look at the first value to determine the math mode
@ -259,15 +260,15 @@ Val* Compiler::compile_imul64(const goos::Object& form, const goos::Object& rest
for (size_t i = 1; i < args.unnamed.size(); i++) {
env->emit(std::make_unique<IR_IntegerMath>(
IntegerMathKind::IMUL_64, result,
to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env)
to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
->to_gpr(env)));
}
return result;
}
case MATH_FLOAT:
case MATH_INVALID:
throw_compile_error(
form, "Cannot determine the math mode for object of type " + first_type.print());
case MATH_BINT:
throw_compiler_error(form, "Cannot do imul64 on a {}.", first_type.print());
break;
default:
assert(false);
@ -279,7 +280,7 @@ Val* Compiler::compile_imul64(const goos::Object& form, const goos::Object& rest
Val* Compiler::compile_sub(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
if (!args.named.empty() || args.unnamed.empty()) {
throw_compile_error(form, "Invalid - form");
throw_compiler_error(form, "Invalid - form");
}
auto first_val = compile_error_guard(args.unnamed.at(0), env);
@ -291,19 +292,19 @@ Val* Compiler::compile_sub(const goos::Object& form, const goos::Object& rest, E
auto result = compile_integer(0, env)->to_gpr(env);
env->emit(std::make_unique<IR_IntegerMath>(
IntegerMathKind::SUB_64, result,
to_math_type(compile_error_guard(args.unnamed.at(0), env), math_type, env)
to_math_type(form, compile_error_guard(args.unnamed.at(0), env), math_type, env)
->to_gpr(env)));
return result;
} else {
auto result = env->make_gpr(first_type);
env->emit(std::make_unique<IR_RegSet>(
result, to_math_type(compile_error_guard(args.unnamed.at(0), env), math_type, env)
result, to_math_type(form, compile_error_guard(args.unnamed.at(0), env), math_type, env)
->to_gpr(env)));
for (size_t i = 1; i < args.unnamed.size(); i++) {
env->emit(std::make_unique<IR_IntegerMath>(
IntegerMathKind::SUB_64, result,
to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env)
to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
->to_gpr(env)));
}
return result;
@ -315,27 +316,26 @@ Val* Compiler::compile_sub(const goos::Object& form, const goos::Object& rest, E
compile_float(0, env, get_parent_env_of_type<FunctionEnv>(env)->segment)->to_xmm(env);
env->emit(std::make_unique<IR_FloatMath>(
FloatMathKind::SUB_SS, result,
to_math_type(compile_error_guard(args.unnamed.at(0), env), math_type, env)
to_math_type(form, compile_error_guard(args.unnamed.at(0), env), math_type, env)
->to_xmm(env)));
return result;
} else {
auto result = env->make_xmm(first_type);
env->emit(std::make_unique<IR_RegSet>(
result, to_math_type(compile_error_guard(args.unnamed.at(0), env), math_type, env)
result, to_math_type(form, compile_error_guard(args.unnamed.at(0), env), math_type, env)
->to_xmm(env)));
for (size_t i = 1; i < args.unnamed.size(); i++) {
env->emit(std::make_unique<IR_FloatMath>(
FloatMathKind::SUB_SS, result,
to_math_type(compile_error_guard(args.unnamed.at(i), env), math_type, env)
to_math_type(form, compile_error_guard(args.unnamed.at(i), env), math_type, env)
->to_xmm(env)));
}
return result;
}
case MATH_INVALID:
throw_compile_error(
form, "Cannot determine the math mode for object of type " + first_type.print());
throw_compiler_error(form, "Cannot do math on a {}.", first_type.print());
break;
default:
assert(false);
@ -347,7 +347,7 @@ Val* Compiler::compile_sub(const goos::Object& form, const goos::Object& rest, E
Val* Compiler::compile_div(const goos::Object& form, const goos::Object& rest, Env* env) {
auto args = get_va(form, rest);
if (!args.named.empty() || args.unnamed.size() != 2) {
throw_compile_error(form, "Invalid / form");
throw_compiler_error(form, "Invalid / form");
}
auto first_val = compile_error_guard(args.unnamed.at(0), env);
@ -368,7 +368,8 @@ Val* Compiler::compile_div(const goos::Object& form, const goos::Object& rest, E
env->emit(std::make_unique<IR_IntegerMath>(
IntegerMathKind::IDIV_32, result,
to_math_type(compile_error_guard(args.unnamed.at(1), env), math_type, env)->to_gpr(env)));
to_math_type(form, compile_error_guard(args.unnamed.at(1), env), math_type, env)
->to_gpr(env)));
return result;
}
@ -377,13 +378,13 @@ Val* Compiler::compile_div(const goos::Object& form, const goos::Object& rest, E
env->emit(std::make_unique<IR_RegSet>(result, first_val->to_xmm(env)));
env->emit(std::make_unique<IR_FloatMath>(
FloatMathKind::DIV_SS, result,
to_math_type(compile_error_guard(args.unnamed.at(1), env), math_type, env)->to_xmm(env)));
to_math_type(form, compile_error_guard(args.unnamed.at(1), env), math_type, env)
->to_xmm(env)));
return result;
}
case MATH_INVALID:
throw_compile_error(
form, "Cannot determine the math mode for object of type " + first_type.print());
throw_compiler_error(form, "Cannot do math on a {}.", first_type.print());
break;
default:
assert(false);
@ -397,7 +398,7 @@ Val* Compiler::compile_shlv(const goos::Object& form, const goos::Object& rest,
va_check(form, args, {{}, {}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
return compile_variable_shift(first, second, env, IntegerMathKind::SHLV_64);
return compile_variable_shift(form, first, second, env, IntegerMathKind::SHLV_64);
}
Val* Compiler::compile_sarv(const goos::Object& form, const goos::Object& rest, Env* env) {
@ -405,7 +406,7 @@ Val* Compiler::compile_sarv(const goos::Object& form, const goos::Object& rest,
va_check(form, args, {{}, {}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
return compile_variable_shift(first, second, env, IntegerMathKind::SARV_64);
return compile_variable_shift(form, first, second, env, IntegerMathKind::SARV_64);
}
Val* Compiler::compile_shrv(const goos::Object& form, const goos::Object& rest, Env* env) {
@ -413,10 +414,11 @@ Val* Compiler::compile_shrv(const goos::Object& form, const goos::Object& rest,
va_check(form, args, {{}, {}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
return compile_variable_shift(first, second, env, IntegerMathKind::SHRV_64);
return compile_variable_shift(form, first, second, env, IntegerMathKind::SHRV_64);
}
Val* Compiler::compile_variable_shift(const RegVal* in,
Val* Compiler::compile_variable_shift(const goos::Object& form,
const RegVal* in,
const RegVal* sa,
Env* env,
IntegerMathKind kind) {
@ -434,7 +436,7 @@ Val* Compiler::compile_variable_shift(const RegVal* in,
if (get_math_mode(in->type()) != MathMode::MATH_INT ||
get_math_mode(sa->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't shift a " + in->type().print() + " by a " + sa->type().print());
throw_compiler_error(form, "Cannot shift a {} by a {}", in->type().print(), sa->type().print());
}
fenv->constrain(sa_con);
@ -448,9 +450,9 @@ Val* Compiler::compile_shl(const goos::Object& form, const goos::Object& rest, E
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto sa = args.unnamed.at(1).as_int();
if (sa < 0 || sa > 64) {
throw_compile_error(form, "Cannot shift by more than 64, or by a negative amount");
throw_compiler_error(form, "Cannot shift by more than 64, or by a negative amount.");
}
return compile_fixed_shift(first, sa, env, IntegerMathKind::SHL_64);
return compile_fixed_shift(form, first, sa, env, IntegerMathKind::SHL_64);
}
Val* Compiler::compile_shr(const goos::Object& form, const goos::Object& rest, Env* env) {
@ -459,9 +461,9 @@ Val* Compiler::compile_shr(const goos::Object& form, const goos::Object& rest, E
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto sa = args.unnamed.at(1).as_int();
if (sa < 0 || sa > 64) {
throw_compile_error(form, "Cannot shift by more than 64, or by a negative amount");
throw_compiler_error(form, "Cannot shift by more than 64, or by a negative amount");
}
return compile_fixed_shift(first, sa, env, IntegerMathKind::SHR_64);
return compile_fixed_shift(form, first, sa, env, IntegerMathKind::SHR_64);
}
Val* Compiler::compile_sar(const goos::Object& form, const goos::Object& rest, Env* env) {
@ -470,19 +472,23 @@ Val* Compiler::compile_sar(const goos::Object& form, const goos::Object& rest, E
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
auto sa = args.unnamed.at(1).as_int();
if (sa < 0 || sa > 64) {
throw_compile_error(form, "Cannot shift by more than 64, or by a negative amount");
throw_compiler_error(form, "Cannot shift by more than 64, or by a negative amount");
}
return compile_fixed_shift(first, sa, env, IntegerMathKind::SAR_64);
return compile_fixed_shift(form, first, sa, env, IntegerMathKind::SAR_64);
}
Val* Compiler::compile_fixed_shift(const RegVal* in, u8 sa, Env* env, IntegerMathKind kind) {
Val* Compiler::compile_fixed_shift(const goos::Object& form,
const RegVal* in,
u8 sa,
Env* env,
IntegerMathKind kind) {
// type check
if (get_math_mode(in->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't shift a " + in->type().print());
throw_compiler_error(form, "Cannot shift a {}.", in->type().print());
}
if (sa > 64) {
throw std::runtime_error("Can't shift by more than 64");
throw_compiler_error(form, "Cannot shift by more than 64.");
}
// copy to result register
@ -502,8 +508,8 @@ Val* Compiler::compile_mod(const goos::Object& form, const goos::Object& rest, E
if (get_math_mode(first->type()) != MathMode::MATH_INT ||
get_math_mode(second->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't mod a " + first->type().print() + " by a " +
second->type().print());
throw_compiler_error(form, "Cannot mod a {} by a {}.", first->type().print(),
second->type().print());
}
auto result = env->make_gpr(first->type());
@ -526,8 +532,8 @@ Val* Compiler::compile_logand(const goos::Object& form, const goos::Object& rest
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
if (get_math_mode(first->type()) != MathMode::MATH_INT ||
get_math_mode(second->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't logand a " + first->type().print() + " by a " +
second->type().print());
throw_compiler_error(form, "Cannot logand a {} by a {}.", first->type().print(),
second->type().print());
}
auto result = env->make_gpr(first->type());
@ -543,8 +549,8 @@ Val* Compiler::compile_logior(const goos::Object& form, const goos::Object& rest
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
if (get_math_mode(first->type()) != MathMode::MATH_INT ||
get_math_mode(second->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't logior a " + first->type().print() + " by a " +
second->type().print());
throw_compiler_error(form, "Cannot logior a {} by a {}.", first->type().print(),
second->type().print());
}
auto result = env->make_gpr(first->type());
@ -560,8 +566,8 @@ Val* Compiler::compile_logxor(const goos::Object& form, const goos::Object& rest
auto second = compile_error_guard(args.unnamed.at(1), env)->to_gpr(env);
if (get_math_mode(first->type()) != MathMode::MATH_INT ||
get_math_mode(second->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't logxor a " + first->type().print() + " by a " +
second->type().print());
throw_compiler_error(form, "Cannot logxor a {} by a {}.", first->type().print(),
second->type().print());
}
auto result = env->make_gpr(first->type());
@ -575,7 +581,7 @@ Val* Compiler::compile_lognot(const goos::Object& form, const goos::Object& rest
va_check(form, args, {{}}, {});
auto first = compile_error_guard(args.unnamed.at(0), env)->to_gpr(env);
if (get_math_mode(first->type()) != MathMode::MATH_INT) {
throw std::runtime_error("Can't lognot a " + first->type().print());
throw_compiler_error(form, "Cannot lognot a {}.", first->type().print());
}
auto result = env->make_gpr(first->type());

View file

@ -62,8 +62,8 @@ Val* Compiler::compile_new_static_bitfield(const goos::Object& form,
field_defs = &pair_cdr(*field_defs);
if (field_name_def.at(0) != ':') {
throw_compile_error(form,
"expected field def name to start with :, instead got " + field_name_def);
throw_compiler_error(
form, "expected field def name to start with :, instead got " + field_name_def);
}
field_name_def = field_name_def.substr(1);
@ -76,10 +76,10 @@ Val* Compiler::compile_new_static_bitfield(const goos::Object& form,
if (is_integer(field_info.result_type)) {
s64 value = 0;
if (!try_getting_constant_integer(field_value, &value, env)) {
throw_compile_error(form,
fmt::format("Field {} is an integer, but the value given couldn't be "
"converted to an integer at compile time.",
field_name_def));
throw_compiler_error(form,
"Field {} is an integer, but the value given couldn't be "
"converted to an integer at compile time.",
field_name_def);
}
// todo, check the integer fits!
@ -90,22 +90,23 @@ Val* Compiler::compile_new_static_bitfield(const goos::Object& form,
// and back right.
or_value >>= (64 - field_size);
if (or_value != unsigned_value) {
throw_compile_error(form, fmt::format("Field {}'s value doesn't fit.", field_name_def));
throw_compiler_error(form, "Field {}'s value doesn't fit.", field_name_def);
}
as_int |= (or_value << field_offset);
} else if (is_float(field_info.result_type)) {
if (field_size != 32) {
throw_compile_error(form,
fmt::format("Tried to put a float into a float bitfield that's not 4 "
"bytes. This is probably not what you wanted to do."));
throw_compiler_error(form,
"Tried to put a float into a float bitfield that's not 4 "
"bytes. This is probably not what you wanted to do.");
}
float value = 0.f;
if (!try_getting_constant_float(field_value, &value, env)) {
throw_compile_error(form, fmt::format("Field {} is a float, but the value given couldn't "
"be converted to a float at compile time.",
field_name_def));
throw_compiler_error(form,
"Field {} is a float, but the value given couldn't "
"be converted to a float at compile time.",
field_name_def);
}
u64 float_value = float_as_u32(value);
as_int |= (float_value << field_offset);
@ -148,8 +149,8 @@ Val* Compiler::compile_new_static_structure_or_basic(const goos::Object& form,
field_defs = &pair_cdr(*field_defs);
if (field_name_def.at(0) != ':') {
throw_compile_error(form,
"expected field def name to start with :, instead got " + field_name_def);
throw_compiler_error(
form, "expected field def name to start with :, instead got " + field_name_def);
}
field_name_def = field_name_def.substr(1);
@ -157,7 +158,7 @@ Val* Compiler::compile_new_static_structure_or_basic(const goos::Object& form,
if (field_info.field.is_dynamic() || field_info.field.is_inline() ||
field_info.field.is_array()) {
throw_compile_error(form, "Static objects not yet implemented for dynamic/inline/array");
throw_compiler_error(form, "Static objects not yet implemented for dynamic/inline/array");
}
auto field_offset = field_info.field.offset();
@ -169,17 +170,17 @@ Val* Compiler::compile_new_static_structure_or_basic(const goos::Object& form,
if (is_integer(field_info.type)) {
s64 value = 0;
if (!try_getting_constant_integer(field_value, &value, env)) {
throw_compile_error(form,
fmt::format("Field {} is an integer, but the value given couldn't be "
"converted to an integer at compile time.",
field_name_def));
throw_compiler_error(form,
"Field {} is an integer, but the value given couldn't be "
"converted to an integer at compile time.",
field_name_def);
}
if (!integer_fits(value, deref_info.load_size, deref_info.sign_extend)) {
throw_compile_error(
form, fmt::format("Field {} is set to a compile time integer value of {} which would "
"overflow (size {} signed {})",
field_name_def, value, deref_info.load_size, deref_info.sign_extend));
throw_compiler_error(form,
"Field {} is set to a compile time integer value of {} which would "
"overflow (size {} signed {})",
field_name_def, value, deref_info.load_size, deref_info.sign_extend);
}
if (field_size == 1 || field_size == 2 || field_size == 4 || field_size == 8) {
@ -200,15 +201,15 @@ Val* Compiler::compile_new_static_structure_or_basic(const goos::Object& form,
u32 linker_val = 0xffffffff;
memcpy(obj->data.data() + field_offset, &linker_val, 4);
} else {
throw_compile_error(
throw_compiler_error(
form, "Setting a basic field to anything other than a symbol is currently unsupported");
}
} else if (is_float(field_info.type)) {
float value = 0.f;
if (!try_getting_constant_float(field_value, &value, env)) {
throw_compile_error(form, fmt::format("Field {} is a float, but the value given couldn't "
"be converted to a float at compile time.",
field_name_def));
throw_compiler_error(form, fmt::format("Field {} is a float, but the value given couldn't "
"be converted to a float at compile time.",
field_name_def));
}
memcpy(obj->data.data() + field_offset, &value, sizeof(float));
}

View file

@ -18,7 +18,8 @@ int get_offset_of_method(int id) {
* Given a type and method name (known at compile time), get the method.
* This can be used for method calls where the type is unknown at run time (non-virtual method call)
*/
RegVal* Compiler::compile_get_method_of_type(const TypeSpec& type,
RegVal* Compiler::compile_get_method_of_type(const goos::Object& form,
const TypeSpec& type,
const std::string& method_name,
Env* env) {
auto info = m_ts.lookup_method(type.base_type(), method_name);
@ -26,7 +27,7 @@ RegVal* Compiler::compile_get_method_of_type(const TypeSpec& type,
auto offset_of_method = get_offset_of_method(info.id);
auto fe = get_parent_env_of_type<FunctionEnv>(env);
auto typ = compile_get_symbol_value(type.base_type(), env)->to_gpr(env);
auto typ = compile_get_symbol_value(form, type.base_type(), env)->to_gpr(env);
MemLoadInfo load_info;
load_info.sign_extend = false;
load_info.size = POINTER_SIZE;
@ -48,7 +49,8 @@ RegVal* Compiler::compile_get_method_of_type(const TypeSpec& type,
* type to look up the method at runtime (virtual call). If we don't know it's a basic, we get the
* method from the compile-time type. (fixed type non-virtual call)
*/
RegVal* Compiler::compile_get_method_of_object(RegVal* object,
RegVal* Compiler::compile_get_method_of_object(const goos::Object& form,
RegVal* object,
const std::string& method_name,
Env* env) {
auto& compile_time_type = object->type();
@ -66,7 +68,7 @@ RegVal* Compiler::compile_get_method_of_object(RegVal* object,
env->emit(std::make_unique<IR_LoadConstOffset>(runtime_type, -4, object, info));
} else {
// can't look up at runtime
runtime_type = compile_get_symbol_value(compile_time_type.base_type(), env)->to_gpr(env);
runtime_type = compile_get_symbol_value(form, compile_time_type.base_type(), env)->to_gpr(env);
}
auto offset_of_method = get_offset_of_method(method_info.id);
@ -98,7 +100,7 @@ Val* Compiler::compile_format_string(const goos::Object& form,
args.insert(args.begin(), compile_get_sym_obj(out_stream, env)->to_gpr(env));
// generate code in the method_env
auto format_function = compile_get_symbol_value("_format", env)->to_gpr(env);
auto format_function = compile_get_symbol_value(form, "_format", env)->to_gpr(env);
return compile_real_function_call(form, format_function, args, env);
}
@ -205,10 +207,10 @@ Val* Compiler::generate_inspector_for_type(const goos::Object& form, Env* env, T
obj_env_inspect->add_function(std::move(method_env));
// call method-set!
auto type_obj = compile_get_symbol_value(structured_type->get_name(), env)->to_gpr(env);
auto type_obj = compile_get_symbol_value(form, structured_type->get_name(), env)->to_gpr(env);
auto id_val = compile_integer(m_ts.lookup_method(structured_type->get_name(), "inspect").id, env)
->to_gpr(env);
auto method_set_val = compile_get_symbol_value("method-set!", env)->to_gpr(env);
auto method_set_val = compile_get_symbol_value(form, "method-set!", env)->to_gpr(env);
return compile_real_function_call(form, method_set_val, {type_obj, id_val, method->to_gpr(env)},
env);
}
@ -235,10 +237,11 @@ Val* Compiler::compile_deftype(const goos::Object& form, const goos::Object& res
if (result.create_runtime_type) {
// get the new method of type object. this is new_type in kscheme.cpp
auto new_type_method = compile_get_method_of_type(m_ts.make_typespec("type"), "new", env);
auto new_type_method = compile_get_method_of_type(form, m_ts.make_typespec("type"), "new", env);
// call (new 'type 'type-name parent-type flags)
auto new_type_symbol = compile_get_sym_obj(result.type.base_type(), env)->to_gpr(env);
auto parent_type = compile_get_symbol_value(result.type_info->get_parent(), env)->to_gpr(env);
auto parent_type =
compile_get_symbol_value(form, result.type_info->get_parent(), env)->to_gpr(env);
auto flags_int = compile_integer(result.flags.flag, env)->to_gpr(env);
compile_real_function_call(form, new_type_method, {new_type_symbol, parent_type, flags_int},
env);
@ -266,10 +269,10 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
auto body = &pair_cdr(*rest);
if (!method_name.is_symbol()) {
throw_compile_error(form, "method name must be a symbol, got " + method_name.print());
throw_compiler_error(form, "Method name must be a symbol, got {}", method_name.print());
}
if (!type_name.is_symbol()) {
throw_compile_error(form, "method type must be a symbol, got " + method_name.print());
throw_compiler_error(form, "Method type must be a symbol, got {}", method_name.print());
}
auto place = fe->alloc_val<LambdaVal>(get_none()->type());
@ -316,7 +319,7 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
// set up arguments
if (lambda.params.size() > 8) {
throw_compile_error(form, "Methods cannot have more than 8 arguments");
throw_compiler_error(form, "Methods cannot have more than 8 arguments");
}
std::vector<RegVal*> args_for_coloring;
for (u32 i = 0; i < lambda.params.size(); i++) {
@ -371,10 +374,10 @@ Val* Compiler::compile_defmethod(const goos::Object& form, const goos::Object& _
auto info =
m_ts.add_method(symbol_string(type_name), symbol_string(method_name), lambda_ts, false);
auto type_obj = compile_get_symbol_value(symbol_string(type_name), env)->to_gpr(env);
auto type_obj = compile_get_symbol_value(form, symbol_string(type_name), env)->to_gpr(env);
auto id_val = compile_integer(info.id, env)->to_gpr(env);
auto method_val = place->to_gpr(env);
auto method_set_val = compile_get_symbol_value("method-set!", env)->to_gpr(env);
auto method_set_val = compile_get_symbol_value(form, "method-set!", env)->to_gpr(env);
return compile_real_function_call(form, method_set_val, {type_obj, id_val, method_val}, env);
}
@ -422,7 +425,7 @@ Val* Compiler::get_field_of_structure(const StructureType* type,
Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest, Env* env) {
auto fe = get_parent_env_of_type<FunctionEnv>(env);
if (_rest.is_empty_list()) {
throw_compile_error(form, "-> must get at least one argument");
throw_compiler_error(form, "-> must get at least one argument");
}
auto& first_arg = pair_car(_rest);
@ -435,7 +438,7 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest
// one argument, do a pointer deref
auto deref_info = m_ts.get_deref_info(result->type());
if (!deref_info.can_deref) {
throw_compile_error(form, "Cannot dereference a " + result->type().print());
throw_compiler_error(form, "Cannot dereference a {}.", result->type().print());
}
if (deref_info.mem_deref) {
@ -475,7 +478,7 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest
auto index_value = compile_error_guard(field_obj, env)->to_gpr(env);
if (!is_integer(index_value->type())) {
throw_compile_error(form, "cannot use -> with " + field_obj.print());
throw_compiler_error(form, "Cannot use -> with {}.", field_obj.print());
}
if (result->type().base_type() == "inline-array") {
@ -498,7 +501,7 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest
result = fe->alloc_val<MemoryDerefVal>(di.result_type, loc, MemLoadInfo(di));
result->mark_as_settable();
} else {
throw_compile_error(form, "can't access array of type " + result->type().print());
throw_compiler_error(form, "Cannot access array of type {}.", result->type().print());
}
}
return result;
@ -513,7 +516,7 @@ Val* Compiler::compile_addr_of(const goos::Object& form, const goos::Object& res
auto loc = compile_error_guard(args.unnamed.at(0), env);
auto as_mem_deref = dynamic_cast<MemoryDerefVal*>(loc);
if (!as_mem_deref) {
throw_compile_error(form, "Cannot take the address of this " + loc->print());
throw_compiler_error(form, "Cannot take the address of {}.", loc->print());
}
return as_mem_deref->base;
}
@ -547,11 +550,11 @@ Val* Compiler::compile_the(const goos::Object& form, const goos::Object& rest, E
if (is_number(base->type())) {
if (m_ts.typecheck(m_ts.make_typespec("binteger"), desired_ts, "", false, false)) {
return number_to_binteger(base, env);
return number_to_binteger(form, base, env);
}
if (m_ts.typecheck(m_ts.make_typespec("integer"), desired_ts, "", false, false)) {
auto result = number_to_integer(base, env);
auto result = number_to_integer(form, base, env);
if (result != base) {
result->set_type(desired_ts);
return result;
@ -562,7 +565,7 @@ Val* Compiler::compile_the(const goos::Object& form, const goos::Object& rest, E
}
if (m_ts.typecheck(m_ts.make_typespec("float"), desired_ts, "", false, false)) {
return number_to_float(base, env);
return number_to_float(form, base, env);
}
}
@ -611,18 +614,17 @@ Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest,
if (!rest->is_empty_list()) {
// got extra arguments
throw_compile_error(form, "new array form got more arguments than expected");
throw_compiler_error(form, "new array form got more arguments than expected");
}
auto ts = is_inline ? m_ts.make_inline_array_typespec(elt_type)
: m_ts.make_pointer_typespec(elt_type);
auto info = m_ts.get_deref_info(ts);
if (!info.can_deref) {
throw_compile_error(form,
fmt::format("Cannot make an {} of {}\n", type_as_string, ts.print()));
throw_compiler_error(form, "Cannot make an {} of {}\n", type_as_string, ts.print());
}
auto malloc_func = compile_get_symbol_value("malloc", env)->to_reg(env);
auto malloc_func = compile_get_symbol_value(form, "malloc", env)->to_reg(env);
std::vector<RegVal*> args;
args.push_back(compile_get_sym_obj(allocation, env)->to_reg(env));
@ -646,13 +648,13 @@ Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest,
// allocation
args.push_back(compile_get_sym_obj(allocation, env)->to_reg(env));
// type
args.push_back(compile_get_symbol_value(type_of_obj.base_type(), env)->to_reg(env));
args.push_back(compile_get_symbol_value(form, type_of_obj.base_type(), env)->to_reg(env));
// the other arguments
for_each_in_list(*rest, [&](const goos::Object& o) {
args.push_back(compile_error_guard(o, env)->to_reg(env));
});
auto new_method = compile_get_method_of_type(type_of_obj, "new", env);
auto new_method = compile_get_method_of_type(form, type_of_obj, "new", env);
auto new_obj = compile_real_function_call(form, new_method, args, env);
new_obj->set_type(type_of_obj);
return new_obj;
@ -681,20 +683,19 @@ Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest,
int64_t constant_count = 0;
bool is_constant_size = try_getting_constant_integer(count_obj, &constant_count, env);
if (!is_constant_size) {
throw_compile_error(form, "cannot create a dynamically sized stack array");
throw_compiler_error(form, "Cannot create a dynamically sized stack array");
}
if (!rest->is_empty_list()) {
// got extra arguments
throw_compile_error(form, "new array form got more arguments than expected");
throw_compiler_error(form, "New array form got more arguments than expected");
}
auto ts = is_inline ? m_ts.make_inline_array_typespec(elt_type)
: m_ts.make_pointer_typespec(elt_type);
auto info = m_ts.get_deref_info(ts);
if (!info.can_deref) {
throw_compile_error(form,
fmt::format("Cannot make an {} of {}\n", type_as_string, ts.print()));
throw_compiler_error(form, "Cannot make an {} of {}\n", type_as_string, ts.print());
}
if (!m_ts.lookup_type(elt_type)->is_reference()) {
@ -708,7 +709,7 @@ Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest,
// todo, stack not-arrays
}
throw_compile_error(form, "unsupported new form");
throw_compiler_error(form, "Unsupported new form");
return get_none();
}
@ -748,15 +749,16 @@ Val* Compiler::compile_method(const goos::Object& form, const goos::Object& rest
if (arg.is_symbol()) {
if (m_ts.fully_defined_type_exists(symbol_string(arg))) {
return compile_get_method_of_type(m_ts.make_typespec(symbol_string(arg)), method_name, env);
return compile_get_method_of_type(form, m_ts.make_typespec(symbol_string(arg)), method_name,
env);
} else if (m_ts.partially_defined_type_exists(symbol_string(arg))) {
throw_compile_error(form,
"The method form is ambiguous when used on a forward declared type.");
throw_compiler_error(form,
"The method form is ambiguous when used on a forward declared type.");
}
}
auto obj = compile_error_guard(arg, env)->to_gpr(env);
return compile_get_method_of_object(obj, method_name, env);
return compile_get_method_of_object(form, obj, method_name, env);
}
Val* Compiler::compile_declare_type(const goos::Object& form, const goos::Object& rest, Env* env) {
@ -772,7 +774,7 @@ Val* Compiler::compile_declare_type(const goos::Object& form, const goos::Object
} else if (kind == "structure") {
m_ts.forward_declare_type_as_structure(type_name);
} else {
throw_compile_error(form, "Invalid declare-type form");
throw_compiler_error(form, "Invalid declare-type form: unrecognized option {}.", kind);
}
return get_none();

View file

@ -22,7 +22,7 @@ std::string FunctionDebugInfo::disassemble_debug_info(bool* had_failure) {
return result;
}
std::string DebugInfo::disassemble_debug_functions(bool* had_failure) {
std::string DebugInfo::disassemble_all_functions(bool* had_failure) {
std::string result;
for (auto& kv : m_functions) {
result += kv.second.disassemble_debug_info(had_failure) + "\n\n";

View file

@ -48,7 +48,7 @@ class DebugInfo {
void clear() { m_functions.clear(); }
std::string disassemble_debug_functions(bool* had_failure);
std::string disassemble_all_functions(bool* had_failure);
private:
std::string m_obj_name;

View file

@ -282,7 +282,7 @@ TEST_F(WithGameTests, DebuggerMemoryMap) {
TEST_F(WithGameTests, DebuggerDisassemble) {
auto di = compiler.get_debugger().get_debug_info_for_object("gcommon");
bool fail = false;
auto result = di.disassemble_debug_functions(&fail);
auto result = di.disassemble_all_functions(&fail);
// printf("Got\n%s\n", result.c_str());
EXPECT_FALSE(fail);
}

566
third-party/fmt/color.h vendored Normal file
View file

@ -0,0 +1,566 @@
// Formatting library for C++ - color support
//
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_COLOR_H_
#define FMT_COLOR_H_
#include "format.h"
FMT_BEGIN_NAMESPACE
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
antique_white = 0xFAEBD7, // rgb(250,235,215)
aqua = 0x00FFFF, // rgb(0,255,255)
aquamarine = 0x7FFFD4, // rgb(127,255,212)
azure = 0xF0FFFF, // rgb(240,255,255)
beige = 0xF5F5DC, // rgb(245,245,220)
bisque = 0xFFE4C4, // rgb(255,228,196)
black = 0x000000, // rgb(0,0,0)
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
blue = 0x0000FF, // rgb(0,0,255)
blue_violet = 0x8A2BE2, // rgb(138,43,226)
brown = 0xA52A2A, // rgb(165,42,42)
burly_wood = 0xDEB887, // rgb(222,184,135)
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
chartreuse = 0x7FFF00, // rgb(127,255,0)
chocolate = 0xD2691E, // rgb(210,105,30)
coral = 0xFF7F50, // rgb(255,127,80)
cornflower_blue = 0x6495ED, // rgb(100,149,237)
cornsilk = 0xFFF8DC, // rgb(255,248,220)
crimson = 0xDC143C, // rgb(220,20,60)
cyan = 0x00FFFF, // rgb(0,255,255)
dark_blue = 0x00008B, // rgb(0,0,139)
dark_cyan = 0x008B8B, // rgb(0,139,139)
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
dark_gray = 0xA9A9A9, // rgb(169,169,169)
dark_green = 0x006400, // rgb(0,100,0)
dark_khaki = 0xBDB76B, // rgb(189,183,107)
dark_magenta = 0x8B008B, // rgb(139,0,139)
dark_olive_green = 0x556B2F, // rgb(85,107,47)
dark_orange = 0xFF8C00, // rgb(255,140,0)
dark_orchid = 0x9932CC, // rgb(153,50,204)
dark_red = 0x8B0000, // rgb(139,0,0)
dark_salmon = 0xE9967A, // rgb(233,150,122)
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
dark_turquoise = 0x00CED1, // rgb(0,206,209)
dark_violet = 0x9400D3, // rgb(148,0,211)
deep_pink = 0xFF1493, // rgb(255,20,147)
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
dim_gray = 0x696969, // rgb(105,105,105)
dodger_blue = 0x1E90FF, // rgb(30,144,255)
fire_brick = 0xB22222, // rgb(178,34,34)
floral_white = 0xFFFAF0, // rgb(255,250,240)
forest_green = 0x228B22, // rgb(34,139,34)
fuchsia = 0xFF00FF, // rgb(255,0,255)
gainsboro = 0xDCDCDC, // rgb(220,220,220)
ghost_white = 0xF8F8FF, // rgb(248,248,255)
gold = 0xFFD700, // rgb(255,215,0)
golden_rod = 0xDAA520, // rgb(218,165,32)
gray = 0x808080, // rgb(128,128,128)
green = 0x008000, // rgb(0,128,0)
green_yellow = 0xADFF2F, // rgb(173,255,47)
honey_dew = 0xF0FFF0, // rgb(240,255,240)
hot_pink = 0xFF69B4, // rgb(255,105,180)
indian_red = 0xCD5C5C, // rgb(205,92,92)
indigo = 0x4B0082, // rgb(75,0,130)
ivory = 0xFFFFF0, // rgb(255,255,240)
khaki = 0xF0E68C, // rgb(240,230,140)
lavender = 0xE6E6FA, // rgb(230,230,250)
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
lawn_green = 0x7CFC00, // rgb(124,252,0)
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
light_blue = 0xADD8E6, // rgb(173,216,230)
light_coral = 0xF08080, // rgb(240,128,128)
light_cyan = 0xE0FFFF, // rgb(224,255,255)
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
light_gray = 0xD3D3D3, // rgb(211,211,211)
light_green = 0x90EE90, // rgb(144,238,144)
light_pink = 0xFFB6C1, // rgb(255,182,193)
light_salmon = 0xFFA07A, // rgb(255,160,122)
light_sea_green = 0x20B2AA, // rgb(32,178,170)
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
light_slate_gray = 0x778899, // rgb(119,136,153)
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
light_yellow = 0xFFFFE0, // rgb(255,255,224)
lime = 0x00FF00, // rgb(0,255,0)
lime_green = 0x32CD32, // rgb(50,205,50)
linen = 0xFAF0E6, // rgb(250,240,230)
magenta = 0xFF00FF, // rgb(255,0,255)
maroon = 0x800000, // rgb(128,0,0)
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
medium_blue = 0x0000CD, // rgb(0,0,205)
medium_orchid = 0xBA55D3, // rgb(186,85,211)
medium_purple = 0x9370DB, // rgb(147,112,219)
medium_sea_green = 0x3CB371, // rgb(60,179,113)
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
medium_violet_red = 0xC71585, // rgb(199,21,133)
midnight_blue = 0x191970, // rgb(25,25,112)
mint_cream = 0xF5FFFA, // rgb(245,255,250)
misty_rose = 0xFFE4E1, // rgb(255,228,225)
moccasin = 0xFFE4B5, // rgb(255,228,181)
navajo_white = 0xFFDEAD, // rgb(255,222,173)
navy = 0x000080, // rgb(0,0,128)
old_lace = 0xFDF5E6, // rgb(253,245,230)
olive = 0x808000, // rgb(128,128,0)
olive_drab = 0x6B8E23, // rgb(107,142,35)
orange = 0xFFA500, // rgb(255,165,0)
orange_red = 0xFF4500, // rgb(255,69,0)
orchid = 0xDA70D6, // rgb(218,112,214)
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
pale_green = 0x98FB98, // rgb(152,251,152)
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
pale_violet_red = 0xDB7093, // rgb(219,112,147)
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
peach_puff = 0xFFDAB9, // rgb(255,218,185)
peru = 0xCD853F, // rgb(205,133,63)
pink = 0xFFC0CB, // rgb(255,192,203)
plum = 0xDDA0DD, // rgb(221,160,221)
powder_blue = 0xB0E0E6, // rgb(176,224,230)
purple = 0x800080, // rgb(128,0,128)
rebecca_purple = 0x663399, // rgb(102,51,153)
red = 0xFF0000, // rgb(255,0,0)
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
royal_blue = 0x4169E1, // rgb(65,105,225)
saddle_brown = 0x8B4513, // rgb(139,69,19)
salmon = 0xFA8072, // rgb(250,128,114)
sandy_brown = 0xF4A460, // rgb(244,164,96)
sea_green = 0x2E8B57, // rgb(46,139,87)
sea_shell = 0xFFF5EE, // rgb(255,245,238)
sienna = 0xA0522D, // rgb(160,82,45)
silver = 0xC0C0C0, // rgb(192,192,192)
sky_blue = 0x87CEEB, // rgb(135,206,235)
slate_blue = 0x6A5ACD, // rgb(106,90,205)
slate_gray = 0x708090, // rgb(112,128,144)
snow = 0xFFFAFA, // rgb(255,250,250)
spring_green = 0x00FF7F, // rgb(0,255,127)
steel_blue = 0x4682B4, // rgb(70,130,180)
tan = 0xD2B48C, // rgb(210,180,140)
teal = 0x008080, // rgb(0,128,128)
thistle = 0xD8BFD8, // rgb(216,191,216)
tomato = 0xFF6347, // rgb(255,99,71)
turquoise = 0x40E0D0, // rgb(64,224,208)
violet = 0xEE82EE, // rgb(238,130,238)
wheat = 0xF5DEB3, // rgb(245,222,179)
white = 0xFFFFFF, // rgb(255,255,255)
white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color
enum class terminal_color : uint8_t {
black = 30,
red,
green,
yellow,
blue,
magenta,
cyan,
white,
bright_black = 90,
bright_red,
bright_green,
bright_yellow,
bright_blue,
bright_magenta,
bright_cyan,
bright_white
};
enum class emphasis : uint8_t {
bold = 1,
italic = 1 << 1,
underline = 1 << 2,
strikethrough = 1 << 3
};
// rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb {
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
FMT_CONSTEXPR rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
FMT_CONSTEXPR rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {}
uint8_t r;
uint8_t g;
uint8_t b;
};
namespace detail {
// color is a struct of either a rgb color or a terminal color.
struct color_type {
FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {}
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true),
value{} {
value.rgb_color = static_cast<uint32_t>(rgb_color);
}
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} {
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
}
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(),
value{} {
value.term_color = static_cast<uint8_t>(term_color);
}
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
};
} // namespace detail
// Experimental text formatting support.
class text_style {
public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
: set_foreground_color(),
set_background_color(),
ems(em) {}
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't OR a terminal color"));
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't OR a terminal color"));
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR text_style operator|(text_style lhs,
const text_style& rhs) {
return lhs |= rhs;
}
FMT_CONSTEXPR text_style& operator&=(const text_style& rhs) {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
FMT_THROW(format_error("can't AND a terminal color"));
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
static_cast<uint8_t>(rhs.ems));
return *this;
}
friend FMT_CONSTEXPR text_style operator&(text_style lhs,
const text_style& rhs) {
return lhs &= rhs;
}
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
return set_foreground_color;
}
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
return set_background_color;
}
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
return static_cast<uint8_t>(ems) != 0;
}
FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color;
}
FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
FMT_ASSERT(has_background(), "no background specified for this style");
return background_color;
}
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return ems;
}
private:
FMT_CONSTEXPR text_style(bool is_foreground,
detail::color_type text_color) FMT_NOEXCEPT
: set_foreground_color(),
set_background_color(),
ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
FMT_NOEXCEPT;
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
FMT_NOEXCEPT;
detail::color_type foreground_color;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
};
FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/true, foreground);
}
FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/false, background);
}
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
return text_style(lhs) | rhs;
}
namespace detail {
template <typename Char> struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
const char* esc) FMT_NOEXCEPT {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
bool is_background = esc == detail::data::background_color;
uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background) value += 10u;
size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) {
buffer[index++] = static_cast<Char>('1');
value %= 100u;
}
buffer[index++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return;
}
for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]);
}
rgb color(text_color.value.rgb_color);
to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0);
}
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
uint8_t em_codes[4] = {};
uint8_t em_bits = static_cast<uint8_t>(em);
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
em_codes[3] = 9;
size_t index = 0;
for (int i = 0; i < 4; ++i) {
if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
}
buffer[index++] = static_cast<Char>(0);
}
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT {
return buffer + std::char_traits<Char>::length(buffer);
}
private:
Char buffer[7u + 3u * 4u + 1u];
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) FMT_NOEXCEPT {
out[0] = static_cast<Char>('0' + c / 100);
out[1] = static_cast<Char>('0' + c / 10 % 10);
out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter);
}
};
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
detail::color_type foreground) FMT_NOEXCEPT {
return ansi_color_escape<Char>(foreground, detail::data::foreground_color);
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
detail::color_type background) FMT_NOEXCEPT {
return ansi_color_escape<Char>(background, detail::data::background_color);
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT {
return ansi_color_escape<Char>(em);
}
template <typename Char>
inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT {
std::fputs(chars, stream);
}
template <>
inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
std::fputws(chars, stream);
}
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
fputs(detail::data::reset_color, stream);
}
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
fputs(detail::data::wreset_color, stream);
}
template <typename Char>
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
const char* begin = data::reset_color;
const char* end = begin + sizeof(data::reset_color) - 1;
buffer.append(begin, end);
}
template <typename Char>
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<Char>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
has_style = true;
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
has_style = true;
auto background = detail::make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
detail::vformat_to(buf, format_str, args);
if (has_style) detail::reset_color<Char>(buf);
}
} // namespace detail
template <typename S, typename Char = char_t<S>>
void vprint(std::FILE* f, const text_style& ts, const S& format,
basic_format_args<buffer_context<Char>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, to_string_view(format), args);
buf.push_back(Char(0));
detail::fputs(buf.data(), f);
}
/**
Formats a string and prints it to the specified file stream using ANSI
escape sequences to specify text formatting.
Example:
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
void print(std::FILE* f, const text_style& ts, const S& format_str,
const Args&... args) {
detail::check_format_string<Args...>(format_str);
using context = buffer_context<char_t<S>>;
format_arg_store<context, Args...> as{args...};
vprint(f, ts, format_str, basic_format_args<context>(as));
}
/**
Formats a string and prints it to stdout using ANSI escape sequences to
specify text formatting.
Example:
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_string<S>::value)>
void print(const text_style& ts, const S& format_str, const Args&... args) {
return print(stdout, ts, format_str, args...);
}
template <typename S, typename Char = char_t<S>>
inline std::basic_string<Char> vformat(
const text_style& ts, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buf;
detail::vformat_to(buf, ts, to_string_view(format_str), args);
return fmt::to_string(buf);
}
/**
\rst
Formats arguments and returns the result as a string using ANSI
escape sequences to specify text formatting.
**Example**::
#include <fmt/color.h>
std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
"The answer is {}", 42);
\endrst
*/
template <typename S, typename... Args, typename Char = char_t<S>>
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
const Args&... args) {
return vformat(ts, to_string_view(format_str),
detail::make_args_checked<Args...>(format_str, args...));
}
FMT_END_NAMESPACE
#endif // FMT_COLOR_H_