Module:parser
Jump to navigation
Jump to search
- The followingdocumentationis located atModule:parser/documentation.[edit]
- Useful links:subpage list•links•transclusions•testcases•sandbox
localexport={}
localconcat=table.concat
localdeepcopy-- Assigned when needed.
localgetmetatable=getmetatable
localinsert=table.insert
localnext=next
localrawget=rawget
localrawset=rawset
localremove=table.remove
localsetmetatable=setmetatable
localtype=type
localunpack=unpack
localclasses={}
localmetamethods=mw.loadData("Module:data/metamethods")
------------------------------------------------------------------------------------
--
-- Helper functions
--
------------------------------------------------------------------------------------
localfunctionget_nested(t,k,...)
ift==nilthen
returnnil
elseif...==nilthen
returnt[k]
end
returnget_nested(t[k],...)
end
localfunctionset_nested(t,k,v,...)
if...~=nilthen
localt_next=t[k]
ift_next==nilthen
t_next={}
t[k]=t_next
end
returnset_nested(t_next,v,...)
end
t[k]=v
end
localfunctioninherit_metamethods(child,parent)
ifparentthen
formethod,valueinnext,parentdo
ifchild[method]==nilandmetamethods[method]~=nilthen
child[method]=value
end
end
end
returnchild
end
localfunctionsigned_index(t,n)
returnnandn<=0and#t+1+norn
end
localfunctionis_node(value)
returnclasses[getmetatable(value)]~=nil
end
-- Recursively calling tostring() adds to the C stack (limit: 200), whereas
-- calling __tostring metamethods directly does not. Occasionally relevant when
-- dealing with very deep nesting.
localtostring
do
local_tostring=_G.tostring
functiontostring(value)
ifis_node(value)then
returnvalue:__tostring(value)
end
return_tostring(value)
end
end
localfunctionclass_else_type(value)
localclass=classes[getmetatable(value)]
ifclass~=nilthen
returnclass
end
returntype(value)
end
------------------------------------------------------------------------------------
--
-- Nodes
--
------------------------------------------------------------------------------------
localNode={}
Node.__index=Node
functionNode:next(i)
i=i+1
returnself[i],self,i
end
functionNode:next_node(i)
localv
repeat
v,self,i=self:next(i)
untilv==niloris_node(v)
returnv,self,i
end
-- Implements recursive iteration over a node tree, using functors to maintain state (which uses a lot less memory than closures). Iterator1 exists only to return the calling node on the first iteration, while Iterator2 uses a stack to store the state of each layer in the tree.
-- When a node is encountered (which may contain other nodes), it is returned on the first iteration, and then any child nodes are returned on each subsequent iteration; the same process is followed if any of those children contain nodes themselves. Once a particular node has been fully traversed, the iterator moves back up one layer and continues with any sibling nodes.
-- Each iteration returns three values: `value`, `node` and `key`. Together, these can be used to manipulate the node tree at any given point without needing to know the full structure. Note that when the input node is returned on the first iteration, `node` and `key` will be nil.
-- By default, the iterator will use the `next` method of each node, but this can be changed with the `next_func` parameter, which accepts a string argument with the name of a next method. This is because trees might consist of several different classes of node, and each might have different next methods that are tailored to their particular structures. In addition, each class of node might have multiple different next methods, which can be named according to their purposes. `next_func` ensures that the iterator uses equivalent next methods between different types of node.
-- Currently, two next methods are available: `next`, which simply iterates over the node conventionally, and `next_node`, which only returns children that are themselves nodes. Custom next methods can be declared by any calling module.
do
localIterator1,Iterator2={},{}
Iterator1.__index=Iterator2-- Not a typo.
Iterator2.__index=Iterator2
functionIterator1:__call()
setmetatable(self,Iterator2)
returnself[1].node
end
functionIterator2:push(node)
locallayer={
k=0,
node=node
}
self[#self+1]=layer
self[-1]=layer
returnself
end
functionIterator2:pop()
locallen=#self
self[len]=nil
self[-1]=self[len-1]
end
functionIterator2:iterate(layer,...)
localv,node,k=...
ifv~=nilthen
layer.k=k
return...
end
self:pop()
layer=self[-1]
iflayer~=nilthen
node=layer.node
returnself:iterate(layer,node[self.next_func](node,layer.k))
end
end
functionIterator2:__call()
locallayer=self[-1]
localnode,k=layer.node,layer.k
localcurr_val=node[k]
ifis_node(curr_val)then
self:push(curr_val)
layer=self[-1]
node,k=layer.node,layer.k
end
returnself:iterate(layer,node[self.next_func](node,k))
end
functionNode:__pairs(next_func)
returnsetmetatable({
next_func=next_func==niland"next"ornext_func
},Iterator1):push(self)
end
end
functionNode:rawpairs()
returnnext,self
end
functionNode:__tostring()
localoutput={}
fori=1,#selfdo
insert(output,tostring(self[i]))
end
returnconcat(output)
end
functionNode:clone()
ifnotdeepcopythen
deepcopy=require("Module:table").deepcopy
end
returndeepcopy(self,"keep",true)
end
functionNode:new_class(class)
localt={type=class}
t.__index=t
t=inherit_metamethods(t,self)
classes[t]=class
returnsetmetatable(t,self)
end
Node.keys_to_remove={"fail","handler","head","override","route"}
functionNode:new(t)
setmetatable(t,nil)
localkeys_to_remove=self.keys_to_remove
fori=1,#keys_to_removedo
t[keys_to_remove[i]]=nil
end
returnsetmetatable(t,self)
end
do
localProxy={}
functionProxy:__index(k)
localv=Proxy[k]
ifv~=nilthen
returnv
end
returnself.__chars[k]
end
functionProxy:__newindex(k,v)
localkey=self.__keys[k]
ifkeythen
self.__chars[k]=v
self.__parents[key]=v
elseifkey==falsethen
error("Character is immutable.")
else
error("Invalid key.")
end
end
functionProxy:build(a,b,c)
locallen=self.__len+1
self.__chars[len]=a
self.__parents[len]=b
self.__keys[len]=c
self.__len=len
end
functionProxy:iter(i)
i=i+1
localchar=self.__chars[i]
ifchar~=nilthen
returni,self[i],self,self.__parents[i],self.__keys[i]
end
end
functionNode:new_proxy()
returnsetmetatable({
__node=self,
__chars={},
__parents={},
__keys={},
__len=0
},Proxy)
end
end
------------------------------------------------------------------------------------
--
-- Parser
--
------------------------------------------------------------------------------------
localParser={}
Parser.__index=Parser
functionParser:read(delta)
localv=self.text[self.head+(deltaor0)]
returnv==niland""orv
end
functionParser:advance(n)
self.head=self.head+(n==niland1orn)
end
functionParser:layer(n)
ifn~=nilthen
returnrawget(self,#self+n)
end
returnself[-1]
end
functionParser:emit(a,b)
locallayer=self[-1]
ifb~=nilthen
insert(layer,signed_index(layer,a),b)
else
rawset(layer,#layer+1,a)
end
end
functionParser:emit_tokens(a,b)
locallayer=self[-1]
ifb~=nilthen
a=signed_index(layer,a)
fori=1,#bdo
insert(layer,a+i-1,b[i])
end
else
locallen=#layer
fori=1,#ado
len=len+1
rawset(layer,len,a[i])
end
end
end
functionParser:remove(n)
locallayer=self[-1]
ifn~=nilthen
returnremove(layer,signed_index(layer,n))
end
locallen=#layer
localtoken=layer[len]
layer[len]=nil
returntoken
end
functionParser:replace(a,b)
locallayer=self[-1]
layer[signed_index(layer,a)]=b
end
-- Unlike default table.concat, this respects __tostring metamethods.
functionParser:concat(a,b,c)
ifa==nilora>0then
returnself:concat(0,a,b)
end
locallayer,ret,n=self:layer(a),{},0
fori=bandsigned_index(layer,b)or1,candsigned_index(layer,c)or#layerdo
n=n+1
ret[n]=tostring(layer[i])
end
returnconcat(ret)
end
functionParser:emitted(delta)
ifdelta==nilthen
delta=-1
end
locali=0
whiletruedo
locallayer=self:layer(i)
iflayer==nilthen
returnnil
end
locallayer_len=#layer
if-delta<=layer_lenthen
returnrawget(layer,layer_len+delta+1)
end
delta=delta+layer_len
i=i-1
end
end
functionParser:push(route)
locallayer={
head=self.head,
route=route
}
self[#self+1]=layer
self[-1]=layer
end
functionParser:push_sublayer(handler,inherit)
localsublayer={
handler=handler,
sublayer=true
}
ifinheritthen
locallayer=self[-1]
setmetatable(sublayer,inherit_metamethods({
__index=layer,
__newindex=layer
},getmetatable(layer)))
end
self[#self+1]=sublayer
self[-1]=sublayer
end
functionParser:pop()
locallen,layer=#self
whiletruedo
layer=self[len]
self[len]=nil
len=len-1
localnew=self[len]
self[-1]=new==nilandselfornew
iflayer.sublayer==nilthen
break
end
self:emit_tokens(layer)
end
returnlayer
end
functionParser:pop_sublayer()
locallen,layer=#self,self[-1]
self[len]=nil
localnew=self[len-1]
self[-1]=new==nilandselfornew
setmetatable(layer,nil)
layer.sublayer=nil
returnlayer
end
functionParser:get(route,...)
self:push(route)
locallayer=route(self,...)
iflayer==nilthen
layer=self:traverse()
end
returnlayer
end
functionParser:try(route,...)
localfailed_layer=get_nested(self.failed_routes,route,self.head)
iffailed_layer~=nilthen
returnfalse,failed_layer
end
locallayer=self:get(route,...)
returnnotlayer.fail,layer
end
functionParser:consume(this,...)
locallayer=self[-1]
ifthis==nilthen
this=self:read()
end
return(layer.overrideorlayer.handler)(self,this,...)
end
functionParser:fail_route()
locallayer=self:pop()
layer.fail=true
set_nested(self,"failed_routes",layer.route,layer.head,layer)
self.head=layer.head
returnlayer
end
functionParser:traverse()
whiletruedo
locallayer=self:consume()
iflayer~=nilthen
returnlayer
end
self:advance()
end
end
-- Converts a handler into a switch table the first time it's called, which avoids creating unnecessary objects, and prevents any scoping issues caused by parser methods being assigned to table keys before they've been declared.
-- false is used as the default key.
do
localSwitch={}
functionSwitch:__call(parser,this)
return(self[this]orself[false])(parser,this)
end
functionParser:switch(func,t)
locallayer=self[-1]
-- Point handler to the new switch table if the calling function is the current handler.
iflayer.handler==functhen
layer.handler=t
end
returnsetmetatable(t,Switch)
end
end
-- Generate a new parser class object, which is used as the template for any parser objects. These should be customized with additional/modified methods as needed.
functionParser:new_class()
localt={}
t.__index=t
returnsetmetatable(inherit_metamethods(t,self),self)
end
-- Generate a new parser object, which is used for a specific parse.
functionParser:new(text)
returnsetmetatable({
text=text,
head=1
},self)
end
functionParser:parse(data)
localparser=self:new(data.text)
localsuccess,tokens=parser:try(unpack(data.route))
if#parser>0then
-- This shouldn't happen.
error("Parser exited with non-empty stack.")
elseifsuccessthen
localnode=data.node
returntrue,node[1]:new(tokens,unpack(node,2)),parser
elseifdata.allow_failthen
returnfalse,nil,parser
end
error("Parser exited with failed route.")
end
export.class_else_type=class_else_type
export.is_node=is_node
export.tostring=tostring
functionexport.new()
returnParser:new_class(),Node:new_class("node")
end
returnexport