mirror of
https://github.com/Reuh/anselme.git
synced 2025-10-27 16:49:31 +00:00
Translation system first draft
This commit is contained in:
parent
ffadc0dd69
commit
c4636343b4
15 changed files with 215 additions and 18 deletions
|
|
@ -3,7 +3,8 @@ local Identifier, Quote
|
|||
|
||||
local attached_block_identifier, attached_block_symbol
|
||||
|
||||
local AttachBlock = ast.abstract.Node {
|
||||
local AttachBlock
|
||||
AttachBlock = ast.abstract.Node {
|
||||
type = "attach block",
|
||||
|
||||
expression = nil,
|
||||
|
|
@ -38,7 +39,18 @@ local AttachBlock = ast.abstract.Node {
|
|||
state.scope:define(attached_block_symbol, Quote:new(self.block))
|
||||
self.expression:prepare(state)
|
||||
state.scope:pop()
|
||||
end
|
||||
end,
|
||||
|
||||
-- class method: if the block identifier is defined in the current scope, wrap node in an AttachBlock so the block is still defined in this node
|
||||
-- used to preserve the defined _ block without the need to build a full closure
|
||||
-- used e.g. for -> translation, as we want to preserve _ while still executing the translation in the Translatable scope and not restore a different scope from a closure
|
||||
-- (operates on un-evaluated nodes!)
|
||||
preserve = function(self, state, node)
|
||||
if state.scope:defined_in_current(attached_block_symbol) then
|
||||
return AttachBlock:new(node, state.scope:get(attached_block_identifier).expression) -- unwrap Quote as that will be rewrap on eval
|
||||
end
|
||||
return node
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = AttachBlock
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ local ResumeParentFunction = ast.abstract.Node {
|
|||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
if resumable_manager:resuming(state, self) then
|
||||
if self:resuming(state) then
|
||||
self.expression:eval(state)
|
||||
return resumable_manager:get_data(state, self):call(state, ArgumentTuple:new())
|
||||
return self:get_data(state):call(state, ArgumentTuple:new())
|
||||
else
|
||||
resumable_manager:set_data(state, self, resumable_manager:capture(state, 1))
|
||||
self:set_data(state, resumable_manager:capture(state, 1))
|
||||
return self.expression:eval(state)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -112,7 +112,17 @@ Struct = ast.abstract.Runtime {
|
|||
has = function(self, key)
|
||||
local hash = key:hash()
|
||||
return not not self.table[hash]
|
||||
end
|
||||
end,
|
||||
iter = function(self)
|
||||
local t, h = self.table, nil
|
||||
return function()
|
||||
local e
|
||||
h, e = next(t, h)
|
||||
if h == nil then return nil
|
||||
else return e[1], e[2]
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = Struct
|
||||
|
|
|
|||
|
|
@ -58,14 +58,8 @@ Table = ast.abstract.Runtime {
|
|||
return s:has(key)
|
||||
end,
|
||||
iter = function(self, state)
|
||||
local t, h = self.branched:get(state).table, nil
|
||||
return function()
|
||||
local e
|
||||
h, e = next(t, h)
|
||||
if h == nil then return nil
|
||||
else return e[1], e[2]
|
||||
end
|
||||
end
|
||||
local s = self.branched:get(state)
|
||||
return s:iter()
|
||||
end,
|
||||
|
||||
to_struct = function(self, state)
|
||||
|
|
|
|||
49
ast/Translatable.lua
Normal file
49
ast/Translatable.lua
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
local ast = require("ast")
|
||||
local TextInterpolation, String
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
local translation_manager
|
||||
|
||||
local Translatable = ast.abstract.Node {
|
||||
type = "translatable",
|
||||
format_priority = operator_priority["%_"],
|
||||
|
||||
expression = nil,
|
||||
|
||||
init = function(self, expression)
|
||||
self.expression = expression
|
||||
self.context = ast.Struct:new()
|
||||
self.context:set(String:new("source"), String:new(self.expression.source))
|
||||
if TextInterpolation:is(self.expression) then
|
||||
self.format_priority = expression.format_priority
|
||||
end
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
if TextInterpolation:is(self.expression) then -- wrapped in translatable by default
|
||||
return self.expression:format(...)
|
||||
else
|
||||
return "%"..self.expression:format_right(...)
|
||||
end
|
||||
end,
|
||||
|
||||
traverse = function(self, fn, ...)
|
||||
fn(self.expression, ...)
|
||||
end,
|
||||
|
||||
_eval = function(self, state)
|
||||
return translation_manager:eval(state, self.context, self)
|
||||
end,
|
||||
|
||||
list_translatable = function(self, t)
|
||||
table.insert(t, self)
|
||||
end
|
||||
}
|
||||
|
||||
package.loaded[...] = Translatable
|
||||
TextInterpolation, String = ast.TextInterpolation, ast.String
|
||||
|
||||
translation_manager = require("state.translation_manager")
|
||||
|
||||
return Translatable
|
||||
|
|
@ -44,6 +44,9 @@ traverse = {
|
|||
end,
|
||||
hash = function(self, t)
|
||||
table.insert(t, self:hash())
|
||||
end,
|
||||
list_translatable = function(self, t)
|
||||
self:list_translatable(t)
|
||||
end
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +122,15 @@ Node = class {
|
|||
self:traverse(traverse.prepare, state)
|
||||
end,
|
||||
|
||||
-- generate a list of translatable nodes that appear in this node
|
||||
-- should only be called on non-runtime nodes
|
||||
-- if a node is translatable, redefine this to add it to the table - note that it shouldn't call :traverse or :list_translatable on its children, as nested translations should not be needed
|
||||
list_translatable = function(self, t)
|
||||
t = t or {}
|
||||
self:traverse(traverse.list_translatable, t)
|
||||
return t
|
||||
end,
|
||||
|
||||
-- same as eval, but make the evaluated expression as a resume boundary
|
||||
-- i.e. if a checkpoint is defined somewhere in this eval, it will start back from this node eval when resuming
|
||||
eval_resumable = function(self, state)
|
||||
|
|
|
|||
|
|
@ -28,13 +28,14 @@ local common = {
|
|||
{ "!", 11 },
|
||||
{ "-", 11 },
|
||||
{ "*", 11 },
|
||||
{ "%", 11 },
|
||||
},
|
||||
suffixes = {
|
||||
{ ";", 1 },
|
||||
{ "!", 12 }
|
||||
},
|
||||
infixes = {
|
||||
{ ";", 1 },
|
||||
{ ";", 1 }, { "->", 1 },
|
||||
{ "#", 2 },
|
||||
{ "~", 4 }, { "~?", 4 },
|
||||
{ "|>", 5 }, { "&", 5 }, { "|", 5 },
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ local primaries = {
|
|||
r("prefix.negation"),
|
||||
r("prefix.not"),
|
||||
r("prefix.mutable"),
|
||||
r("prefix.translatable"),
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
16
parser/expression/primary/prefix/translatable.lua
Normal file
16
parser/expression/primary/prefix/translatable.lua
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
local prefix = require("parser.expression.primary.prefix.prefix")
|
||||
|
||||
local ast = require("ast")
|
||||
local Translatable = ast.Translatable
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return prefix {
|
||||
operator = "%",
|
||||
identifier = "%_",
|
||||
priority = operator_priority["%_"],
|
||||
|
||||
build_ast = function(self, right)
|
||||
return Translatable:new(right)
|
||||
end
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
local string = require("parser.expression.primary.string")
|
||||
|
||||
local ast = require("ast")
|
||||
local TextInterpolation = ast.TextInterpolation
|
||||
local TextInterpolation, Translatable = ast.TextInterpolation, ast.Translatable
|
||||
|
||||
return string {
|
||||
type = "text",
|
||||
|
|
@ -11,6 +11,7 @@ return string {
|
|||
interpolation = TextInterpolation,
|
||||
|
||||
parse = function(self, source, str, limit_pattern)
|
||||
local start_source = source:clone()
|
||||
local interpolation, rem = string.parse(self, source, str, limit_pattern)
|
||||
|
||||
-- restore | when chaining with a choice operator
|
||||
|
|
@ -19,6 +20,6 @@ return string {
|
|||
source:increment(-1)
|
||||
end
|
||||
|
||||
return interpolation, rem
|
||||
return Translatable:new(interpolation):set_source(start_source), rem
|
||||
end
|
||||
}
|
||||
|
|
|
|||
9
parser/expression/secondary/infix/translate.lua
Normal file
9
parser/expression/secondary/infix/translate.lua
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
local infix_quote_both = require("parser.expression.secondary.infix.infix_quote_both")
|
||||
|
||||
local operator_priority = require("common").operator_priority
|
||||
|
||||
return infix_quote_both {
|
||||
operator = "->",
|
||||
identifier = "_->_",
|
||||
priority = operator_priority["_->_"]
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ local secondaries = {
|
|||
-- binary infix operators
|
||||
-- 1
|
||||
r("infix.semicolon"),
|
||||
r("infix.translate"),
|
||||
-- 2
|
||||
r("infix.tuple"),
|
||||
r("infix.tag"),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ local ScopeStack = require("state.ScopeStack")
|
|||
local tag_manager = require("state.tag_manager")
|
||||
local event_manager = require("state.event_manager")
|
||||
local resumable_manager = require("state.resumable_manager")
|
||||
local translation_manager = require("state.translation_manager")
|
||||
local uuid = require("common").uuid
|
||||
local parser = require("parser")
|
||||
local binser = require("lib.binser")
|
||||
|
|
@ -32,6 +33,7 @@ State = class {
|
|||
event_manager:setup(self)
|
||||
tag_manager:setup(self)
|
||||
resumable_manager:setup(self)
|
||||
translation_manager:setup(self)
|
||||
end
|
||||
end,
|
||||
|
||||
|
|
|
|||
78
state/translation_manager.lua
Normal file
78
state/translation_manager.lua
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
local class = require("class")
|
||||
|
||||
local ast = require("ast")
|
||||
local Table, Identifier
|
||||
|
||||
local translations_identifier, translations_symbol
|
||||
|
||||
local translation_manager = class {
|
||||
init = false,
|
||||
|
||||
setup = function(self, state)
|
||||
state.scope:define(translations_symbol, Table:new(state))
|
||||
end,
|
||||
|
||||
-- context is the context Struct - when translating, the translation will only be used for nodes that at least match this context
|
||||
-- original is the original node (non-evaluated)
|
||||
-- translated is the translated node (non-evaluated)
|
||||
set = function(self, state, context, original, translated)
|
||||
local translations = state.scope:get(translations_identifier)
|
||||
if not translations:has(state, original) then
|
||||
translations:set(state, original, Table:new(state))
|
||||
end
|
||||
local tr = translations:get(state, original)
|
||||
return tr:set(state, context, translated)
|
||||
end,
|
||||
|
||||
-- context is the context Struct of the calling translation
|
||||
-- original is the original node to translate (non-evaluated)
|
||||
-- returns the (evaluated) translated node, or the original node if no translation defined
|
||||
eval = function(self, state, context, original)
|
||||
local translations = state.scope:get(translations_identifier)
|
||||
if translations:has(state, original) then
|
||||
local tr = translations:get(state, original)
|
||||
|
||||
-- find most specific translation
|
||||
local translated, specificity = nil, -1
|
||||
for match_context, match_translated in tr:iter(state) do
|
||||
local matched, match_specificity = true, 0
|
||||
for key, val in match_context:iter() do
|
||||
if context:has(key) and val:hash() == context:get(key):hash() then
|
||||
match_specificity = match_specificity + 1
|
||||
else
|
||||
matched = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if matched then
|
||||
if match_specificity > specificity then
|
||||
translated, specificity = match_translated, match_specificity
|
||||
elseif match_specificity == specificity then
|
||||
print("a a dà é payé")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- found, evaluate translated
|
||||
if translated then
|
||||
-- eval in a scope where all active translations, as translating the translation would be stupid
|
||||
state.scope:push_partial(translations_identifier)
|
||||
state.scope:define(translations_symbol, Table:new(state))
|
||||
local r = translated:eval(state)
|
||||
state.scope:pop()
|
||||
return r
|
||||
end
|
||||
end
|
||||
|
||||
-- no matching translation
|
||||
return original.expression:eval(state)
|
||||
end,
|
||||
}
|
||||
|
||||
package.loaded[...] = translation_manager
|
||||
Table, Identifier = ast.Table, ast.Identifier
|
||||
|
||||
translations_identifier = Identifier:new("_translations") -- Table of { Translatable = Table{ Struct context = translated node, ... }, ... }
|
||||
translations_symbol = translations_identifier:to_symbol()
|
||||
|
||||
return translation_manager
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
local ast = require("ast")
|
||||
local Nil, Choice = ast.Nil, ast.Choice
|
||||
local Nil, Choice, AttachBlock = ast.Nil, ast.Choice, ast.AttachBlock
|
||||
|
||||
local event_manager = require("state.event_manager")
|
||||
local translation_manager = require("state.translation_manager")
|
||||
local tag_manager = require("state.tag_manager")
|
||||
|
||||
return {
|
||||
-- text
|
||||
|
|
@ -21,4 +23,13 @@ return {
|
|||
return Nil:new()
|
||||
end
|
||||
},
|
||||
|
||||
-- translation
|
||||
{
|
||||
"_->_", "(original::is(\"quote\"), translated::is(\"quote\"))",
|
||||
function(state, original, translated)
|
||||
translation_manager:set(state, tag_manager:get(state), original.expression, AttachBlock:preserve(state, translated.expression))
|
||||
return Nil:new()
|
||||
end
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue