git.s-ol.nu mmm / abefbf8 mmm / mmmfs / conversion.moon
abefbf8

Tree @abefbf8 (Download .tar.gz)

conversion.moon @abefbf8raw · history · blame

require = relative ..., 1
import Queue from require '.queue'

count = (base, pattern='->') -> select 2, base\gsub pattern, ''
escape_pattern = (inp) -> "^#{inp\gsub '([^%w])', '%%%1'}$"
escape_inp = (inp) -> "^#{inp\gsub '([-/])', '%%%1'}$"

local print_conversions

-- attempt to find a conversion path from 'have' to 'want'
-- * have - start type string or list of type strings
-- * want - stop type pattern
-- * limit - limit conversion amount
-- returns a list of conversion steps
get_conversions = (want, have, converts=PLUGINS and PLUGINS.converts, limit=5) ->
  assert have, 'need starting type(s)'
  assert converts,  'need to pass list of converts'

  if 'string' == type have
    have = { have }

  assert #have > 0, 'need starting type(s) (list was empty)'

  want = escape_pattern want
  limit = limit + 3 * math.max table.unpack [count type for type in *have]

  had = {}
  queue = Queue!
  for start in *have
    return {}, start if want\match start
    queue\add { :start, rest: start, conversions: {} }, 0, start

  best = Queue!

  while true
    entry, cost = queue\pop!
    if not entry or cost > limit
      break

    { :start, :rest, :conversions } = entry
    had[rest] = true
    for convert in *converts
      inp = escape_inp convert.inp
      continue unless rest\match inp
      result = rest\gsub inp, convert.out
      continue unless result
      continue if had[result]

      next_entry = {
        :start
        rest: result
        cost: cost + convert.cost
        conversions: { { :convert, from: rest, to: result }, table.unpack conversions }
      }

      if result\match want
        best\add next_entry, next_entry.cost
      else
        queue\add next_entry, next_entry.cost, result


  if solution = best\pop!
    -- print "BEST: (#{solution.cost})"
    -- print_conversions solution.conversions
    solution.conversions, solution.start if solution

-- stringify conversions for debugging
-- * conversions - conversions from get_conversions
print_conversions = (conversions) ->
  print "converting:"
  for i=#conversions,1,-1
    step = conversions[i]
    print "- #{step.from} -> #{step.to} (#{step.convert.cost})"

-- apply transforms for conversion path sequentially
-- * conversions - conversions from get_conversions
-- * value - value
-- * ... - other transform parameters (fileder, key)
-- returns converted value
err_and_trace = (msg) -> debug.traceback msg, 2
apply_conversions = (conversions, value, ...) ->
  for i=#conversions,1,-1
    step = conversions[i]
    ok, value = xpcall step.convert.transform, err_and_trace, step, value, ...
    if not ok
      f, k = ...
      error "error while converting #{f} #{k} from '#{step.from}' to '#{step.to}':\n#{value}"

  value

-- find and apply a conversion path from 'have' to 'want'
-- * have - start type string or list of type strings
-- * want - stop type pattern
-- * value - value or map from have-types to values
-- returns converted value
convert = (have, want, value, ...) ->
  conversions, start = get_conversions want, have

  if not conversions
    warn "couldn't convert from '#{have}' to '#{want}'"
    return

  if 'string' ~= type have
     value = value[start]

  apply_conversions conversions, value, ...

{
  :get_conversions
  :apply_conversions
  :convert
}