1
0
Fork 0
mirror of https://github.com/Reuh/anselme.git synced 2025-10-27 16:49:31 +00:00

Improve translation template generation, only add the source context if needed

This commit is contained in:
Étienne Fildadut 2024-01-04 19:16:22 +01:00
parent 41f85181a3
commit 581c60048d
10 changed files with 118 additions and 32 deletions

View file

@ -2,16 +2,16 @@
local ast = require("anselme.ast") local ast = require("anselme.ast")
local Overloadable = ast.abstract.Overloadable local Overloadable = ast.abstract.Overloadable
local ReturnBoundary, Environment local ReturnBoundary, Environment, Identifier, Symbol
local operator_priority = require("anselme.common").operator_priority local operator_priority = require("anselme.common").operator_priority
local resume_manager, calling_environment_manager local resume_manager, calling_environment_manager
local function list_cache_upvalues(v, state, list, scope) local function list_cache_upvalues(v, state, list, scope)
if ast.Identifier:is(v) then if Identifier:is(v) then
list[v.name] = scope:precache(state, v) list[v.name] = scope:precache(state, v)
elseif ast.Symbol:is(v) then elseif Symbol:is(v) then
list[v.string] = scope:precache(state, v:to_identifier()) list[v.string] = scope:precache(state, v:to_identifier())
end end
v:traverse(list_cache_upvalues, state, list, scope) v:traverse(list_cache_upvalues, state, list, scope)
@ -66,8 +66,8 @@ Function = Overloadable {
-- list & cache upvalues so they aren't affected by future redefinition in a parent scope -- list & cache upvalues so they aren't affected by future redefinition in a parent scope
local upvalues = {} local upvalues = {}
self.expression:traverse(list_cache_upvalues, state, upvalues, scope) self.expression:traverse(list_cache_upvalues, state, upvalues, scope)
if scope:defined(state, ast.Identifier:new("_")) then if scope:defined(state, Identifier:new("_")) then
scope:get(state, ast.Identifier:new("_")):traverse(list_cache_upvalues, state, upvalues, scope) scope:get(state, Identifier:new("_")):traverse(list_cache_upvalues, state, upvalues, scope)
end end
return Function:new(self.parameters:eval(state), self.expression, scope, upvalues) return Function:new(self.parameters:eval(state), self.expression, scope, upvalues)
@ -143,7 +143,7 @@ Function = Overloadable {
} }
package.loaded[...] = Function package.loaded[...] = Function
ReturnBoundary, Environment = ast.ReturnBoundary, ast.Environment ReturnBoundary, Environment, Identifier, Symbol = ast.ReturnBoundary, ast.Environment, ast.Identifier, ast.Symbol
resume_manager = require("anselme.state.resume_manager") resume_manager = require("anselme.state.resume_manager")
calling_environment_manager = require("anselme.state.calling_environment_manager") calling_environment_manager = require("anselme.state.calling_environment_manager")

View file

