Skip to content

Instantly share code, notes, and snippets.

@tbrunz
Last active March 9, 2020 16:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tbrunz/18ef96476b176f2b48a5b25398b1c787 to your computer and use it in GitHub Desktop.
Save tbrunz/18ef96476b176f2b48a5b25398b1c787 to your computer and use it in GitHub Desktop.
Lua metatables demonstrated by implementing a Lua 'case statement' object
#! /usr/bin/env lua
--
-- Lua metatables demonstrated by implementing a Lua 'case statement' object.
--
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--
-- Show test results of executing a case statement.
--
--[[
This function is purely for test purposes;
It just provides a nicely-formatted output
of executing a case statement case.
]]--
function show ( CaseStatement, enumLabel, argument )
print( "Executing case statement for enum '"..enumLabel..
"' for case statement '"..CaseStatement.name..
"', result = '"..argument.."'" )
return enumLabel
end
-------------------------------------------------------------------------------
--
-- Programming error: Trap bad table references & throw an error to report.
--
handleBadTableRef = function ( table, key, value )
local tableName
local errorMsg
-- It's possible this function was called without proper arguments.
-- Make sure the first argument is a table before we try to access
-- its fields. (Otherwise 'rawget' would bark at us.)
if type(table) == "table" then
-- Get the table's name, but be SURE to use 'rawget'!
-- If the 'name' field isn't defined, we'll get 'nil'.
-- Otherwise, using 'table.name' we could end up
-- looping right back to this method -- endlessly!
name = rawget( table, "name" )
else
return error "Missing/insensible arguments"
end
-- It's possible that the key was 'nil' (i.e., not provided)...
if key then
errorMsg = "Unknown attribute/method, '"..tostring(key).."', in"
else
errorMsg = "Nil key provided for"
end
return error( errorMsg.." table '"..tableName.."'")
end
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--
-- Prototype Case Statement object
--
CaseStatement = { }
--[[
Use Lua's "metatable" mechanism to trap and handle bad references in code
that uses a case statement object. If a 'case' (table key) is referenced,
but never defined, the 'handleBadTableRef' function will be called to
generate a sensible message & throw an error.
]]--
CaseStatement.__index = handleBadTableRef
-------------------------------------------------------------------------------
--
-- Case Statement Execution: 'doCase'
--
-- Use a given enum to execute a case in a specific case statement object.
-- All case statement objects inherit this method from 'CaseStatement'.
function CaseStatement:doCase ( enum, ... )
local argType
-- Reference the case statement name (for messaging).
-- If 'name' is missing, an error will be thrown.
local caseStatementName = self.name
-- Verify that the type of 'enum' is a table. (In Lua, all data types
-- have metatables that can resolve methods applied to non-table types.)
argType = type(enum)
if argType ~= "table" then
error( "doCase called on '"..caseStatementName.."' using a "..
argType.." for a case tag enum; must be a table." )
end
-- 'enum' is a case tag, which means it's a table with a name, etc.
local enumName = enum.name
-- Extract the case handler function by treating 'enum' as a table key.
-- This will throw an error if the 'enum' case tag is undefined.
-- The 'value' the 'key' refers to is the case function to execute.
local handler = self[ enum ]
-- Validate the handler 'value' as being a function we can call.
argType = type(handler)
if argType ~= "function" then
error( "Case tag '"..enumName.."' for case statement '"..
caseStatementName.."' maps to a "..argType..", not a function." )
end
-- If we reach this point, the case function is defined, so call it
-- with the remaining arguments we were called with, returning all
-- the return values it generates. Note that this is a tail call.
return handler( ... )
end
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--
-- Prototype Enum object
--
Enumerator = { }
--[[
Use Lua's "metatable" mechanism to trap and handle bad references in code
that uses an enumerator object. If an enum (table key) is referenced,
but never defined, the 'handleBadTableRef' function will be called to
generate a sensible message & throw an error.
]]--
Enumerator.__index = handleBadTableRef
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--
-- Specific enum object 'This'
--
ThisEnum = { }
-- Refer non-specific attribute/method lookups to the prototype enum object:
setmetatable( ThisEnum, {__index=Enumerator} )
-- Give this enum a name, for diagnostic messages:
ThisEnum.name = "ThisEnumerator"
-- By using (empty) tables for each enum, all enums, system-wide, are unique...
ThisEnum.labelA = { }
ThisEnum.labelB = { }
ThisEnum.labelC = { }
-------------------------------------------------------------------------------
--
-- Specific enum object 'That'
--
ThatEnum = { }
-- Refer non-specific attribute/method lookups to the prototype enum object:
setmetatable( ThatEnum, {__index=Enumerator} )
-- Give this enum a name, for diagnostic messages:
ThatEnum.name = "ThatEnumerator"
-- By using (empty) tables for each enum, all enums, system-wide, are unique...
ThatEnum.label1 = { }
ThatEnum.label2 = { }
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--
-- Specific case statement definition A
--
ThisCaseStmt = { }
-- Refer non-specific attribute/method lookups to the prototype case object:
setmetatable( ThisCaseStmt, {__index=CaseStatement} )
-- Give this case statement a name, for diagnostic messages:
ThisCaseStmt.name = "ThisCaseStatement"
-- Define the behavior of each case for this case statement:
--
ThisCaseStmt[ ThisEnum.labelA ] = function ( arg1 )
return show( ThisCaseStmt, "labelA", arg1 * arg1 )
end
ThisCaseStmt[ ThisEnum.labelB ] = function ( arg1, arg2 )
return show( ThisCaseStmt, "labelB", arg1 * arg2 )
end
ThisCaseStmt[ ThisEnum.labelC ] = function ( arg1, arg2, arg3 )
return show( ThisCaseStmt, "labelC", arg1 + arg2 + arg3 )
end
-------------------------------------------------------------------------------
--
-- Specific case statement definition B
--
ThatCaseStmt = { }
-- Refer non-specific attribute/method lookups to the prototype case object:
setmetatable( ThatCaseStmt, {__index=CaseStatement} )
-- Give this case statement a name, for diagnostic messages:
ThatCaseStmt.name = "ThatCaseStatement"
-- Define the behavior of each case for this case statement:
--
ThatCaseStmt[ ThatEnum.label1 ] = function ( argA )
return show( ThatCaseStmt, "label1", argA * argA )
end
ThatCaseStmt[ ThatEnum.label2 ] = function ( argA, argB )
return show( ThatCaseStmt, "label2", argA * argB )
end
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
--
-- Test cases for case statements
-- Note: We really *should* be using Lua Rock 'busted' for testing...
--
-- Test using an indirect case statement (as a variable):
Case = ThisCaseStmt
result = Case:doCase( ThisEnum.labelA, 5 )
result = Case:doCase( ThisEnum.labelB, 3, 4 )
-- Test intentional errors:
--result = Case:doThisCase( ThisEnum.labelC, 3, 7, 11 ) -- Err: bad method
--result = Case:doCase( ThisEnum.labelD, 15 ) -- Err: bad enum
--result = notaCase:doCase( ThisEnum.labelB, 3, 4 ) -- Err: bad table
-- Test using an explicitly-named case statement:
result = ThatCaseStmt:doCase( ThatEnum.label1, 13 )
print( "I just executed a case for selector '"..result.."'... " )
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment