Lua Notes

nelua enums
Login
  1. You can import enums into a scope with <using>
  2. You can convert between enums and strings with Lua
  3. You can have <comptime> enums
  4. Enums can be used as namespaces, similar to records

You can import enums into a scope

Consider:

local Days = @enum{
  Sunday = 0,
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday
}

function Days.isworkday(d: Days)
  switch d
  case Days.Sunday, Days.Saturday then return true
  else return false
  end
end

check(Days.isworkday(Days.Sunday))

This isn't bad at all, but in worse code you can locally drop the Days. prefix:

function Days.isworkday(d: Days)
  local any_name <using> = Days
  switch d
  case Sunday, Saturday then return true
  else return false
  end
end

You can convert between enums and strings with Lua

Nelua doesn't bake in a string conversion for enums, but you can synthesize such functions as needed:

require 'string'

local M = @record{}
local M.WordForm = @enum{
  ru_adj_superlative = 0,
  ru_adj_comparative,
  ru_verb_participle_passive_present,
  ru_verb_participle_passive_past,
  ru_base,
  -- there are 60 of these
}
function M.WordForm.parse(s: string): M.WordForm
  ## for _, field in ipairs(M.value.metafields.WordForm.value.fields) do
    if s == #[field.name]# then return M.WordForm.#|field.name|# end
  ## end
  assert(false, string.format('invalid wordform: %s', s))
  return 0
end
function M.WordForm.show(wf: M.WordForm): string
  ## for _, field in ipairs(M.value.metafields.WordForm.value.fields) do
    if wf == M.WordForm.#|field.name|# then return #[field.name]# end
  ## end
  assert(false, 'invalid M.WordForm')
  return ''
end

check(M.WordForm.parse('ru_base') == M.WordForm.ru_base)
check(M.WordForm.ru_adj_superlative:show() == 'ru_adj_superlative')

You can have <comptime> enums

Consider:

local Duration = @enum{ s = 1, m, h, d, ms, us }
local function sleepunits(value: number, unit: Duration <comptime>)
  ## local units = { 1, 60, 3600, 86400, 1/1000, 1/1000000 }
  print('faking sleep for: ', value * #[units[unit.value]]#)
end

sleepunits(3, Duration.h)
sleepunits(2, Duration.h)
sleepunits(3, Duration.ms)

This compiles to:

void sleep_units_sleepunits_1(double value, nlniltype unit) {
  nelua_print_1(((nlstring){(uint8_t*)"faking sleep for: ", 18}), (value * 3600));
}
void sleep_units_sleepunits_2(double value, nlniltype unit) {
  nelua_print_1(((nlstring){(uint8_t*)"faking sleep for: ", 18}), (value * 0.001));
}
int nelua_main(int argc, char** argv) {
  sleep_units_sleepunits_1(3.0, NELUA_NIL);
  sleep_units_sleepunits_1(2.0, NELUA_NIL);
  sleep_units_sleepunits_2(3.0, NELUA_NIL);
  return 0;
}

Enums can be used as namespaces, similar to records

local Days = @enum{
  Sunday = 0,
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday
}
## Days.value.fields.Sunday.is_weekend = true
## Days.value.fields.Saturday.is_weekend = true

function Days.is_weekday(self: Days)
  return self ~= Days.Sunday and self ~= Days.Saturday
end
## for _, day in ipairs({'Sunday', 'Saturday'}) do
  check(not Days.#|day|#:is_weekday())
## end
## for i, day in ipairs(Days.value.fields) do
  ## if not day.is_weekend then
    check(Days.#|day.name|#:is_weekday())
  ## end
## end

Here you can see

  1. metadata added to enum values, visible only to Lua
  2. a function defined in an enum namespace
  3. the way to define a 'method' that takes its received by value instead of by pointer
  4. exhaustive generation of tests, albeit, longer than less clear than just writing the tests manually