def test_parse_script(self): # Test :meth:`parse_script()`. from myokit._parsing import parse_script # Test simple code = ( '[[script]]', ) script = parse_script(code) self.assertIsInstance(script, basestring) script = parse_script(''.join(code)) self.assertIsInstance(script, basestring) code = ( '[[script]]\n', ) script = parse_script(code) self.assertIsInstance(script, basestring) code = ( '[[script]]\n', 'print("hi")', ) script = parse_script(code) self.assertIsInstance(script, basestring) code = ( '[[script]]\n', 'print("hi")\n', ) script = parse_script(code) self.assertIsInstance(script, basestring) # Not a script code = ( '[[hello]]\n', 'print("hi")\n', ) self.assertRaisesRegex( myokit.ParseError, r'Expecting \[\[script]]', parse_script, code) # Not a script, parsing from stream from myokit._parsing import Tokenizer, parse_script_from_stream raw = iter(code) stream = Tokenizer(raw) self.assertRaisesRegex( myokit.ParseError, r'Expecting \[\[script]]', parse_script_from_stream, stream, raw)
def test_parse_protocol(self): """ Test :meth:`parse_protocol()`. """ from myokit._parsing import parse_protocol # Test simple # Level Start Length Period Multiplier code = ('[[protocol]]\n', ) protocol = parse_protocol(code) self.assertIsInstance(protocol, myokit.Protocol) protocol = parse_protocol(''.join(code)) self.assertIsInstance(protocol, myokit.Protocol) code = ( '[[protocol]]\n', '0 0 1 0 0\n', ) protocol = parse_protocol(code) self.assertIsInstance(protocol, myokit.Protocol) # Funny numbers code = ('[[protocol]]\n', '-1 0 1 0 0\n') protocol = parse_protocol(code) self.assertIsInstance(protocol, myokit.Protocol) code = ('[[protocol]]\n', '-1 0 1e3 0 0\n') protocol = parse_protocol(code) self.assertIsInstance(protocol, myokit.Protocol) code = ('[[protocol]]\n', '-1 0 1e3 1000 +3\n') protocol = parse_protocol(code) self.assertIsInstance(protocol, myokit.Protocol) code = ('[[protocol]]\n', '0.1 0 1 0 0\n') protocol = parse_protocol(code) self.assertIsInstance(protocol, myokit.Protocol) code = ('[[protocol]]\n', '0.1e-2 0 1 0 0\n') protocol = parse_protocol(code) self.assertIsInstance(protocol, myokit.Protocol) code = ('[[protocol]]\n', '.1e-2 0 1 0 0\n') protocol = parse_protocol(code) self.assertIsInstance(protocol, myokit.Protocol) # Not a protocol code = ( '[[protcle]]\n', '0 0 1 0 0\n', ) self.assertRaisesRegex(myokit.ParseError, r'Expecting \[\[protocol]]', parse_protocol, code) # Not a protocol (in stream) from myokit._parsing import parse_protocol_from_stream, Tokenizer stream = Tokenizer(iter(code)) self.assertRaisesRegex(myokit.ParseError, r'Expecting \[\[protocol]]', parse_protocol_from_stream, stream) # Test using 'next' code = ( '[[protocol]]\n', '00 0 100 0 0\n', '10 next 100 0 0\n', '20 next 100 0 0\n', ) p1 = parse_protocol(code) code = ( '[[protocol]]\n', '00 0 100 0 0\n', '10 100 100 0 0\n', '20 200 100 0 0\n', ) p2 = parse_protocol(code) self.assertEqual(p1.code(), p2.code()) # Test invalid use of next (after periodic event) code = ( '[[protocol]]\n', '00 0 100 1000 0\n', '10 next 100 0 0\n', ) self.assertRaisesRegex(myokit.ProtocolParseError, 'Invalid next', parse_protocol, code) # Simultaneous events code = ( '[[protocol]]\n', '00 0 100 0 0\n', '10 0 100 0 0\n', ) self.assertRaisesRegex(myokit.ProtocolParseError, 'same time', parse_protocol, code) code = ( '[[protocol]]\n', '00 0 100 1000 0\n', '10 0 1100 0 0\n', ) self.assertRaisesRegex(myokit.ProtocolParseError, 'same time', parse_protocol, code) # Wrong order code = ( '[[protocol]]\n', '0 10 1 0 0\n', '1 0 1 0 0\n', ) self.assertRaisesRegex(myokit.ProtocolParseError, 'chronological', parse_protocol, code) # Duration <= 0 code = ('[[protocol]]\n', '0 10 -1 0 0\n') self.assertRaisesRegex(myokit.ProtocolParseError, 'Invalid duration', parse_protocol, code) code = ('[[protocol]]\n', '0 10 0 0 0\n') self.assertRaisesRegex(myokit.ProtocolParseError, 'Invalid duration', parse_protocol, code) # Negative period code = ('[[protocol]]\n', '0 10 1 -1 0\n') self.assertRaisesRegex(myokit.ProtocolParseError, 'Negative period', parse_protocol, code) # Negative multiplier # Multiplier for non-periodic event code = ('[[protocol]]\n', '0 10 1 100 -1\n') self.assertRaisesRegex(myokit.ProtocolParseError, 'Negative multiplier', parse_protocol, code) # Multiplier for non-periodic event code = ('[[protocol]]\n', '0 10 1 0 1\n') self.assertRaisesRegex(myokit.ProtocolParseError, 'Invalid multiplier', parse_protocol, code) # Found a bug when parsing this code = ( '[[protocol]]\n', '-80 next 300 0 0\n', '-120 3196.0 50.0 0 0\n', ) parse_protocol(code)
def p(code): return parse_model_from_stream(Tokenizer(iter(code)))
def test_tokenizer(self): """ Test basic Tokenizer functionality. """ import myokit._parsing as p from myokit._parsing import Tokenizer s = Tokenizer('5') self.assertEqual(next(s), (p.INTEGER, '5', 1, 0)) s = Tokenizer('3.0') self.assertEqual(next(s), (p.FLOAT, '3.0', 1, 0)) s = Tokenizer('3.') self.assertEqual(next(s), (p.FLOAT, '3.', 1, 0)) s = Tokenizer('3.E0') self.assertEqual(next(s), (p.FLOAT, '3.E0', 1, 0)) s = Tokenizer('.30') self.assertEqual(next(s), (p.FLOAT, '.30', 1, 0)) # Finished? Then should get a StopIteration self.assertEqual(s.peek()[0], p.EOL) self.assertEqual(next(s)[0], p.EOL) self.assertEqual(s.peek()[0], p.EOF) self.assertEqual(next(s)[0], p.EOF) self.assertRaises(IndexError, s.peek) self.assertRaises(StopIteration, next, s) # Unknown token self.assertRaisesRegex(myokit.ParseError, 'invalid token', Tokenizer, '@') s = Tokenizer('x @') self.assertRaisesRegex(myokit.ParseError, 'invalid token', s.next) # Block-comment s = Tokenizer('"""Hello"""') self.assertEqual(next(s)[0], p.EOF) # Multi-line string s = Tokenizer('x: """Hello\nWo\trld"""') self.assertEqual(next(s)[0], p.META_NAME) self.assertEqual(next(s)[0], p.COLON) self.assertEqual(s.peek()[0], p.TEXT) self.assertEqual(next(s)[1], 'Hello\nWo\trld') self.assertEqual(next(s)[0], p.EOL) s = Tokenizer('x: """Hello"""World') self.assertEqual(next(s)[0], p.META_NAME) self.assertRaisesRegex(myokit.ParseError, 'after closing of multi-line string', next, s) s = Tokenizer('x: """He\nll\no"""World') self.assertEqual(next(s)[0], p.META_NAME) self.assertRaisesRegex(myokit.ParseError, 'after closing of multi-line string', next, s) s = Tokenizer('x: """Hello\n') self.assertEqual(next(s)[0], p.META_NAME) self.assertRaisesRegex(myokit.ParseError, 'Unclosed multi-line', s.next) # Indented is removed from multi-line string s = Tokenizer(' x: """Hello \n world"""') self.assertEqual(next(s)[0], p.META_NAME) self.assertEqual(next(s)[0], p.COLON) self.assertEqual(s.peek()[0], p.TEXT) self.assertEqual(s.peek()[1], 'Hello\nworld') s = Tokenizer('\tx: """Hello \n\tworld"""') self.assertEqual(next(s)[0], p.META_NAME) self.assertEqual(next(s)[0], p.COLON) self.assertEqual(s.peek()[0], p.TEXT) self.assertEqual(s.peek()[1], 'Hello\nworld') # Empty lines s = Tokenizer('\n\n\nx: """Hello\nWorld"""\n\n\n') next(s) next(s) self.assertEqual(next(s)[1], 'Hello\nWorld') s = Tokenizer('\n\n\nx: """Hello\n\n\nWorld"""\n\n\n') next(s) next(s) self.assertEqual(next(s)[1], 'Hello\n\n\nWorld') # Line continuation s = Tokenizer('x: this is \\\nthe value of x') next(s) next(s) self.assertEqual(next(s)[1], 'this is the value of x') self.assertEqual(next(s)[0], p.EOL) s = Tokenizer('x: this is \\\nthe\\\n\n\n value of x') next(s) next(s) self.assertEqual(next(s)[1], 'this is the') self.assertEqual(next(s)[0], p.EOL) s = Tokenizer('x: this is \\\nthe\\\n\n\n value of x') next(s) next(s) self.assertEqual(next(s)[1], 'this is the') self.assertEqual(next(s)[0], p.EOL) # Comment doesn't end line continuation s = Tokenizer('x: this is \\\n#Hi mike\nthe value of x') next(s) next(s) self.assertEqual(next(s)[1], 'this is the value of x') # Line continuation must be last character on line s = Tokenizer('1 + \\3') next(s) self.assertRaisesRegex(myokit.ParseError, ' Backslash must be last', s.next) # But backslash is allowed in text (only seen as line cont. if last) s = Tokenizer('x: Hello\\michael\nHow are you') self.assertEqual(next(s)[0], p.META_NAME) self.assertEqual(next(s)[0], p.COLON) self.assertEqual(next(s)[1], 'Hello\\michael') # Brackets s = Tokenizer('x = (\n1 + \n\n2 + (\n3\n\n)\n\n) + 4') next(s) next(s) from myokit._parsing import parse_expression_stream e = parse_expression_stream(s) self.assertEqual(e.code(), '1 + 2 + 3 + 4') # Mismatched brackets s = Tokenizer('x = (1 + 2') self.assertEqual(next(s)[0], p.NAME) self.assertEqual(next(s)[0], p.EQUAL) self.assertEqual(next(s)[0], p.PAREN_OPEN) self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.PLUS) self.assertRaisesRegex(myokit.ParseError, 'Parentheses mismatch', s.next) s = Tokenizer('x = 1 + 2)') self.assertEqual(next(s)[0], p.NAME) self.assertEqual(next(s)[0], p.EQUAL) self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.PLUS) self.assertRaisesRegex(myokit.ParseError, 'Parentheses mismatch', s.next) # Test indenting (Tabs count as 8 spaces) s = '\n'.join([ '1', ' 2', '\t3', ' 4', '5', ]) s = Tokenizer(s) self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.EOL) self.assertEqual(next(s)[0], p.INDENT) self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.EOL) self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.EOL) self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.EOL) self.assertEqual(next(s)[0], p.DEDENT) self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.EOL) # Test indenting error s = Tokenizer('\n'.join([ '1', ' 2', ' 3', ])) self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.EOL) self.assertEqual(next(s)[0], p.INDENT) self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.EOL) self.assertRaisesRegex(myokit.ParseError, 'Unexpected indenting level', s.next) # Line feed counts as a newline s = Tokenizer('123\f456') self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.EOL) self.assertEqual(next(s)[0], p.INTEGER) self.assertEqual(next(s)[0], p.EOL)