Moduuli:Translitteroija/muunnin

Wikisanakirjasta

Tämän moduulin ohjeistuksen voi tehdä sivulle Moduuli:Translitteroija/muunnin/ohje

--- Translitteroi merkkijonon annetun taulukon mukaan.

local common = require("Moduuli:Translitteroija/yhteinen")

local export = {}

local conversions = {}
local in_max = 0
local out_max = 0
local prefix_max = 0
local pseudo_row_len = 0
local input_buffer

local index_first_for_input
local index_last_for_input
   
local index_first_for_prefix
local index_last_for_prefix

local tostring = common.tostring
local get_index_key = common.get_index_key


local log = false

local function compare_buffers(buf1, start1, len1, buf2, start2, len2)
   local diff
   local pos1, pos2

   for diff = 1, math.min(len1, len2) do
      pos1 = start1 + diff - 1
      pos2 = start2 + diff - 1

      if pos1 > #buf1 then
	 return -1
      elseif pos2 > #buf2 then
	 return 1
      elseif buf1[pos1] ~= buf2[pos2] then
	 return buf1[pos1] - buf2[pos2]
      end
   end

   if len1 ~= len2 then
      return len1 - len2
   end

   return 0
end


---
-- @return  0, jos teksti mätsää muunnokseen
--         -1, jos haettava on järjestykessä ensin
--          1, jos haettava on järjestyksessä jälkeen
local function compare(state, position, forward_length, backward_length, pseudo_index)
   local offset = (pseudo_index - 1) * pseudo_row_len + 1
   local target_backward_length = conversions[offset + 0]
   local target_forward_length  = conversions[offset + 1 + prefix_max]
   local input_buffer  = state.input_buffer
   local output_buffer = state.output_buffer
   local output_pos    = state.output_pos

   local prefix_start = position - backward_length
   
   
   if log then
       mw.log("    compare:",
	    tostring(state.input_buffer, prefix_start, backward_length, true) ..
	    "|" ..
	    tostring(state.input_buffer, position, forward_length, true),
	    "to: index " .. pseudo_index .. ":",
	    tostring(conversions, offset + 1, target_backward_length, true) ..
	    "|" ..
	    tostring(conversions, offset + 1 + prefix_max + 2, target_forward_length, true)
      )
   end
   

   -- Huom. Vaikka luetut välit eivät vastaisi muunnostaulukon rivin välejä, täytyy tekstiä kuitenkin verrata,
   -- että tiedetään kumpaan suuntaan hakua jatketaan
   
   local val =  compare_buffers(
      input_buffer,
      position,
      forward_length,
      conversions,
      offset + 1 + prefix_max + 2,
      target_forward_length
   )

   if val ~= 0 then
      return val, "pääteksti eri"
   end
   
   val = compare_buffers(
      input_buffer,
      prefix_start,
      backward_length,
      conversions,
      offset + 1,
      target_backward_length
   )

   if val ~= 0 then
      return val, "prefiksit eri"
   end

   return val, nil
end



local function binary_search(state, start, last, position, forward_length, backward_length)
   --  Initialise numbers
   local i_start = start or 1
   local i_end = last
   local i_mid = 0
   local fail_reason
   
   while i_start <= i_end do
      i_mid = math.floor((i_start + i_end) / 2)

      local compare_result
      compare_result, fail_reason = compare(state, position, forward_length, backward_length, i_mid)

      if compare_result == 0 then
	 return i_mid
      elseif compare_result < 0 then
	 i_end = i_mid - 1
      else
	 i_start = i_mid + 1
      end
   end
   return nil, fail_reason
end


local function search(state, position, forward_length, backward_length)

   local key_for_input = get_index_key(state.input_buffer, 1, position, forward_length)

   local first_for_input = index_first_for_input[key_for_input]
   local last_for_input = index_last_for_input[key_for_input]

   if not first_for_input then
      return nil, "ei löydy päähakemistosta"
   end

   local key_for_prefix = get_index_key(state.input_buffer, 1, position - backward_length, backward_length)
   
   local first_for_prefix = index_first_for_prefix[key_for_prefix]
   local last_for_prefix = index_last_for_prefix[key_for_prefix]

   if not first_for_prefix then
      return nil, "ei löydy prefiksihakemistosta"
   end

   -- Kavennetaan inputin ja prefiksin väliä siten, että otetaan niiden yhteinen alue.
   -- Jos ensimäinen ja viimeinen vaihtaa paikkaa, ovat alueet erillisiä eikä kyseisillä parametereilla ole tulosta.
   local first = math.max(first_for_input, first_for_prefix)
   local last  = math.min(last_for_input, last_for_prefix)

   if first > last then
      return nil, "erilliset alueet"
   end


   return binary_search(state, first, last, position, forward_length, backward_length)
