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:
parent
41f85181a3
commit
581c60048d
10 changed files with 118 additions and 32 deletions
|
|
@ -2,16 +2,16 @@
|
|||
|
||||
local ast = require("anselme.ast")
|
||||
local Overloadable = ast.abstract.Overloadable
|
||||
local ReturnBoundary, Environment
|
||||
local ReturnBoundary, Environment, Identifier, Symbol
|
||||
|
||||
local operator_priority = require("anselme.common").operator_priority
|
||||
|
||||
local resume_manager, calling_environment_manager
|
||||
|
||||
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)
|
||||
elseif ast.Symbol:is(v) then
|
||||
elseif Symbol:is(v) then
|
||||
list[v.string] = scope:precache(state, v:to_identifier())
|
||||
end
|
||||
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
|
||||
local upvalues = {}
|
||||
self.expression:traverse(list_cache_upvalues, state, upvalues, scope)
|
||||
if scope:defined(state, ast.Identifier:new("_")) then
|
||||
scope:get(state, ast.Identifier:new("_")):traverse(list_cache_upvalues, state, upvalues, scope)
|
||||
if scope:defined(state, Identifier:new("_")) then
|
||||
scope:get(state, Identifier:new("_")):traverse(list_cache_upvalues, state, upvalues, scope)
|
||||
end
|
||||
|
||||
return Function:new(self.parameters:eval(state), self.expression, scope, upvalues)
|
||||
|
|
@ -143,7 +143,7 @@ Function = Overloadable {
|
|||
}
|
||||
|
||||
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")
|
||||
calling_environment_manager = require("anselme.state.calling_environment_manager")
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ List = ast.abstract.Runtime {
|
|||
return self.branched:get(state):len()
|
||||
end,
|
||||
iter = function(self, state)
|
||||
return ipairs(self.branched:get(state).list)
|
||||
return self.branched:get(state):iter()
|
||||
end,
|
||||
get = function(self, state, index)
|
||||
local list = self.branched:get(state)
|
||||
|
|
|
|||
|
|
@ -44,7 +44,12 @@ Struct = ast.abstract.Runtime {
|
|||
self.table = {}
|
||||
end,
|
||||
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,
|
||||
include = function(self, other) -- only for construction
|
||||
for _, e in pairs(other.table) do
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
local ast = require("anselme.ast")
|
||||
local TextInterpolation, String
|
||||
local TextInterpolation, String, Struct
|
||||
|
||||
local operator_priority = require("anselme.common").operator_priority
|
||||
|
||||
|
|
@ -10,11 +10,14 @@ local Translatable = ast.abstract.Node {
|
|||
hide_in_stacktrace = true,
|
||||
|
||||
expression = nil,
|
||||
context = nil, -- struct
|
||||
|
||||
init = function(self, 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("file"), String:new(self.expression.source:match("^([^%:]*)")))
|
||||
-- TODO: add parent script/function to context
|
||||
end,
|
||||
|
||||
_format = function(self, ...)
|
||||
|
|
@ -46,7 +49,7 @@ local Translatable = ast.abstract.Node {
|
|||
}
|
||||
|
||||
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")
|
||||
|
||||
|
|
|
|||
|
|
@ -61,9 +61,17 @@ Tuple = ast.abstract.Node {
|
|||
if index > #self.list or index == 0 then error("tuple index out of bounds", 0) end
|
||||
return self.list[index]
|
||||
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)
|
||||
return #self.list
|
||||
end
|
||||
end,
|
||||
iter = function(self)
|
||||
return ipairs(self.list)
|
||||
end,
|
||||
}
|
||||
|
||||
return Tuple
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ local operator_priority = require("anselme.common").operator_priority
|
|||
|
||||
local format_identifier
|
||||
|
||||
local ArgumentTuple
|
||||
|
||||
local Typed
|
||||
Typed = ast.abstract.Runtime {
|
||||
type = "typed",
|
||||
|
|
@ -20,7 +22,7 @@ Typed = ast.abstract.Runtime {
|
|||
-- try custom format
|
||||
if state and state.scope:defined(format_identifier) then
|
||||
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)
|
||||
if fn then
|
||||
return custom_format:call(state, d_args):format(state, prio, ...)
|
||||
|
|
@ -37,5 +39,6 @@ Typed = ast.abstract.Runtime {
|
|||
|
||||
package.loaded[...] = Typed
|
||||
format_identifier = ast.Identifier:new("format")
|
||||
ArgumentTuple = ast.ArgumentTuple
|
||||
|
||||
return Typed
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ local class = require("anselme.lib.class")
|
|||
local fmt = require("anselme.common").fmt
|
||||
local binser = require("anselme.lib.binser")
|
||||
local utf8 = utf8 or require("lua-utf8")
|
||||
local unpack = table.unpack or unpack
|
||||
|
||||
-- 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
|
||||
|
|
@ -13,7 +14,7 @@ local utf8 = utf8 or require("lua-utf8")
|
|||
|
||||
local uuid = require("anselme.common").uuid
|
||||
|
||||
local Call, Identifier, ArgumentTuple
|
||||
local Call, Identifier, ArgumentTuple, Struct, Tuple, String, Pair
|
||||
local resume_manager
|
||||
|
||||
local custom_call_identifier
|
||||
|
|
@ -143,17 +144,86 @@ Node = class {
|
|||
|
||||
-- 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
|
||||
-- TODO: generate more stable context than source position, and only add necessery context to the tag
|
||||
generate_translation_template = function(self)
|
||||
local l = self:list_translatable()
|
||||
local r = {}
|
||||
for _, tr in ipairs(l) do
|
||||
table.insert(r, "(("..tr.source.."))")
|
||||
table.insert(r, Call:new(Identifier:new("_#_"), ArgumentTuple:new(tr.context, Identifier:new("_"))):format())
|
||||
table.insert(r, "\t"..Call:new(Identifier:new("_->_"), ArgumentTuple:new(tr, tr)):format())
|
||||
table.insert(r, "")
|
||||
local mandatory_context = {"file"} -- will always be present in template
|
||||
local discriminating_context = "source" -- only used when there may be conflicts
|
||||
|
||||
-- if there are several identical translations in the tuple, wrap them in a struct with the discriminating context
|
||||
local function classify_discriminating(tr_tuple)
|
||||
local r_tuple = Tuple:new()
|
||||
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
|
||||
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,
|
||||
|
||||
-- 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.
|
||||
_i_hate_cycles = function(self)
|
||||
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("_!")
|
||||
|
||||
resume_manager = require("anselme.state.resume_manager")
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
local ast = require("anselme.ast")
|
||||
local Nil = ast.Nil
|
||||
|
||||
local persistent_manager = require("anselme.state.persistent_manager")
|
||||
|
||||
|
|
@ -13,7 +14,7 @@ return {
|
|||
"persist", "(key, default) = value",
|
||||
function(state, key, default, value)
|
||||
persistent_manager:set(state, key, value)
|
||||
return ast.Nil:new()
|
||||
return Nil:new()
|
||||
end
|
||||
},
|
||||
{
|
||||
|
|
@ -26,7 +27,7 @@ return {
|
|||
"persist", "(key) = value",
|
||||
function(state, key, value)
|
||||
persistent_manager:set(state, key, value)
|
||||
return ast.Nil:new()
|
||||
return Nil:new()
|
||||
end
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 translation_manager = require("anselme.state.translation_manager")
|
||||
|
|
@ -34,7 +34,7 @@ return {
|
|||
{
|
||||
"_->_", "(original::is(\"quote\"), translated::is(\"quote\"))",
|
||||
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)
|
||||
return Nil:new()
|
||||
end
|
||||
|
|
|
|||
4
ideas.md
4
ideas.md
|
|
@ -14,10 +14,6 @@ Translation.
|
|||
|
||||
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?
|
||||
|
||||
---
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue