Skip to content
Merged

Fixes #187

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Write Your Python Program - CHANGELOG

* 2.0.8 (2025-10-16)
* Fix vscode warning for literals
* Fix highlighting for files with umlauts
* 2.0.7 (2025-10-14)
* Fix #184 (filenames with dots)
* Support python 3.13
* Support python 3.14
* 2.0.6 (2025-10-08)
* Settings for language #180
* Only warn of settings cannot be saved #182
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "Write Your Python Program!",
"description": "A user friendly python environment for beginners",
"license": "See license in LICENSE",
"version": "2.0.7",
"version": "2.0.8",
"publisher": "StefanWehr",
"icon": "icon.png",
"engines": {
Expand Down
18 changes: 5 additions & 13 deletions python/code/wypp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,13 @@

import typing

# Exported names that are available for star imports (in alphabetic order)
Any = w.Any
Callable = w.Callable
Generator = w.Generator
Iterable = w.Iterable
Iterator = w.Iterator
Literal = w.Literal
Mapping = w.Mapping
Optional = w.Optional
Sequence = w.Sequence
Protocol = w.Protocol
Union = w.Union
# Exported names that are available for star imports (mostly in alphabetic order)
from typing import Any, Callable, Generator, Iterable, Iterator, Literal, Mapping, Optional, \
Protocol, Sequence, Union
from dataclasses import dataclass

check = w.check
checkFail = w.checkFail
dataclass = w.dataclass
floatNegative = w.floatNegative
floatNonNegative = w.floatNonNegative
floatNonPositive = w.floatNonPositive
Expand Down
8 changes: 5 additions & 3 deletions python/code/wypp/drawingLib.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import time
import threading
import writeYourProgram as _w
from typing import Literal, Sequence

# Do not import tkinter at the top-level. Someone with no installation of tkinter should
# be able to user WYPP without drawing support.

Expand All @@ -14,9 +16,9 @@ class Point:
x: float
y: float

ShapeKind = _w.Literal['ellipsis', 'rectangle']
type ShapeKind = Literal['ellipsis', 'rectangle']

Color = _w.Literal['red', 'green', 'blue', 'yellow', 'black', 'white']
type Color = Literal['red', 'green', 'blue', 'yellow', 'black', 'white']

@_w.record
class FixedShape:
Expand Down Expand Up @@ -58,7 +60,7 @@ def _drawCoordinateSystem(canvas, windowSize: Size):
canvas.create_line(x, 0, x, windowSize.height, dash=(4,2))
canvas.create_line(0, y, windowSize.width, y, dash=(4,2))

def drawFixedShapes(shapes: _w.Sequence[FixedShape],
def drawFixedShapes(shapes: Sequence[FixedShape],
withCoordinateSystem=False,
stopAfter=None) -> None:
try:
Expand Down
74 changes: 67 additions & 7 deletions python/code/wypp/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,66 @@
import parsecache
from parsecache import FunMatcher
import paths
import tokenize
import os

@dataclass
class EncodedBytes:
bytes: bytes
encoding: str
def __len__(self):
return len(self.bytes)
def countLeadingSpaces(self) -> int:
return len(self.bytes) - len(self.bytes.lstrip())
def decoded(self) -> str:
return self.bytes.decode(self.encoding, errors='replace')
@overload
def __getitem__(self, key: int) -> int: ...
@overload
def __getitem__(self, key: slice) -> str: ...
def __getitem__(self, key: int | slice) -> int | str:
if isinstance(key, int):
return self.bytes[key]
else:
b = self.bytes[key]
return b.decode(self.encoding, errors='replace')

@dataclass
class EncodedByteLines:
bytes: list[bytes]
encoding: str

_cache: dict[str, EncodedByteLines] = {}
def getline(filename, lineno):
"""
Returns a line of some source file as a bytearray. We use byte arrays because
location offsets are byte offsets.
"""
p = os.path.normpath(os.path.abspath(filename))
if p in _cache:
lines = _cache[p]
else:
try:
with open(filename, 'rb') as f:
byteLines = f.readlines()
except Exception:
byteLines = []
i = 0
def nextLine() -> bytes:
nonlocal i
if i < len(byteLines):
x = byteLines[i]
i = i + 1
return x
else:
return b''
encoding, _ = tokenize.detect_encoding(nextLine)
lines = EncodedByteLines(byteLines, encoding)
if 1 <= lineno <= len(lines.bytes):
x = lines.bytes[lineno - 1].rstrip(b'\n')
else:
x = b''
return EncodedBytes(x, encoding)

@dataclass
class Loc:
Expand All @@ -38,7 +98,7 @@ def code(self) -> Optional[str]:
case (startLine, startCol, endLine, endCol):
result = []
for lineNo in range(startLine, startLine+1):
line = linecache.getline(self.filename, lineNo).rstrip("\n")
line = getline(self.filename, lineNo)
c1 = startCol if lineNo == startLine else 0
c2 = endCol if lineNo == endLine else len(line)
result.append(line[c1:c2])
Expand Down Expand Up @@ -84,27 +144,27 @@ def highlight(s: str, mode: HighlightMode) -> str:

@dataclass
class SourceLine:
line: str # without trailing \n
line: EncodedBytes # without trailing \n
span: Optional[tuple[int, int]] # (inclusive, exclusive)

def highlight(self, mode: HighlightMode | Literal['fromEnv'] = 'fromEnv'):
def highlight(self, mode: HighlightMode | Literal['fromEnv'] = 'fromEnv') -> str:
mode = getHighlightMode(mode)
if self.span:
l = self.line
return l[:self.span[0]] + highlight(l[self.span[0]:self.span[1]], mode) + l[self.span[1]:]
else:
return self.line
return self.line.decoded()

def highlightedLines(loc: Loc) -> list[SourceLine]:
match loc.fullSpan():
case None:
line = linecache.getline(loc.filename, loc.startLine).rstrip("\n")
line = getline(loc.filename, loc.startLine)
return [SourceLine(line, None)]
case (startLine, startCol, endLine, endCol):
result = []
for lineNo in range(startLine, startLine+1):
line = linecache.getline(loc.filename, lineNo).rstrip("\n")
leadingSpaces = len(line) - len(line.lstrip())
line = getline(loc.filename, lineNo)
leadingSpaces = line.countLeadingSpaces()
c1 = startCol if lineNo == startLine else leadingSpaces
c2 = endCol if lineNo == endLine else len(line)
result.append(SourceLine(line, (c1, c2)))
Expand Down
22 changes: 3 additions & 19 deletions python/code/wypp/writeYourProgram.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import typing
from typing import Any
import dataclasses
import inspect
import errors
Expand All @@ -16,23 +17,6 @@ def _debug(s):
if _DEBUG:
print('[DEBUG] ' + s)

# Types
Any = typing.Any
Optional = typing.Optional
Union = typing.Union
Literal = typing.Literal
Iterable = typing.Iterable
Iterator = typing.Iterator
Sequence = typing.Sequence
Generator = typing.Generator
ForwardRef = typing.ForwardRef
Protocol = typing.Protocol

Mapping = typing.Mapping

Callable = typing.Callable

dataclass = dataclasses.dataclass
record = records.record

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

class Lock(Protocol):
class Lock(typing.Protocol):
def acquire(self, blocking: bool = True, timeout:int = -1) -> Any:
pass
def release(self) -> Any:
pass
def locked(self) -> Any:
pass

LockFactory = typing.Annotated[Callable[[], Lock], 'LockFactory']
LockFactory = typing.Annotated[typing.Callable[[], Lock], 'LockFactory']

T = typing.TypeVar('T')
U = typing.TypeVar('U')
Expand Down
Empty file.
1 change: 1 addition & 0 deletions python/file-test-data/basics/13.10.VL_ok.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello
3 changes: 3 additions & 0 deletions python/file-test-data/basics/13.10.VL_ok.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from wypp import *

print('Hello')
17 changes: 17 additions & 0 deletions python/file-test-data/basics/umlaute.err
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Traceback (most recent call last):
File "file-test-data/basics/umlaute.py", line 6, in <module>
check(äüßö("1"), 1)

WyppTypeError: "1"

Der Aufruf der Funktion `äüßö` erwartet einen Wert vom Typ `int` als erstes Argument.
Aber der übergebene Wert hat den Typ `str`.

## Datei file-test-data/basics/umlaute.py
## Fehlerhafter Aufruf in Zeile 6:

check(äüßö("1"), 1)

## Typ deklariert in Zeile 3:

def äüßö(x: int) -> int:
Empty file.
6 changes: 6 additions & 0 deletions python/file-test-data/basics/umlaute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from wypp import *

def äüßö(x: int) -> int:
return x + 1

check(äüßö("1"), 1)
Empty file.
1 change: 1 addition & 0 deletions python/file-test-data/basics/x.y_ok.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hello
3 changes: 3 additions & 0 deletions python/file-test-data/basics/x.y_ok.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from wypp import *

print('Hello')
2 changes: 1 addition & 1 deletion python/file-test-data/extras/testImpossible.err
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Traceback (most recent call last):
File "file-test-data/extras/testImpossible.py", line 3, in <module>
impossible()
File "code/wypp/writeYourProgram.py", line 334, in impossible
File "code/wypp/writeYourProgram.py", line 318, in impossible
raise errors.ImpossibleError(msg)

Das Unmögliche ist passiert!
2 changes: 1 addition & 1 deletion python/file-test-data/extras/testTodo.err
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Traceback (most recent call last):
File "file-test-data/extras/testTodo.py", line 3, in <module>
todo()
File "code/wypp/writeYourProgram.py", line 328, in todo
File "code/wypp/writeYourProgram.py", line 312, in todo
raise errors.TodoError(msg)

TODO
1 change: 1 addition & 0 deletions python/tests/test_misc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import unittest
from writeYourProgram import *
from typing import *

setDieOnCheckFailures(True)

Expand Down
1 change: 1 addition & 0 deletions python/tests/test_record.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import traceback
import dataclasses
import stacktrace
from typing import Literal

initModule()

Expand Down
5 changes: 3 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,9 @@ async function fixPylanceConfig(
// extraPaths
const keyExtraPaths = 'analysis.extraPaths';
const extra = cfg.get<string[]>(keyExtraPaths) ?? [];
if (extra.length !== 1 || extra[0] !== libDir) {
await tryUpdate(keyExtraPaths, [libDir]);
if (!extra.includes(libDir)) {
const newExtra = [...extra, libDir];
await tryUpdate(keyExtraPaths, newExtra);
}

// typechecking off
Expand Down