end



function export.convert_byte_buffer(input_buffer)
   assert ( conversions ~= nil, "Muuttajaa ei ole asetettu" )
   
   local out = {}
   local inp = 1
   local outp = 1

   local state = {
      input_buffer = input_buffer,
      input_pos = inp,
      output_buffer = out,
      output_pos = outp
   }


   -- Löytynyt taulukon ”rivi”.
   local matching_pseudo_index

   -- Vertailtavat merkkimäärät
   local read_in_input, read_in_prefix
   
   -- debuggraukseen
   local reason

   if log then
       mw.log("****************************************")
       mw.log("INPUT BUFFER ALUSSA:",
	 tostring(input_buffer, nil, nil, true))
   end

   local counter = 100
   while inp <= #input_buffer do
      state.input_pos = inp
      state.output_pos = outp

      if log then
          mw.log("INPUT BUFFER (" .. prefix_max .. "|" .. in_max .. ") @ " .. inp .. ":",
                 tostring(input_buffer, inp, in_max, true)		  
          )
      end
      
      -- Yritetään löytää taulukosta inputin kohtaa vastaava rivi. Aloitetaan pisimmästä ja lyhennetään
      -- sitä kunnes rivi löytyy tai kaikki mahdolliset on käyty läpi. Näin tehdään sekä
      -- inputtiin mätsäävälle että outputin prefiksiin mätsäävälle.

      read_in_input = math.min(in_max, #input_buffer - inp + 1)
      
      while read_in_input > 0 do
	 read_in_prefix = math.min(prefix_max, inp - 1)

	 while read_in_prefix >= 0 do
	    matching_pseudo_index = nil

	    matching_pseudo_index, reason = search(
	       state,
	       inp,
	       read_in_input,
	       read_in_prefix
	    )

	    if matching_pseudo_index ~= nil then
	       read_in_input = -1
	       break
	    end

	    read_in_prefix = read_in_prefix - 1

	 end


	 read_in_input = read_in_input - 1
      end

      if matching_pseudo_index == nil then
	 error(
	    "Ei sopivaa riviä taulukossa: " .. tostring(input_buffer, inp, in_max, true) .. "... "
	       .. "Keskeytetty kohdassa " .. string.char(unpack(out))
	 )
      end

      -- Offsetit rivin soluihin.
      local row_offset               = (matching_pseudo_index - 1) * pseudo_row_len + 1

      -- koko pituus - suffiksin offset
      local input_advance_len        = conversions[row_offset + 1 + prefix_max + 1]

      local output_len_offset        = row_offset + 1 + prefix_max + 2 + in_max
      local output_text_offset       = output_len_offset + 1
      local output_len               = conversions[output_len_offset]

      if log then
          mw.log(
              "  ------------------------>",
              tostring(
                  conversions,
                  output_text_offset,
                  output_len,
                  true
              )
          )
      end

      local j
      for j = 1, output_len do
	 out[outp] = conversions[output_text_offset + j - 1]
	 outp = outp + 1
      end

      inp = inp + input_advance_len

      counter = counter - 1
      if counter == 0 then
	 break
      end

   end

   return out
end

function export.convert_string(str)
   local padded = " " .. str .. " "
   local chars = { string.byte(padded, 1, string.len(padded)) }
   
   local result_table = export.convert_byte_buffer(chars)
   local result = string.char(unpack(result_table))

   return string.sub(result, 2, -2)
end


function export.set_conversion_table(conversion_table)
   conversions = conversion_table
   in_max      = conversions.in_max
   prefix_max  = conversions.prefix_max
   out_max     = conversions.out_max
   pseudo_row_len = conversions.pseudo_row_length

   index_first_for_input = conversions.index_first_for_input
   index_last_for_input = conversions.index_last_for_input
   
   index_first_for_prefix = conversions.index_first_for_prefix
   index_last_for_prefix = conversions.index_last_for_prefix   
end

return export