Skip to content

Commit

Permalink
Add set literals (closes #827)
Browse files Browse the repository at this point in the history
  • Loading branch information
refi64 authored and berkerpeksag committed Jul 14, 2015
1 parent 920c801 commit c94c0e8
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 2 deletions.
2 changes: 2 additions & 0 deletions docs/tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ Hy. Let's experiment with this in the hy interpreter::
{'dog': 'bark', 'cat': 'meow'}
=> (, 1 2 3)
(1, 2, 3)
=> #{3 1 2}
{1, 2, 3}

If you are familiar with other Lisps, you may be interested that Hy
supports the Common Lisp method of quoting:
Expand Down
1 change: 1 addition & 0 deletions hy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from hy.models.float import HyFloat # NOQA
from hy.models.dict import HyDict # NOQA
from hy.models.list import HyList # NOQA
from hy.models.set import HySet # NOQA
from hy.models.cons import HyCons # NOQA


Expand Down
28 changes: 27 additions & 1 deletion hy/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from hy.models.symbol import HySymbol
from hy.models.float import HyFloat
from hy.models.list import HyList
from hy.models.set import HySet
from hy.models.dict import HyDict
from hy.models.cons import HyCons

Expand Down Expand Up @@ -628,7 +629,7 @@ def _render_quoted_form(self, form, level):
name = form.__class__.__name__
imports = set([name])

if isinstance(form, (HyList, HyDict)):
if isinstance(form, (HyList, HyDict, HySet)):
if not form:
contents = HyList()
else:
Expand Down Expand Up @@ -1974,6 +1975,31 @@ def compile_list(self, expression):
col_offset=expression.start_column)
return ret

@builds(HySet)
def compile_set(self, expression):
elts, ret, _ = self._compile_collect(expression)
if PY27:
ret += ast.Set(elts=elts,
ctx=ast.Load(),
lineno=expression.start_line,
col_offset=expression.start_column)
else:
ret += ast.Call(func=ast.Name(id='set',
ctx=ast.Load(),
lineno=expression.start_line,
col_offset=expression.start_column),
args=[
ast.List(elts=elts,
ctx=ast.Load(),
lineno=expression.start_line,
col_offset=expression.start_column)],
keywords=[],
starargs=None,
kwargs=None,
lineno=expression.start_line,
col_offset=expression.start_column)
return ret

@builds("lambda")
@builds("fn")
@checkargs(min=1)
Expand Down
3 changes: 2 additions & 1 deletion hy/lex/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@
lg.add('RBRACKET', r'\]')
lg.add('LCURLY', r'\{')
lg.add('RCURLY', r'\}')
lg.add('HLCURLY', r'#\{')
lg.add('QUOTE', r'\'%s' % end_quote)
lg.add('QUASIQUOTE', r'`%s' % end_quote)
lg.add('UNQUOTESPLICE', r'~@%s' % end_quote)
lg.add('UNQUOTE', r'~%s' % end_quote)
lg.add('HASHBANG', r'#!.*[^\r\n]')
lg.add('HASHREADER', r'#.')
lg.add('HASHREADER', r'#[^{]')

# A regexp which matches incomplete strings, used to support
# multi-line strings in the interpreter
Expand Down
14 changes: 14 additions & 0 deletions hy/lex/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from hy.models.integer import HyInteger
from hy.models.keyword import HyKeyword
from hy.models.list import HyList
from hy.models.set import HySet
from hy.models.string import HyString
from hy.models.symbol import HySymbol

Expand Down Expand Up @@ -152,6 +153,7 @@ def list_contents_single(p):
@pg.production("term : paren")
@pg.production("term : dict")
@pg.production("term : list")
@pg.production("term : set")
@pg.production("term : string")
def term(p):
return p[0]
Expand Down Expand Up @@ -190,6 +192,18 @@ def hash_reader(p):
return HyExpression([HySymbol("dispatch_reader_macro"), str_object, expr])


@pg.production("set : HLCURLY list_contents RCURLY")
@set_boundaries
def t_set(p):
return HySet(p[1])


@pg.production("set : HLCURLY RCURLY")
@set_boundaries
def empty_set(p):
return HySet([])


@pg.production("dict : LCURLY list_contents RCURLY")
@set_boundaries
def t_dict(p):
Expand Down
36 changes: 36 additions & 0 deletions hy/models/set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright (c) 2013 Paul Tagliamonte <paultag@debian.org>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

from hy.models.list import HyList
from functools import reduce


class HySet(HyList):
"""
Hy set (actually a list that pretends to be a set)
"""

def __init__(self, items):
items = sorted(items)
items = list(reduce(lambda r, v: v in r and r or r+[v], items, []))
super(HySet, self).__init__(items)

def __repr__(self):
return "#{%s}" % (" ".join([repr(x) for x in self]))
16 changes: 16 additions & 0 deletions tests/lex/test_lex.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from hy.models.string import HyString
from hy.models.dict import HyDict
from hy.models.list import HyList
from hy.models.set import HySet
from hy.models.cons import HyCons

from hy.lex import LexException, PrematureEndOfInput, tokenize
Expand Down Expand Up @@ -202,6 +203,21 @@ def test_dicts():
])]


def test_sets():
""" Ensure that we can tokenize a set. """
objs = tokenize("#{1 2}")
assert objs == [HySet([HyInteger(1), HyInteger(2)])]
objs = tokenize("(bar #{foo bar baz})")
assert objs == [HyExpression([HySymbol("bar"),
HySet(["foo", "bar", "baz"])])]

objs = tokenize("#{(foo bar) (baz quux)}")
assert objs == [HySet([
HyExpression([HySymbol("foo"), HySymbol("bar")]),
HyExpression([HySymbol("baz"), HySymbol("quux")])
])]


def test_nospace():
""" Ensure we can tokenize without spaces if we have to """
entry = tokenize("(foo(one two))")[0]
Expand Down
8 changes: 8 additions & 0 deletions tests/models/test_set.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from hy.models.set import HySet


hyset = HySet([3, 1, 2, 2])


def test_set():
assert hyset == [1, 2, 3]
6 changes: 6 additions & 0 deletions tests/native_tests/language.hy
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
(assert (= {1 2 3 4} {1 (+ 1 1) 3 (+ 2 2)})))


(defn test-sets []
"NATIVE: test sets work right"
(assert (= #{1 2 3 4} (| #{1 2} #{3 4})))
(assert (= #{} (set))))


(defn test-setv-get []
"NATIVE: test setv works on a get expression"
(setv foo [0 1 2])
Expand Down

0 comments on commit c94c0e8

Please sign in to comment.