@ -45,7 +45,7 @@ List = ast.abstract.Runtime {
return self.branched:get(state):len() return self.branched:get(state):len()
end, end,
iter = function(self, state) iter = function(self, state)
return ipairs(self.branched:get(state).list) return self.branched:get(state):iter()
end, end,
get = function(self, state, index) get = function(self, state, index)
local list = self.branched:get(state) local list = self.branched:get(state)

View file

@ -44,7 +44,12 @@ Struct = ast.abstract.Runtime {
self.table = {} self.table = {}
end, end,
set = function(self, key, value) -- only for construction set = function(self, key, value) -- only for construction
self.table[key:hash()] = { key, value } local hash = key:hash()
if Nil:is(value) then
self.table[hash] = nil
else
self.table[hash] = { key, value }
end
end, end,
include = function(self, other) -- only for construction include = function(self, other) -- only for construction
for _, e in pairs(other.table) do for _, e in pairs(other.table) do

View file

@ -1,5 +1,5 @@
local ast = require("anselme.ast") local ast = require("anselme.ast")
local TextInterpolation, String local TextInterpolation, String, Struct
local operator_priority = require("anselme.common").operator_priority local operator_priority = require("anselme.common").operator_priority
@ -10,11 +10,14 @@ local Translatable = ast.abstract.Node {
hide_in_stacktrace = true, hide_in_stacktrace = true,
expression = nil, expression = nil,
context = nil, -- struct
init = function(self, expression) init = function(self, expression)
self.expression = expression self.expression = expression
self.context = ast.Struct:new() self.context = Struct:new()
self.context:set(String:new("source"), String:new(self.expression.source)) self.context:set(String:new("source"), String:new(self.expression.source))
self.context:set(String:new("file"), String:new(self.expression.source:match("^([^%:]*)")))
-- TODO: add parent script/function to context
end, end,
_format = function(self, ...) _format = function(self, ...)
@ -46,7 +49,7 @@ local Translatable = ast.abstract.Node {
} }
package.loaded[...] = Translatable package.loaded[...] = Translatable
TextInterpolation, String = ast.TextInterpolation, ast.String TextInterpolation, String, Struct = ast.TextInterpolation, ast.String, ast.Struct
translation_manager = require("anselme.state.translation_manager") translation_manager = require("anselme.state.translation_manager")

View file

@ -61,9 +61,17 @@ Tuple = ast.abstract.Node {
if index > #self.list or index == 0 then error("tuple index out of bounds", 0) end if index > #self.list or index == 0 then error("tuple index out of bounds", 0) end
return self.list[index] return self.list[index]
end, end,
set = function(self, index, value)
if index < 0 then index = #self.list + 1 + index end
if index > #self.list or index == 0 then error("tuple index out of bounds", 0) end
self.list[index] = value
end,
len = function(self) len = function(self)
return #self.list return #self.list
end end,
iter = function(self)
return ipairs(self.list)
end,
} }
return Tuple return Tuple

View file

@ -4,6 +4,8 @@ local operator_priority = require("anselme.common").operator_priority
local format_identifier local format_identifier
local ArgumentTuple
local Typed local Typed
Typed = ast.abstract.Runtime { Typed = ast.abstract.Runtime {
type = "typed", type = "typed",
@ -20,7 +22,7 @@ Typed = ast.abstract.Runtime {
-- try custom format -- try custom format
if state and state.scope:defined(format_identifier) then if state and state.scope:defined(format_identifier) then
local custom_format = format_identifier:eval(state) local custom_format = format_identifier:eval(state)
local args = ast.ArgumentTuple:new(self) local args = ArgumentTuple:new(self)
local fn, d_args = custom_format:dispatch(state, args) local fn, d_args = custom_format:dispatch(state, args)
if fn then if fn then
return custom_format:call(state, d_args):format(state, prio, ...) return custom_format:call(state, d_args):format(state, prio, ...)
@ -37,5 +39,6 @@ Typed = ast.abstract.Runtime {
package.loaded[...] = Typed package.loaded[...] = Typed
format_identifier = ast.Identifier:new("format") format_identifier = ast.Identifier:new("format")
ArgumentTuple = ast.ArgumentTuple
return Typed return Typed

View file

@ -2,6 +2,7 @@ local class = require("anselme.lib.class")
local fmt = require("anselme.common").fmt local fmt = require("anselme.common").fmt
local binser = require("anselme.lib.binser") local binser = require("anselme.lib.binser")
local utf8 = utf8 or require("lua-utf8") local utf8 = utf8 or require("lua-utf8")
local unpack = table.unpack or unpack
-- NODES SHOULD BE IMMUTABLE AFTER CREATION IF POSSIBLE! -- NODES SHOULD BE IMMUTABLE AFTER CREATION IF POSSIBLE!
-- i don't think i actually rely on this behavior for anything but it makes me feel better about life in general -- i don't think i actually rely on this behavior for anything but it makes me feel better about life in general
@ -13,7 +14,7 @@ local utf8 = utf8 or require("lua-utf8")
local uuid = require("anselme.common").uuid local uuid = require("anselme.common").uuid
local Call, Identifier, ArgumentTuple local Call, Identifier, ArgumentTuple, Struct, Tuple, String, Pair
local resume_manager local resume_manager
local custom_call_identifier local custom_call_identifier
@ -143,17 +144,86 @@ Node = class {
-- generate anselme code that can be used as a base for a translation file -- generate anselme code that can be used as a base for a translation file
-- will include every translatable element found in this node and its children -- will include every translatable element found in this node and its children
-- TODO: generate more stable context than source position, and only add necessery context to the tag
generate_translation_template = function(self) generate_translation_template = function(self)
local l = self:list_translatable() local mandatory_context = {"file"} -- will always be present in template
local r = {} local discriminating_context = "source" -- only used when there may be conflicts
for _, tr in ipairs(l) do
table.insert(r, "(("..tr.source.."))") -- if there are several identical translations in the tuple, wrap them in a struct with the discriminating context
table.insert(r, Call:new(Identifier:new("_#_"), ArgumentTuple:new(tr.context, Identifier:new("_"))):format()) local function classify_discriminating(tr_tuple)
table.insert(r, "\t"..Call:new(Identifier:new("_->_"), ArgumentTuple:new(tr, tr)):format()) local r_tuple = Tuple:new()
table.insert(r, "") local discovered = Struct:new()
for _, tr in tr_tuple:iter() do
if not discovered:has(tr) then
r_tuple:insert(tr)
discovered:set(tr, r_tuple:len())
else
local context_name = String:new(discriminating_context)
-- replace previousley discovered translation
local discovered_index = discovered:get(tr)
if discovered_index ~= -1 then
local discovered_tr = r_tuple:get(discovered_index)
local context = Pair:new(context_name, discovered_tr.context:get(context_name))
local discovered_strct = Struct:new()
discovered_strct:set(context, discovered_tr)
r_tuple:set(discovered_index, discovered_strct)
discovered:set(tr, -1)
end
-- add current translation
local context = Pair:new(context_name, tr.context:get(context_name))
local strct = Struct:new()
strct:set(context, tr)
r_tuple:insert(strct)
end
end
return r_tuple
end end
return table.concat(r, "\n")
-- build a Struct{ [context1] = Struct{ [context2]} = Tuple[translatable, Struct { [context3] = translatable }, ...], ... }, ... }
local function classify(tr_tuple, context_i)
local r = Struct:new()
local context_name = String:new(mandatory_context[context_i])
for _, tr in tr_tuple:iter() do
local context = Pair:new(context_name, tr.context:get(context_name))
if not r:has(context) then
r:set(context, Tuple:new())
end
local context_tr_tuple = r:get(context)
context_tr_tuple:insert(tr)
end
for context, context_tr_tuple in r:iter() do
if context_i < #mandatory_context then
r:set(context, classify(context_tr_tuple, context_i+1))
elseif context_i == #mandatory_context then
r:set(context, classify_discriminating(context_tr_tuple))
end
end
return r
end
local classified = classify(Tuple:new(unpack(self:list_translatable())), 1)
local function build_str(tr, level)
level = level or 0
local indent = ("\t"):rep(level)
local r = {}
if Struct:is(tr) then
for context, context_tr in tr:iter() do
table.insert(r, indent..Call:new(Identifier:new("_#_"), ArgumentTuple:new(context, Identifier:new("_"))):format():gsub(" _$", ""))
table.insert(r, build_str(context_tr, level+1))
end
elseif Tuple:is(tr) then
for _, v in tr:iter() do
table.insert(r, build_str(v, level))
table.insert(r, "")
end
else
table.insert(r, indent.."/* "..tr.source.." */")
table.insert(r, indent..Call:new(Identifier:new("_->_"), ArgumentTuple:new(tr, tr)):format())
end
return table.concat(r, "\n")
end
return build_str(classified)
end, end,
-- call the node with the given arguments -- call the node with the given arguments
@ -332,7 +402,7 @@ Node = class {
-- Thus, any require here that may require other Nodes shall be done here. This method is called in anselme.lua after everything else is required. -- Thus, any require here that may require other Nodes shall be done here. This method is called in anselme.lua after everything else is required.
_i_hate_cycles = function(self) _i_hate_cycles = function(self)
local ast = require("anselme.ast") local ast = require("anselme.ast")
Call, Identifier, ArgumentTuple = ast.Call, ast.Identifier, ast.ArgumentTuple Call, Identifier, ArgumentTuple, Struct, Tuple, String, Pair = ast.Call, ast.Identifier, ast.ArgumentTuple, ast.Struct, ast.Tuple, ast.String, ast.Pair
custom_call_identifier = Identifier:new("_!") custom_call_identifier = Identifier:new("_!")
resume_manager = require("anselme.state.resume_manager") resume_manager = require("anselme.state.resume_manager")

View file

@ -1,4 +1,5 @@
local ast = require("anselme.ast") local ast = require("anselme.ast")
local Nil = ast.Nil
local persistent_manager = require("anselme.state.persistent_manager") local persistent_manager = require("anselme.state.persistent_manager")
@ -13,7 +14,7 @@ return {
"persist", "(key, default) = value", "persist", "(key, default) = value",
function(state, key, default, value) function(state, key, default, value)
persistent_manager:set(state, key, value) persistent_manager:set(state, key, value)
return ast.Nil:new() return Nil:new()
end end
}, },
{ {
@ -26,7 +27,7 @@ return {
"persist", "(key) = value", "persist", "(key) = value",
function(state, key, value) function(state, key, value)
persistent_manager:set(state, key, value) persistent_manager:set(state, key, value)
return ast.Nil:new() return Nil:new()
end end
}, },
} }

View file

@ -1,5 +1,5 @@
local ast = require("anselme.ast") local ast = require("anselme.ast")
local Nil, Choice, PartialScope, ArgumentTuple = ast.Nil, ast.Choice, ast.PartialScope, ast.ArgumentTuple local Nil, Choice, PartialScope, ArgumentTuple, Identifier = ast.Nil, ast.Choice, ast.PartialScope, ast.ArgumentTuple, ast.Identifier
local event_manager = require("anselme.state.event_manager") local event_manager = require("anselme.state.event_manager")
local translation_manager = require("anselme.state.translation_manager") local translation_manager = require("anselme.state.translation_manager")
@ -34,7 +34,7 @@ return {
{ {
"_->_", "(original::is(\"quote\"), translated::is(\"quote\"))", "_->_", "(original::is(\"quote\"), translated::is(\"quote\"))",
function(state, original, translated) function(state, original, translated)
local exp = PartialScope:preserve(state, translated.expression, ast.Identifier:new("_")) local exp = PartialScope:preserve(state, translated.expression, Identifier:new("_"))
translation_manager:set(state, tag_manager:get(state), original.expression, exp) translation_manager:set(state, tag_manager:get(state), original.expression, exp)
return Nil:new() return Nil:new()
end end

View file

@ -14,10 +14,6 @@ Translation.
Translation model for stdlib: ? Translation model for stdlib: ?
For translation context/disambiguation:
* text+line+file - OK
* func/var hierarchy?
Do some more fancy scope work to allow the translation to access variables defined in the translation file? Do some more fancy scope work to allow the translation to access variables defined in the translation file?
--- ---