Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,8 @@ class CSharpBackend(setup: BackendSetup<CSharpBackend>) : Backend<CSharpBackend>
private val stdLibraryResources: List<ResourceDescriptor> =
declareResources(
base = dirPath("lang", "temper", "be", "csharp", "std"),
filePath("Io", "IoSupport.cs"),
filePath("Keyboard", "KeyboardSupport.cs"),
filePath("Regex", "IntRangeSet.cs"),
filePath("Regex", "RegexSupport.cs"),
filePath("Temporal", "TemporalSupport.cs"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,21 @@ private val promiseBuilderGetPromise = PropertyAccess(
"Task",
)

private val stdSleep = StaticCall(
"stdSleep",
StandardNames.temperStdIoStdSleep,
)

private val stdReadLine = StaticCall(
"stdReadLine",
StandardNames.temperStdIoStdReadLine,
)

private val stdNextKeypress = StaticCall(
"stdNextKeypress",
StandardNames.temperStdKeyboardStdNextKeypress,
)

private val stdNetSend = StaticCall(
"stdNetSend",
StandardNames.temperCoreNetCoreStdNetSend,
Expand Down Expand Up @@ -1497,6 +1512,9 @@ private val connectedReferences = listOf(
stringToInt,
stringToInt64,
stdNetSend,
stdSleep,
stdReadLine,
stdNextKeypress,
).flatMap { ref -> ref.connectedNames.map { it to ref } }.toMap()

private val connectedTypes = mapOf<String, Pair<AbstractTypeName, ((List<Type2>) -> List<Type2>)?>>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ data class CsProj(
attribute("Sdk", "Microsoft.NET.Sdk")
"PropertyGroup" {
// Currently, dotnet warns if you request net5.0 or earlier.
"TargetFramework" { -"net6.0" }
"TargetFramework" { -"net8.0" }
// Core project info.
outputType?.let { "OutputType" { -it } }
rootNamespace?.let { "RootNamespace" { -it } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ object StandardNames {
val temperStdTemporalTemporalSupportYearsBetween = temperStdTemporalTemporalSupport.member("YearsBetween")
val temperStdTemporalTemporalSupportIsoWeekdayNum = temperStdTemporalTemporalSupport.member("IsoWeekdayNum")
val temperStdTemporalTemporalSupportFromIsoString = temperStdTemporalTemporalSupport.member("FromIsoString")
private val temperStdIo = temperStd.space("Io")
private val temperStdIoIoSupport = temperStdIo.type("IoSupport")
val temperStdIoStdSleep = temperStdIoIoSupport.member("StdSleep")
val temperStdIoStdReadLine = temperStdIoIoSupport.member("StdReadLine")
private val temperStdKeyboard = temperStd.space("Keyboard")
private val temperStdKeyboardKeyboardSupport = temperStdKeyboard.type("KeyboardSupport")
val temperStdKeyboardStdNextKeypress = temperStdKeyboardKeyboardSupport.member("StdNextKeypress")

private val temperStdNet = temperStd.space("Net")
val temperCoreNetINetResponse = temperStdNet.type("INetResponse")
val temperCoreNetSupport = temperStdNet.type("NetSupport")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net48;net6.0</TargetFrameworks>
<TargetFrameworks>net48;net8.0</TargetFrameworks>
<RootNamespace>RootNamespaceSpot</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Condition="'$(TargetFramework)' == 'net6.0'" Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Condition="'$(TargetFramework)' == 'net8.0'" Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<ProjectReference Include="../temper-core/TemperLang.Core.csproj"/>
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Threading.Tasks;

namespace TemperLang.Std.Io
{
public static class IoSupport
{
public static async Task<Tuple<object?>> StdSleep(int ms)
{
await Task.Delay(ms);
return Tuple.Create<object?>(null);
}

public static async Task<string?> StdReadLine()
{
return await Task.Run(() => Console.ReadLine());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Threading.Tasks;

namespace TemperLang.Std.Keyboard
{
public static class KeyboardSupport
{
public static async Task<string?> StdNextKeypress()
{
return await Task.Run(() =>
{
if (Console.IsInputRedirected) return null;
var key = Console.ReadKey(true);
switch (key.Key)
{
case ConsoleKey.UpArrow: return "ArrowUp";
case ConsoleKey.DownArrow: return "ArrowDown";
case ConsoleKey.LeftArrow: return "ArrowLeft";
case ConsoleKey.RightArrow: return "ArrowRight";
case ConsoleKey.Escape: return "Escape";
case ConsoleKey.Enter: return "Enter";
case ConsoleKey.Backspace: return "Backspace";
case ConsoleKey.Tab: return "Tab";
default: return key.KeyChar.ToString();
}
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ RegexRefs regexRefs
);
}
var groupsDict = new ReadOnlyDictionaryWrapper<string, Group>(
new OrderedDictionary<string, Group>(resultGroups)
new TemperLang.Core.OrderedDictionary<string, Group>(resultGroups)
);
var full = match.Groups[0].Captures[0];
var fullGroup = new Group("full", full.Value, full.Index, full.Index + full.Length);
Expand Down Expand Up @@ -204,7 +204,7 @@ internal static IReadOnlyList<string> CompiledSplit(
RegexRefs regexRefs
)
{
return ((R::Regex)compiled).Split(text).AsReadOnly();
return TemperLang.Core.Listed.AsReadOnly(((R::Regex)compiled).Split(text));
}

static IRegexNode IntRangeSetToUtf16CodePattern(List<IntRange> ranges)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,13 @@ val JavaLang.doneResult by receiver {
// Async support
val JavaLang.runAsync by receiver { separateCode(temperRunAsync) }

// std/io support
val JavaLang.stdSleep by receiver { separateCode(temperStdSleep) }
val JavaLang.stdReadLine by receiver { separateCode(temperStdReadLine) }

// std/keyboard support
val JavaLang.stdNextKeypress by receiver { separateCode(temperStdNextKeypress) }

// std/net support
val JavaLang.netCoreStdNetSend by receiver { separateCode(temperNetCoreStdNetSend) }

Expand Down Expand Up @@ -1590,4 +1597,7 @@ private val connections: Map<String, ((JavaLang) -> SupportCode)> = mapOf(
"empty" to { it.empty },
"ignore" to { it.doNothing },
"stdNetSend" to { it.netCoreStdNetSend },
"stdSleep" to { it.stdSleep },
"stdReadLine" to { it.stdReadLine },
"stdNextKeypress" to { it.stdNextKeypress },
)
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,13 @@ val temperRegexCompiledReplace = temperRegexCore.qualifyKnownSafe("regexCompiled
val temperRegexCompiledSplit = temperRegexCore.qualifyKnownSafe("regexCompiledSplit")
val temperRegexFormatterPushCodeTo = temperRegexCore.qualifyKnownSafe("regexFormatterPushCodeTo")

// std/io
val temperStdSleep = temperCore.qualifyKnownSafe("stdSleep")
val temperStdReadLine = temperCore.qualifyKnownSafe("stdReadLine")

// std/keyboard
val temperStdNextKeypress = temperCore.qualifyKnownSafe("stdNextKeypress")

// std/net
val temperNetPkg = temperPkg.qualifyKnownSafe("net")
val temperNetCore = temperNetPkg.qualifyKnownSafe("Core")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1924,10 +1924,141 @@ public static void runAsync(Supplier<Generator<Optional<? super Object>>> genera
*/
public static void waitUntilTasksComplete() {
ForkJoinPool commonPool = ForkJoinPool.commonPool();
// This timeout is sufficient for functional tests.
// If a long running main method needs more time, it should
// negotiate promises for termination with the tasks it spawns.
commonPool.awaitQuiescence(10L, TimeUnit.SECONDS);
// Wait until the pool is truly idle (all tasks complete).
while (!commonPool.isQuiescent()) {
commonPool.awaitQuiescence(60L, TimeUnit.SECONDS);
}
}

// std/io support

@SuppressWarnings("unchecked")
public static java.util.concurrent.CompletableFuture<Optional<? super Object>> stdSleep(int ms) {
java.util.concurrent.CompletableFuture<Optional<? super Object>> future = new java.util.concurrent.CompletableFuture<>();
ForkJoinPool.commonPool().execute(() -> {
try {
Thread.sleep(ms);
future.complete(Optional.empty());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
future.completeExceptionally(e);
}
});
return future;
}

public static java.util.concurrent.CompletableFuture<String> stdReadLine() {
java.util.concurrent.CompletableFuture<String> future = new java.util.concurrent.CompletableFuture<>();
ForkJoinPool.commonPool().execute(() -> {
try {
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(System.in, java.nio.charset.StandardCharsets.UTF_8));
String line = reader.readLine();
future.complete(line); // null on EOF
} catch (Exception e) {
future.completeExceptionally(e);
}
});
return future;
}

private static final boolean IS_WINDOWS = System.getProperty("os.name", "").toLowerCase().startsWith("win");

public static java.util.concurrent.CompletableFuture<String> stdNextKeypress() {
java.util.concurrent.CompletableFuture<String> future = new java.util.concurrent.CompletableFuture<>();
ForkJoinPool.commonPool().execute(() -> {
try {
if (IS_WINDOWS) {
stdNextKeypressWindows(future);
} else {
stdNextKeypressUnix(future);
}
} catch (Exception e) {
future.completeExceptionally(e);
}
});
return future;
}

private static void stdNextKeypressUnix(java.util.concurrent.CompletableFuture<String> future) throws Exception {
if (System.console() == null) {
future.complete(null);
return;
}
String[] cmd = {"/bin/sh", "-c", "stty raw -echo </dev/tty"};
Runtime.getRuntime().exec(cmd).waitFor();
try {
int ch = System.in.read();
if (ch == -1) {
future.complete(null);
} else if (ch == 27) {
int next = System.in.read();
if (next == '[') {
int arrow = System.in.read();
switch (arrow) {
case 'A': future.complete("ArrowUp"); break;
case 'B': future.complete("ArrowDown"); break;
case 'C': future.complete("ArrowRight"); break;
case 'D': future.complete("ArrowLeft"); break;
default: future.complete("Escape"); break;
}
} else {
future.complete("Escape");
}
} else if (ch == '\r' || ch == '\n') {
future.complete("Enter");
} else {
future.complete(String.valueOf((char) ch));
}
} finally {
String[] restore = {"/bin/sh", "-c", "stty sane </dev/tty"};
Runtime.getRuntime().exec(restore).waitFor();
}
}

private static void stdNextKeypressWindows(java.util.concurrent.CompletableFuture<String> future) throws Exception {
// Use PowerShell to read a single key without echo.
// Returns "VirtualKeyCode,KeyChar" e.g. "38,0" for ArrowUp or "65,a" for 'a'.
ProcessBuilder pb = new ProcessBuilder(
"powershell", "-NoProfile", "-Command",
"$k=[Console]::ReadKey($true); Write-Host \"$($k.Key),$($k.KeyChar)\""
);
pb.redirectErrorStream(true);
Process proc = pb.start();
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(proc.getInputStream(), java.nio.charset.StandardCharsets.UTF_8));
String line = reader.readLine();
proc.waitFor();
if (line == null || line.isEmpty()) {
future.complete(null);
return;
}
int comma = line.indexOf(',');
if (comma < 0) {
future.complete(line);
return;
}
String vk = line.substring(0, comma).trim();
String ch = line.substring(comma + 1).trim();
switch (vk) {
case "UpArrow": future.complete("ArrowUp"); break;
case "DownArrow": future.complete("ArrowDown"); break;
case "LeftArrow": future.complete("ArrowLeft"); break;
case "RightArrow": future.complete("ArrowRight"); break;
case "Enter": future.complete("Enter"); break;
case "Escape": future.complete("Escape"); break;
case "Backspace": future.complete("Backspace"); break;
case "Tab": future.complete("Tab"); break;
case "Spacebar": future.complete(" "); break;
default:
// For regular characters, use the KeyChar value
if (ch.length() == 1 && ch.charAt(0) != 0) {
future.complete(ch);
} else {
future.complete(vk);
}
break;
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions be-js/src/commonMain/kotlin/lang/temper/be/js/JsBackend.kt
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,8 @@ class JsBackend private constructor(
filePath("float.js"),
filePath("int.js"),
filePath("interface.js"),
filePath("io.js"),
filePath("keyboard.js"),
filePath("listed.js"),
filePath("mapped.js"),
filePath("net.js"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,11 @@ private val supportedAutoConnecteds = setOf(
"String::toInt32",
"String::toInt64",
"StringBuilder::appendCodePoint",
// std/io
"stdSleep",
"stdReadLine",
// std/keyboard
"stdNextKeypress",
// std/net
"stdNetSend",
"NetResponse",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from "./int.js";
export * from "./interface.js";
export * from "./listed.js";
export * from "./mapped.js";
export * from "./io.js";
export * from "./net.js";
export * from "./pair.js";
export * from "./regex.js";
Expand Down
30 changes: 30 additions & 0 deletions be-js/src/commonMain/resources/lang/temper/be/js/temper-core/io.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { empty } from "./core.js";
import { createInterface } from "readline";

/**
* @param {number} ms
* @returns {Promise<Empty>}
*/
export function stdSleep(ms) {
return new Promise(resolve => setTimeout(() => resolve(empty()), ms));
}

/**
* @returns {Promise<string | null>}
*/
export function stdReadLine() {
return new Promise(resolve => {
if (typeof process !== 'undefined' && process.stdin) {
const rl = createInterface({ input: process.stdin });
rl.once('line', line => {
rl.close();
resolve(line);
});
rl.once('close', () => {
resolve(null); // EOF
});
} else {
resolve(null);
}
});
}
Loading