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 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")
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
4
ideas.md
4
ideas.md
|
|
@ -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?
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue