SupyML: Add a lot of features, unit tests, and fill the README.txt with a description of the language syntax.

master
Valentin Lorentz 2010-11-03 20:09:25 +01:00
parent 62bb41045d
commit bbf9f37356
3 changed files with 185 additions and 34 deletions

View File

@ -1 +1,65 @@
Insert a description of your plugin here, with any notes, etc. about using it.
This is a document that describe the SupyML language
Global syntax
=============
SupyML is a language based on the XML syntax. The official SupyML interpreter
uses the xml.dom.minidom parser. Minidom handles the format error; the
exceptions it raises are not handled by the SupyML parser.
Commands
========
SupyML is based on call to SupyBot commands. A command is call like that:
<commandName>arg1 arg2 arg3</commandName>
If you want to call a command in a specific plugin, use this syntax:
<pluginName>commandName arg1 arg2 arg3</pluginName>
Variables
=========
To get the value of a variable, type:
<var name="variableName" />
To set the value, type:
<set name="variableName">newValue</set>
Note that all variable are strings, because SupyML and SupyBot commands
processing both use only strings.
Some plugins, like Conditional provides different handlings for strings and
numerics, but it is their own problems.
Variable lifetime
-----------------
When setting a new variable, the variable is readable and writable in the
node where it is created, and in the child nodes.
If a child node write a variable defined in a parent node, the new value of
the variable will be available in the parent node.
About the names
---------------
Because of/thanks to the syntax of the language, names can easily be anything,
even if things other language doesn't like, for example: special chars,
spaces, starting with a number, empty, etc.
But, this is highly deprecated, and it raises a warning.
Conditions
==========
SupyML has not (yet) conditions. Use the Conditional plugin to do that.
Loops
=====
Loops are the main feature provided by SupyML. Here is the global syntax:
<loop><loopType>boolean</loopType>command arg1 arg2 arg3</loop>
Use conditions to have changing booleans ;)
While loop
----------
The syntax of the while loop is:
<loop><while>boolean</while>command arg1 arg2 arg3</loop>
The command is run while the boolean is true.

View File

@ -28,7 +28,10 @@
###
import re
import copy
import Queue
import supybot.world as world
from xml.dom import minidom
import supybot.utils as utils
from supybot.commands import *
@ -70,9 +73,11 @@ class SupyMLParser:
self._irc = irc
self._msg = msg
self._code = code
self.warnings = []
self._parse()
def _run(self, code, proxify):
"""Runs the command using Supybot engine"""
tokens = callbacks.tokenize(str(code))
if proxify:
fakeIrc = FakeIrc(self._irc)
@ -83,47 +88,88 @@ class SupyMLParser:
return fakeIrc._data
def _parse(self):
"""Returns a dom object from self._code."""
dom = minidom.parseString(self._code)
return self._processDocument(dom)
def _processDocument(self, dom):
"""Handles the root node and call child nodes"""
for childNode in dom.childNodes:
if childNode.__class__ == minidom.Element:
self._processNode(childNode, False)
def _processNode(self, node, proxify=True):
if node.nodeName == 'loop':
return self._processLoop(node, proxify)
elif node.nodeName == 'if':
return self._processId(node, proxify)
elif node.nodeName == 'var':
return self._processVar(node, proxify)
if isinstance(childNode, minidom.Element):
self._processNode(childNode, {}, False)
def _processNode(self, node, variables, proxifyIrc=True):
"""Returns the value of an internapreted node.
Takes an optional attribute, passed to self._run() that mean if the
Irc object should be proxified. If it is not, the real Irc object is
used, datas are sent to IRC, and this function will return None."""
if isinstance(node, minidom.Text):
return node.data
output = node.nodeName + ' '
newVariables = copy.deepcopy(variables)
for childNode in node.childNodes:
if childNode.__class__ == minidom.Text:
output += childNode.data
elif childNode.__class__ == minidom.Element:
output += self._processNode(childNode)
value = self._run(str(output), proxify)
if not repr(node) == repr(childNode.parentNode):
print "CONTINUING"
continue
if childNode.nodeName == 'loop':
output += self._processLoop(childNode, newVariables)
elif childNode.nodeName == 'if':
output += self._processId(childNode, newVariables)
elif childNode.nodeName == 'var':
output += self._processVar(childNode, newVariables)
elif childNode.nodeName == 'set':
output += self._processSet(childNode, newVariables)
else:
output += self._processNode(childNode, newVariables) or ''
for key in variables:
variables[key] = newVariables[key]
value = self._run(output, proxifyIrc)
return value
def _processLoop(self, node, proxify=True):
raise NotImplemented
def _processIf(self, node, proxify=True):
raise NotImplemented
def _processVar(self, node, proxify=True):
raise NotImplemented
# Don't proxify variables
def _processSet(self, node, variables):
"""Handles the <set> tag"""
variableName = str(node.attributes['name'].value)
value = ''
for childNode in node.childNodes:
value += self._processNode(childNode, variables)
variables.update({variableName: value})
return ''
def _processVar(self, node, variables):
"""Handles the <var /> tag"""
variableName = node.attributes['name'].value
try:
return variables[variableName]
except KeyError:
self.warnings.append('Access to non-existing variable: %s' %
variableName)
return ''
def _processLoop(self, node, variables):
"""Handles the <loop> tag"""
return '<NOT IMPLEMENTED>'
def _checkVariableName(self, variableName):
if len(variableName) == 0:
self.warnings.append('Empty variable name')
if re.match('\W+', variableName):
self.warnings.append('Variable name shouldn\'t contain '
'special chars (%s)' % variableName)
class SupyML(callbacks.Plugin):
"""SupyML is a plugin that read SupyML scripts.
This scripts (Supybot Markup Language) are script written in a XML-based
language."""
threaded = True
#threaded = True
def eval(self, irc, msg, args, code):
"""<SupyML script>
Executes the <SupyML script>"""
parser = SupyMLParser(self, irc, msg, code)
if world.testing and len(parser.warnings) != 0:
print parser.warnings
eval=wrap(eval, ['text'])
SupyML = internationalizeDocstring(SupyML)

