Module:parser

From Wiktionary, the free dictionary
Jump to navigation Jump to search

This module needs documentation.
Pleasedocumentthis module by describing its purpose and usage on thedocumentation page.

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