Skip to content

Commit

Permalink
Merge pull request #271 from earlbread/add-numeric-constraints
Browse files Browse the repository at this point in the history
Add numeric-constraints annotation
  • Loading branch information
dahlia committed Aug 17, 2018
2 parents fe79548 + f56cea6 commit f2edb0f
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 8 deletions.
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*]
indent_style = space
indent_size = 4
charset = utf-8
max_line_length = 80

[{*.yml, *.yaml}]
indent_style = space
indent_size = 2
5 changes: 5 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ To be released.

- The `uri` type has completly gone; use `url` instead.
[[#126], [#281] by Jonghun Park]
- Added [`@numeric-constraints`](docs/annotation.md#numeric-constraints)
annotation to constraint the range of unboxed types' values.
[[#206], [#271] by Seunghun Lee]

### Docs target

Expand Down Expand Up @@ -57,7 +60,9 @@ To be released.
(from [spoqa/nirum](https://hub.docker.com/r/spoqa/nirum/)).

[#126]: https://github.com/nirum-lang/nirum/issues/126
[#206]: https://github.com/nirum-lang/nirum/issues/206
[#225]: https://github.com/nirum-lang/nirum/issues/225
[#271]: https://github.com/nirum-lang/nirum/pull/271
[#281]: https://github.com/nirum-lang/nirum/pull/281
[#283]: https://github.com/spoqa/nirum/pull/283
[#297]: https://github.com/nirum-lang/nirum/issues/297
Expand Down
29 changes: 29 additions & 0 deletions docs/annotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,32 @@ class FileNotFound(FileError):
class FileNotReadable(FileError):
...
~~~~~~~~

### `@numeric-constraints` {#numeric-constraints}

`@numeric-constraints` annotation constrains the range of unboxed types' values.
Currently, available annotation arguments are below:

`min`
: Minimum input value; inclusive.

`max`
: Maximum input value; inclusive.

For example, the following first Nirum code is compiled to the second Python
code:

~~~~~~~~ nirum
@numeric-constraints(min=1, max=12)
unboxed month (int32);
~~~~~~~~

~~~~~~~~ python
class Month:
def __init__(self, value: int) -> None:
if not value <= 12:
raise ValueError("value is greater than 12")
if not value >= 1:
raise ValueError("value is less than 1")
...
~~~~~~~~
25 changes: 23 additions & 2 deletions src/Nirum/Targets/Python.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module Nirum.Targets.Python
import Control.Monad (forM)
import Control.Monad.State (modify)
import qualified Data.List as L
import Data.Maybe (catMaybes, fromMaybe)
import Data.Maybe (catMaybes, fromMaybe, isJust)
import GHC.Exts (IsList (toList))

import qualified Data.ByteString.Lazy
Expand Down Expand Up @@ -685,12 +685,33 @@ compileTypeDeclaration src d@TypeDeclaration { typename = typename'
|]
compileTypeDeclaration src d@TypeDeclaration { typename = typename'
, type' = UnboxedType itype
, typeAnnotations = annots
} = do
let className = toClassName' typename'
itypeExpr <- compileTypeExpression' src (Just itype)
insertStandardImport "typing"
pyVer <- getPythonVersion
Validator typePred valueValidators' <- compileValidator' src itype "value"
Validator typePred valueValidatorsProto <-
compileValidator' src itype "value"
valueValidators' <- case A.lookup "numeric-constraints" annots of
Just A.Annotation { A.arguments = args } -> do
let constraintValidators =
[ case (name', value) of
("min", Integer v) ->
Just $ ValueValidator
[qq|value >= ($v)|]
[qq|value is less than $v|]
("max", Integer v) ->
Just $ ValueValidator
[qq|value <= ($v)|]
[qq|value is greater than $v|]
_ -> Nothing
| (name', value) <- toList args
]
if all isJust constraintValidators
then return $ catMaybes constraintValidators
else fail "Unsupported arguments on @numeric-constraints"
Nothing -> return valueValidatorsProto
deserializer <- compileDeserializer' src itype "value" "rv" "on_error"
defaultErrorHandler <- defaultDeserializerErrorHandler
return [compileText|
Expand Down
54 changes: 49 additions & 5 deletions test/Nirum/Targets/PythonSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
module Nirum.Targets.PythonSpec where

import qualified Data.Map.Strict as M
import Data.Either
import System.FilePath ((</>))
import Test.Hspec.Meta

import Nirum.Constructs.Annotation hiding (null)
import Nirum.Constructs.Annotation.Internal
import Nirum.Constructs.Module (Module (Module))
import Nirum.Package.Metadata (Target (compilePackage))
import Nirum.Constructs.TypeDeclaration hiding (Text)
import qualified Nirum.Package.Metadata as M
import Nirum.Targets.Python
( Source (Source)
, parseModulePath
Expand All @@ -19,7 +23,7 @@ spec = do
describe "compilePackage" $ do
it "returns a Map of file paths and their contents to generate" $ do
let (Source pkg _) = makeDummySource $ Module [] Nothing
files = compilePackage pkg
files = M.compilePackage pkg
directoryStructure =
[ "src-py2" </> "foo" </> "__init__.py"
, "src-py2" </> "foo" </> "bar" </> "__init__.py"
Expand All @@ -34,7 +38,7 @@ spec = do
it "creates an emtpy Python package directory if necessary" $ do
let (Source pkg _) = makeDummySource' ["test"] (Module [] Nothing)
[]
files = compilePackage pkg
files = M.compilePackage pkg
directoryStructure =
[ "src-py2" </> "test" </> "__init__.py"
, "src-py2" </> "test" </> "foo" </> "__init__.py"
Expand All @@ -51,7 +55,7 @@ spec = do
it "generates renamed package dirs if renames are configured" $ do
let (Source pkg _) = makeDummySource' [] (Module [] Nothing)
[(["foo"], ["quz"])]
files = compilePackage pkg
files = M.compilePackage pkg
directoryStructure =
[ "src-py2" </> "quz" </> "__init__.py"
, "src-py2" </> "quz" </> "bar" </> "__init__.py"
Expand All @@ -65,7 +69,7 @@ spec = do
M.keysSet files `shouldBe` directoryStructure
let (Source pkg' _) = makeDummySource' [] (Module [] Nothing)
[(["foo", "bar"], ["bar"])]
files' = compilePackage pkg'
files' = M.compilePackage pkg'
directoryStructure' =
[ "src-py2" </> "foo" </> "__init__.py"
, "src-py2" </> "bar" </> "__init__.py"
Expand All @@ -90,3 +94,43 @@ spec = do
parseModulePath "foo..bar" `shouldBe` Nothing
parseModulePath "foo.bar>" `shouldBe` Nothing
parseModulePath "foo.bar-" `shouldBe` Nothing

describe "@numeric-constraints" $ do
it "fails if unsupported arguments are present" $ do
let Right annots = fromList
[ Annotation
"numeric-constraints"
[("min", Integer 1), ("unsupported", Integer 2)]
]
compareErrorMessage annots

it "fails if unsupported arguments type is given" $ do
let Right annots = fromList
[ Annotation
"numeric-constraints"
[("min", Integer 1), ("max", Text "2")]
]
compareErrorMessage annots

it "success" $ do
let Right annots = fromList
[ Annotation
"numeric-constraints"
[("min", Integer 1), ("max", Integer 2)]
]
let Just result = getResult annots
isRight result `shouldBe` True
where
compareErrorMessage annots = do
let Just result = getResult annots
let Left errorMessage = result
errorMessage `shouldBe`
"Unsupported arguments on @numeric-constraints"

getResult annots =
let
unboxed = TypeDeclaration "foo" (UnboxedType "int32") annots
(Source pkg _) = makeDummySource $ Module [unboxed] Nothing
files = M.compilePackage pkg
in
M.lookup ("src" </> "foo" </> "__init__.py") files
2 changes: 2 additions & 0 deletions test/nirum_fixture/fixture/constraints.nrm
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@numeric-constraints(min=1, max=12)
unboxed month (int32);
15 changes: 15 additions & 0 deletions test/python/constraints_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from pytest import raises

from fixture.constraints import Month


def test_numeric_constraints():
month = Month(1)
assert month.value == 1

with raises(ValueError):
Month(0)

with raises(ValueError):
Month(13)
3 changes: 2 additions & 1 deletion test/python/setup_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_setup_metadata():
assert set(pkg['Provides']) == {
'fixture', 'fixture.foo', 'fixture.foo.bar', 'fixture.qux',
'fixture.reserved_keyword_enum', 'fixture.reserved_keyword_union',
'fixture.types', 'fixture.alias',
'fixture.types', 'fixture.alias', 'fixture.constraints',
'renamed', 'renamed.foo', 'renamed.foo.bar',
'fixture.datetime',
'fixture.name',
Expand All @@ -46,6 +46,7 @@ def test_module_entry_points():
'fixture.reserved-keyword-enum', 'fixture.reserved-keyword-union',
'fixture.types',
'fixture.alias',
'fixture.constraints',
'renames.test.foo', 'renames.test.foo.bar',
'fixture.datetime',
'fixture.name',
Expand Down

0 comments on commit f2edb0f

Please sign in to comment.