1
0
Fork 0
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:
Étienne Fildadut 2023-12-23 21:09:12 +01:00
parent ffadc0dd69
commit c4636343b4
15 changed files with 215 additions and 18 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View 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

View file

@ -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)

View file

@ -28,13 +28,14 @@ local common = {
{ "!", 11 },
{ "-", 11 },
{ "*", 11 },
{ "%", 11 },
},
suffixes = {
{ ";", 1 },
{ "!", 12 }
},
infixes = {
{ ";", 1 },
{ ";", 1 }, { "->", 1 },
{ "#", 2 },
{ "~", 4 }, { "~?", 4 },
{ "|>", 5 }, { "&", 5 }, { "|", 5 },

View file

@ -28,6 +28,7 @@ local primaries = {
r("prefix.negation"),
r("prefix.not"),
r("prefix.mutable"),
r("prefix.translatable"),
}
return {

View 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
}

View file

@ -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
}

View 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["_->_"]
}

View file

@ -8,6 +8,7 @@ local secondaries = {
-- binary infix operators
-- 1
r("infix.semicolon"),
r("infix.translate"),
-- 2
r("infix.tuple"),
r("infix.tag"),

View file

@ -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,

View 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

View file

@ -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
}
}