Skip to content

Commit

Permalink
Merge pull request #574 from pallene-lang/typedecl
Browse files Browse the repository at this point in the history
Replace typedecl.match_tag by typedecl.typename
  • Loading branch information
hugomg authored May 18, 2023
2 parents 11157c1 + 0bae405 commit c8ce3de
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 86 deletions.
35 changes: 21 additions & 14 deletions spec/typedecl_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,36 @@
local typedecl = require "pallene.typedecl"

describe("Typedecl", function()

setup(function()
local foo = {}
typedecl.declare(foo, "foo", "Bar", {
ABC = {"a", "b", "c"},
DEF = {"d", "e", "f"},
})
end)

it("forbids repeated tags", function()
assert.has_error(function()
local mod = {}
typedecl.declare(mod, "TESTTYPE", "Foo", { Bar = {"x"} })
typedecl.declare(mod, "TESTTYPE", "Foo", { Bar = {"x"} })
end, "tag name 'TESTTYPE.Foo.Bar' is already being used")
end, [[tag name "TESTTYPE.Foo.Bar" is already being used]])
end)

describe("match_tag", function ()
it("returns the tag name", function ()
assert.equals("baz", typedecl.match_tag("foo.Bar.baz", "foo.Bar"))
end)
it("typeof works for declared type", function ()
assert.equals("foo.Bar", typedecl.typename("foo.Bar.ABC"))
end)

it("doesn't crash with a non-string tag", function()
assert.equals(false, typedecl.match_tag(nil, "types.T"))
end)
it("typeof rejects undeclared types", function ()
assert.equals(nil, typedecl.typename("foo.Bar.LMN"))
end)

it("doesn't treat a '.' in the prefix string as regex", function ()
assert.equals(false, typedecl.match_tag("foo.Bar.baz", "f.o.Bar"))
end)
it("consname works for declared type", function ()
assert.equals("ABC", typedecl.consname("foo.Bar.ABC"))
end)

it("doesn't require a '.' at the end of prefix.", function ()
assert.equals(false, typedecl.match_tag("types.T.Float", "types.T."))
end)
it("consname rejects undeclared type", function ()
assert.equals(nil, typedecl.consname("foo.Bar.LMN"))
end)
end)
4 changes: 2 additions & 2 deletions src/pallene/assignment_conversion.lua
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ function Converter:visit_prog(prog_ast)
end
else
-- skip record declarations and type aliases
assert(typedecl.match_tag(tl_node._tag, "ast.Toplevel"))
assert(typedecl.typename(tl_node._tag) == "ast.Toplevel")
end
end
end
Expand Down Expand Up @@ -461,7 +461,7 @@ function Converter:visit_exp(exp)
self:visit_exp(exp.lhs)
self:visit_exp(exp.rhs)

elseif not typedecl.match_tag(tag, "ast.Exp") then
elseif typedecl.typename(tag) ~= "ast.Exp" then
typedecl.tag_error(tag)
end
end
Expand Down
5 changes: 3 additions & 2 deletions src/pallene/coder.lua
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ function Coder:c_value(value)
return self:c_var(value.id)
elseif tag == "ir.Value.Upvalue" then
return self:c_upval(value.id)
elseif typedecl.match_tag(tag, "ir.Value") then
elseif typedecl.tagname(tag) == "ir.Value" then
typedecl.tag_error(tag, "unable to get C expression for this value type.")
else
typedecl.tag_error(tag)
Expand Down Expand Up @@ -1676,7 +1676,8 @@ gen_cmd["CheckGC"] = function(self, cmd, func)
end

function Coder:generate_cmd(func, cmd)
local name = assert(typedecl.match_tag(cmd._tag, "ir.Cmd"))
assert(typedecl.typename(cmd._tag) == "ir.Cmd")
local name = typedecl.consname(cmd._tag)
local f = assert(gen_cmd[name], "impossible")
local out = f(self, cmd, func)

Expand Down
6 changes: 3 additions & 3 deletions src/pallene/print_ir.lua
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,9 @@ local function Cmd(cmd)
rhs = "CallStatic ".. Call(Val(cmd.src_f), Vals(cmd.srcs))
elseif tag == "ir.Cmd.CallDyn" then
rhs = "CallDyn ".. Call(Val(cmd.src_f), Vals(cmd.srcs))
else
local tagname = assert(typedecl.match_tag(cmd._tag, "ir.Cmd"))
rhs = Call(tagname, Vals(ir.get_srcs(cmd)))
elseif typedecl.typename(cmd._tag) == "ir.Cmd" then
local name = typedecl.consname(cmd._tag)
rhs = Call(name, Vals(ir.get_srcs(cmd)))
end

