Skip to content

Commit 9da46dc

Browse files
authored
Add a Persistent Queue data type (#606)
* Adds a Persistent Queue data structure, which `conj`s at the tail but `pop`s from the head. * Convert all builtin collection types to use a single `EMPTY` constant for `empty()`, which should hopefully reduce the amount of time spent allocating new empty collections for such calls.
1 parent 0f7bfc9 commit 9da46dc

File tree

17 files changed

+388
-18
lines changed

17 files changed

+388
-18
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
* Added default reader features for matching Python version ranges (`:lpy36+`, `:lpy38-`, etc.) (#593)
1616
* Added `lazy-cat` function for lazily concatenating sequences (#588)
1717
* Added support for writing EDN strings from `basilisp.edn` (#600)
18+
* Added a persistent queue data type (#606)
1819

1920
### Changed
2021
* Moved `basilisp.lang.runtime.to_seq` to `basilisp.lang.seq` so it can be used within that module and by `basilisp.lang.runtime` without circular import (#588)

src/basilisp/lang/compiler/analyzer.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from basilisp.lang import keyword as kw
3636
from basilisp.lang import list as llist
3737
from basilisp.lang import map as lmap
38+
from basilisp.lang import queue as lqueue
3839
from basilisp.lang import reader as reader
3940
from basilisp.lang import runtime as runtime
4041
from basilisp.lang import set as lset
@@ -114,12 +115,9 @@
114115
PyList,
115116
PySet,
116117
PyTuple,
117-
Quote,
118-
Recur,
119-
Reify,
120-
Require,
121-
RequireAlias,
122118
)
119+
from basilisp.lang.compiler.nodes import Queue as QueueNode
120+
from basilisp.lang.compiler.nodes import Quote, Recur, Reify, Require, RequireAlias
123121
from basilisp.lang.compiler.nodes import Set as SetNode
124122
from basilisp.lang.compiler.nodes import SetBang, SpecialFormNode, Throw, Try, VarRef
125123
from basilisp.lang.compiler.nodes import Vector as VectorNode
@@ -3339,6 +3337,25 @@ def _map_node_or_quoted(
33393337
return _map_node(form, ctx)
33403338

33413339

3340+
@_with_meta
3341+
def _queue_node(form: lqueue.PersistentQueue, ctx: AnalyzerContext) -> QueueNode:
3342+
return QueueNode(
3343+
form=form,
3344+
items=vec.vector(map(lambda form: _analyze_form(form, ctx), form)),
3345+
env=ctx.get_node_env(pos=ctx.syntax_position),
3346+
)
3347+
3348+
3349+
@_analyze_form.register(lqueue.PersistentQueue)
3350+
@_with_loc
3351+
def _queue_node_or_quoted(
3352+
form: lqueue.PersistentQueue, ctx: AnalyzerContext
3353+
) -> Union[Const, QueueNode]:
3354+
if ctx.is_quoted:
3355+
return _const_node(form, ctx)
3356+
return _queue_node(form, ctx)
3357+
3358+
33423359
@_with_meta
33433360
def _set_node(form: lset.PersistentSet, ctx: AnalyzerContext) -> SetNode:
33443361
return SetNode(
@@ -3395,6 +3412,7 @@ def _const_node_type(_: Any) -> ConstType:
33953412
list: ConstType.PY_LIST,
33963413
llist.PersistentList: ConstType.SEQ,
33973414
lmap.PersistentMap: ConstType.MAP,
3415+
lqueue.PersistentQueue: ConstType.QUEUE,
33983416
lset.PersistentSet: ConstType.SET,
33993417
IRecord: ConstType.RECORD,
34003418
ISeq: ConstType.SEQ,
@@ -3438,6 +3456,7 @@ def _const_node(form: ReaderForm, ctx: AnalyzerContext) -> Const:
34383456
vec.PersistentVector,
34393457
llist.PersistentList,
34403458
lmap.PersistentMap,
3459+
lqueue.PersistentQueue,
34413460
lset.PersistentSet,
34423461
ISeq,
34433462
),

src/basilisp/lang/compiler/generator.py

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from basilisp.lang import keyword as kw
3232
from basilisp.lang import list as llist
3333
from basilisp.lang import map as lmap
34+
from basilisp.lang import queue as lqueue
3435
from basilisp.lang import reader as reader
3536
from basilisp.lang import runtime as runtime
3637
from basilisp.lang import set as lset
@@ -86,12 +87,9 @@
8687
PyList,
8788
PySet,
8889
PyTuple,
89-
Quote,
90-
ReaderLispForm,
91-
Recur,
92-
Reify,
93-
Require,
9490
)
91+
from basilisp.lang.compiler.nodes import Queue as QueueNode
92+
from basilisp.lang.compiler.nodes import Quote, ReaderLispForm, Recur, Reify, Require
9593
from basilisp.lang.compiler.nodes import Set as SetNode
9694
from basilisp.lang.compiler.nodes import SetBang, Throw, Try, VarRef
9795
from basilisp.lang.compiler.nodes import Vector as VectorNode
@@ -591,6 +589,7 @@ def _var_ns_as_python_sym(name: str) -> str:
591589
_MAP_ALIAS = genname("lmap")
592590
_MULTIFN_ALIAS = genname("multifn")
593591
_PROMISE_ALIAS = genname("promise")
592+
_QUEUE_ALIAS = genname("queue")
594593
_READER_ALIAS = genname("reader")
595594
_RUNTIME_ALIAS = genname("runtime")
596595
_SEQ_ALIAS = genname("seq")
@@ -613,6 +612,7 @@ def _var_ns_as_python_sym(name: str) -> str:
613612
"basilisp.lang.map": _MAP_ALIAS,
614613
"basilisp.lang.multifn": _MULTIFN_ALIAS,
615614
"basilisp.lang.promise": _PROMISE_ALIAS,
615+
"basilisp.lang.queue": _QUEUE_ALIAS,
616616
"basilisp.lang.reader": _READER_ALIAS,
617617
"basilisp.lang.runtime": _RUNTIME_ALIAS,
618618
"basilisp.lang.seq": _SEQ_ALIAS,
@@ -631,6 +631,7 @@ def _var_ns_as_python_sym(name: str) -> str:
631631
_NEW_KW_FN_NAME = _load_attr(f"{_KW_ALIAS}.keyword_from_hash")
632632
_NEW_LIST_FN_NAME = _load_attr(f"{_LIST_ALIAS}.list")
633633
_EMPTY_LIST_FN_NAME = _load_attr(f"{_LIST_ALIAS}.List.empty")
634+
_NEW_QUEUE_FN_NAME = _load_attr(f"{_QUEUE_ALIAS}.queue")
634635
_NEW_MAP_FN_NAME = _load_attr(f"{_MAP_ALIAS}.map")
635636
_NEW_REGEX_FN_NAME = _load_attr(f"{_UTIL_ALIAS}.regex_from_str")
636637
_NEW_SET_FN_NAME = _load_attr(f"{_SET_ALIAS}.set")
@@ -2975,6 +2976,35 @@ def _map_to_py_ast(
29752976
)
29762977

29772978

2979+
@_with_ast_loc
2980+
def _queue_to_py_ast(
2981+
ctx: GeneratorContext, node: QueueNode, meta_node: Optional[MetaNode] = None
2982+
) -> GeneratedPyAST:
2983+
assert node.op == NodeOp.QUEUE
2984+
2985+
meta_ast: Optional[GeneratedPyAST]
2986+
if meta_node is not None:
2987+
meta_ast = gen_py_ast(ctx, meta_node)
2988+
else:
2989+
meta_ast = None
2990+
2991+
elem_deps, elems = _chain_py_ast(*map(partial(gen_py_ast, ctx), node.items))
2992+
return GeneratedPyAST(
2993+
node=ast.Call(
2994+
func=_NEW_QUEUE_FN_NAME,
2995+
args=[ast.List(list(elems), ast.Load())],
2996+
keywords=Maybe(meta_ast)
2997+
.map(lambda p: [ast.keyword(arg="meta", value=p.node)])
2998+
.or_else_get([]),
2999+
),
3000+
dependencies=list(
3001+
chain(
3002+
Maybe(meta_ast).map(lambda p: p.dependencies).or_else_get([]), elem_deps
3003+
)
3004+
),
3005+
)
3006+
3007+
29783008
@_with_ast_loc
29793009
def _set_to_py_ast(
29803010
ctx: GeneratorContext, node: SetNode, meta_node: Optional[MetaNode] = None
@@ -3087,6 +3117,7 @@ def _py_tuple_to_py_ast(ctx: GeneratorContext, node: PyTuple) -> GeneratedPyAST:
30873117
_WITH_META_EXPR_HANDLER = {
30883118
NodeOp.FN: _fn_to_py_ast,
30893119
NodeOp.MAP: _map_to_py_ast,
3120+
NodeOp.QUEUE: _queue_to_py_ast,
30903121
NodeOp.REIFY: _reify_to_py_ast,
30913122
NodeOp.SET: _set_to_py_ast,
30923123
NodeOp.VECTOR: _vec_to_py_ast,
@@ -3289,6 +3320,24 @@ def _const_map_to_py_ast(
32893320
)
32903321

32913322

3323+
@_const_val_to_py_ast.register(lqueue.PersistentQueue)
3324+
def _const_queue_to_py_ast(
3325+
form: lqueue.PersistentQueue, ctx: GeneratorContext
3326+
) -> GeneratedPyAST:
3327+
elem_deps, elems = _chain_py_ast(*_collection_literal_to_py_ast(ctx, form))
3328+
meta = _const_meta_kwargs_ast(ctx, form)
3329+
return GeneratedPyAST(
3330+
node=ast.Call(
3331+
func=_NEW_QUEUE_FN_NAME,
3332+
args=[ast.List(list(elems), ast.Load())],
3333+
keywords=Maybe(meta).map(lambda p: [p.node]).or_else_get([]),
3334+
),
3335+
dependencies=list(
3336+
chain(elem_deps, Maybe(meta).map(lambda p: p.dependencies).or_else_get([]))
3337+
),
3338+
)
3339+
3340+
32923341
@_const_val_to_py_ast.register(lset.PersistentSet)
32933342
def _const_set_to_py_ast(
32943343
form: lset.PersistentSet, ctx: GeneratorContext
@@ -3447,6 +3496,7 @@ def _const_node_to_py_ast(ctx: GeneratorContext, lisp_ast: Const) -> GeneratedPy
34473496
NodeOp.PY_LIST: _py_list_to_py_ast,
34483497
NodeOp.PY_SET: _py_set_to_py_ast,
34493498
NodeOp.PY_TUPLE: _py_tuple_to_py_ast,
3499+
NodeOp.QUEUE: _queue_to_py_ast,
34503500
NodeOp.QUOTE: _quote_to_py_ast,
34513501
NodeOp.RECUR: _recur_to_py_ast, # type: ignore
34523502
NodeOp.REIFY: _reify_to_py_ast,

src/basilisp/lang/compiler/nodes.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from basilisp.lang import keyword as kw
1919
from basilisp.lang import map as lmap
20+
from basilisp.lang import queue as lqueue
2021
from basilisp.lang import set as lset
2122
from basilisp.lang import symbol as sym
2223
from basilisp.lang import vector as vec
@@ -90,6 +91,7 @@ class NodeOp(Enum):
9091
PY_LIST = kw.keyword("py-list")
9192
PY_SET = kw.keyword("py-set")
9293
PY_TUPLE = kw.keyword("py-tuple")
94+
QUEUE = kw.keyword("queue")
9395
QUOTE = kw.keyword("quote")
9496
RECUR = kw.keyword("recur")
9597
REIFY = kw.keyword("reify")
@@ -259,6 +261,7 @@ class NodeSyntacticPosition(Enum):
259261
class ConstType(Enum):
260262
NIL = kw.Keyword("nil")
261263
MAP = kw.keyword("map")
264+
QUEUE = kw.keyword("queue")
262265
SET = kw.keyword("set")
263266
VECTOR = kw.keyword("vector")
264267
BOOL = kw.keyword("bool")
@@ -774,6 +777,17 @@ class PyTuple(Node[tuple]):
774777
raw_forms: IPersistentVector[LispForm] = vec.PersistentVector.empty()
775778

776779

780+
@attr.s(auto_attribs=True, frozen=True, slots=True)
781+
class Queue(Node[lqueue.PersistentQueue]):
782+
form: lqueue.PersistentQueue
783+
items: Iterable[Node]
784+
env: NodeEnv
785+
children: Sequence[kw.Keyword] = vec.v(ITEMS)
786+
op: NodeOp = NodeOp.QUEUE
787+
top_level: bool = False
788+
raw_forms: IPersistentVector[LispForm] = vec.PersistentVector.empty()
789+
790+
777791
@attr.s(auto_attribs=True, frozen=True, slots=True)
778792
class Quote(Node[SpecialForm]):
779793
form: SpecialForm
@@ -918,7 +932,7 @@ class Vector(Node[IPersistentVector]):
918932
class WithMeta(Node[LispForm]):
919933
form: LispForm
920934
meta: Union[Const, Map]
921-
expr: Union[Fn, Map, Set, Vector]
935+
expr: Union[Fn, Map, Queue, Set, Vector]
922936
env: NodeEnv
923937
children: Sequence[kw.Keyword] = vec.v(META, EXPR)
924938
op: NodeOp = NodeOp.WITH_META

src/basilisp/lang/list.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from basilisp.lang.interfaces import IPersistentList, IPersistentMap, ISeq, IWithMeta
77
from basilisp.lang.obj import seq_lrepr as _seq_lrepr
8-
from basilisp.lang.seq import EMPTY
8+
from basilisp.lang.seq import EMPTY as _EMPTY_SEQ
99

1010
T = TypeVar("T")
1111

@@ -60,7 +60,7 @@ def first(self):
6060
@property
6161
def rest(self) -> ISeq[T]:
6262
if self._inner.rest is _EMPTY_PLIST:
63-
return EMPTY
63+
return _EMPTY_SEQ
6464
return PersistentList(self._inner.rest)
6565

6666
def cons(self, *elems: T) -> "PersistentList[T]":
@@ -70,8 +70,8 @@ def cons(self, *elems: T) -> "PersistentList[T]":
7070
return PersistentList(l)
7171

7272
@staticmethod
73-
def empty(meta=None) -> "PersistentList": # pylint:disable=arguments-differ
74-
return l(meta=meta)
73+
def empty() -> "PersistentList":
74+
return EMPTY
7575

7676
def peek(self):
7777
return self.first
@@ -82,6 +82,9 @@ def pop(self) -> "PersistentList[T]":
8282
return cast(PersistentList, self.rest)
8383

8484

85+
EMPTY: PersistentList = PersistentList(plist())
86+
87+
8588
def list(members, meta=None) -> PersistentList: # pylint:disable=redefined-builtin
8689
"""Creates a new list."""
8790
return PersistentList( # pylint: disable=abstract-class-instantiated

src/basilisp/lang/map.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def cons( # type: ignore[override]
244244

245245
@staticmethod
246246
def empty() -> "PersistentMap":
247-
return m()
247+
return EMPTY
248248

249249
def seq(self) -> ISeq[IMapEntry[K, V]]:
250250
return sequence(MapEntry.of(k, v) for k, v in self._inner.items())
@@ -253,6 +253,9 @@ def to_transient(self) -> TransientMap[K, V]:
253253
return TransientMap(self._inner.mutate())
254254

255255

256+
EMPTY: PersistentMap = PersistentMap.from_coll(())
257+
258+
256259
def map( # pylint:disable=redefined-builtin
257260
kvs: Mapping[K, V], meta: Optional[IPersistentMap] = None
258261
) -> PersistentMap[K, V]:

0 commit comments

Comments
 (0)