Repository: vyperlang/vyper

Issue:
Add pragma tag
### What's your issue about?
We should add a simple pragma tag. That the current compiler will compare to, and warn the user if the version is not the same.

Format:
```
# pragma: x.y.z
```

Change the severity of the warning based on:
Given version format: x.y.z

z -> Patch, minor changes. As long x.y is the same, should compile the same.
x.y -> major changes, will most likely not compile.

### How can it be fixed?
Also discussed at length in https://github.com/ethereum/vyper/issues/456.

#### Cute Animal Picture
![](https://media.giphy.com/media/11JfYxBmCpQDde/giphy.gif)



Discussion:
usually in semantic version aka `[major].[minor].[patch]` "minor" should not be backwards incompatible (although sometimes it can be, depending on the fixes applied), so I would modify that statement a bit.
Also, maybe we can do something like `# @version x.y.z` to do it more natspec-style?
Sure that is an option
Settled on:
```
# @version x.y.z
```

```
# @version >=x.y.z
```
@fubuloubu I was thinking on this some more. 
Perhaps using,

```
version: '0.0.4'
```

Aligns better with what we do with our other syntax?

For example units defined as:
```
units: {...}
```
What do you think?

I am proceeding with the decided one for now.
Hmm, it's not bad I suppose. I think these extra variable-looking things are a bit opaque as to why they're there like that (but clear as to their intent).

Could they both be a bit more pythonic with the underbars?
```python
__version__: '>=0.0.4'
__units__: { ... }
```

Resolving patch:
diff --git a/vyper/__init__.py b/vyper/__init__.py
--- a/vyper/__init__.py
+++ b/vyper/__init__.py
@@ -10,4 +10,4 @@
 try:
     __version__ = pkg_resources.get_distribution('vyper').version
 except pkg_resources.DistributionNotFound:
-    __version__ = 'development'
+    __version__ = '0.0.0development'
diff --git a/vyper/parser/parser.py b/vyper/parser/parser.py
--- a/vyper/parser/parser.py
+++ b/vyper/parser/parser.py
@@ -1,8 +1,14 @@
 import ast
 import tokenize
 import io
+import re
 
-from tokenize import OP, NAME, TokenInfo
+from tokenize import (
+    OP,
+    NAME,
+    TokenInfo,
+    COMMENT
+)
 
 from vyper.exceptions import (
     InvalidLiteralException,
@@ -61,6 +67,7 @@
     calc_mem_gas,
     is_varname_valid,
 )
+from vyper import __version__
 
 
 if not hasattr(ast, 'AnnAssign'):
@@ -84,6 +91,8 @@ def pre_parser(code):
     for token in g:
 
         # Alias contract definition to class definition.
+        if token.type == COMMENT and "@version" in token.string:
+            parse_version_pragma(token.string[1:])
         if (token.type, token.string, token.start[1]) == (NAME, "contract", 0):
             token = TokenInfo(token.type, "class", token.start, token.end, token.line)
         # Prevent semi-colon line statements.
@@ -94,6 +103,27 @@ def pre_parser(code):
     return tokenize.untokenize(result).decode('utf-8')
 
 
+def _parser_version_str(version_str):
+    version_regex = re.compile(r'^(\d+\.)?(\d+\.)?(\w*)$')
+    if None in version_regex.match(version_str).groups():
+        raise Exception('Could not parse given version: %s' % version_str)
+    return version_regex.match(version_str).groups()
+
+
+# Do a version check.
+def parse_version_pragma(version_str):
+    version_arr = version_str.split('@version')
+
+    file_version = version_arr[1].strip()
+    file_major, file_minor, file_patch = _parser_version_str(file_version)
+    compiler_major, compiler_minor, compiler_patch = _parser_version_str(__version__)
+
+    if (file_major, file_minor) != (compiler_major, compiler_minor):
+        raise Exception('Given version "{}" is not compatible with the compiler ({}): '.format(
+            file_version, __version__
+        ))
+
+
 # Parser for a single line
 def parse_line(code):
     o = ast.parse(code).body[0]


Test patch:
diff --git a/tests/compiler/test_pre_parser.py b/tests/compiler/test_pre_parser.py
--- a/tests/compiler/test_pre_parser.py
+++ b/tests/compiler/test_pre_parser.py
@@ -52,3 +52,47 @@ def foo(contract_address: address) -> int128:
     c1 = get_contract(contract_1)
     c2 = get_contract(contract_2)
     assert c2.foo(c1.address) == 1
+
+
+def test_version_pragma(get_contract):
+    code = """
+# @version 0.0.4
+
+@public
+def test():
+    pass
+    """
+    assert get_contract(code)
+
+
+def test_version_empty_version(assert_compile_failed, get_contract):
+    code = """
+#@version
+
+@public
+def test():
+    pass
+    """
+    assert_compile_failed(lambda: get_contract(code))
+
+
+def test_version_empty_version_mismatch(assert_compile_failed, get_contract):
+    code = """
+# @version 9.9.9
+
+@public
+def test():
+    pass
+    """
+    assert_compile_failed(lambda: get_contract(code))
+
+
+def test_version_empty_invalid_version_string(assert_compile_failed, get_contract):
+    code = """
+# @version hello
+
+@public
+def test():
+    pass
+    """
+    assert_compile_failed(lambda: get_contract(code))