if lhs == "" then
Expand Down
10 changes: 5 additions & 5 deletions src/pallene/typechecker.lua
Original file line number Diff line number Diff line change
Expand Up @@ -138,25 +138,25 @@ end

function Typechecker:add_type_symbol(name, typ)
assert(type(name) == "string")
assert(typedecl.match_tag(typ._tag, "types.T"))
assert(typedecl.typename(typ._tag) == "types.T")
return self.symbol_table:add_symbol(name, typechecker.Symbol.Type(typ))
end

function Typechecker:add_value_symbol(name, typ, def)
assert(type(name) == "string")
assert(typedecl.match_tag(typ._tag, "types.T"))
assert(typedecl.typename(typ._tag) == "types.T")
return self.symbol_table:add_symbol(name, typechecker.Symbol.Value(typ, def))
end

function Typechecker:add_module_symbol(name, typ, symbols)
assert(type(name) == "string")
assert((not typ) or typedecl.match_tag(typ._tag, "types.T"))
assert((not typ) or typedecl.typename(typ._tag) == "types.T")
return self.symbol_table:add_symbol(name, typechecker.Symbol.Module(typ, symbols))
end

function Typechecker:export_value_symbol(name, typ, def)
assert(type(name) == "string")
assert(typedecl.match_tag(typ._tag, "types.T"))
assert(typedecl.typename(typ._tag) == "types.T")
assert(self.module_symbol)
if self.module_symbol.symbols[name] then
type_error(loc_of_def(def), "multiple definitions for module field '%s'", name)
Expand Down Expand Up @@ -734,7 +734,7 @@ function Typechecker:coerce_numeric_exp_to_float(exp)
return exp
elseif tag == "types.T.Integer" then
return self:check_exp_synthesize(ast.Exp.ToFloat(exp.loc, exp))
elseif typedecl.match_tag(tag, "types.T") then
elseif typedecl.typename(tag) == "types.T" then
typedecl.tag_error(tag, "this type cannot be coerced to float.")
else
typedecl.tag_error(tag)
Expand Down
104 changes: 48 additions & 56 deletions src/pallene/typedecl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,63 +5,66 @@

-- TAGGED UNIONS
-- =============
-- Pallene uses a lot of tagged unions / variant records. In Lua we represent
-- them as tables with a `_tag` field that is an unique string. Since there are
-- so many of them, we made a helper function to help construct these objects,
-- which resides in this module.
-- Pallene's compiler uses many tagged unions / variant records. We represent
-- them as tables with a string `_tag`. This module exports helper functions for
-- custructing such tagged unions.
--
-- For example, inside the `ast` module there is the following block of code:
-- ```
-- declare_type("Var", {
-- Name = {"loc", "name"},
-- Bracket = {"loc", "t", "k"},
-- Dot = {"loc", "exp", "name"}
-- })
-- ```
-- and what it does is create three functions, `ast.Var.Name`, `ast.Var.Bracket`, and `ast.Var.Dot`.
-- For example, the following block of code in the `ast` module creates
-- three constructor functions called `ast.Var.Name`, `ast.Var.Bracket`, and
-- `ast.Var.Dot`.
--
-- The `ast.Var.Name` function receives two parameters (the source code location and the name) and
-- returns a table that looks like this:
-- ```
-- {
-- _tag = "ast.Var.Name",
-- loc = loc,
-- name = name,
-- }
-- ```
local typedecl = {}

-- Unique tag names:
-- declare_type("Var", {
-- Name = {"loc", "name"},
-- Bracket = {"loc", "t", "k"},
-- Dot = {"loc", "exp", "name"}
-- })
--
-- And we can call them like this
--
-- We keep track of all the type tags that we define, so that no two constructors attempt to use the
-- same type tag.
-- node = ast.Var.Name(loc, name)
--
-- and it produces a table like this:
--
-- {
-- _tag = "ast.Var.Name",
-- loc = loc,
-- name = name,
-- }

local existing_tags = {}
local typedecl = {}

