Skip to content

Commit bf9467d

Browse files
authored
Merge pull request #187 from skogsbaer/sw/fixes-2025-10-16
Fixes
2 parents e31bb7d + aedd563 commit bf9467d

20 files changed

+123
-48
lines changed

ChangeLog.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
# Write Your Python Program - CHANGELOG
22

3+
* 2.0.8 (2025-10-16)
4+
* Fix vscode warning for literals
5+
* Fix highlighting for files with umlauts
36
* 2.0.7 (2025-10-14)
47
* Fix #184 (filenames with dots)
5-
* Support python 3.13
8+
* Support python 3.14
69
* 2.0.6 (2025-10-08)
710
* Settings for language #180
811
* Only warn of settings cannot be saved #182

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Write Your Python Program!",
44
"description": "A user friendly python environment for beginners",
55
"license": "See license in LICENSE",
6-
"version": "2.0.7",
6+
"version": "2.0.8",
77
"publisher": "StefanWehr",
88
"icon": "icon.png",
99
"engines": {

python/code/wypp/__init__.py

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

66
import typing
77

8-
# Exported names that are available for star imports (in alphabetic order)
9-
Any = w.Any
10-
Callable = w.Callable
11-
Generator = w.Generator
12-
Iterable = w.Iterable
13-
Iterator = w.Iterator
14-
Literal = w.Literal
15-
Mapping = w.Mapping
16-
Optional = w.Optional
17-
Sequence = w.Sequence
18-
Protocol = w.Protocol
19-
Union = w.Union
8+
# Exported names that are available for star imports (mostly in alphabetic order)
9+
from typing import Any, Callable, Generator, Iterable, Iterator, Literal, Mapping, Optional, \
10+
Protocol, Sequence, Union
11+
from dataclasses import dataclass
12+
2013
check = w.check
2114
checkFail = w.checkFail
22-
dataclass = w.dataclass
2315
floatNegative = w.floatNegative
2416
floatNonNegative = w.floatNonNegative
2517
floatNonPositive = w.floatNonPositive

python/code/wypp/drawingLib.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import time
22
import threading
33
import writeYourProgram as _w
4+
from typing import Literal, Sequence
5+
46
# Do not import tkinter at the top-level. Someone with no installation of tkinter should
57
# be able to user WYPP without drawing support.
68

@@ -14,9 +16,9 @@ class Point:
1416
x: float
1517
y: float
1618

17-
ShapeKind = _w.Literal['ellipsis', 'rectangle']
19+
type ShapeKind = Literal['ellipsis', 'rectangle']
1820

19-
Color = _w.Literal['red', 'green', 'blue', 'yellow', 'black', 'white']
21+
type Color = Literal['red', 'green', 'blue', 'yellow', 'black', 'white']
2022

2123
@_w.record
2224
class FixedShape:
@@ -58,7 +60,7 @@ def _drawCoordinateSystem(canvas, windowSize: Size):
5860
canvas.create_line(x, 0, x, windowSize.height, dash=(4,2))
5961
canvas.create_line(0, y, windowSize.width, y, dash=(4,2))
6062

61-
def drawFixedShapes(shapes: _w.Sequence[FixedShape],
63+
def drawFixedShapes(shapes: Sequence[FixedShape],
6264
withCoordinateSystem=False,
6365
stopAfter=None) -> None:
6466
try:

python/code/wypp/location.py

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,66 @@
1313
import parsecache
1414
from parsecache import FunMatcher
1515
import paths
16+
import tokenize
17+
import os
18+
19+
@dataclass
20+
class EncodedBytes:
21+
bytes: bytes
22+
encoding: str
23+
def __len__(self):
24+
return len(self.bytes)
25+
def countLeadingSpaces(self) -> int:
26+
return len(self.bytes) - len(self.bytes.lstrip())
27+
def decoded(self) -> str:
28+
return self.bytes.decode(self.encoding, errors='replace')
29+
@overload
30+
def __getitem__(self, key: int) -> int: ...
31+
@overload
32+
def __getitem__(self, key: slice) -> str: ...
33+
def __getitem__(self, key: int | slice) -> int | str:
34+
if isinstance(key, int):
35+
return self.bytes[key]
36+
else:
37+
b = self.bytes[key]
38+
return b.decode(self.encoding, errors='replace')
39+
40+
@dataclass
41+
class EncodedByteLines:
42+
bytes: list[bytes]
43+
encoding: str
44+
45+
_cache: dict[str, EncodedByteLines] = {}
46+
def getline(filename, lineno):
47+
"""
48+
Returns a line of some source file as a bytearray. We use byte arrays because
49+
location offsets are byte offsets.
50+
"""
51+
p = os.path.normpath(os.path.abspath(filename))
52+
if p in _cache:
53+
lines = _cache[p]
54+
else:
55+
try:
56+
with open(filename, 'rb') as f:
57+
byteLines = f.readlines()
58+
except Exception:
59+
byteLines = []
60+
i = 0
61+
def nextLine() -> bytes:
62+
nonlocal i
63+
if i < len(byteLines):
64+
x = byteLines[i]
65+
i = i + 1
66+
return x
67+
else:
68+
return b''
69+
encoding, _ = tokenize.detect_encoding(nextLine)
70+
lines = EncodedByteLines(byteLines, encoding)
71+
if 1 <= lineno <= len(lines.bytes):
72+
x = lines.bytes[lineno - 1].rstrip(b'\n')
73+
else:
74+
x = b''
75+
return EncodedBytes(x, encoding)
1676

1777
@dataclass
1878
class Loc:
@@ -38,7 +98,7 @@ def code(self) -> Optional[str]:
3898
case (startLine, startCol, endLine, endCol):
3999
result = []
40100
for lineNo in range(startLine, startLine+1):
41-
line = linecache.getline(self.filename, lineNo).rstrip("\n")
101+
line = getline(self.filename, lineNo)
42102
c1 = startCol if lineNo == startLine else 0
43103
c2 = endCol if lineNo == endLine else len(line)
44104
result.append(line[c1:c2])
@@ -84,27 +144,27 @@ def highlight(s: str, mode: HighlightMode) -> str:
84144

85145
@dataclass
86146
class SourceLine:
87-
line: str # without trailing \n
147+
line: EncodedBytes # without trailing \n
88148
span: Optional[tuple[int, int]] # (inclusive, exclusive)
89149

90-
def highlight(self, mode: HighlightMode | Literal['fromEnv'] = 'fromEnv'):
150+
def highlight(self, mode: HighlightMode | Literal['fromEnv'] = 'fromEnv') -> str:
91151
mode = getHighlightMode(mode)
92152
if self.span:
93153
l = self.line
94154
return l[:self.span[0]] + highlight(l[self.span[0]:self.span[1]], mode) + l[self.span[1]:]
95155
else:
96-
return self.line
156+
return self.line.decoded()
97157

98158
def highlightedLines(loc: Loc) -> list[SourceLine]:
99159
match loc.fullSpan():
100160
case None:
101-
line = linecache.getline(loc.filename, loc.startLine).rstrip("\n")
161+
line = getline(loc.filename, loc.startLine)
102162
return [SourceLine(line, None)]
103163
case (startLine, startCol, endLine, endCol):
104164
result = []
105165
for lineNo in range(startLine, startLine+1):
106-
line = linecache.getline(loc.filename, lineNo).rstrip("\n")
107-
leadingSpaces = len(line) - len(line.lstrip())
166+
line = getline(loc.filename, lineNo)
167+
leadingSpaces = line.countLeadingSpaces()
108168
c1 = startCol if lineNo == startLine else leadingSpaces
109169
c2 = endCol if lineNo == endLine else len(line)
110170
result.append(SourceLine(line, (c1, c2)))

python/code/wypp/writeYourProgram.py

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import typing
2+
from typing import Any
23
import dataclasses
34
import inspect
45
import errors
@@ -16,23 +17,6 @@ def _debug(s):
1617
if _DEBUG:
1718
print('[DEBUG] ' + s)
1819

19-
# Types
20-
Any = typing.Any
21-
Optional = typing.Optional
22-
Union = typing.Union
23-
Literal = typing.Literal
24-
Iterable = typing.Iterable
25-
Iterator = typing.Iterator
26-
Sequence = typing.Sequence
27-
Generator = typing.Generator
28-
ForwardRef = typing.ForwardRef
29-
Protocol = typing.Protocol
30-
31-
Mapping = typing.Mapping
32-
33-
Callable = typing.Callable
34-
35-
dataclass = dataclasses.dataclass
3620
record = records.record
3721

3822
intPositive = typing.Annotated[int, lambda i: i > 0, 'intPositive']
@@ -46,15 +30,15 @@ def _debug(s):
4630
floatNegative = typing.Annotated[float, lambda x: x < 0, 'floatNegative']
4731
floatNonPositive = typing.Annotated[float, lambda x: x <= 0, 'floatNonPositive']
4832

49-
class Lock(Protocol):
33+
class Lock(typing.Protocol):
5034
def acquire(self, blocking: bool = True, timeout:int = -1) -> Any:
5135
pass
5236
def release(self) -> Any:
5337
pass
5438
def locked(self) -> Any:
5539
pass
5640

57-
LockFactory = typing.Annotated[Callable[[], Lock], 'LockFactory']
41+
LockFactory = typing.Annotated[typing.Callable[[], Lock], 'LockFactory']
5842

5943
T = typing.TypeVar('T')
6044
U = typing.TypeVar('U')

python/file-test-data/basics/13.10.VL_ok.err

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from wypp import *
2+
3+
print('Hello')
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Traceback (most recent call last):
2+
File "file-test-data/basics/umlaute.py", line 6, in <module>
3+
check(äüßö("1"), 1)
4+
5+
WyppTypeError: "1"
6+
7+
Der Aufruf der Funktion `äüßö` erwartet einen Wert vom Typ `int` als erstes Argument.
8+
Aber der übergebene Wert hat den Typ `str`.
9+
10+
## Datei file-test-data/basics/umlaute.py
11+
## Fehlerhafter Aufruf in Zeile 6:
12+
13+
check(äüßö("1"), 1)
14+
15+
## Typ deklariert in Zeile 3:
16+
17+
def äüßö(x: int) -> int:

0 commit comments

Comments
 (0)