Skip to content

Commit

Permalink
Merge pull request #33 from jmanuel1/stubs
Browse files Browse the repository at this point in the history
Stubs
  • Loading branch information
jmanuel1 authored Oct 12, 2024
2 parents f04a81c + 55706b5 commit 6f4ab9e
Show file tree
Hide file tree
Showing 45 changed files with 4,286 additions and 2,448 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ jobs:
uses: actions/setup-python@v3
with:
python-version: "3.7"
- name: Install development dependencies
- name: Install test dependencies
run: |
pip install -e .[dev]
pip install -e .[test]
- name: Test
run: |
tox run
coverage run -m nose2 --pretty-assert concat.tests
- name: Collect coverage into one file
run: |
coverage combine
coverage xml
coverage lcov
- name: Report test coverage to DeepSource
uses: deepsourcelabs/test-coverage-action@master
with:
Expand Down
49 changes: 20 additions & 29 deletions concat/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@

import argparse
from concat.transpile import parse, transpile_ast, typecheck
import concat.astutils
from concat.error_reporting import get_line_at, create_parsing_failure_message
import concat.execute
import concat.lex
import concat.parser_combinators
import concat.stdlib.repl
import concat.typecheck
import io
import json
import os.path
import sys
import textwrap
from typing import Callable, IO, AnyStr, Sequence, TextIO
from typing import Callable, IO, AnyStr


filename = '<stdin>'
Expand All @@ -31,29 +29,6 @@ def func(name: str) -> IO[AnyStr]:
return func


def get_line_at(file: TextIO, location: concat.astutils.Location) -> str:
file.seek(0, io.SEEK_SET)
lines = [*file]
return lines[location[0] - 1]


def create_parsing_failure_message(
file: TextIO,
stream: Sequence[concat.lex.Token],
failure: concat.parser_combinators.FailureTree,
) -> str:
location = stream[failure.furthest_index].start
line = get_line_at(file, location)
message = f'Expected {failure.expected} at line {location[0]}, column {location[1] + 1}:\n{line.rstrip()}\n{" " * location[1] + "^"}'
if failure.children:
message += '\nbecause:'
for f in failure.children:
message += '\n' + textwrap.indent(
create_parsing_failure_message(file, stream, f), ' '
)
return message