-- Ensure type tags are unique
-- And keep track of who is the "parent" type
local typename_of = {} -- For example, "ast.Exp.Name" => "ast.Exp"
local consname_of = {} -- For example, "ast.Exp.Name" => "

local function is_valid_name_component(s)
-- In particular, this rules out the separator character "."
-- In particular this does not allow ".", which is our separator
return string.match(s, "[A-Za-z_][A-Za-z_0-9]*")
end

local function make_tag(mod_name, type_name, cons_name)
assert(is_valid_name_component(mod_name))
assert(is_valid_name_component(type_name))
assert(is_valid_name_component(cons_name))
local tag = mod_name .. "." .. type_name .. "." .. cons_name
if existing_tags[tag] then
error("tag name '" .. tag .. "' is already being used")
local typ = mod_name .. "." .. type_name
local tag = typ .. "." .. cons_name
if typename_of[tag] then
error(string.format("tag name %q is already being used", tag))
else
existing_tags[tag] = true
typename_of[tag] = typ
consname_of[tag] = cons_name
return tag
end
return tag
end

-- Create a properly-namespaced algebraic datatype. Objects belonging to this type can be pattern
-- matched by inspecting their _tag field. See `ast.lua` and `types.lua` for usage examples.
-- Create a namespaced algebraic datatype.
-- These objects can be pattern matched by their _tag.
-- See `ast.lua` and `types.lua` for usage examples.
--
-- @param module Module table where the type is being defined
-- @param mod_name Name of the type's module (only used by tostring)
-- @param type_name Name of the type
-- @param module Module table where the type is being defined
-- @param mod_name Name of the type's module (only used by tostring)
-- @param type_name Name of the type
-- @param constructors Table describing the constructors of the ADT.
function typedecl.declare(module, mod_name, type_name, constructors)
module[type_name] = {}
Expand All @@ -84,28 +87,17 @@ function typedecl.declare(module, mod_name, type_name, constructors)
end
end

-- Check if the given type tag belongs to the specified type.
-- If it does, returns the last component of the tag name.
--
-- Examples:
-- typedecl.match_tag("ast.Exp.Bool", "ast.Exp") --> "Bool"
-- typedecl.match_tag("ast.Stat.While", "ast.Exp") --> false
function typedecl.match_tag(tag, tag_prefix)
local n = #tag_prefix
function typedecl.typename(tag)
return typename_of[tag]
end

if type(tag) == "string" and
string.sub(tag, 1, n) == tag_prefix and
string.byte(tag, n + 1) == 46 -- "."
then
return string.sub(tag, n + 2)
else
return false
end
function typedecl.consname(tag)
return consname_of[tag]
end

-- Throw an error at the given tag.
--
-- @param tag The type tag (or token string) at which the error is to be thown (string)
-- @param tag The type tag (or token string) at which the error is to be thown (string)
-- @param message The optional error message. (?string)
function typedecl.tag_error(tag, message)
message = message or "input has the wrong type or an elseif case is missing"
Expand Down
6 changes: 3 additions & 3 deletions src/pallene/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function types.indices(t)
elseif tag == "types.T.Record" then
return t.field_types

elseif typedecl.match_tag(tag, "types.T") then
elseif typedecl.typename(tag) == "types.T" then
typedecl.tag_error(tag, "cannot index this type.")
else
typedecl.tag_error(tag)
Expand All @@ -119,8 +119,8 @@ function types.equals(t1, t2)
local tag1 = t1._tag
local tag2 = t2._tag

assert(typedecl.match_tag(tag1, "types.T"))
assert(typedecl.match_tag(tag2, "types.T"))
assert(typedecl.typename(tag1) == "types.T")
assert(typedecl.typename(tag2) == "types.T")

if tag1 ~= tag2 then
return false
Expand Down
2 changes: 1 addition & 1 deletion src/pallene/uninitialized.lua
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ local function test(cmd, uninit, loop)
return true, loop.uninit
end

elseif typedecl.match_tag(cmd._tag, "ir.Cmd") then
elseif typedecl.typename(cmd._tag) == "ir.Cmd" then
for _, val in ipairs(ir.get_srcs(cmd)) do
if val._tag == "ir.Value.LocalVar" then
-- `SetField` instructions can count as initializers when the target is an
Expand Down

0 comments on commit c8ce3de

Please sign in to comment.