View File

@ -36,7 +36,7 @@ class SupyMLTestCase(ChannelPluginTestCase):
#################################
# Utilities
def _getIfAnswerIsEqual(self, msg):
time.sleep(0.5)
time.sleep(0.2)
m = self.irc.takeMsg()
while m is not None:
if repr(m) == repr(msg):
@ -57,20 +57,61 @@ class SupyMLTestCase(ChannelPluginTestCase):
self.failIf(self._getIfAnswerIsEqual(answer) == False)
def testNoMoreThanOneAnswer(self):
self.assertResponse('SupyML eval <echo><echo>foo</echo>'
'<echo>bar</echo></echo>',
self.assertResponse('SupyML eval '
'<echo>'
'<echo>foo</echo>'
'<echo>bar</echo>'
'</echo>',
'foobar')
def testVar(self):
self.assertResponse('SupyML eval <echo><var>foo<set>bar</set></var>'
'<echo><var>foo</var></echo></echo>',
self.assertResponse('SupyML eval '
'<echo>'
'<set name="foo">bar</set>'
'<echo>'
'<var name="foo" />'
'</echo>'
'</echo>',
'bar')
def testVarLifetime(self):
self.assertResponse('SupyML eval '
'<echo>'
'<set name="foo">bar</set>'
'<echo>'
'<var name="foo" />'
'baz'
'</echo>'
'</echo>',
'barbaz')
self.assertResponse('SupyML eval '
'<echo>'
'<set name="foo">bar</set>'
'<echo>'
'<set name="foo">bar</set>'
'<var name="foo" />'
'</echo>'
'<echo>'
'<var name="foo" />'
'</echo>'
'</echo>',
'bar')
def testWhile(self):
self.assertResponse('SupyML eval <echo>'
'<var>foo<set>1</set></var>'
'<loop><while><nne><var>foo</var> 5</nne></while>'
'<echo>bar</echo></loop></echo>',
self.assertResponse('SupyML eval '
'<echo>'
'<set name="foo">bar</set>'
'<loop>'
'<while>'
'<nne>'
'<var name="foo" /> 5'
'</nne>'
'</while>'
'<echo>'
'bar'
'</echo>'
'</loop>'
'</echo>',
'bar'*5)