arg_parser = argparse.ArgumentParser(description='Run a Concat program.')
arg_parser.add_argument(
'file',
Expand All @@ -68,6 +43,12 @@ def create_parsing_failure_message(
default=False,
help='turn stack debugging on',
)
arg_parser.add_argument(
'--verbose',
action='store_true',
default=False,
help='print internal logs and errors',
)
arg_parser.add_argument(
'--tokenize',
action='store_true',
Expand Down Expand Up @@ -103,11 +84,21 @@ def create_parsing_failure_message(
typecheck(concat_ast, source_dir)
python_ast = transpile_ast(concat_ast)
except concat.typecheck.StaticAnalysisError as e:
print('Static Analysis Error:\n')
if e.path is None:
in_path = ''
else:
in_path = ' in file ' + str(e.path)
print(f'Static Analysis Error{in_path}:\n')
print(e, 'in line:')
if e.location:
print(get_line_at(args.file, e.location), end='')
if e.path is not None:
with e.path.open() as f:
print(get_line_at(f, e.location), end='')
else:
print(get_line_at(args.file, e.location), end='')
print(' ' * e.location[1] + '^')
if args.verbose:
raise
except concat.parser_combinators.ParseError as e:
print('Parse Error:')
print(
Expand Down
28 changes: 28 additions & 0 deletions concat/error_reporting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import concat.astutils
import concat.parser_combinators
import io
import textwrap
from typing import Sequence, TextIO


def get_line_at(file: TextIO, location: concat.astutils.Location) -> str:
file.seek(0, io.SEEK_SET)
lines = [*file]
return lines[location[0] - 1]


def create_parsing_failure_message(
file: TextIO,
stream: Sequence[concat.lex.Token],
failure: concat.parser_combinators.FailureTree,
) -> str:
location = stream[failure.furthest_index].start
line = get_line_at(file, location)
message = f'Expected {failure.expected} at line {location[0]}, column {location[1] + 1}:\n{line.rstrip()}\n{" " * location[1] + "^"}'
if failure.children:
message += '\nbecause:'
for f in failure.children:
message += '\n' + textwrap.indent(
create_parsing_failure_message(file, stream, f), ' '
)
return message
8 changes: 6 additions & 2 deletions concat/examples/continuation.cat
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ from concat.stdlib.continuation import ContinuationMonad
from concat.stdlib.continuation import call_with_current_continuation
from concat.stdlib.continuation import eval_cont
from concat.stdlib.continuation import cont_pure
from concat.stdlib.continuation import map_cont
from concat.stdlib.continuation import bind_cont
from concat.stdlib.continuation import cont_from_cps
from concat.stdlib.pyinterop import to_dict
# from concat.stdlib.execution import loop # FIXME: This line should cause an error when there's no @@types in that module


def abort(k:forall *s. (*s x:`a -- *s n:none) -- n:none):
Expand All @@ -21,10 +21,14 @@ def abort(k:forall *s. (*s x:`a -- *s n:none) -- n:none):
# arguments.


def ignore_int(i:int -- n:none):
drop None


def ten_times(k:forall *s. (*s i:int -- *s c:ContinuationMonad[none, int]) -- c:ContinuationMonad[none, none]):
# k
0 $(k:forall *s. (*s i:int -- *s c:ContinuationMonad[none, int]) n:int:
1 + dup pick call eval_cont drop dup 10 <
1 + dup pick call $:~ignore_int map_cont eval_cont drop dup 10 <
) loop drop drop
$:~abort cont_from_cps

Expand Down
3 changes: 2 additions & 1 deletion concat/examples/strstr.cat
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def simple_str(obj:object -- string:str):
None swap None swap to_str

def strstr(haystack:str needle:str -- index:int):
[(), None, None] None to_dict swap pick$.find py_call cast (int) nip
# FIXME: These casts should be unnecessary
[(), None, None] None to_dict swap pick cast (str) $.find py_call cast (int) nip

get_input get_input strstr simple_str put_output
1 change: 0 additions & 1 deletion concat/examples/symmetric_tree.cat
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
# Tree input format: [<left subtree>, <root>, <right subtree>]
# None is the empty tree

from builtins import eval
from concat.stdlib.pyinterop import getitem
from concat.stdlib.pyinterop import to_str
from concat.stdlib.pyinterop import to_dict
Expand Down
55 changes: 55 additions & 0 deletions concat/kind-poly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Kind Polymorphism

* Will probably want kind variables in the future
* Introduce a kind `Kind` at that point
* And kind aliases, too

Probably, the easiest migration is to take the individual variable syntax:

```
`t
```

And use it to mean item-kinded variables instead.

## Prior art

* [Adding kind-polymorphism to the Scala programming language](https://codesync.global/media/adding-kind-polymorphism-to-the-scala-programming-language/)
* [`AnyKind`](https://www.scala-lang.org/api/current/scala/AnyKind.html)

## Kinds

* (?) Item: the kind of types of stack items
* Just use Individual?
* I use kinds for arity checking too, so I think that would make it harder
* Needed to exclude Sequence from kind-polymorphic type parameters where a
Sequence is invalid, e.g:
* `def drop[t : Item](*s t -- *s)` (possible syntax)
* Individual: the kind of zero-arity types of values
* Generic: type constructors
* they have arities
* they always construct an individual type
* I should change this
* they can be the type of a value, e.g. polymorphic functions
* Sequence: stack types

### Subkinding

```
Item
/ \
Individual Generic
Sequence
```

## Syntax

* `def drop[t : Item](*s t -- *s)`
* First thing I thought of
* Looks more natural to me
* Similar to type parameter syntax for classes
* `def drop(forall (t : Item). (*s t -- *s)):`
* Uses existing forall syntax, but extended
* Opens the door to allowing any type syntax as a type annotation
2 changes: 2 additions & 0 deletions concat/lex.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ def _tokens(self) -> Iterator['Token']:
continue
elif tok.value == '$':
tok.type = 'DOLLARSIGN'
elif tok.value == '!':
tok.type = 'EXCLAMATIONMARK'
elif tok.value in {'def', 'import', 'from'}:
tok.type = tok.value.upper()
tok.is_keyword = True
Expand Down
116 changes: 116 additions & 0 deletions concat/linked_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from typing import (
Callable,
Iterable,
Iterator,
List,
Optional,
Sequence,
Tuple,
TypeVar,
Union,
overload,
)
from typing_extensions import Never

_T_co = TypeVar('_T_co', covariant=True)
_T = TypeVar('_T')


class LinkedList(Sequence[_T_co]):
def __init__(
self, _val: Optional[Tuple[_T_co, 'LinkedList[_T_co]']]
) -> None:
self._val = _val
self._length: Optional[int] = None

@classmethod
def from_iterable(cls, iterable: Iterable[_T_co]) -> 'LinkedList[_T_co]':
if isinstance(iterable, cls):
return iterable
l: LinkedList[_T_co] = cls(None)
head = l
for el in iterable:
next_node = cls(None)
l._val = (el, next_node)
l = next_node
return head

@overload
def __getitem__(self, i: int) -> _T_co:
pass

@overload
def __getitem__(self, i: slice) -> 'LinkedList[_T_co]':
pass

def __getitem__(
self, i: Union[slice, int]
) -> Union['LinkedList[_T_co]', _T_co]:
if isinstance(i, slice):
raise NotImplementedError
for _ in range(i):
if self._val is None:
raise IndexError
self = self._val[1]
if self._val is None:
raise IndexError
return self._val[0]

def __len__(self) -> int:
if self._length is None:
node = self
length = 0
while node._val is not None:
node = node._val[1]
length += 1
self._length = length
return self._length

def __add__(self, other: 'LinkedList[_T_co]') -> 'LinkedList[_T_co]':
if not isinstance(other, LinkedList):
return NotImplemented
for el in reversed(list(self)):
other = LinkedList((el, other))
return other

def filter(self, p: Callable[[_T_co], bool]) -> 'LinkedList[_T_co]':
if self._val is None:
return self
# FIXME: Stack safety
# TODO: Reuse as much of tail as possible
if p(self._val[0]):
tail = self._val[1].filter(p)
return LinkedList((self._val[0], tail))
return self._val[1].filter(p)

def _tails(self) -> 'List[LinkedList[_T_co]]':
res: List[LinkedList[_T_co]] = []
while self._val is not None:
res.append(self)
self = self._val[1]
return res

def __iter__(self) -> Iterator[_T_co]:
while self._val is not None:
yield self._val[0]
self = self._val[1]

def __str__(self) -> str:
return str(list(self))

def __repr__(self) -> str:
return f'LinkedList.from_iterable({list(self)!r})'

# "supertype defines the argument type as object"
def __eq__(self, other: object) -> bool:
if not isinstance(other, LinkedList):
return NotImplemented
if len(self) != len(other):
return False
for a, b in zip(self, other):
if a != b:
return False
return True


empty_list = LinkedList[Never](None)
Loading

0 comments on commit 6f4ab9e

Please sign in to comment.