Allow enum names to serve as types (using the enum's type) (#372)

* Allow enum names to serve as types (using the enum's type)

* Formatting

* add doc to `get_enum_type_name`

* Ban enum forward-declaring, and remove unneeded checks
This commit is contained in:
ManDude
2021-04-20 01:29:38 +01:00
committed by GitHub
parent c09e18f3a7
commit 0560136f08
13 changed files with 268 additions and 72 deletions
+1
View File
@@ -10,6 +10,7 @@ add_library(common
goos/TextDB.cpp
goos/ReplUtils.cpp
log/log.cpp
type_system/defenum.cpp
type_system/deftype.cpp
type_system/Type.cpp
type_system/TypeFieldLookup.cpp
@@ -6,10 +6,11 @@
#include "common/type_system/TypeSpec.h"
struct GoalEnum {
std::string name;
TypeSpec base_type;
bool is_bitfield = false;
std::unordered_map<std::string, s64> entries;
bool operator==(const GoalEnum& other) const;
bool operator!=(const GoalEnum& other) const;
};
};
+51 -2
View File
@@ -30,7 +30,7 @@ Type* TypeSystem::add_type(const std::string& name, std::unique_ptr<Type> type)
if (*kv->second != *type) {
// exists, and we are trying to change it!
fmt::print("[TypeSystem] type {} was originally\n{}\nand is redefined as\n{}\n",
fmt::print("[TypeSystem] Type {} was originally\n{}\nand is redefined as\n{}\n",
kv->second->get_name(), kv->second->print(), type->print());
if (m_allow_redefinition) {
@@ -70,6 +70,37 @@ Type* TypeSystem::add_type(const std::string& name, std::unique_ptr<Type> type)
return m_types[name].get();
}
/*!
* Add a new 'enum type'. This maps enum names to the their type's name, allowing the enum name to
* be used as if it were a type name.
*/
Type* TypeSystem::add_enum_type(const std::string& name, const std::string& type) {
auto kv = m_enum_types.find(name);
if (kv != m_enum_types.end()) {
// exists already
if (kv->second != type) {
// exists, and we are trying to change it!
fmt::print("[TypeSystem] Enum {} was originally\n{}\nand is redefined as\n{}\n", name,
kv->second, type);
throw std::runtime_error("Enum type was redefined.");
}
} else {
// an enum has been forward declared, which is only allowed for types
if (m_forward_declared_types.find(name) != m_forward_declared_types.end()) {
fmt::print("[TypeSystem] Enum {} was forward-declared, enums cannot be forward-declared\n",
name);
throw std::runtime_error("Enum was forward-declared.");
}
m_enum_types[name] = type;
}
return lookup_type(m_enum_types[name]);
}
/*!
* Inform the type system that there will eventually be a type named "name".
* This will allow the type system to generate TypeSpecs for this type, but not access detailed
@@ -185,6 +216,9 @@ TypeSpec TypeSystem::make_typespec(const std::string& name) const {
if (m_types.find(name) != m_types.end() ||
m_forward_declared_types.find(name) != m_forward_declared_types.end()) {
return TypeSpec(name);
} else if (m_enum_types.find(name) != m_enum_types.end()) {
// simply return the enum's type instead
return TypeSpec(m_enum_types.at(name));
} else {
fmt::print("[TypeSystem] The type {} is unknown.\n", name);
throw std::runtime_error("make_typespec failed");
@@ -199,6 +233,10 @@ bool TypeSystem::partially_defined_type_exists(const std::string& name) const {
return m_forward_declared_types.find(name) != m_forward_declared_types.end();
}
bool TypeSystem::enum_type_exists(const std::string& name) const {
return m_enum_types.find(name) != m_enum_types.end();
}
TypeSpec TypeSystem::make_array_typespec(const TypeSpec& element_type) const {
return TypeSpec("array", {element_type});
}
@@ -321,6 +359,17 @@ MethodInfo TypeSystem::add_method(const std::string& type_name,
return add_method(lookup_type(make_typespec(type_name)), method_name, ts, allow_new_method);
}
/*!
* Return the type name of an enum. Throws if the enum does not exist.
*/
std::string TypeSystem::get_enum_type_name(const std::string& name) const {
if (m_enum_types.find(name) != m_enum_types.end()) {
return m_enum_types.at(name);
} else {
throw std::runtime_error("get_enum_type_name failed");
}
}
/*!
* Add a method, if it doesn't exist. If the method already exists (possibly in a parent), checks to
* see if this is an identical definition. If not, it's an error, and if so, nothing happens.
@@ -1474,4 +1523,4 @@ std::string TypeSystem::generate_deftype(const Type* type) const {
return fmt::format(
";; cannot generate deftype for {}, it is not a structure, basic, or bitfield (parent {})\n",
type->get_name(), type->get_parent());
}
}
+5
View File
@@ -91,6 +91,7 @@ class TypeSystem {
TypeSystem();
Type* add_type(const std::string& name, std::unique_ptr<Type> type);
Type* add_enum_type(const std::string& name, const std::string& type);
void forward_declare_type(const std::string& name);
void forward_declare_type_as_basic(const std::string& name);
void forward_declare_type_as_structure(const std::string& name);
@@ -101,6 +102,7 @@ class TypeSystem {
bool fully_defined_type_exists(const std::string& name) const;
bool partially_defined_type_exists(const std::string& name) const;
bool enum_type_exists(const std::string& name) const;
TypeSpec make_typespec(const std::string& name) const;
TypeSpec make_array_typespec(const TypeSpec& element_type) const;
TypeSpec make_function_typespec(const std::vector<std::string>& arg_types,
@@ -117,6 +119,8 @@ class TypeSystem {
Type* lookup_type_allow_partial_def(const TypeSpec& ts) const;
Type* lookup_type_allow_partial_def(const std::string& name) const;
std::string get_enum_type_name(const std::string& name) const;
MethodInfo add_method(const std::string& type_name,
const std::string& method_name,
const TypeSpec& ts,
@@ -229,6 +233,7 @@ class TypeSystem {
enum ForwardDeclareKind { TYPE, STRUCTURE, BASIC };
std::unordered_map<std::string, std::unique_ptr<Type>> m_types;
std::unordered_map<std::string, std::string> m_enum_types;
std::unordered_map<std::string, ForwardDeclareKind> m_forward_declared_types;
std::vector<std::unique_ptr<Type>> m_old_types;
+154
View File
@@ -0,0 +1,154 @@
/*!
* @file defenum.cpp
* Parser for the GOAL "defenum" form.
* This is used both in the compiler and in the decompiler for the type definition file.
*/
#include "Enum.h"
#include "defenum.h"
#include "deftype.h"
#include "third-party/fmt/core.h"
namespace {
const goos::Object& car(const goos::Object* x) {
if (!x->is_pair()) {
throw std::runtime_error("invalid defenum form");
}
return x->as_pair()->car;
}
const goos::Object* cdr(const goos::Object* x) {
if (!x->is_pair()) {
throw std::runtime_error("invalid defenum form");
}
return &x->as_pair()->cdr;
}
bool is_type(const std::string& expected, const TypeSpec& actual, const TypeSystem* ts) {
return ts->tc(ts->make_typespec(expected), actual);
}
bool integer_fits(s64 in, int size, bool is_signed) {
switch (size) {
case 1:
if (is_signed) {
return in >= INT8_MIN && in <= INT8_MAX;
} else {
return in >= 0 && in <= UINT8_MAX;
}
case 2:
if (is_signed) {
return in >= INT16_MIN && in <= INT16_MAX;
} else {
return in >= 0 && in <= UINT16_MAX;
}
case 4:
if (is_signed) {
return in >= INT32_MIN && in <= INT32_MAX;
} else {
return in >= 0 && in <= UINT32_MAX;
}
case 8:
return true;
default:
assert(false);
return false;
}
}
template <typename T>
void for_each_in_list(const goos::Object& list, T f) {
const goos::Object* iter = &list;
while (iter->is_pair()) {
auto lap = iter->as_pair();
f(lap->car);
iter = &lap->cdr;
}
if (!iter->is_empty_list()) {
throw std::runtime_error("invalid list in for_each_in_list: " + list.print());
}
}
std::string symbol_string(const goos::Object& obj) {
if (obj.is_symbol()) {
return obj.as_symbol()->name;
}
throw std::runtime_error(obj.print() + " was supposed to be a symbol, but isn't");
}
} // namespace
void parse_defenum(const goos::Object& defenum, TypeSystem* ts, GoalEnum& goalenum) {
// default enum type will be int32.
goalenum.base_type = ts->make_typespec("int32");
goalenum.is_bitfield = false;
auto iter = &defenum;
auto& enum_name_obj = car(iter);
iter = cdr(iter);
if (!enum_name_obj.is_symbol()) {
throw std::runtime_error("defenum must be given a symbol as its name");
}
goalenum.name = enum_name_obj.as_symbol()->name;
auto current = car(iter);
while (current.is_symbol() && symbol_string(current).at(0) == ':') {
auto option_name = symbol_string(current);
iter = cdr(iter);
auto option_value = car(iter);
iter = cdr(iter);
current = car(iter);
if (option_name == ":type") {
goalenum.base_type = parse_typespec(ts, option_value);
} else if (option_name == ":bitfield") {
if (symbol_string(option_value) == "#t") {
goalenum.is_bitfield = true;
} else if (symbol_string(option_value) == "#f") {
goalenum.is_bitfield = false;
} else {
fmt::print("Invalid option {} to :bitfield option.\n", option_value.print());
throw std::runtime_error("invalid bitfield option");
}
} else {
fmt::print("Unknown option {} for defenum.\n", option_name);
throw std::runtime_error("unknown option for defenum");
}
}
auto type = ts->lookup_type(goalenum.base_type);
while (!iter->is_empty_list()) {
auto field = car(iter);
auto name = symbol_string(car(&field));
auto rest = cdr(&field);
auto& value = car(rest);
if (!value.is_int()) {
fmt::print("Expected integer for enum value, got {}\n", value.print());
}
auto entry_val = value.integer_obj.value;
if (!integer_fits(entry_val, type->get_load_size(), type->get_load_signed())) {
fmt::print("Integer {} does not fit inside a {}\n", entry_val, type->get_name());
}
rest = cdr(rest);
if (!rest->is_empty_list()) {
fmt::print("Got too many items in defenum {} entry {}\n", goalenum.name, name);
}
goalenum.entries[name] = entry_val;
iter = cdr(iter);
}
if (is_type("integer", goalenum.base_type, ts)) {
ts->add_enum_type(goalenum.name, goalenum.base_type.base_type());
} else {
throw std::runtime_error("Creating an enum with type " + goalenum.base_type.print() +
" is not allowed or not supported yet.");
}
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
/*!
* @file defenum.h
* Parser for the GOAL "defenum" form.
* This is used both in the compiler and in the decompiler for the type definition file.
*/
#include "TypeSystem.h"
#include "Enum.h"
#include "common/goos/Object.h"
void parse_defenum(const goos::Object& defenum, TypeSystem* ts, GoalEnum& goalenum);
-1
View File
@@ -8,7 +8,6 @@
#include "TypeSystem.h"
#include "common/goos/Object.h"
#include "common/type_system/TypeSystem.h"
struct DeftypeResult {
TypeFlags flags;
+11 -1
View File
@@ -2931,10 +2931,20 @@
)
:flag-assert #x900000008
)
(defenum gs-prim-type
:type uint8
(point 0)
(line 1)
(line-strip 2)
(tri 3)
(tri-strip 4)
(tri-fan 5)
(sprite 6)
)
;; the GS's PRIM register specifies the types of drawing primitives and various attributes, and
;; initializes the contents of the vertex queue.
(deftype gs-prim (uint64)
((prim uint8 :offset 0 :size 3)
((prim gs-prim-type :offset 0 :size 3)
(iip uint8 :offset 3 :size 1)
(tme uint8 :offset 4 :size 1)
(fge uint8 :offset 5 :size 1)
+7 -1
View File
@@ -1,7 +1,9 @@
#include "DecompilerTypeSystem.h"
#include "common/goos/Reader.h"
#include "common/type_system/defenum.h"
#include "common/type_system/deftype.h"
#include "decompiler/Disasm/Register.h"
#include "common/type_system/Enum.h"
#include "common/log/log.h"
#include "TP_Type.h"
@@ -77,6 +79,10 @@ void DecompilerTypeSystem::parse_type_defs(const std::vector<std::string>& file_
} else {
throw std::runtime_error("bad declare-type");
}
} else if (car(o).as_symbol()->name == "defenum") {
GoalEnum new_enum;
parse_defenum(cdr(o), &ts, new_enum);
// TODO we do nothing with the enum for now
} else {
throw std::runtime_error("Decompiler cannot parse " + car(o).print());
}
@@ -395,4 +401,4 @@ TypeSpec DecompilerTypeSystem::lookup_symbol_type(const std::string& name) const
return kv->second;
}
}
} // namespace decompiler
} // namespace decompiler
+1 -1
View File
@@ -1264,7 +1264,7 @@ Get the size of a type, in bytes.
```lisp
(size-of <type-name>)
```
Get the size of a type, by name. The type must be a plain name, like `pointer` or `dma-bucket`. Compound types are not supported. It works on value and structure types and returns the size in memory. For dynamic types, it returns the size if there if the dynamic part has 0 size. For weird types like `none` it throws an error.
Get the size of a type, by name. The type must be a plain name, like `pointer` or `dma-bucket`. Compound types are not supported. It works on value and structure types and returns the size in memory. For dynamic types, it returns the size as if the dynamic part has 0 size. For weird types like `none` it throws an error.
This value can be used in most places where the compiler is expecting a constant integer as well, such as the size of a stack array, which must be known at compile time.
+3 -2
View File
@@ -242,7 +242,8 @@
:flag-assert #x900000008
)
(defenum gs-prim-type :bitfield #f
(defenum gs-prim-type
:type uint8
(point 0)
(line 1)
(line-strip 2)
@@ -254,7 +255,7 @@
;; the GS's PRIM register specifies the types of drawing primitives and various attributes, and
;; initializes the contents of the vertex queue.
(deftype gs-prim (uint64)
((prim uint8 :offset 0 :size 3)
((prim gs-prim-type :offset 0 :size 3)
(iip uint8 :offset 3 :size 1)
(tme uint8 :offset 4 :size 1)
(fge uint8 :offset 5 :size 1)
+1 -1
View File
@@ -17,7 +17,7 @@
#include "third-party/fmt/color.h"
#include "CompilerException.h"
#include "goalc/compiler/SymbolInfo.h"
#include "Enum.h"
#include "common/type_system/Enum.h"
#include "common/goos/ReplUtils.h"
enum MathMode { MATH_INT, MATH_BINT, MATH_FLOAT, MATH_INVALID };
+19 -62
View File
@@ -1,7 +1,8 @@
#include "goalc/compiler/Compiler.h"
#include "third-party/fmt/core.h"
#include "common/type_system/defenum.h"
#include "common/type_system/deftype.h"
#include "goalc/compiler/Enum.h"
#include "common/type_system/Enum.h"
namespace {
@@ -230,7 +231,7 @@ Val* Compiler::compile_deftype(const goos::Object& form, const goos::Object& res
auto kv = m_symbol_types.find(result.type.base_type());
if (kv != m_symbol_types.end() && kv->second.base_type() != "type") {
// we already have something that's not a type with the same name, this is bad.
fmt::print("[Warning] deftype will redefined {} from {} to a type.\n", result.type.base_type(),
fmt::print("[Warning] deftype will redefine {} from {} to a type.\n", result.type.base_type(),
kv->second.print());
}
// remember that this is a type
@@ -993,69 +994,20 @@ Val* Compiler::compile_none(const goos::Object& form, const goos::Object& rest,
return get_none();
}
Val* Compiler::compile_defenum(const goos::Object& form, const goos::Object& _rest, Env* env) {
Val* Compiler::compile_defenum(const goos::Object& form, const goos::Object& rest, Env* env) {
// format is (defenum name [options] [entries])
(void)form;
(void)env;
auto* rest = &_rest;
// name
auto enum_name = symbol_string(pair_car(*rest));
rest = &pair_cdr(*rest);
// default enum type will be int32.
auto enum_type = m_ts.make_typespec("int32");
bool is_bitfield = false;
auto current = pair_car(*rest);
while (current.is_symbol() && symbol_string(current).at(0) == ':') {
auto option_name = symbol_string(current);
rest = &pair_cdr(*rest);
auto option_value = pair_car(*rest);
rest = &pair_cdr(*rest);
current = pair_car(*rest);
if (option_name == ":type") {
enum_type = parse_typespec(option_value);
} else if (option_name == ":bitfield") {
if (symbol_string(option_value) == "#t") {
is_bitfield = true;
} else if (symbol_string(option_value) == "#f") {
is_bitfield = false;
} else {
throw_compiler_error(form, "Invalid option {} to :bitfield option.", option_value.print());
}
} else {
throw_compiler_error(form, "Unknown option {} for defenum.", option_name);
}
}
GoalEnum new_enum;
new_enum.base_type = enum_type;
new_enum.is_bitfield = is_bitfield;
parse_defenum(rest, &m_ts, new_enum);
while (!rest->is_empty_list()) {
auto def = pair_car(*rest);
auto name = symbol_string(pair_car(def));
def = pair_cdr(def);
auto value = pair_car(def);
if (!value.is_int()) {
throw_compiler_error(def, "Expected integer for enum value, got {}", value.print());
}
def = pair_cdr(def);
if (!def.is_empty_list()) {
throw_compiler_error(def, "Got too many items in defenum defintion.");
}
new_enum.entries[name] = value.integer_obj.value;
rest = &pair_cdr(*rest);
}
auto existing_kv = m_enums.find(enum_name);
auto existing_kv = m_enums.find(new_enum.name);
if (existing_kv != m_enums.end() && existing_kv->second != new_enum) {
print_compiler_warning("defenum changes the definition of existing enum {}", enum_name.c_str());
print_compiler_warning("defenum changes the definition of existing enum {}",
new_enum.name.c_str());
}
m_enums[enum_name] = new_enum;
m_enums[new_enum.name] = new_enum;
return get_none();
}
@@ -1144,13 +1096,18 @@ int Compiler::get_size_for_size_of(const goos::Object& form, const goos::Object&
auto args = get_va(form, rest);
va_check(form, args, {goos::ObjectType::SYMBOL}, {});
if (!m_ts.fully_defined_type_exists(args.unnamed.at(0).as_symbol()->name)) {
auto type_to_look_for = args.unnamed.at(0).as_symbol()->name;
if (m_ts.enum_type_exists(type_to_look_for)) {
type_to_look_for = m_ts.get_enum_type_name(type_to_look_for);
}
if (!m_ts.fully_defined_type_exists(type_to_look_for)) {
throw_compiler_error(
form, "The type {} given to size-of could not be found, or was not fully defined",
form, "The type or enum {} given to size-of could not be found, or was not fully defined",
args.unnamed.at(0).print());
}
auto type = m_ts.lookup_type(args.unnamed.at(0).as_symbol()->name);
auto type = m_ts.lookup_type(type_to_look_for);
auto as_value = dynamic_cast<ValueType*>(type);
auto as_structure = dynamic_cast<StructureType*>(type);
@@ -1168,4 +1125,4 @@ int Compiler::get_size_for_size_of(const goos::Object& form, const goos::Object&
Val* Compiler::compile_size_of(const goos::Object& form, const goos::Object& rest, Env* env) {
return compile_integer(get_size_for_size_of(form, rest), env);
}
}