Skip to content

Commit 6848017

Browse files
Web 0.14.21: Array-Ausgabe wie in Java (Referenz), Arrays.toString zeigt den Inhalt (#166)
System.out.println(zahlen) gibt in der Simulation und in Programmieren jetzt wie echtes Java eine Referenz wie [I@… aus. Arrays.toString und Arrays.deepToString zeigen den Inhalt; Arrays braucht import java.util.Arrays (Code-Export ergänzt ihn). Lektion 15 erklärt das in de/en/pt. Claude-Session: https://claude.ai/code/session_01Ugz28VVb2pkKNmmHKgsfxu Co-authored-by: Claude <noreply@anthropic.com>
1 parent c8a5047 commit 6848017

17 files changed

Lines changed: 1488 additions & 1386 deletions

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format is loosely inspired by Keep a Changelog.
88

99
### Changed
1010

11+
- **Web 0.14.21, array output like in Java:** `System.out.println(zahlen)` now prints a reference such as `[I@1e3779b1` in Simulation and Programmieren, as real Java does (`[D@`, `[Z@`, `[Ljava.lang.String;@`, `[[I@`; the same array keeps its id). `Arrays.toString(…)` and `Arrays.deepToString(…)` show the content; `Arrays` needs `import java.util.Arrays;` like javac, and the Java export adds it. The variable table still shows the content. Lesson 15 „Jetzt in Java“ explains reference and `Arrays.toString` (de/en/pt).
1112
- **Web 0.14.18, upper and lower case, exercises „Warum läuft das nicht?“:** lesson 4 gets the new step „Groß- und Kleinschreibung“ (case-sensitive: `zahl`, `Zahl` and `ZAHL` are three names) with a structure chart exercise (`zahl` and `Zahl` both declared, `ZAHL` used), a Kompetenz item and a quiz question; glossary entry case-sensitive. Lessons 4–17 and 19 each get a Java exercise „Warum läuft das nicht?“: the start code doesn't run (misspelled variable such as `Alter`/`Grenze`/`properson`, variable used before it is created or never created, `system.out`, `.Length`, `charat`), the student reads the error message, fixes it and clicks Check (de/en/pt). The Code area now reports wrong case in Java words (`system.out.println`, `System.Out`, `Println`, `math.sqrt`, `text.Length()`, `name.charat(0)`) with the right spelling, blocks Run like javac and offers the 💡 fix; the simulation says „Unbekannte Variable: Zahl. Java unterscheidet Groß- und Kleinschreibung: Meintest du zahl?“ when the name exists in another case (all 21 languages).
1213
- **Web 0.14.17, hint on a missing line break:** when a Java exercise's output is right except that lines run together (e.g. `System.out.print` twice instead of `println`), the check now says „Fast! Es fehlt nur ein Zeilenumbruch …“ and explains print vs. println, instead of only showing a mismatch. `\n` inside an output now starts a new line, so `System.out.print("…\n")` counts like `println` (de/en/pt; all exercises).
1314
- **Web 0.14.16, „Im Editor öffnen“ under the Java examples in lessons 4–19:** every runnable Java example in the lesson text of lessons 4–19 now has the button „Im Editor öffnen“ (like the Mia example in lesson 4), so students can run and change it in Programmieren. Not in lesson 18, whose examples are a method and its call on their own, and not in cloze texts or games (de/en/pt).

‎web/lessons/de/15-arrays.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ for (int i = 0; i < zahlen.length; i++) {
166166
- Ein leeres Array mit 5 Fächern legst du mit `new int[5]` an.
167167
- `zahlen[i]` ist das Fach mit dem Index `i`, `zahlen.length` die Länge.
168168

169+
Gibst du das ganze Array aus, also `System.out.println(zahlen);`, zeigt Java nicht die Zahlen. Du siehst nur einen Text wie `[I@1b6d3586`. Das ist die **Referenz**, also die Stelle, an der das Array im Speicher liegt. Den Inhalt zeigt erst `Arrays.toString(zahlen)`, das ergibt `[2, 4, 6, 8, 10]`. Dafür schreibst du ganz oben `import java.util.Arrays;`.
170+
169171
::: übung l15-java-noten
170172
titel: Notensumme in Java
171173
code: java

‎web/lessons/en/15-arrays.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ for (int i = 0; i < numbers.length; i++) {
166166
- You create an empty array with 5 slots with `new int[5]`.
167167
- `numbers[i]` is the slot with index `i`, `numbers.length` is the length.
168168

169+
If you output the whole array, that is `System.out.println(numbers);`, Java does not show the numbers. You only see a text like `[I@1b6d3586`. This is the **reference**, the place where the array is stored in memory. `Arrays.toString(numbers)` shows the content: `[2, 4, 6, 8, 10]`. For this you write `import java.util.Arrays;` at the very top.
170+
169171
::: übung l15-java-noten
170172
titel: Sum of grades in Java
171173
code: java

‎web/lessons/pt/15-arrays.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ for (int i = 0; i < numeros.length; i++) {
166166
- Um array vazio com 5 posições cria-se com `new int[5]`.
167167
- `numeros[i]` é a posição com o índice `i`, `numeros.length` é o comprimento.
168168

169+
Se mostrares o array inteiro, ou seja `System.out.println(numeros);`, o Java não mostra os números. Só vês um texto como `[I@1b6d3586`. É a **referência**, o sítio onde o array está guardado na memória. O conteúdo só aparece com `Arrays.toString(numeros)`, que dá `[2, 4, 6, 8, 10]`. Para isso escreves `import java.util.Arrays;` logo no início.
170+
169171
::: übung l15-java-noten
170172
titel: Soma das notas em Java
171173
code: java

‎web/package-lock.json‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎web/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "visustruct-web",
33
"private": true,
4-
"version": "0.14.20",
4+
"version": "0.14.21",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

‎web/src/core/codeGenerator.ts‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,27 +1095,31 @@ function documentContainsJavaInput(doc: VSDocument): boolean {
10951095
return found
10961096
}
10971097

1098+
const arraysImport = 'import java.util.Arrays;'
1099+
10981100
/**
10991101
* Imports für Dateien: `Files`/`Path` aus `java.nio.file`, `File`, `PrintWriter`, `FileReader` und `BufferedReader` aus
1100-
* `java.io`, dazu `java.util.Scanner` für einen Scanner auf einer Datei.
1102+
* `java.io`, dazu `java.util.Scanner` für einen Scanner auf einer Datei und `java.util.Arrays` für `Arrays.toString(…)`.
11011103
*/
11021104
export function fileImports(doc: VSDocument): string[] {
11031105
let nio = false
11041106
let io = false
11051107
let scanner = false
1108+
let arrays = false
11061109
walkElements(doc.elements, (e) => {
11071110
for (const l of e.textLines) {
1111+
if (/\bArrays\s*\./.test(l)) arrays = true
11081112
if (/\bFiles\s*\./.test(l)) nio = true
11091113
if (/\bnew\s+(?:File|PrintWriter|FileReader|FileWriter|BufferedReader)\s*\(/.test(l)) io = true
11101114
if (/\bnew\s+Scanner\s*\(\s*new\s+File\b/.test(l)) scanner = true
11111115
}
11121116
})
1113-
return [...(io ? ['import java.io.*;'] : []), ...(nio ? ['import java.nio.file.*;'] : []), ...(scanner ? ['import java.util.Scanner;'] : [])]
1117+
return [...(io ? ['import java.io.*;'] : []), ...(nio ? ['import java.nio.file.*;'] : []), ...(scanner ? ['import java.util.Scanner;'] : []), ...(arrays ? [arraysImport] : [])]
11141118
}
11151119

11161120
/** Arbeitet das Programm mit Dateien? Dann kann eine IOException kommen, und Java verlangt `throws` an der Methode. */
11171121
export function documentReadsFiles(doc: VSDocument): boolean {
1118-
return fileImports(doc).length > 0
1122+
return fileImports(doc).some((i) => i !== arraysImport)
11191123
}
11201124

11211125
function documentContainsJavaMathRandom(doc: VSDocument): boolean {
@@ -1804,7 +1808,7 @@ export function generateJavaClassFile(
18041808
const scannerField = methods.length > 0 && (hasInput || methodInput)
18051809
// `Files.readString` kann eine IOException werfen; Java verlangt dann `throws` an der Methode.
18061810
const imports = [...new Set([document, ...methodDocs.map((m) => m.d)].flatMap(fileImports))]
1807-
const files = imports.length > 0
1811+
const files = imports.some((i) => i !== arraysImport)
18081812
if ((hasInput || methodInput) && !imports.includes('import java.util.Scanner;')) imports.push('import java.util.Scanner;')
18091813
for (const line of imports) out += line + '\n'
18101814
if (imports.length) out += '\n'

‎web/src/core/javaParser.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,14 +227,15 @@ class Parser {
227227

228228
/**
229229
* Wie javac: `Scanner` ohne `import java.util.Scanner;` (oder `java.util.*`) ist ein Fehler, ebenso `Files` und `Path` ohne
230-
* `java.nio.file` und `File`, `PrintWriter`, `FileReader`, `FileWriter`, `BufferedReader` ohne `java.io`.
230+
* `java.nio.file`, `File`, `PrintWriter`, `FileReader`, `FileWriter`, `BufferedReader` ohne `java.io` und `Arrays` ohne
231+
* `java.util.Arrays`.
231232
*/
232233
checkImports() {
233234
const io = ['File', 'PrintWriter', 'FileReader', 'FileWriter', 'BufferedReader', 'IOException', 'FileNotFoundException'].map((n) => [n, 'java.io'])
234-
for (const [name, pkg] of [['Scanner', 'java.util'], ['Files', 'java.nio.file'], ['Path', 'java.nio.file'], ['Paths', 'java.nio.file'], ...io]) {
235+
for (const [name, pkg] of [['Scanner', 'java.util'], ['Arrays', 'java.util'], ['Files', 'java.nio.file'], ['Path', 'java.nio.file'], ['Paths', 'java.nio.file'], ...io]) {
235236
const known = this.imports.some((i) => i === `import${pkg}.${name};` || i === `import${pkg}.*;`)
236237
// `Files.readString(…)`: nur als Klasse vor einem Punkt, damit eine Variable `path` o. Ä. nicht zählt.
237-
const use = this.toks.find((t, k) => t.kind === 'id' && t.text === name && (pkg !== 'java.nio.file' || this.toks[k + 1]?.text === '.'))
238+
const use = this.toks.find((t, k) => t.kind === 'id' && t.text === name && (name !== 'Arrays' && pkg !== 'java.nio.file' || this.toks[k + 1]?.text === '.'))
238239
if (!known && use) {
239240
this.problem('web_parse_missing_import', use, pkg === 'java.util' ? `import ${pkg}.${name};` : `import ${pkg}.*;`)
240241
return

‎web/src/core/simulation.ts‎

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,38 @@ function elementDefault(type: SimValueType): number | string | boolean {
245245
return type === 'string' ? '' : type === 'boolean' ? false : 0
246246
}
247247

248+
/**
249+
* Was `System.out.println(zahlen)` in Java ausgibt: nicht den Inhalt, sondern Typ und Kennung der Referenz,
250+
* z. B. `[I@1b6d3586`. Den Inhalt zeigt erst `Arrays.toString(zahlen)`. Die Kennung bleibt für dasselbe Array gleich.
251+
*/
252+
const arrayIds = new WeakMap<object, string>()
253+
let nextArrayId = 0
254+
255+
export function javaReference(v: SimValue): string {
256+
const code = (t: SimValueType) => ({ int: 'I', double: 'D', boolean: 'Z', string: 'Ljava.lang.String;' })[t]
257+
const prefix =
258+
v.kind === 'grid' ? '[[' + code(v.elementType)
259+
: v.kind === 'intArray' ? '[I'
260+
: v.kind === 'doubleArray' ? '[D'
261+
: v.kind === 'booleanArray' ? '[Z'
262+
: v.kind === 'stringArray' ? '[Ljava.lang.String;'
263+
: undefined
264+
if (!prefix || !('value' in v)) return describeValue(v)
265+
const key = v.value as object
266+
let id = arrayIds.get(key)
267+
if (!id) {
268+
id = ((Math.imul(++nextArrayId, 0x9e3779b1) >>> 0) & 0x7fffffff).toString(16)
269+
arrayIds.set(key, id)
270+
}
271+
return prefix + '@' + id
272+
}
273+
274+
/** Ein Fach wurde geändert und das Array dabei kopiert: Es ist in Java aber dasselbe Array, also dieselbe Kennung. */
275+
function keepArrayId(from: object, to: object): void {
276+
const id = arrayIds.get(from)
277+
if (id) arrayIds.set(to, id)
278+
}
279+
248280
function isArrayValue(v: SimValue): boolean {
249281
return v.kind === 'intArray' || v.kind === 'doubleArray' || v.kind === 'stringArray' || v.kind === 'booleanArray' || v.kind === 'grid'
250282
}
@@ -1938,7 +1970,10 @@ export class SimulationEngine {
19381970
}
19391971
}
19401972

1941-
/** `Integer.parseInt(…)`, `Double.parseDouble(…)`, `String.valueOf(…)`, `Files.readString(Path.of(…))`. */
1973+
/**
1974+
* `Integer.parseInt(…)`, `Double.parseDouble(…)`, `String.valueOf(…)`, `Files.readString(Path.of(…))`,
1975+
* `Arrays.toString(…)` und `Arrays.deepToString(…)`.
1976+
*/
19421977
private evaluateStaticCall(text: string, call: { target: string; method: string; args: string[] }): SimValue | undefined {
19431978
const one = () => {
19441979
if (call.args.length !== 1) throw invalid(text)
@@ -1964,6 +1999,17 @@ export class SimulationEngine {
19641999
}
19652000
case 'String.valueOf':
19662001
return { kind: 'string', value: this.evaluateOutputExpression(one()) }
2002+
case 'Arrays.toString':
2003+
case 'Arrays.deepToString': {
2004+
const array = this.state.variables.get(one().trim())
2005+
if (!array || !isArrayValue(array)) throw invalid(text)
2006+
// `Arrays.toString` zeigt bei einem 2D-Array nur die Referenzen der Zeilen, `deepToString` alles.
2007+
if (array.kind === 'grid' && call.method === 'toString') {
2008+
const rows = array.value.map((row) => javaReference({ kind: `${array.elementType}Array`, value: row } as SimValue))
2009+
return { kind: 'string', value: '[' + rows.join(', ') + ']' }
2010+
}
2011+
return { kind: 'string', value: describeValue(array) }
2012+
}
19672013
case 'Files.readString': {
19682014
const path = /^(?:Path\s*\.\s*of|Paths\s*\.\s*get)\s*\(\s*("(?:[^"\\]|\\.)*")\s*\)$/u.exec(one().trim())
19692015
if (!path) throw invalid(text)
@@ -2076,6 +2122,7 @@ export class SimulationEngine {
20762122
if (raw === undefined || isArrayValue(value)) throw invalid(trimmed)
20772123
const rows = old.value.map((row) => [...row])
20782124
rows[indices[0]][indices[1]] = raw as number | string | boolean
2125+
keepArrayId(old.value, rows)
20792126
this.state.variables.set(access.arrayName, { ...old, value: rows })
20802127
return
20812128
}
@@ -2095,6 +2142,7 @@ export class SimulationEngine {
20952142
}
20962143
if (index >= (updated.value as unknown[]).length) throw invalid(trimmed)
20972144
;(updated.value as unknown[])[index] = value.value
2145+
keepArrayId(old.value, updated.value)
20982146
this.state.variables.set(access.arrayName, updated)
20992147
}
21002148

@@ -2122,7 +2170,8 @@ export class SimulationEngine {
21222170
else sum = sum.kind === 'int' && value.kind === 'int' ? int(sum.value + value.value) : dbl((sum as { value: number }).value + (value as { value: number }).value)
21232171
continue
21242172
}
2125-
text = (text ?? (sum ? describeValue(sum) : '')) + describeValue(value)
2173+
// Ein ganzes Array gibt Java nur als Referenz aus, z. B. `[I@1b6d3586`.
2174+
text = (text ?? (sum ? describeValue(sum) : '')) + javaReference(value)
21262175
sum = undefined
21272176
}
21282177
return text ?? (sum ? describeValue(sum) : '')

‎web/test/javaParser.test.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,13 @@ describe('Java tippen → Struktogramm', () => {
214214
expect(ask(fach + 'note[i] = scanner.nextInt();').request?.prompt).toBe('note[i]:')
215215
})
216216

217+
it('Arrays ohne import ist ein Fehler, wie bei javac', () => {
218+
const keys = (src: string) => parse(src).problems.map((p) => p.key)
219+
expect(keys('int[] a = {1, 2};\nSystem.out.println(Arrays.toString(a));')).toEqual(['web_parse_missing_import'])
220+
expect(keys('import java.util.Arrays;\nint[] a = {1, 2};\nSystem.out.println(Arrays.toString(a));')).toEqual([])
221+
expect(keys('int[] a = {1, 2};\nSystem.out.println(a);')).toEqual([])
222+
})
223+
217224
it('Aufruf eines Unterprogramms aus einem anderen Reiter', () => {
218225
expect(parse('int y = quadrat(4);', new Set(['quadrat'])).elements[0].type).toBe(ElementType.aufruf)
219226
expect(parse('int y = quadrat(4);').elements[0].type).toBe(ElementType.anweisung)

0 commit comments

Comments
 (0)