From ce951d2e10dd24136d60978e9aeddf669611c0ff Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 16 Feb 2026 07:51:59 +0000 Subject: [PATCH] first commit --- .gitattributes | 12 + .gitignore | 10 + README.md | 1 + buildSrc/build.gradle.kts | 8 + buildSrc/settings.gradle.kts | 9 + ...le.java-application-conventions.gradle.kts | 4 + .../gradle.java-common-conventions.gradle.kts | 30 +++ ...adle.java-container-conventions.gradle.kts | 8 + ...gradle.java-library-conventions.gradle.kts | 4 + gradle.properties | 5 + gradle/libs.versions.toml | 11 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 248 ++++++++++++++++++ gradlew.bat | 93 +++++++ .../prometeu-language-pbs/Cargo.toml | 9 + .../src/language_spec.rs | 16 ++ .../prometeu-language-pbs/src/lib.rs | 3 + .../prometeu-languages-registry/Cargo.toml | 11 + .../src/language_spec_registry.rs | 20 ++ .../prometeu-languages-registry/src/lib.rs | 3 + .../prometeu-build-pipeline/Cargo.toml | 24 ++ .../prometeu-build-pipeline/src/cli.rs | 158 +++++++++++ .../prometeu-build-pipeline/src/config.rs | 23 ++ .../prometeu-build-pipeline/src/ctx.rs | 71 +++++ .../src/emit_artifacts.rs | 17 ++ .../prometeu-build-pipeline/src/lib.rs | 12 + .../prometeu-build-pipeline/src/main.rs | 7 + .../src/phases/boot.rs | 12 + .../src/phases/emit.rs | 7 + .../src/phases/language.rs | 11 + .../src/phases/load_source.rs | 117 +++++++++ .../src/phases/lowering.rs | 6 + .../prometeu-build-pipeline/src/phases/mod.rs | 5 + .../prometeu-build-pipeline/src/pipeline.rs | 59 +++++ old-compiler/prometeu-core/Cargo.toml | 10 + old-compiler/prometeu-core/src/lib.rs | 3 + .../prometeu-core/src/source/diagnostics.rs | 81 ++++++ .../prometeu-core/src/source/file_db.rs | 69 +++++ old-compiler/prometeu-core/src/source/ids.rs | 60 +++++ .../prometeu-core/src/source/line_index.rs | 41 +++ old-compiler/prometeu-core/src/source/mod.rs | 13 + .../prometeu-core/src/source/name_interner.rs | 56 ++++ old-compiler/prometeu-core/src/source/span.rs | 39 +++ .../tests/source/file_db_line_index.rs | 69 +++++ .../prometeu-core/tests/source/span_tests.rs | 14 + old-compiler/prometeu-deps/Cargo.toml | 19 ++ old-compiler/prometeu-deps/src/lib.rs | 19 ++ .../prometeu-deps/src/load_sources.rs | 97 +++++++ .../prometeu-deps/src/model/build_stack.rs | 6 + .../prometeu-deps/src/model/cache_blobs.rs | 7 + .../prometeu-deps/src/model/cache_plan.rs | 4 + .../prometeu-deps/src/model/deps_config.rs | 7 + .../prometeu-deps/src/model/loaded_file.rs | 5 + .../prometeu-deps/src/model/loaded_sources.rs | 8 + .../prometeu-deps/src/model/manifest.rs | 75 ++++++ old-compiler/prometeu-deps/src/model/mod.rs | 11 + .../src/model/project_descriptor.rs | 14 + .../src/model/project_sources.rs | 8 + .../prometeu-deps/src/model/resolved_graph.rs | 16 ++ .../src/model/resolved_project.rs | 9 + .../prometeu-deps/src/workspace/host.rs | 32 +++ .../prometeu-deps/src/workspace/mod.rs | 6 + .../prometeu-deps/src/workspace/model.rs | 31 +++ .../src/workspace/phases/discover.rs | 131 +++++++++ .../src/workspace/phases/localize.rs | 62 +++++ .../src/workspace/phases/materialize.rs | 144 ++++++++++ .../prometeu-deps/src/workspace/phases/mod.rs | 10 + .../src/workspace/phases/policy.rs | 17 ++ .../src/workspace/phases/run_all.rs | 50 ++++ .../src/workspace/phases/stack.rs | 97 +++++++ .../src/workspace/phases/state.rs | 58 ++++ .../src/workspace/phases/validate.rs | 108 ++++++++ .../src/workspace/resolve_workspace.rs | 10 + old-compiler/prometeu-language-api/Cargo.toml | 10 + .../src/language_spec.rs | 21 ++ old-compiler/prometeu-language-api/src/lib.rs | 3 + old-compiler/prometeu-lowering/Cargo.toml | 19 ++ old-compiler/prometeu-lowering/src/lib.rs | 0 prometeu-compiler/build.gradle.kts | 7 + prometeu-infra/build.gradle.kts | 6 + prometeu-studio/build.gradle.kts | 20 ++ .../src/main/java/p/studio/App.java | 31 +++ .../src/main/java/p/studio/Container.java | 21 ++ .../java/p/studio/utilities/ThemeService.java | 12 + .../java/p/studio/utilities/i18n/I18n.java | 33 +++ .../p/studio/utilities/i18n/I18nService.java | 44 ++++ .../main/java/p/studio/window/MainView.java | 29 ++ .../main/java/p/studio/window/MenuBar.java | 36 +++ .../java/p/studio/window/WorkspaceBar.java | 47 ++++ .../workspaces/PlaceholderWorkspace.java | 20 ++ .../java/p/studio/workspaces/Workspace.java | 12 + .../p/studio/workspaces/WorkspaceHost.java | 45 ++++ .../java/p/studio/workspaces/WorkspaceId.java | 8 + .../workspaces/editor/EditorToolbar.java | 40 +++ .../workspaces/editor/EditorWorkspace.java | 34 +++ .../main/resources/i18n/messages.properties | 16 ++ .../resources/i18n/messages_pt_BR.properties | 16 ++ .../resources/themes/default-prometeu.css | 36 +++ .../java/p/studio/app/MessageUtilsTest.java | 14 + settings.gradle.kts | 9 + 101 files changed, 3146 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/settings.gradle.kts create mode 100644 buildSrc/src/main/kotlin/gradle.java-application-conventions.gradle.kts create mode 100644 buildSrc/src/main/kotlin/gradle.java-common-conventions.gradle.kts create mode 100644 buildSrc/src/main/kotlin/gradle.java-container-conventions.gradle.kts create mode 100644 buildSrc/src/main/kotlin/gradle.java-library-conventions.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 old-compiler/languages/prometeu-language-pbs/Cargo.toml create mode 100644 old-compiler/languages/prometeu-language-pbs/src/language_spec.rs create mode 100644 old-compiler/languages/prometeu-language-pbs/src/lib.rs create mode 100644 old-compiler/languages/prometeu-languages-registry/Cargo.toml create mode 100644 old-compiler/languages/prometeu-languages-registry/src/language_spec_registry.rs create mode 100644 old-compiler/languages/prometeu-languages-registry/src/lib.rs create mode 100644 old-compiler/prometeu-build-pipeline/Cargo.toml create mode 100644 old-compiler/prometeu-build-pipeline/src/cli.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/config.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/ctx.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/emit_artifacts.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/lib.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/main.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/phases/boot.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/phases/emit.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/phases/language.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/phases/load_source.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/phases/lowering.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/phases/mod.rs create mode 100644 old-compiler/prometeu-build-pipeline/src/pipeline.rs create mode 100644 old-compiler/prometeu-core/Cargo.toml create mode 100644 old-compiler/prometeu-core/src/lib.rs create mode 100644 old-compiler/prometeu-core/src/source/diagnostics.rs create mode 100644 old-compiler/prometeu-core/src/source/file_db.rs create mode 100644 old-compiler/prometeu-core/src/source/ids.rs create mode 100644 old-compiler/prometeu-core/src/source/line_index.rs create mode 100644 old-compiler/prometeu-core/src/source/mod.rs create mode 100644 old-compiler/prometeu-core/src/source/name_interner.rs create mode 100644 old-compiler/prometeu-core/src/source/span.rs create mode 100644 old-compiler/prometeu-core/tests/source/file_db_line_index.rs create mode 100644 old-compiler/prometeu-core/tests/source/span_tests.rs create mode 100644 old-compiler/prometeu-deps/Cargo.toml create mode 100644 old-compiler/prometeu-deps/src/lib.rs create mode 100644 old-compiler/prometeu-deps/src/load_sources.rs create mode 100644 old-compiler/prometeu-deps/src/model/build_stack.rs create mode 100644 old-compiler/prometeu-deps/src/model/cache_blobs.rs create mode 100644 old-compiler/prometeu-deps/src/model/cache_plan.rs create mode 100644 old-compiler/prometeu-deps/src/model/deps_config.rs create mode 100644 old-compiler/prometeu-deps/src/model/loaded_file.rs create mode 100644 old-compiler/prometeu-deps/src/model/loaded_sources.rs create mode 100644 old-compiler/prometeu-deps/src/model/manifest.rs create mode 100644 old-compiler/prometeu-deps/src/model/mod.rs create mode 100644 old-compiler/prometeu-deps/src/model/project_descriptor.rs create mode 100644 old-compiler/prometeu-deps/src/model/project_sources.rs create mode 100644 old-compiler/prometeu-deps/src/model/resolved_graph.rs create mode 100644 old-compiler/prometeu-deps/src/model/resolved_project.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/host.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/mod.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/model.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/phases/discover.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/phases/localize.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/phases/materialize.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/phases/mod.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/phases/policy.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/phases/run_all.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/phases/stack.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/phases/state.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/phases/validate.rs create mode 100644 old-compiler/prometeu-deps/src/workspace/resolve_workspace.rs create mode 100644 old-compiler/prometeu-language-api/Cargo.toml create mode 100644 old-compiler/prometeu-language-api/src/language_spec.rs create mode 100644 old-compiler/prometeu-language-api/src/lib.rs create mode 100644 old-compiler/prometeu-lowering/Cargo.toml create mode 100644 old-compiler/prometeu-lowering/src/lib.rs create mode 100644 prometeu-compiler/build.gradle.kts create mode 100644 prometeu-infra/build.gradle.kts create mode 100644 prometeu-studio/build.gradle.kts create mode 100644 prometeu-studio/src/main/java/p/studio/App.java create mode 100644 prometeu-studio/src/main/java/p/studio/Container.java create mode 100644 prometeu-studio/src/main/java/p/studio/utilities/ThemeService.java create mode 100644 prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java create mode 100644 prometeu-studio/src/main/java/p/studio/utilities/i18n/I18nService.java create mode 100644 prometeu-studio/src/main/java/p/studio/window/MainView.java create mode 100644 prometeu-studio/src/main/java/p/studio/window/MenuBar.java create mode 100644 prometeu-studio/src/main/java/p/studio/window/WorkspaceBar.java create mode 100644 prometeu-studio/src/main/java/p/studio/workspaces/PlaceholderWorkspace.java create mode 100644 prometeu-studio/src/main/java/p/studio/workspaces/Workspace.java create mode 100644 prometeu-studio/src/main/java/p/studio/workspaces/WorkspaceHost.java create mode 100644 prometeu-studio/src/main/java/p/studio/workspaces/WorkspaceId.java create mode 100644 prometeu-studio/src/main/java/p/studio/workspaces/editor/EditorToolbar.java create mode 100644 prometeu-studio/src/main/java/p/studio/workspaces/editor/EditorWorkspace.java create mode 100644 prometeu-studio/src/main/resources/i18n/messages.properties create mode 100644 prometeu-studio/src/main/resources/i18n/messages_pt_BR.properties create mode 100644 prometeu-studio/src/main/resources/themes/default-prometeu.css create mode 100644 prometeu-studio/src/test/java/p/studio/app/MessageUtilsTest.java create mode 100644 settings.gradle.kts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..f91f6460 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,12 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + +# Binary files should be left untouched +*.jar binary + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f81771b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Ignore Gradle project-specific cache directory +.gradle +.idea +.output.txt + +# Ignore Gradle build output directory +build + +# Ignore Kotlin plugin data +.kotlin diff --git a/README.md b/README.md new file mode 100644 index 00000000..5af8c4aa --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Prometeu Studio \ No newline at end of file diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 00000000..e4a377cb --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + `kotlin-dsl` +} + +repositories { + gradlePluginPortal() + mavenCentral() +} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 00000000..105225c6 --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,9 @@ +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} + +rootProject.name = "buildSrc" diff --git a/buildSrc/src/main/kotlin/gradle.java-application-conventions.gradle.kts b/buildSrc/src/main/kotlin/gradle.java-application-conventions.gradle.kts new file mode 100644 index 00000000..50e22f6d --- /dev/null +++ b/buildSrc/src/main/kotlin/gradle.java-application-conventions.gradle.kts @@ -0,0 +1,4 @@ +plugins { + id("gradle.java-common-conventions") + application +} diff --git a/buildSrc/src/main/kotlin/gradle.java-common-conventions.gradle.kts b/buildSrc/src/main/kotlin/gradle.java-common-conventions.gradle.kts new file mode 100644 index 00000000..f2f60c15 --- /dev/null +++ b/buildSrc/src/main/kotlin/gradle.java-common-conventions.gradle.kts @@ -0,0 +1,30 @@ +plugins { + java +} + +repositories { + mavenCentral() +} + +dependencies { + constraints { + } + + compileOnly("org.projectlombok:lombok:1.18.32") + annotationProcessor("org.projectlombok:lombok:1.18.32") + testCompileOnly("org.projectlombok:lombok:1.18.32") + testAnnotationProcessor("org.projectlombok:lombok:1.18.32") + + testImplementation("org.junit.jupiter:junit-jupiter:5.12.1") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +tasks.named("test") { + useJUnitPlatform() +} diff --git a/buildSrc/src/main/kotlin/gradle.java-container-conventions.gradle.kts b/buildSrc/src/main/kotlin/gradle.java-container-conventions.gradle.kts new file mode 100644 index 00000000..fb65844c --- /dev/null +++ b/buildSrc/src/main/kotlin/gradle.java-container-conventions.gradle.kts @@ -0,0 +1,8 @@ +plugins { + id("gradle.java-library-conventions") +} + +dependencies { + implementation("com.google.dagger:dagger:2.50") + annotationProcessor("com.google.dagger:dagger-compiler:2.50") +} diff --git a/buildSrc/src/main/kotlin/gradle.java-library-conventions.gradle.kts b/buildSrc/src/main/kotlin/gradle.java-library-conventions.gradle.kts new file mode 100644 index 00000000..02a7b1be --- /dev/null +++ b/buildSrc/src/main/kotlin/gradle.java-library-conventions.gradle.kts @@ -0,0 +1,4 @@ +plugins { + id("gradle.java-common-conventions") + `java-library` +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..8cea2f00 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +# This file was generated by the Gradle 'init' task. +# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties + +org.gradle.configuration-cache=false + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..d6d65fa5 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,11 @@ +[versions] +javafx = "23.0.2" +richtextfx = "0.11.2" + +[libraries] +javafx-controls = { group = "org.openjfx", name = "javafx-controls", version.ref = "javafx" } +javafx-fxml = { group = "org.openjfx", name = "javafx-fxml", version.ref = "javafx" } +richtextfx = { group = "org.fxmisc.richtext", name = "richtextfx", version.ref = "richtextfx" } + +[plugins] +javafx = { id = "org.openjfx.javafxplugin", version = "0.1.0" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..61285a659d17295f1de7c53e24fdf13ad755c379 GIT binary patch literal 46175 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

pVX@HRij*jw$g4?}Pn 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..37f78a6a --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..adff685a --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..c4bdd3ab --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/old-compiler/languages/prometeu-language-pbs/Cargo.toml b/old-compiler/languages/prometeu-language-pbs/Cargo.toml new file mode 100644 index 00000000..d09a2da5 --- /dev/null +++ b/old-compiler/languages/prometeu-language-pbs/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "prometeu-language-pbs" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "" + +[dependencies] +prometeu-language-api = { path = "../../prometeu-language-api" } \ No newline at end of file diff --git a/old-compiler/languages/prometeu-language-pbs/src/language_spec.rs b/old-compiler/languages/prometeu-language-pbs/src/language_spec.rs new file mode 100644 index 00000000..82b20ca0 --- /dev/null +++ b/old-compiler/languages/prometeu-language-pbs/src/language_spec.rs @@ -0,0 +1,16 @@ +use std::sync::OnceLock; +use prometeu_language_api::{LanguageSpec, SourcePolicy}; + +pub static LANGUAGE_SPEC: OnceLock = OnceLock::new(); + +fn registry() -> &'static LanguageSpec { + LANGUAGE_SPEC.get_or_init(|| { + LanguageSpec { + id: "pbs", + source_policy: SourcePolicy { + extensions: vec!["pbs"], + case_sensitive: true, + }, + } + }) +} \ No newline at end of file diff --git a/old-compiler/languages/prometeu-language-pbs/src/lib.rs b/old-compiler/languages/prometeu-language-pbs/src/lib.rs new file mode 100644 index 00000000..046a7b10 --- /dev/null +++ b/old-compiler/languages/prometeu-language-pbs/src/lib.rs @@ -0,0 +1,3 @@ +mod language_spec; + +pub use language_spec::LANGUAGE_SPEC; \ No newline at end of file diff --git a/old-compiler/languages/prometeu-languages-registry/Cargo.toml b/old-compiler/languages/prometeu-languages-registry/Cargo.toml new file mode 100644 index 00000000..8950c14d --- /dev/null +++ b/old-compiler/languages/prometeu-languages-registry/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "prometeu-languages-registry" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "" + +[dependencies] +prometeu-language-api = { path = "../../prometeu-language-api" } + +prometeu-language-pbs = { path = "../prometeu-language-pbs" } \ No newline at end of file diff --git a/old-compiler/languages/prometeu-languages-registry/src/language_spec_registry.rs b/old-compiler/languages/prometeu-languages-registry/src/language_spec_registry.rs new file mode 100644 index 00000000..98e7569d --- /dev/null +++ b/old-compiler/languages/prometeu-languages-registry/src/language_spec_registry.rs @@ -0,0 +1,20 @@ +use prometeu_language_api::LanguageSpec; +use std::collections::HashMap; +use std::sync::OnceLock; + +use prometeu_language_pbs::LANGUAGE_SPEC as PBS_LANGUAGE_SPEC; + +static REGISTRY: OnceLock> = OnceLock::new(); + +fn registry() -> &'static HashMap<&'static str, LanguageSpec> { + let pbs = PBS_LANGUAGE_SPEC.get().unwrap(); + REGISTRY.get_or_init(|| { + HashMap::from([ + (pbs.id, pbs.clone()), + ]) + }) +} + +pub fn get_language_spec(id: &str) -> Option<&LanguageSpec> { + registry().get(id) +} diff --git a/old-compiler/languages/prometeu-languages-registry/src/lib.rs b/old-compiler/languages/prometeu-languages-registry/src/lib.rs new file mode 100644 index 00000000..f9cca34d --- /dev/null +++ b/old-compiler/languages/prometeu-languages-registry/src/lib.rs @@ -0,0 +1,3 @@ +mod language_spec_registry; + +pub use language_spec_registry::get_language_spec; \ No newline at end of file diff --git a/old-compiler/prometeu-build-pipeline/Cargo.toml b/old-compiler/prometeu-build-pipeline/Cargo.toml new file mode 100644 index 00000000..d2beffa2 --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "prometeu-build-pipeline" +version = "0.1.0" +edition = "2021" +license.workspace = true +repository.workspace = true + +[[bin]] +name = "prometeu-build-pipeline" +path = "src/main.rs" + +[package.metadata.dist] +dist = true +include = ["../../VERSION.txt"] + +[dependencies] +prometeu-deps = { path = "../prometeu-deps" } +prometeu-core = { path = "../prometeu-core" } +prometeu-languages-registry = { path = "../languages/prometeu-languages-registry" } +clap = { version = "4.5.54", features = ["derive"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +anyhow = "1.0.100" +camino = "1.2.2" diff --git a/old-compiler/prometeu-build-pipeline/src/cli.rs b/old-compiler/prometeu-build-pipeline/src/cli.rs new file mode 100644 index 00000000..397fb7bb --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/cli.rs @@ -0,0 +1,158 @@ +use crate::pipeline::run_phases; +use crate::{BuildMode, PipelineConfig, PipelineInput, PipelineOutput}; +use anyhow::{Context, Result}; +use clap::{Parser, Subcommand}; +use prometeu_deps::{load_sources, resolve_workspace, DepsConfig}; +use std::path::{Path, PathBuf}; +use camino::Utf8Path; +use crate::emit_artifacts::{emit_artifacts, EmitOptions}; + +/// Command line interface for the Prometeu Compiler. +#[derive(Parser)] +#[command(name = "prometeu")] +#[command(version, about = "PROMETEU toolchain entrypoint", long_about = None)] +pub struct Cli { + #[command(subcommand)] + pub command: Commands, +} + +/// Available subcommands for the compiler. +#[derive(Subcommand)] +pub enum Commands { + /// Builds a Prometeu project by compiling source code into an artifact (pbc/program image). + Build { + /// Path to the project root directory. + project_dir: PathBuf, + + /// Path to save the compiled artifact. + /// If omitted, deps/pipeline decide a default under target/ or dist/. + #[arg(short, long)] + out: Option, + + /// Whether to generate a .json symbols file for source mapping. + #[arg(long, default_value_t = true)] + emit_symbols: bool, + + /// Whether to generate a .disasm file for debugging. + #[arg(long, default_value_t = true)] + emit_disasm: bool, + + /// Whether to explain the dependency resolution process. + #[arg(long)] + explain_deps: bool, + + /// Build mode (debug/release). + #[arg(long, default_value = "debug")] + mode: String, + }, + + /// Verifies if a Prometeu project is valid without emitting code. + Verify { + project_dir: PathBuf, + + /// Whether to explain the dependency resolution process. + #[arg(long)] + explain_deps: bool, + }, +} + +pub fn run() -> Result<()> { + let cli = Cli::parse(); + + match cli.command { + Commands::Build { + project_dir, + out, + emit_disasm, + emit_symbols, + explain_deps, + mode, + } => { + let build_mode = parse_mode(&mode)?; + let cfg = PipelineConfig { + mode: build_mode, + enable_cache: true, + enable_frontends: false, + }; + + let pipeline_output = run_pipeline(cfg, &project_dir, explain_deps) + .context("pipeline: failed to execute pipeline")?; + + for diagnostics in &pipeline_output.diagnostics { + eprintln!("{:?}", diagnostics); + } + + let emit_opts = EmitOptions { + out, + emit_symbols, + emit_disasm, + }; + + emit_artifacts(&emit_opts, &pipeline_output) + .context("emit: failed to write artifacts")?; + + if pipeline_output.diagnostics.iter().any(|d| d.severity.is_error()) { + anyhow::bail!("build failed due to errors"); + } + } + + Commands::Verify { + project_dir, + explain_deps, + } => { + let cfg = PipelineConfig { + mode: BuildMode::Test, + enable_cache: true, + enable_frontends: false, + }; + + let pipeline_output = run_pipeline(cfg, &project_dir, explain_deps) + .context("pipeline: failed to execute pipeline")?; + + for diagnostic in &pipeline_output.diagnostics { + eprintln!("{:?}", diagnostic); + } + + if pipeline_output.diagnostics.iter().any(|d| d.severity.is_error()) { + anyhow::bail!("verify failed due to errors"); + } + } + } + + Ok(()) +} + + +fn run_pipeline(cfg: PipelineConfig, project_dir: &Path, explain_deps: bool) -> Result { + let deps_cfg = DepsConfig { + explain: explain_deps, + cache_dir: Default::default(), + registry_dirs: vec![], + }; + + let project_dir_path_buf = Utf8Path::from_path(project_dir) + .with_context(|| format!("deps: failed to convert project_dir to Utf8Path: {:?}", project_dir))?; + let resolved = resolve_workspace(&deps_cfg, project_dir_path_buf) + .with_context(|| format!("deps: failed to resolve project at {:?}", project_dir))?; + + let sources = load_sources(&deps_cfg, &resolved) + .context("deps: failed to load sources")?; + + let input = PipelineInput { + graph: resolved.graph, + stack: resolved.stack, + sources + }; + + Ok(run_phases(cfg, input)) +} + +/// Parse `--mode` from CLI. +fn parse_mode(s: &str) -> Result { + match s.to_ascii_lowercase().as_str() { + "debug" => Ok(BuildMode::Debug), + "release" => Ok(BuildMode::Release), + "test" => Ok(BuildMode::Test), + other => anyhow::bail!("invalid --mode '{}': expected debug|release|test", other), + } +} diff --git a/old-compiler/prometeu-build-pipeline/src/config.rs b/old-compiler/prometeu-build-pipeline/src/config.rs new file mode 100644 index 00000000..8cf9b38c --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/config.rs @@ -0,0 +1,23 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum BuildMode { + Debug, + Release, + Test, +} + +#[derive(Debug, Clone)] +pub struct PipelineConfig { + pub mode: BuildMode, + pub enable_cache: bool, + pub enable_frontends: bool, +} + +impl Default for PipelineConfig { + fn default() -> Self { + Self { + mode: BuildMode::Debug, + enable_cache: true, + enable_frontends: false, // Hard Reset default: pipeline runs with no FE. + } + } +} diff --git a/old-compiler/prometeu-build-pipeline/src/ctx.rs b/old-compiler/prometeu-build-pipeline/src/ctx.rs new file mode 100644 index 00000000..7e4097ec --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/ctx.rs @@ -0,0 +1,71 @@ +use std::any::Any; +use prometeu_core::{Diagnostic, FileDB, FileId, NameInterner, ProjectId}; +use prometeu_deps::BuildStack; + +/// Per-project arena slot created from the BuildStack order. +/// The pipeline owns this vector and indexes it by stack position. +#[derive(Debug)] +pub struct ProjectCtx { + pub project_id: ProjectId, + + /// FileIds inserted into `source_db` for this project. + pub files: Vec, + + /// Frontend output (TypedHIRBundle or similar) - intentionally opaque. + pub frontend_out: Option>, + + /// Backend output (ProgramImage / BytecodeModule / Artifact). + /// Keep as opaque until you finalize your bytecode/image crate. + pub backend_out: Option>, +} + +impl ProjectCtx { + pub fn new(project_id: ProjectId) -> Self { + Self { + project_id, + files: Vec::new(), + frontend_out: None, + backend_out: None, + } + } +} + +#[derive(Debug)] +pub struct PipelineCtx { + pub source_db: FileDB, + pub interner: NameInterner, + pub diagnostics: Vec, + pub projects: Vec, +} + +impl PipelineCtx { + pub fn new() -> Self { + Self { + source_db: FileDB::new(), + interner: NameInterner::new(), + diagnostics: Vec::new(), + projects: Vec::new(), + } + } + + pub fn push_diagnostic(&mut self, d: Diagnostic) { + self.diagnostics.push(d); + } + + /// Initialize per-project contexts from the BuildStack order. + pub fn init_projects_from_stack(&mut self, stack: &BuildStack) { + self.projects.clear(); + self.projects.reserve(stack.projects.len()); + for project_id in &stack.projects { + self.projects.push(ProjectCtx::new(project_id.clone())); + } + } + + pub fn project_ctx_mut(&mut self, index_in_stack: usize) -> &mut ProjectCtx { + &mut self.projects[index_in_stack] + } + + pub fn project_ctx(&self, index_in_stack: usize) -> &ProjectCtx { + &self.projects[index_in_stack] + } +} diff --git a/old-compiler/prometeu-build-pipeline/src/emit_artifacts.rs b/old-compiler/prometeu-build-pipeline/src/emit_artifacts.rs new file mode 100644 index 00000000..ce6f601f --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/emit_artifacts.rs @@ -0,0 +1,17 @@ +use std::path::PathBuf; +use crate::PipelineOutput; + +pub struct EmitOptions { + pub(crate) out: Option, + pub(crate) emit_symbols: bool, + pub(crate) emit_disasm: bool, +} + +pub fn emit_artifacts(_opts: &EmitOptions, _outp: &PipelineOutput) -> anyhow::Result<()> { + // Later: + // - decide output dir (opts.out or default) + // - write .pbc / program image + // - write symbols.json (if exists) + // - write disasm (if exists) + Ok(()) +} \ No newline at end of file diff --git a/old-compiler/prometeu-build-pipeline/src/lib.rs b/old-compiler/prometeu-build-pipeline/src/lib.rs new file mode 100644 index 00000000..9f0cb56b --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/lib.rs @@ -0,0 +1,12 @@ +pub mod cli; +pub mod config; +pub mod ctx; +pub mod pipeline; +pub mod phases; +mod emit_artifacts; + +pub use config::*; +pub use ctx::*; +pub use pipeline::*; +pub use cli::run; + diff --git a/old-compiler/prometeu-build-pipeline/src/main.rs b/old-compiler/prometeu-build-pipeline/src/main.rs new file mode 100644 index 00000000..b8db3c41 --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/main.rs @@ -0,0 +1,7 @@ +use anyhow::Result; + +/// Main entry point for the Prometeu Compiler binary. +/// It delegates execution to the library's `run` function. +fn main() -> Result<()> { + prometeu_build_pipeline::run() +} diff --git a/old-compiler/prometeu-build-pipeline/src/phases/boot.rs b/old-compiler/prometeu-build-pipeline/src/phases/boot.rs new file mode 100644 index 00000000..fbb0a164 --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/phases/boot.rs @@ -0,0 +1,12 @@ +use crate::{ + config::PipelineConfig, + ctx::PipelineCtx, + pipeline::{PipelineInput}, +}; + +pub fn run(_cfg: &PipelineConfig, input: &PipelineInput, ctx: &mut PipelineCtx) { + // Arena init: one ProjectCtx per project in stack order. + ctx.init_projects_from_stack(&input.stack); + + // NOTE: no filesystem, no FE/BE assumptions here. +} diff --git a/old-compiler/prometeu-build-pipeline/src/phases/emit.rs b/old-compiler/prometeu-build-pipeline/src/phases/emit.rs new file mode 100644 index 00000000..704f8298 --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/phases/emit.rs @@ -0,0 +1,7 @@ +use crate::{config::PipelineConfig, ctx::PipelineCtx, pipeline::{Artifacts, PipelineInput}}; + +pub fn run(_cfg: &PipelineConfig, _input: &PipelineInput, _ctx: &mut PipelineCtx) -> Artifacts { + // Hard Reset stub: + // - later: emit build outputs (to FS via deps if you want strict IO centralization). + Artifacts::default() +} diff --git a/old-compiler/prometeu-build-pipeline/src/phases/language.rs b/old-compiler/prometeu-build-pipeline/src/phases/language.rs new file mode 100644 index 00000000..06318e91 --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/phases/language.rs @@ -0,0 +1,11 @@ +use crate::{config::PipelineConfig, ctx::PipelineCtx, pipeline::PipelineInput}; + +pub fn run(cfg: &PipelineConfig, _input: &PipelineInput, _ctx: &mut PipelineCtx) { + if !cfg.enable_frontends { + return; + } + + // Hard Reset: + // - no FE wired yet. + // - later: iterate projects in stack order and call FE plugin(s). +} diff --git a/old-compiler/prometeu-build-pipeline/src/phases/load_source.rs b/old-compiler/prometeu-build-pipeline/src/phases/load_source.rs new file mode 100644 index 00000000..785abc07 --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/phases/load_source.rs @@ -0,0 +1,117 @@ +use prometeu_core::{Diagnostic, Severity, Span}; +use prometeu_deps::LoadedSources; +use crate::{ + config::PipelineConfig, + ctx::PipelineCtx, + pipeline::PipelineInput, +}; + +pub fn run(_cfg: &PipelineConfig, input: &PipelineInput, ctx: &mut PipelineCtx) { + load_sources(&input.sources, ctx); + + for i in 0..ctx.projects.len() { + let is_empty = ctx.projects[i].files.is_empty(); + + if is_empty { + let project_id = &input.stack.projects[i]; + let project_name = input.graph.project(project_id).unwrap().name.clone(); + + ctx.push_diagnostic(Diagnostic { + severity: Severity::Warning, + code: "PIPELINE_NO_SOURCES".into(), + message: format!( + "Project '{}' has no source files loaded.", + project_name + ), + span: Span::none(), + related: vec![], + }); + } + } +} + +fn load_sources(sources: &LoadedSources, ctx: &mut PipelineCtx) { + let stack_len = ctx.projects.len(); + let src_len = sources.per_project.len(); + + // 1) Diagnostic is sizes don't match + if src_len != stack_len { + ctx.push_diagnostic(Diagnostic { + severity: Severity::Error, + code: "PIPELINE_SOURCES_STACK_LEN_MISMATCH".into(), + message: format!( + "LoadedSources.per_project len ({}) does not match BuildStack len ({}).", + src_len, stack_len + ), + span: Span::none(), + related: vec![], + }); + } + + // 2) Process the bare minimum (don't panic, just keep running with diagnostics) + let n = stack_len.min(src_len); + + for i in 0..n { + let expected = ctx.projects[i].project_id; + let got = sources.per_project[i].project_id; + + if got != expected { + ctx.push_diagnostic(Diagnostic { + severity: Severity::Error, + code: "PIPELINE_SOURCES_STACK_ORDER_MISMATCH".into(), + message: format!( + "LoadedSources is not aligned with BuildStack at index {}: expected project_id {:?}, got {:?}.", + i, expected, got + ), + span: Span::none(), + related: vec![], + }); + + // there is no fix tolerance here, if it is wrong, it is wrong + // just catch as much diagnostics as possible before "crashing" + continue; + } + + for f in &sources.per_project[i].files { + let file_id = ctx.source_db.upsert(&f.uri, &f.text); + ctx.projects[i].files.push(file_id); + } + } + + // 3) If any LoadSources remains, it is a deps bug + if src_len > stack_len { + for extra in &sources.per_project[stack_len..] { + ctx.push_diagnostic(Diagnostic { + severity: Severity::Error, + code: "PIPELINE_SOURCES_EXTRA_PROJECT".into(), + message: format!( + "LoadedSources contains extra project_id {:?} not present in BuildStack.", + extra.project_id + ), + span: Span::none(), + related: vec![], + }); + } + } + + // 4) If missing inputs, it is another deps bug... + if stack_len > src_len { + let mut diagnostics: Vec = Vec::new(); + for missing in &ctx.projects[src_len..] { + diagnostics.push(Diagnostic { + severity: Severity::Error, + code: "PIPELINE_SOURCES_MISSING_PROJECT".into(), + message: format!( + "LoadedSources missing sources for project_id {:?} present in BuildStack.", + missing.project_id + ), + span: Span::none(), + related: vec![], + }); + } + for diagnostic in diagnostics { + ctx.push_diagnostic(diagnostic); + } + } +} + diff --git a/old-compiler/prometeu-build-pipeline/src/phases/lowering.rs b/old-compiler/prometeu-build-pipeline/src/phases/lowering.rs new file mode 100644 index 00000000..305cc8f1 --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/phases/lowering.rs @@ -0,0 +1,6 @@ +use crate::{config::PipelineConfig, ctx::PipelineCtx, pipeline::PipelineInput}; + +pub fn run(_cfg: &PipelineConfig, _input: &PipelineInput, _ctx: &mut PipelineCtx) { + // Hard Reset stub: + // - later: consume TypedHIRBundle(s) and lower into ProgramImage/BytecodeModule. +} diff --git a/old-compiler/prometeu-build-pipeline/src/phases/mod.rs b/old-compiler/prometeu-build-pipeline/src/phases/mod.rs new file mode 100644 index 00000000..25bb69e6 --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/phases/mod.rs @@ -0,0 +1,5 @@ +pub mod boot; +pub mod load_source; +pub mod language; +pub mod lowering; +pub mod emit; diff --git a/old-compiler/prometeu-build-pipeline/src/pipeline.rs b/old-compiler/prometeu-build-pipeline/src/pipeline.rs new file mode 100644 index 00000000..b8c5d73a --- /dev/null +++ b/old-compiler/prometeu-build-pipeline/src/pipeline.rs @@ -0,0 +1,59 @@ +use crate::{config::PipelineConfig, ctx::PipelineCtx, phases}; +use prometeu_core::Diagnostic; +use prometeu_deps::{BuildStack, LoadedSources, ResolvedGraph}; + +#[derive(Debug, Clone)] +pub struct PipelineInput { + pub graph: ResolvedGraph, + pub stack: BuildStack, + pub sources: LoadedSources +} + +#[derive(Debug, Default, Clone)] +pub struct PipelineStats { + pub projects_count: usize, + pub files_count: usize, +} + +#[derive(Debug, Default, Clone)] +pub struct Artifacts { + // placeholder: later include produced ProgramImage(s), debug bundles, logs, etc. +} + +#[derive(Debug, Default)] +pub struct PipelineOutput { + pub diagnostics: Vec, + pub artifacts: Artifacts, + pub stats: PipelineStats, +} + +pub(crate) fn run_phases(cfg: PipelineConfig, input: PipelineInput) -> PipelineOutput { + let mut ctx = PipelineCtx::new(); + + // Boot: create project slots in arena order. + phases::boot::run(&cfg, &input, &mut ctx); + + // Load source: populate FileDB from LoadedSources. + phases::load_source::run(&cfg, &input, &mut ctx); + + // Frontend phase (stub / optional). + phases::language::run(&cfg, &input, &mut ctx); + + // Backend phase (stub). + phases::lowering::run(&cfg, &input, &mut ctx); + + // Emit phase (stub). + let artifacts = phases::emit::run(&cfg, &input, &mut ctx); + + // Stats (basic). + let mut stats = PipelineStats::default(); + stats.projects_count = ctx.projects.len(); + stats.files_count = ctx.projects.iter().map(|p| p.files.len()).sum(); + + PipelineOutput { + diagnostics: ctx.diagnostics, + artifacts, + stats, + } +} + diff --git a/old-compiler/prometeu-core/Cargo.toml b/old-compiler/prometeu-core/Cargo.toml new file mode 100644 index 00000000..da0e0cf8 --- /dev/null +++ b/old-compiler/prometeu-core/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "prometeu-core" +version = "0.1.0" +edition = "2024" +license.workspace = true + +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +prometeu-bytecode = { path = "../prometeu-bytecode" } diff --git a/old-compiler/prometeu-core/src/lib.rs b/old-compiler/prometeu-core/src/lib.rs new file mode 100644 index 00000000..07f283b9 --- /dev/null +++ b/old-compiler/prometeu-core/src/lib.rs @@ -0,0 +1,3 @@ +mod source; + +pub use source::*; \ No newline at end of file diff --git a/old-compiler/prometeu-core/src/source/diagnostics.rs b/old-compiler/prometeu-core/src/source/diagnostics.rs new file mode 100644 index 00000000..bde61425 --- /dev/null +++ b/old-compiler/prometeu-core/src/source/diagnostics.rs @@ -0,0 +1,81 @@ +use serde::{Serialize, Serializer}; +use crate::Span; + +#[derive(Debug, Clone, PartialEq)] +pub enum Severity { + Error, + Warning, +} + +impl Severity { + pub fn is_error(&self) -> bool { + match self { + Severity::Error => true, + Severity::Warning => false, + } + } +} + +impl Serialize for Severity { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Severity::Error => serializer.serialize_str("error"), + Severity::Warning => serializer.serialize_str("warning"), + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct Diagnostic { + pub severity: Severity, + pub code: String, + pub message: String, + pub span: Span, + pub related: Vec<(String, Span)>, +} + +#[derive(Debug, Clone, Serialize)] +pub struct DiagnosticBundle { + pub diagnostics: Vec, +} + +impl DiagnosticBundle { + pub fn new() -> Self { + Self { + diagnostics: Vec::new(), + } + } + + pub fn push(&mut self, diagnostic: Diagnostic) { + self.diagnostics.push(diagnostic); + } + + pub fn error(code: &str, message: String, span: Span) -> Self { + let mut bundle = Self::new(); + bundle.push(Diagnostic { + severity: Severity::Error, + code: code.to_string(), + message, + span, + related: Vec::new(), + }); + bundle + } + + pub fn has_errors(&self) -> bool { + self.diagnostics + .iter() + .any(|d| matches!(d.severity, Severity::Error)) + } +} + +impl From for DiagnosticBundle { + fn from(diagnostic: Diagnostic) -> Self { + let mut bundle = Self::new(); + bundle.push(diagnostic); + bundle + } +} diff --git a/old-compiler/prometeu-core/src/source/file_db.rs b/old-compiler/prometeu-core/src/source/file_db.rs new file mode 100644 index 00000000..dbc9f698 --- /dev/null +++ b/old-compiler/prometeu-core/src/source/file_db.rs @@ -0,0 +1,69 @@ +use std::collections::HashMap; +use crate::FileId; +use crate::LineIndex; + +#[derive(Default, Debug)] +pub struct FileDB { + files: Vec, + uri_to_id: HashMap, +} + +#[derive(Debug)] +struct FileData { + uri: String, + text: String, + line_index: LineIndex, +} + +impl FileDB { + pub fn new() -> Self { + Self { + files: Vec::new(), + uri_to_id: HashMap::new(), + } + } + + pub fn upsert(&mut self, uri: &str, text: &str) -> FileId { + if let Some(&id) = self.uri_to_id.get(uri) { + let line_index = LineIndex::new(&text); + self.files[id.0 as usize] = FileData { + uri: uri.to_owned(), + text: text.to_owned(), + line_index, + }; + id + } else { + let id = FileId(self.files.len() as u32); + let line_index = LineIndex::new(&text); + self.files.push(FileData { + uri: uri.to_owned(), + text: text.to_owned(), + line_index, + }); + self.uri_to_id.insert(uri.to_string(), id); + id + } + } + + pub fn file_id(&self, uri: &str) -> Option { + self.uri_to_id.get(uri).copied() + } + + pub fn uri(&self, id: FileId) -> &str { + &self.files[id.0 as usize].uri + } + + pub fn text(&self, id: FileId) -> &str { + &self.files[id.0 as usize].text + } + + pub fn line_index(&self, id: FileId) -> &LineIndex { + &self.files[id.0 as usize].line_index + } + + /// Returns a list of all known file IDs in insertion order. + pub fn all_files(&self) -> Vec { + (0..self.files.len()).map(|i| FileId(i as u32)).collect() + } +} + diff --git a/old-compiler/prometeu-core/src/source/ids.rs b/old-compiler/prometeu-core/src/source/ids.rs new file mode 100644 index 00000000..d7965e07 --- /dev/null +++ b/old-compiler/prometeu-core/src/source/ids.rs @@ -0,0 +1,60 @@ +macro_rules! define_id { + ($name:ident) => { + #[repr(transparent)] + #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, serde::Serialize, serde::Deserialize)] + pub struct $name(pub u32); + + impl $name { + pub const NONE: $name = $name(u32::MAX); + + #[inline] + pub const fn as_u32(self) -> u32 { self.0 } + + #[inline] + pub fn is_none(self) -> bool { + self == $name::NONE + } + } + + impl From for $name { + #[inline] + fn from(value: u32) -> Self { Self(value) } + } + + impl From<$name> for u32 { + #[inline] + fn from(value: $name) -> Self { value.0 } + } + }; +} + +define_id!(FileId); +define_id!(NodeId); +define_id!(NameId); +define_id!(SymbolId); +define_id!(TypeId); +define_id!(ModuleId); +define_id!(ProjectId); + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use std::mem::size_of; + + #[test] + fn ids_are_repr_transparent_and_hashable() { + assert_eq!(size_of::(), 4); + assert_eq!(size_of::(), 4); + assert_eq!(size_of::(), 4); + assert_eq!(size_of::(), 4); + assert_eq!(size_of::(), 4); + assert_eq!(size_of::(), 4); + assert_eq!(size_of::(), 4); + + // Hash/Eq usage + let mut m: HashMap = HashMap::new(); + m.insert(SymbolId(1), "one"); + assert_eq!(m.get(&SymbolId(1)).copied(), Some("one")); + } +} diff --git a/old-compiler/prometeu-core/src/source/line_index.rs b/old-compiler/prometeu-core/src/source/line_index.rs new file mode 100644 index 00000000..87d98dae --- /dev/null +++ b/old-compiler/prometeu-core/src/source/line_index.rs @@ -0,0 +1,41 @@ +#[derive(Debug)] +pub struct LineIndex { + line_starts: Vec, + total_len: u32, +} + +impl LineIndex { + pub fn new(text: &str) -> Self { + let mut line_starts = vec![0]; + for (offset, c) in text.char_indices() { + if c == '\n' { + line_starts.push((offset + 1) as u32); + } + } + Self { + line_starts, + total_len: text.len() as u32, + } + } + + pub fn offset_to_line_col(&self, offset: u32) -> (u32, u32) { + let line = match self.line_starts.binary_search(&offset) { + Ok(line) => line as u32, + Err(line) => (line - 1) as u32, + }; + let col = offset - self.line_starts[line as usize]; + (line, col) + } + + pub fn line_col_to_offset(&self, line: u32, col: u32) -> Option { + let start = *self.line_starts.get(line as usize)?; + let offset = start + col; + + let next_start = self.line_starts.get(line as usize + 1).copied().unwrap_or(self.total_len); + if offset < next_start || (offset == next_start && offset == self.total_len) { + Some(offset) + } else { + None + } + } +} diff --git a/old-compiler/prometeu-core/src/source/mod.rs b/old-compiler/prometeu-core/src/source/mod.rs new file mode 100644 index 00000000..69551f4a --- /dev/null +++ b/old-compiler/prometeu-core/src/source/mod.rs @@ -0,0 +1,13 @@ +mod ids; +mod span; +mod file_db; +mod name_interner; +mod diagnostics; +mod line_index; + +pub use ids::*; +pub use span::Span; +pub use file_db::FileDB; +pub use line_index::LineIndex; +pub use name_interner::NameInterner; +pub use diagnostics::*; \ No newline at end of file diff --git a/old-compiler/prometeu-core/src/source/name_interner.rs b/old-compiler/prometeu-core/src/source/name_interner.rs new file mode 100644 index 00000000..1381ee2a --- /dev/null +++ b/old-compiler/prometeu-core/src/source/name_interner.rs @@ -0,0 +1,56 @@ +use std::collections::HashMap; +use crate::NameId; + +#[derive(Debug, Default, Clone)] +pub struct NameInterner { + names: Vec, + ids: HashMap, +} + +impl NameInterner { + pub fn new() -> Self { + Self { + names: Vec::new(), + ids: HashMap::new(), + } + } + + pub fn intern(&mut self, s: &str) -> NameId { + if let Some(id) = self.ids.get(s) { + return *id; + } + + let id = NameId(self.names.len() as u32); + self.names.push(s.to_string()); + self.ids.insert(self.names[id.0 as usize].clone(), id); + id + } + + pub fn get(&self, s: &str) -> Option { + self.ids.get(s).copied() + } + + pub fn resolve(&self, id: NameId) -> &str { + &self.names[id.0 as usize] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn interner_intern_resolve_roundtrip() { + let mut interner = NameInterner::new(); + let id = interner.intern("foo"); + assert_eq!(interner.resolve(id), "foo"); + } + + #[test] + fn interner_dedups_strings() { + let mut interner = NameInterner::new(); + let id1 = interner.intern("bar"); + let id2 = interner.intern("bar"); + assert_eq!(id1, id2); + } +} \ No newline at end of file diff --git a/old-compiler/prometeu-core/src/source/span.rs b/old-compiler/prometeu-core/src/source/span.rs new file mode 100644 index 00000000..2bd17b7c --- /dev/null +++ b/old-compiler/prometeu-core/src/source/span.rs @@ -0,0 +1,39 @@ +use crate::FileId; + +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct Span { + pub file: FileId, + pub start: u32, // byte offset + pub end: u32, // byte offset, exclusive +} + +impl Span { + #[inline] + pub fn new(file: FileId, start: u32, end: u32) -> Self { + Self { file, start, end } + } + + #[inline] + pub fn none() -> Self { + Self { + file: FileId::NONE, + start: 0, + end: 0, + } + } + + #[inline] + pub fn is_none(&self) -> bool { + self.file.is_none() + } + + #[inline] + pub fn len(&self) -> u32 { + self.end.saturating_sub(self.start) + } + + #[inline] + pub fn contains(&self, byte: u32) -> bool { + self.start <= byte && byte < self.end + } +} diff --git a/old-compiler/prometeu-core/tests/source/file_db_line_index.rs b/old-compiler/prometeu-core/tests/source/file_db_line_index.rs new file mode 100644 index 00000000..df7e6893 --- /dev/null +++ b/old-compiler/prometeu-core/tests/source/file_db_line_index.rs @@ -0,0 +1,69 @@ +use prometeu_core::{FileDB, LineIndex}; + +#[test] +fn test_line_index_roundtrip() { + let text = "line 1\nline 2\nline 3"; + let index = LineIndex::new(text); + + // Roundtrip for each character + for (offset, _) in text.char_indices() { + let (line, col) = index.offset_to_line_col(offset as u32); + let recovered_offset = index.line_col_to_offset(line, col).expect("Should recover offset"); + assert_eq!(offset as u32, recovered_offset, "Offset mismatch at line {}, col {}", line, col); + } +} + +#[test] +fn test_line_index_boundaries() { + let text = "a\nbc\n"; + let index = LineIndex::new(text); + + // "a" -> (0, 0) + assert_eq!(index.offset_to_line_col(0), (0, 0)); + assert_eq!(index.line_col_to_offset(0, 0), Some(0)); + + // "\n" -> (0, 1) + assert_eq!(index.offset_to_line_col(1), (0, 1)); + assert_eq!(index.line_col_to_offset(0, 1), Some(1)); + + // "b" -> (1, 0) + assert_eq!(index.offset_to_line_col(2), (1, 0)); + assert_eq!(index.line_col_to_offset(1, 0), Some(2)); + + // "c" -> (1, 1) + assert_eq!(index.offset_to_line_col(3), (1, 1)); + assert_eq!(index.line_col_to_offset(1, 1), Some(3)); + + // "\n" (second) -> (1, 2) + assert_eq!(index.offset_to_line_col(4), (1, 2)); + assert_eq!(index.line_col_to_offset(1, 2), Some(4)); + + // EOF (after last \n) -> (2, 0) + assert_eq!(index.offset_to_line_col(5), (2, 0)); + assert_eq!(index.line_col_to_offset(2, 0), Some(5)); + + // Out of bounds + assert_eq!(index.line_col_to_offset(2, 1), None); + assert_eq!(index.line_col_to_offset(3, 0), None); +} + +#[test] +fn test_file_db_upsert_and_access() { + let mut db = FileDB::new(); + let uri = "file:///test.txt"; + let text = "hello\nworld".to_string(); + + let id = db.upsert(uri, text.clone()); + assert_eq!(db.file_id(uri), Some(id)); + assert_eq!(db.uri(id), uri); + assert_eq!(db.text(id), &text); + + let index = db.line_index(id); + assert_eq!(index.offset_to_line_col(6), (1, 0)); // 'w' is at offset 6 + + // Update existing file + let new_text = "new content".to_string(); + let same_id = db.upsert(uri, new_text.clone()); + assert_eq!(id, same_id); + assert_eq!(db.text(id), &new_text); +} diff --git a/old-compiler/prometeu-core/tests/source/span_tests.rs b/old-compiler/prometeu-core/tests/source/span_tests.rs new file mode 100644 index 00000000..657a975b --- /dev/null +++ b/old-compiler/prometeu-core/tests/source/span_tests.rs @@ -0,0 +1,14 @@ +use prometeu_core::{FileId, Span}; + +#[test] +fn span_end_is_exclusive() { + let file = FileId(1); + let s = Span::new(file, 2, 5); + // len = end - start + assert_eq!(s.len(), 3); + // contains is [start, end) + assert!(s.contains(2)); + assert!(s.contains(3)); + assert!(s.contains(4)); + assert!(!s.contains(5)); +} diff --git a/old-compiler/prometeu-deps/Cargo.toml b/old-compiler/prometeu-deps/Cargo.toml new file mode 100644 index 00000000..9b923bd9 --- /dev/null +++ b/old-compiler/prometeu-deps/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "prometeu-deps" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "" + +[dependencies] +serde = { version = "1.0.228", features = ["derive"] } +prometeu-core = { path = "../prometeu-core" } +prometeu-language-api = { path = "../prometeu-language-api" } +prometeu-languages-registry = { path = "../languages/prometeu-languages-registry" } +anyhow = "1.0.101" +camino = "1.2.2" +walkdir = "2.5.0" +serde_json = "1.0.149" + +[features] +default = [] diff --git a/old-compiler/prometeu-deps/src/lib.rs b/old-compiler/prometeu-deps/src/lib.rs new file mode 100644 index 00000000..06b40cfb --- /dev/null +++ b/old-compiler/prometeu-deps/src/lib.rs @@ -0,0 +1,19 @@ +mod model; +mod load_sources; +mod workspace; + +pub use workspace::resolve_workspace; +pub use load_sources::load_sources; + +pub use model::manifest::*; +pub use model::resolved_project::ResolvedWorkspace; +pub use model::deps_config::DepsConfig; +pub use model::project_descriptor::ProjectDescriptor; +pub use model::build_stack::BuildStack; +pub use model::resolved_graph::ResolvedGraph; +pub use model::loaded_sources::LoadedSources; +pub use model::project_sources::ProjectSources; +pub use model::loaded_file::LoadedFile; +pub use model::cache_blobs::CacheBlobs; +pub use model::cache_plan::CachePlan; + diff --git a/old-compiler/prometeu-deps/src/load_sources.rs b/old-compiler/prometeu-deps/src/load_sources.rs new file mode 100644 index 00000000..0a8c64b5 --- /dev/null +++ b/old-compiler/prometeu-deps/src/load_sources.rs @@ -0,0 +1,97 @@ +use anyhow::{Context, Result}; +use camino::Utf8PathBuf; +use walkdir::WalkDir; + +use crate::{ + DepsConfig, + LoadedFile, + LoadedSources, + ProjectSources, + ResolvedWorkspace, +}; + +pub fn load_sources(cfg: &DepsConfig, resolved: &ResolvedWorkspace) -> Result { + let mut per_project = Vec::with_capacity(resolved.stack.projects.len()); + + for project_id in &resolved.stack.projects { + let project = resolved + .graph + .project(project_id) + .with_context(|| format!("deps: unknown project_id {:?} in build stack", project_id))?; + + if cfg.explain { + eprintln!( + "[deps] load_sources: project {}@{} ({:?})", + project.name, project.version, project.project_dir + ); + } + + let mut files: Vec = Vec::new(); + + for root in &project.source_roots { + let abs_root = project.project_dir.join(root); + + if cfg.explain { + eprintln!("[deps] scanning {:?}", abs_root); + } + + if !abs_root.exists() { + anyhow::bail!( + "deps: source root does not exist for project {}@{}: {:?}", + project.name, + project.version, + abs_root + ); + } + + // Walk recursively. + for entry in WalkDir::new(&abs_root) + .follow_links(false) + .into_iter() + .filter_map(|e| e.ok()) + { + let ft = entry.file_type(); + if !ft.is_file() { + continue; + } + + let path = entry.path(); + + + // TODO: precisamos mexer no prometeu.json para configurar o frontend do projeto + // Filter extensions: start with PBS only. + if path.extension().and_then(|s| s.to_str()) != Some("pbs") { + continue; + } + + // Convert to Utf8Path (the best effort) and use a stable "uri". + let path_utf8: Utf8PathBuf = match Utf8PathBuf::from_path_buf(path.to_path_buf()) { + Ok(p) => p, + Err(_) => { + anyhow::bail!("deps: non-utf8 path found while scanning sources: {:?}", path); + } + }; + + let text = std::fs::read_to_string(&path_utf8) + .with_context(|| format!("deps: failed to read source file {:?}", path_utf8))?; + + // TODO: normalize newlines + + files.push(LoadedFile { + uri: path_utf8.to_string(), + text, + }); + } + } + + // Determinism: sort a file list by uri (important for stable builds). + files.sort_by(|a, b| a.uri.cmp(&b.uri)); + + per_project.push(ProjectSources { + project_id: project_id.clone(), + files, + }); + } + + Ok(LoadedSources { per_project }) +} diff --git a/old-compiler/prometeu-deps/src/model/build_stack.rs b/old-compiler/prometeu-deps/src/model/build_stack.rs new file mode 100644 index 00000000..e3e85727 --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/build_stack.rs @@ -0,0 +1,6 @@ +use prometeu_core::ProjectId; + +#[derive(Debug, Clone)] +pub struct BuildStack { + pub projects: Vec, +} diff --git a/old-compiler/prometeu-deps/src/model/cache_blobs.rs b/old-compiler/prometeu-deps/src/model/cache_blobs.rs new file mode 100644 index 00000000..e92281eb --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/cache_blobs.rs @@ -0,0 +1,7 @@ +/// Cache blobs computed/validated by deps. +/// The pipeline may decide when to store, but deps executes IO and cache validity. +#[derive(Debug, Clone)] +pub struct CacheBlobs { + // placeholder + pub _unused: (), +} diff --git a/old-compiler/prometeu-deps/src/model/cache_plan.rs b/old-compiler/prometeu-deps/src/model/cache_plan.rs new file mode 100644 index 00000000..be7f0236 --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/cache_plan.rs @@ -0,0 +1,4 @@ +#[derive(Debug, Clone)] +pub struct CachePlan { + +} \ No newline at end of file diff --git a/old-compiler/prometeu-deps/src/model/deps_config.rs b/old-compiler/prometeu-deps/src/model/deps_config.rs new file mode 100644 index 00000000..e20e137e --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/deps_config.rs @@ -0,0 +1,7 @@ +use camino::Utf8PathBuf; + +pub struct DepsConfig { + pub explain: bool, + pub cache_dir: Utf8PathBuf, + pub registry_dirs: Vec, // or sources ? +} \ No newline at end of file diff --git a/old-compiler/prometeu-deps/src/model/loaded_file.rs b/old-compiler/prometeu-deps/src/model/loaded_file.rs new file mode 100644 index 00000000..f1763402 --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/loaded_file.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Clone)] +pub struct LoadedFile { + pub uri: String, + pub text: String, +} diff --git a/old-compiler/prometeu-deps/src/model/loaded_sources.rs b/old-compiler/prometeu-deps/src/model/loaded_sources.rs new file mode 100644 index 00000000..b5cb3085 --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/loaded_sources.rs @@ -0,0 +1,8 @@ +use crate::model::project_sources::ProjectSources; + +/// Sources already loaded by deps (IO happens in deps, not in pipeline). +#[derive(Debug, Clone)] +pub struct LoadedSources { + /// For each project in the stack, a list of files (uri + text). + pub per_project: Vec, +} diff --git a/old-compiler/prometeu-deps/src/model/manifest.rs b/old-compiler/prometeu-deps/src/model/manifest.rs new file mode 100644 index 00000000..2404d945 --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/manifest.rs @@ -0,0 +1,75 @@ +use camino::Utf8PathBuf; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Manifest { + pub name: String, + pub version: String, + + #[serde(default)] + pub source_roots: Vec, + + pub language: LanguageDecl, + + #[serde(default)] + pub deps: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LanguageDecl { + pub id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(untagged)] +pub enum DepDecl { + Local { + path: String, + }, + Git { + git: String, + rev: Option, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PrometeuLock { + pub schema: u32, + #[serde(default)] + pub mappings: Vec, +} + +impl PrometeuLock { + pub fn blank() -> Self { + Self { + schema: 0, + mappings: vec![], + } + } + + pub fn lookup_git_local_dir(&self, url: &str, rev: &str) -> Option<&String> { + self.mappings.iter().find_map(|m| match m { + LockMapping::Git { + git, rev: r, local_dir + } if git == url && r == rev => Some(local_dir), + _ => None, + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "kind", rename_all = "lowercase")] +pub enum LockMapping { + Git { + git: String, + rev: String, + local_dir: String, + }, + Registry { + registry: String, + version: String, + local_dir: String, + }, +} + + diff --git a/old-compiler/prometeu-deps/src/model/mod.rs b/old-compiler/prometeu-deps/src/model/mod.rs new file mode 100644 index 00000000..d75c23c6 --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/mod.rs @@ -0,0 +1,11 @@ +pub mod deps_config; +pub mod project_descriptor; +pub mod build_stack; +pub mod resolved_graph; +pub mod loaded_sources; +pub mod project_sources; +pub mod loaded_file; +pub mod cache_blobs; +pub mod resolved_project; +pub mod cache_plan; +pub mod manifest; \ No newline at end of file diff --git a/old-compiler/prometeu-deps/src/model/project_descriptor.rs b/old-compiler/prometeu-deps/src/model/project_descriptor.rs new file mode 100644 index 00000000..a1436312 --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/project_descriptor.rs @@ -0,0 +1,14 @@ +use camino::Utf8PathBuf; +use prometeu_core::ProjectId; +use prometeu_language_api::SourcePolicy; + +#[derive(Debug, Clone)] +pub struct ProjectDescriptor { + pub project_id: ProjectId, + pub name: String, + pub version: String, + pub project_dir: Utf8PathBuf, + pub source_roots: Vec, + pub language_id: String, + pub source_policy: SourcePolicy, +} diff --git a/old-compiler/prometeu-deps/src/model/project_sources.rs b/old-compiler/prometeu-deps/src/model/project_sources.rs new file mode 100644 index 00000000..8034b9c3 --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/project_sources.rs @@ -0,0 +1,8 @@ +use prometeu_core::ProjectId; +use crate::model::loaded_file::LoadedFile; + +#[derive(Debug, Clone)] +pub struct ProjectSources { + pub project_id: ProjectId, + pub files: Vec, +} diff --git a/old-compiler/prometeu-deps/src/model/resolved_graph.rs b/old-compiler/prometeu-deps/src/model/resolved_graph.rs new file mode 100644 index 00000000..357b088e --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/resolved_graph.rs @@ -0,0 +1,16 @@ +use prometeu_core::ProjectId; +use crate::ProjectDescriptor; + +#[derive(Debug, Clone)] +pub struct ResolvedGraph { + pub root: ProjectId, + pub projects: Vec, // arena + // opcional: adjacency list para checks + pub edges: Vec>, // edges[from] = vec[to] +} + +impl ResolvedGraph { + pub fn project(&self, id: &ProjectId) -> Option<&ProjectDescriptor> { + self.projects.get(id.0 as usize) + } +} diff --git a/old-compiler/prometeu-deps/src/model/resolved_project.rs b/old-compiler/prometeu-deps/src/model/resolved_project.rs new file mode 100644 index 00000000..a9af9f3d --- /dev/null +++ b/old-compiler/prometeu-deps/src/model/resolved_project.rs @@ -0,0 +1,9 @@ +use prometeu_core::ProjectId; +use crate::{BuildStack, ResolvedGraph}; + +#[derive(Debug, Clone)] +pub struct ResolvedWorkspace { + pub project_id: ProjectId, + pub graph: ResolvedGraph, + pub stack: BuildStack, +} diff --git a/old-compiler/prometeu-deps/src/workspace/host.rs b/old-compiler/prometeu-deps/src/workspace/host.rs new file mode 100644 index 00000000..19335c27 --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/host.rs @@ -0,0 +1,32 @@ +use anyhow::{Context, Result}; +use camino::{Utf8Path, Utf8PathBuf}; + +use crate::workspace::model::DepRef; + +pub trait DepsHost { + fn read_to_string(&self, path: &Utf8Path) -> Result; + + // fn ensure_project_local(&self, from_dir: &Utf8Path, dep: &DepRef) -> Result; +} + +pub struct FsHost; + +impl DepsHost for FsHost { + fn read_to_string(&self, path: &Utf8Path) -> Result { + std::fs::read_to_string(path) + .with_context(|| format!("failed to read {:?}", path)) + } + + // fn ensure_project_local(&self, from_dir: &Utf8Path, dep: &DepRef) -> Result { + // match dep { + // DepRef::Local { path } => { + // let joined = from_dir.join(path); + // let canon = joined.canonicalize() + // .with_context(|| format!("deps: dep path does not exist: {:?}", joined))?; + // Utf8PathBuf::from_path_buf(canon) + // .map_err(|p| anyhow::anyhow!("deps: non-utf8 dep dir: {:?}", p)) + // } + // _ => unimplemented!(), + // } + // } +} diff --git a/old-compiler/prometeu-deps/src/workspace/mod.rs b/old-compiler/prometeu-deps/src/workspace/mod.rs new file mode 100644 index 00000000..944786d8 --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/mod.rs @@ -0,0 +1,6 @@ +mod resolve_workspace; +mod host; +mod model; +mod phases; + +pub use resolve_workspace::resolve_workspace; \ No newline at end of file diff --git a/old-compiler/prometeu-deps/src/workspace/model.rs b/old-compiler/prometeu-deps/src/workspace/model.rs new file mode 100644 index 00000000..ddd6e65e --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/model.rs @@ -0,0 +1,31 @@ +use camino::Utf8PathBuf; +use prometeu_core::ProjectId; +use prometeu_language_api::SourcePolicy; + +use crate::Manifest; + +#[derive(Debug, Clone)] +pub struct RawProjectNode { + pub dir: Utf8PathBuf, + pub manifest_path: Utf8PathBuf, + pub manifest: Manifest, +} + +#[derive(Debug, Clone)] +pub enum DepRef { + Local { + path: Utf8PathBuf + }, +} + +#[derive(Debug, Clone)] +pub struct ProjectNode { + pub id: ProjectId, + pub dir: Utf8PathBuf, + pub name: String, + pub version: String, + pub source_roots: Vec, + pub language_id: String, + pub deps: Vec, + pub source_policy: SourcePolicy, +} diff --git a/old-compiler/prometeu-deps/src/workspace/phases/discover.rs b/old-compiler/prometeu-deps/src/workspace/phases/discover.rs new file mode 100644 index 00000000..51184a08 --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/phases/discover.rs @@ -0,0 +1,131 @@ +use crate::model::manifest::DepDecl; +use crate::workspace::host::DepsHost; +use crate::workspace::model::RawProjectNode; +use crate::workspace::phases::state::ResolverState; +use crate::Manifest; +use anyhow::{anyhow, bail, Context, Result}; +use camino::Utf8PathBuf; +use serde_json; +use std::fs::canonicalize; + +/// Phase 1: Discover all projects in the workspace. +/// +/// - Reads `prometeu.json` from each pending project directory. +/// - Parses `Manifest`. +/// - Registers the raw node. +/// - Enqueues local-path deps for discovery (v0). +/// +/// Does NOT: +/// - assign ProjectId +/// - build edges +/// - validate versions +pub fn discover( + cfg: &crate::DepsConfig, + host: &dyn DepsHost, + state: &mut ResolverState, +) -> Result<()> { + while let Some(canon_dir) = state.pending.pop_front() { + // de-dup by directory + if state.raw_by_dir.contains_key(&canon_dir) { + continue; + } + + let manifest_path = canon_dir.join("prometeu.json"); + if !manifest_path.exists() || !manifest_path.is_file() { + bail!( + "deps: manifest not found: expected a file {:?} (project dir {:?})", + manifest_path, + canon_dir + ); + } + + if cfg.explain { + eprintln!("[deps][discover] reading {:?}", manifest_path); + } + + let text = host + .read_to_string(&manifest_path) + .with_context(|| format!("deps: failed to read manifest {:?}", manifest_path))?; + + let manifest: Manifest = serde_json::from_str(&text) + .with_context(|| format!("deps: invalid manifest JSON {:?}", manifest_path))?; + + // Register raw node + let raw_idx = state.raw.len(); + state.raw.push(RawProjectNode { + dir: canon_dir.clone(), + manifest_path: manifest_path.clone(), + manifest: manifest.clone(), + }); + state.raw_by_dir.insert(canon_dir.clone(), raw_idx); + + for dep in &manifest.deps { + match dep { + DepDecl::Local { path } => { + let dep_dir = canon_dir.join(path); + + let dep_dir_std = dep_dir.canonicalize().with_context(|| { + format!( + "deps: dep path does not exist: {:?} (from {:?})", + dep_dir, canon_dir + ) + })?; + + let dep_dir_canon = Utf8PathBuf::from_path_buf(dep_dir_std) + .map_err(|p| anyhow!("deps: non-utf8 dep dir: {:?}", p))?; + + if cfg.explain { + eprintln!("[deps][discover] local dep '{}' -> {:?}", path, dep_dir_canon); + } + state.pending.push_back(dep_dir_canon); + } + + DepDecl::Git { git, rev } => { + let Some(rev) = rev.as_deref() else { + bail!( + "deps: git dependency '{}' requires an explicit 'rev' (commit hash) for now", + git + ); + }; + + let Some(local_dir) = state.lock.lookup_git_local_dir(git, rev) else { + bail!( + "deps: git dependency requires prometeu.lock mapping, but entry not found: git='{}' rev='{}'", + git, + rev + ); + }; + + // canonicalize the lock-provided local dir to keep identity stable + let local_dir_std = canonicalize(local_dir) + .with_context(|| format!("deps: prometeu.lock local_dir does not exist: {:?}", local_dir))?; + + let local_dir_canon = Utf8PathBuf::from_path_buf(local_dir_std) + .map_err(|p| anyhow!("deps: non-utf8 lock local_dir: {:?}", p))?; + + // validate manifest exists at the mapped project root + // (this check should not belong here, but it is ok) + let mapped_manifest = local_dir_canon.join("prometeu.json"); + if !mapped_manifest.exists() || !mapped_manifest.is_file() { + bail!( + "deps: prometeu.lock maps git dep to {:?}, but manifest is missing: {:?}", + local_dir_canon, + mapped_manifest + ); + } + + if cfg.explain { + eprintln!( + "[deps][discover] git dep '{}' rev '{}' -> {:?}", + git, rev, local_dir_canon + ); + } + + state.pending.push_back(local_dir_canon); + } + } + } + } + + Ok(()) +} diff --git a/old-compiler/prometeu-deps/src/workspace/phases/localize.rs b/old-compiler/prometeu-deps/src/workspace/phases/localize.rs new file mode 100644 index 00000000..aa900bc1 --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/phases/localize.rs @@ -0,0 +1,62 @@ +use anyhow::{Context, Result}; + +use prometeu_core::ProjectId; + +use crate::workspace::model::DepRef; +use crate::workspace::phases::state::ResolverState; + +/// Phase 3: Localize dependencies and build graph edges. +/// +/// For each project node: +/// - For each DepRef: +/// - host.ensure_project_local(from_dir, dep) -> dep_dir (local on disk) +/// - map dep_dir to ProjectId via st.by_dir +/// - st.edges[from].push(dep_id) +/// +/// v0 policy: +/// - Only DepRef::LocalPath is supported. +/// - Git/Registry cause a hard error (future extension point). +pub fn localize(cfg: &crate::DepsConfig, state: &mut ResolverState) -> Result<()> { + // Reset edges (allows re-run / deterministic behavior) + for e in &mut state.edges { + e.clear(); + } + + for from_idx in 0..state.nodes.len() { + let from_id: ProjectId = state.nodes[from_idx].id; + let from_dir = state.nodes[from_idx].dir.clone(); + + if cfg.explain { + eprintln!( + "[deps][localize] from id={:?} dir={:?}", + from_id, from_dir + ); + } + + // Clone deps to avoid borrow conflicts (simple + safe for now) + let deps = state.nodes[from_idx].deps.clone(); + + for dep in deps { + match &dep { + DepRef::Local { + path + } => { + let dep_id = state.by_dir.get(path).copied().with_context(|| { + format!( + "deps: localized dep dir {:?} was not discovered; \ + ensure the dep has a prometeu.json and is reachable via local paths", + path + ) + })?; + state.edges[from_id.0 as usize].push(dep_id); + } + } + } + + // Optional: keep edges deterministic + state.edges[from_id.0 as usize].sort_by_key(|id| id.0); + state.edges[from_id.0 as usize].dedup(); + } + + Ok(()) +} diff --git a/old-compiler/prometeu-deps/src/workspace/phases/materialize.rs b/old-compiler/prometeu-deps/src/workspace/phases/materialize.rs new file mode 100644 index 00000000..7aca4ddd --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/phases/materialize.rs @@ -0,0 +1,144 @@ +use crate::model::manifest::DepDecl; +use crate::workspace::model::{DepRef, ProjectNode}; +use crate::workspace::phases::state::ResolverState; +use anyhow::{anyhow, bail, Context, Result}; +use camino::Utf8PathBuf; +use prometeu_core::ProjectId; +use prometeu_languages_registry::get_language_spec; +use std::fs::canonicalize; + +/// Phase 2: Materialize projects (allocate ProjectId / arena nodes). +/// +/// Inputs: +/// - st.raw (RawProjectNode: dir + manifest) +/// +/// Outputs: +/// - st.nodes (ProjectNode arena) +/// - st.by_dir (dir -> ProjectId) +/// - st.edges (allocated adjacency lists, empty for now) +/// - st.root (ProjectId for root_dir) +/// +/// Does NOT: +/// - resolve deps to local dirs (that's phase localize) +/// - validate version conflicts/cycles +/// - resolve language/source policy +pub fn materialize(cfg: &crate::DepsConfig, state: &mut ResolverState) -> Result<()> { + // Reset materialized state (allows rerun in future refactors/tests) + state.nodes.clear(); + state.by_dir.clear(); + state.edges.clear(); + state.root = None; + + state.nodes.reserve(state.raw.len()); + state.edges.reserve(state.raw.len()); + + for (idx, raw) in state.raw.iter().enumerate() { + let id = ProjectId(idx as u32); + + // Default source roots if omitted + let source_roots: Vec = raw + .manifest + .source_roots + .iter() + .map(|root| Utf8PathBuf::from(root)) + .collect(); + if source_roots.is_empty() { + bail!( + "deps: no source roots specified for project {}", + raw.manifest.name + ) + } + + // Convert DepDecl -> DepRef (no localization yet) + let mut deps: Vec = Vec::with_capacity(raw.manifest.deps.len()); + for d in &raw.manifest.deps { + match d { + DepDecl::Local { path } => { + let joined = raw.dir.join(path); + let dir_std = joined.canonicalize() + .with_context(|| format!("deps: local dep path does not exist: {:?} (from {:?})", joined, raw.dir))?; + + let dir_canon = Utf8PathBuf::from_path_buf(dir_std) + .map_err(|p| anyhow!("deps: non-utf8 dep dir: {:?}", p))?; + deps.push(DepRef::Local { + path: dir_canon + }); + } + DepDecl::Git { git, rev } => { + let Some(rev) = rev.as_deref() else { + bail!( + "deps: git dependency '{}' requires an explicit 'rev' (commit hash) for now", + git + ); + }; + + let Some(local_dir) = state.lock.lookup_git_local_dir(git, rev) else { + bail!( + "deps: git dependency requires prometeu.lock mapping, but entry not found: git='{}' rev='{}'", + git, + rev + ); + }; + + // canonicalize the lock-provided local dir to keep identity stable + let path = canonicalize(local_dir).with_context(|| { + format!( + "deps: prometeu.lock local_dir does not exist: {:?}", + local_dir + ) + })?; + + let local_dir_canon = Utf8PathBuf::from_path_buf(path) + .map_err(|p| anyhow!("deps: non-utf8 lock local_dir: {:?}", p))?; + + deps.push(DepRef::Local { + path: local_dir_canon, + }); + } + } + } + + if cfg.explain { + eprintln!( + "[deps][materialize] id={:?} {}@{} dir={:?} language={}", + id, raw.manifest.name, raw.manifest.version, raw.dir, raw.manifest.language.id + ); + } + + let source_policy = get_language_spec(raw.manifest.language.id.as_str()) + .map(|spec| spec.source_policy.clone()) + .ok_or(anyhow!( + "deps: unknown language spec: {}", + raw.manifest.language.id + ))?; + + // Record node + state.nodes.push(ProjectNode { + id, + dir: raw.dir.clone(), + name: raw.manifest.name.clone(), + version: raw.manifest.version.clone(), + source_roots, + language_id: raw.manifest.language.id.clone(), + deps, + source_policy, + }); + + state.by_dir.insert(raw.dir.clone(), id); + state.edges.push(Vec::new()); + } + + // Determine root id + if let Some(root_id) = state.by_dir.get(&state.root_dir).copied() { + state.root = Some(root_id); + } else { + // This should never happen if seed/discover worked. + // Keep it as a hard failure (in a later validate phase you can convert to a nicer diagnostic). + anyhow::bail!( + "deps: root project dir {:?} was not discovered/materialized", + state.root_dir + ); + } + + Ok(()) +} diff --git a/old-compiler/prometeu-deps/src/workspace/phases/mod.rs b/old-compiler/prometeu-deps/src/workspace/phases/mod.rs new file mode 100644 index 00000000..25fbb3e4 --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/phases/mod.rs @@ -0,0 +1,10 @@ +mod run_all; +mod state; +mod discover; +mod materialize; +mod localize; +mod validate; +mod policy; +mod stack; + +pub use run_all::run_all; \ No newline at end of file diff --git a/old-compiler/prometeu-deps/src/workspace/phases/policy.rs b/old-compiler/prometeu-deps/src/workspace/phases/policy.rs new file mode 100644 index 00000000..f2340ef7 --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/phases/policy.rs @@ -0,0 +1,17 @@ +use anyhow::{bail, Result}; + +use crate::workspace::phases::state::ResolverState; + +pub fn policy(_cfg: &crate::DepsConfig, state: &mut ResolverState) -> Result<()> { + for node in &state.nodes { + if node.source_policy.extensions.is_empty() { + bail!( + "deps: project {}@{} has empty source_policy.extensions (language={})", + node.name, + node.version, + node.language_id + ); + } + } + Ok(()) +} diff --git a/old-compiler/prometeu-deps/src/workspace/phases/run_all.rs b/old-compiler/prometeu-deps/src/workspace/phases/run_all.rs new file mode 100644 index 00000000..62d71d6b --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/phases/run_all.rs @@ -0,0 +1,50 @@ +use anyhow::{Context, Result}; +use camino::Utf8Path; + +use crate::{BuildStack, DepsConfig, ProjectDescriptor, ResolvedGraph, ResolvedWorkspace}; +use crate::workspace::host::FsHost; +use crate::workspace::phases::{discover, localize, materialize, policy, stack, state, validate}; + +pub fn run_all(cfg: &DepsConfig, fs_host: &FsHost, root_dir: &Utf8Path) -> Result { + let mut st = state::seed(cfg, root_dir)?; + + discover::discover(cfg, fs_host, &mut st)?; + materialize::materialize(cfg, &mut st)?; + localize::localize(cfg, &mut st)?; + + validate::validate(cfg, &st)?; + policy::policy(cfg, &mut st)?; + + let build_stack: BuildStack = stack::stack(cfg, &mut st)?; + + let root = st + .root + .context("deps: internal error: root ProjectId not set")?; + + // Build the arena expected by ResolvedGraph: index == ProjectId.0 + // materialize already assigns ProjectId(idx), so st.nodes order is stable. + let mut projects: Vec = Vec::with_capacity(st.nodes.len()); + for n in &st.nodes { + projects.push(ProjectDescriptor { + project_id: n.id, + name: n.name.clone(), + version: n.version.clone(), + project_dir: n.dir.clone(), + source_roots: n.source_roots.clone(), + language_id: n.language_id.clone(), + source_policy: n.source_policy.clone(), + }); + } + + let graph = ResolvedGraph { + root, + projects, + edges: st.edges, + }; + + Ok(ResolvedWorkspace { + project_id: root, + graph, + stack: build_stack, + }) +} diff --git a/old-compiler/prometeu-deps/src/workspace/phases/stack.rs b/old-compiler/prometeu-deps/src/workspace/phases/stack.rs new file mode 100644 index 00000000..34e5e49a --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/phases/stack.rs @@ -0,0 +1,97 @@ +use anyhow::{Context, Result}; +use prometeu_core::ProjectId; +use std::collections::VecDeque; +use crate::BuildStack; +use crate::workspace::phases::state::ResolverState; + +/// Phase: BuildStack (deps-first topo order). +/// +/// Output: +/// - state.stack: Vec where deps appear before dependents. +/// +/// Determinism: +/// - ties are resolved by ProjectId order (stable across runs if discovery is stable). +pub fn stack(cfg: &crate::DepsConfig, state: &mut ResolverState) -> Result { + let n = state.nodes.len(); + let _root = state.root.context("deps: internal error: root ProjectId not set")?; + + // Build indegree + let mut indeg = vec![0usize; n]; + for outs in &state.edges { + for &to in outs { + indeg[to.0 as usize] += 1; + } + } + + // Deterministic queue: push in ProjectId order + let mut q = VecDeque::new(); + for i in 0..n { + if indeg[i] == 0 { + q.push_back(i); + } + } + + let mut order: Vec = Vec::with_capacity(n); + + while let Some(i) = q.pop_front() { + order.push(ProjectId(i as u32)); + + // Ensure deterministic traversal of outgoing edges too + // (your localize already sort/dedup edges, but this doesn't hurt) + for &to in &state.edges[i] { + let j = to.0 as usize; + indeg[j] -= 1; + if indeg[j] == 0 { + // Deterministic insert: keep queue ordered by ProjectId + // Simple O(n) insertion is fine for now. + insert_sorted_by_id(&mut q, j); + } + } + } + + // If validate ran, this should already be cycle-free; still keep a guard. + if order.len() != n { + anyhow::bail!( + "deps: internal error: stack generation did not visit all nodes ({} of {})", + order.len(), + n + ); + } + + if cfg.explain { + eprintln!("[deps][stack] build order:"); + for id in &order { + let node = &state.nodes[id.0 as usize]; + eprintln!(" - {:?} {}@{} dir={:?}", id, node.name, node.version, node.dir); + } + } + + Ok(BuildStack { + projects: order, + }) +} + +/// Insert node index `i` into queue `q` keeping it sorted by ProjectId (index). +fn insert_sorted_by_id(q: &mut VecDeque, i: usize) { + // Common fast path: append if >= last + if let Some(&last) = q.back() { + if i >= last { + q.push_back(i); + return; + } + } + + // Otherwise find insertion point + let mut pos = 0usize; + for &v in q.iter() { + if i < v { + break; + } + pos += 1; + } + + // VecDeque has no insert, so rebuild (small sizes OK for hard reset) + let mut tmp: Vec = q.drain(..).collect(); + tmp.insert(pos, i); + *q = VecDeque::from(tmp); +} diff --git a/old-compiler/prometeu-deps/src/workspace/phases/state.rs b/old-compiler/prometeu-deps/src/workspace/phases/state.rs new file mode 100644 index 00000000..5eef4842 --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/phases/state.rs @@ -0,0 +1,58 @@ +use camino::{Utf8Path, Utf8PathBuf}; +use std::collections::{HashMap, VecDeque}; +use anyhow::Context; +use crate::workspace::model::{RawProjectNode, ProjectNode}; +use prometeu_core::ProjectId; +use crate::PrometeuLock; +use serde_json; + +pub struct ResolverState { + pub root_dir: Utf8PathBuf, + + // phase1 output + pub raw: Vec, + pub raw_by_dir: HashMap, + pub pending: VecDeque, + + // phase2+ + pub nodes: Vec, + pub by_dir: HashMap, + pub edges: Vec>, + + pub root: Option, + + pub lock: PrometeuLock, +} + +pub fn seed(_cfg: &crate::DepsConfig, root_dir: &Utf8Path) -> anyhow::Result { + let path_buf = root_dir.canonicalize()?; + let root_dir_canon = Utf8PathBuf::from_path_buf(path_buf) + .map_err(|p| anyhow::anyhow!("deps: non-utf8 root dir: {:?}", p))?; + + let lock_path = root_dir_canon.join("prometeu.lock"); + let lock = if lock_path.exists() { + let txt = std::fs::read_to_string(&lock_path)?; + serde_json::from_str::(&txt) + .with_context(|| format!("invalid prometeu.lock at {:?}", lock_path))? + } else { + PrometeuLock::blank() + }; + + let mut pending = VecDeque::new(); + pending.push_back(root_dir_canon.clone()); + + Ok(ResolverState { + root_dir: root_dir_canon.clone(), + raw: vec![], + raw_by_dir: HashMap::new(), + pending, + + nodes: vec![], + by_dir: HashMap::new(), + edges: vec![], + + root: None, + + lock, + }) +} diff --git a/old-compiler/prometeu-deps/src/workspace/phases/validate.rs b/old-compiler/prometeu-deps/src/workspace/phases/validate.rs new file mode 100644 index 00000000..b49a61ff --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/phases/validate.rs @@ -0,0 +1,108 @@ +use anyhow::{bail, Context, Result}; +use prometeu_core::ProjectId; +use std::collections::{HashMap, VecDeque}; + +use crate::workspace::phases::state::ResolverState; + +/// Phase: Validate workspace graph & invariants (v0). +/// +/// Checks: +/// - root present +/// - edges are in-range +/// - no cycles +/// - no version conflicts for same project name +pub fn validate(cfg: &crate::DepsConfig, state: &ResolverState) -> Result<()> { + // 1) root present + let root = state.root.context("deps: internal error: root ProjectId not set")?; + if cfg.explain { + eprintln!("[deps][validate] root={:?}", root); + } + + // 2) edges sanity + let n = state.nodes.len(); + for (from_idx, outs) in state.edges.iter().enumerate() { + for &to in outs { + let to_idx = to.0 as usize; + if to_idx >= n { + bail!( + "deps: invalid edge: from {:?} -> {:?} (to out of range; nodes={})", + ProjectId(from_idx as u32), + to, + n + ); + } + } + } + + // 3) version conflicts by name + // name -> (version -> ProjectId) + let mut by_name: HashMap<&str, HashMap<&str, ProjectId>> = HashMap::new(); + for node in &state.nodes { + let vmap = by_name.entry(node.name.as_str()).or_default(); + vmap.entry(node.version.as_str()).or_insert(node.id); + } + for (name, versions) in &by_name { + if versions.len() > 1 { + // create deterministic message + let mut vs: Vec<(&str, ProjectId)> = versions.iter().map(|(v, id)| (*v, *id)).collect(); + vs.sort_by(|a, b| a.0.cmp(b.0)); + + let mut msg = format!("deps: version conflict for project '{}':", name); + for (v, id) in vs { + let dir = &state.nodes[id.0 as usize].dir; + msg.push_str(&format!("\n - {} at {:?} (id={:?})", v, dir, id)); + } + bail!(msg); + } + } + + // 4) cycle detection (Kahn + leftover nodes) + // Build indegree + let mut indeg = vec![0usize; n]; + for outs in &state.edges { + for &to in outs { + indeg[to.0 as usize] += 1; + } + } + + let mut q = VecDeque::new(); + for i in 0..n { + if indeg[i] == 0 { + q.push_back(i); + } + } + + let mut visited = 0usize; + while let Some(i) = q.pop_front() { + visited += 1; + for &to in &state.edges[i] { + let j = to.0 as usize; + indeg[j] -= 1; + if indeg[j] == 0 { + q.push_back(j); + } + } + } + + if visited != n { + // Nodes with indeg>0 are part of cycles (or downstream of them) + let mut cyclic: Vec = Vec::new(); + for i in 0..n { + if indeg[i] > 0 { + cyclic.push(ProjectId(i as u32)); + } + } + + // Deterministic error output + cyclic.sort_by_key(|id| id.0); + + let mut msg = "deps: dependency cycle detected among:".to_string(); + for id in cyclic { + let node = &state.nodes[id.0 as usize]; + msg.push_str(&format!("\n - {:?} {}@{} dir={:?}", id, node.name, node.version, node.dir)); + } + bail!(msg); + } + + Ok(()) +} diff --git a/old-compiler/prometeu-deps/src/workspace/resolve_workspace.rs b/old-compiler/prometeu-deps/src/workspace/resolve_workspace.rs new file mode 100644 index 00000000..8e595efb --- /dev/null +++ b/old-compiler/prometeu-deps/src/workspace/resolve_workspace.rs @@ -0,0 +1,10 @@ +use anyhow::Result; +use camino::Utf8Path; + +use crate::{DepsConfig, ResolvedWorkspace}; +use crate::workspace::host::FsHost; + +pub fn resolve_workspace(cfg: &DepsConfig, root_dir: &Utf8Path) -> Result { + let host = FsHost; + crate::workspace::phases::run_all(cfg, &host, root_dir) +} diff --git a/old-compiler/prometeu-language-api/Cargo.toml b/old-compiler/prometeu-language-api/Cargo.toml new file mode 100644 index 00000000..880c247e --- /dev/null +++ b/old-compiler/prometeu-language-api/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "prometeu-language-api" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Canonical language contract for Prometeu Backend: identifiers, references, and strict Frontend trait." +repository = "https://github.com/prometeu/runtime" + +[dependencies] + diff --git a/old-compiler/prometeu-language-api/src/language_spec.rs b/old-compiler/prometeu-language-api/src/language_spec.rs new file mode 100644 index 00000000..ed1ff031 --- /dev/null +++ b/old-compiler/prometeu-language-api/src/language_spec.rs @@ -0,0 +1,21 @@ +#[derive(Debug, Clone)] +pub struct SourcePolicy { + pub extensions: Vec<&'static str>, + pub case_sensitive: bool, +} + +impl SourcePolicy { + pub fn matches_ext(&self, ext: &str) -> bool { + if self.case_sensitive { + self.extensions.iter().any(|e| *e == ext) + } else { + self.extensions.iter().any(|e| e.eq_ignore_ascii_case(ext)) + } + } +} + +#[derive(Debug, Clone)] +pub struct LanguageSpec { + pub id: &'static str, + pub source_policy: SourcePolicy, +} \ No newline at end of file diff --git a/old-compiler/prometeu-language-api/src/lib.rs b/old-compiler/prometeu-language-api/src/lib.rs new file mode 100644 index 00000000..522059e9 --- /dev/null +++ b/old-compiler/prometeu-language-api/src/lib.rs @@ -0,0 +1,3 @@ +mod language_spec; + +pub use language_spec::*; diff --git a/old-compiler/prometeu-lowering/Cargo.toml b/old-compiler/prometeu-lowering/Cargo.toml new file mode 100644 index 00000000..60035bed --- /dev/null +++ b/old-compiler/prometeu-lowering/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "prometeu-lowering" +version = "0.1.0" +edition = "2021" +license.workspace = true +repository.workspace = true + +[dependencies] +prometeu-bytecode = { path = "../prometeu-bytecode" } +prometeu-core = { path = "../prometeu-core" } +prometeu-language-api = { path = "../prometeu-language-api" } +clap = { version = "4.5.54", features = ["derive"] } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +anyhow = "1.0.100" +pathdiff = "0.2.1" + +[dev-dependencies] +tempfile = "3.10.1" diff --git a/old-compiler/prometeu-lowering/src/lib.rs b/old-compiler/prometeu-lowering/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/prometeu-compiler/build.gradle.kts b/prometeu-compiler/build.gradle.kts new file mode 100644 index 00000000..8874f7a8 --- /dev/null +++ b/prometeu-compiler/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + id("gradle.java-library-conventions") +} + +dependencies { + api(project(":prometeu-infra")) +} \ No newline at end of file diff --git a/prometeu-infra/build.gradle.kts b/prometeu-infra/build.gradle.kts new file mode 100644 index 00000000..83c581d8 --- /dev/null +++ b/prometeu-infra/build.gradle.kts @@ -0,0 +1,6 @@ +plugins { + id("gradle.java-library-conventions") +} + +dependencies { +} \ No newline at end of file diff --git a/prometeu-studio/build.gradle.kts b/prometeu-studio/build.gradle.kts new file mode 100644 index 00000000..7a1766d4 --- /dev/null +++ b/prometeu-studio/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("gradle.java-application-conventions") + alias(libs.plugins.javafx) +} + +dependencies { + implementation(project(":prometeu-infra")) + implementation(libs.javafx.controls) + implementation(libs.javafx.fxml) + implementation(libs.richtextfx) +} + +javafx { + version = libs.versions.javafx.get() + modules("javafx.controls", "javafx.fxml") +} + +application { + mainClass = "p.studio.App" +} diff --git a/prometeu-studio/src/main/java/p/studio/App.java b/prometeu-studio/src/main/java/p/studio/App.java new file mode 100644 index 00000000..d79b215a --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/App.java @@ -0,0 +1,31 @@ +package p.studio; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.stage.Stage; +import p.studio.utilities.i18n.I18n; +import p.studio.window.MainView; + +public class App extends Application { + + @Override + public void init() throws Exception { + super.init(); + Container.init(); + } + + @Override + public void start(Stage stage) { + var root = new MainView(); + var scene = new Scene(root, 1200, 800); + + scene.getStylesheets().add(Container.theme().getDefaultTheme()); + stage.titleProperty().bind(Container.i18n().bind(I18n.APP_TITLE)); + stage.setScene(scene); + stage.show(); + } + + public static void main(String[] args) { + launch(); + } +} diff --git a/prometeu-studio/src/main/java/p/studio/Container.java b/prometeu-studio/src/main/java/p/studio/Container.java new file mode 100644 index 00000000..048bedfe --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/Container.java @@ -0,0 +1,21 @@ +package p.studio; + +import p.studio.utilities.ThemeService; +import p.studio.utilities.i18n.I18nService; + +public class Container { + private static final I18nService i18nService; + private static final ThemeService themeService; + + static { + i18nService = new I18nService(); + themeService = new ThemeService(); + } + + public static void init() { + } + + public static I18nService i18n() { return i18nService; } + + public static ThemeService theme() { return new ThemeService(); } +} diff --git a/prometeu-studio/src/main/java/p/studio/utilities/ThemeService.java b/prometeu-studio/src/main/java/p/studio/utilities/ThemeService.java new file mode 100644 index 00000000..4313e733 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/utilities/ThemeService.java @@ -0,0 +1,12 @@ +package p.studio.utilities; + +import java.util.Objects; + +public class ThemeService { + private static final String THEME_PATH = "/themes/"; + private static final String DEFAULT_THEME = THEME_PATH + "default-prometeu.css"; + + public String getDefaultTheme() { + return Objects.requireNonNull(getClass().getResource(DEFAULT_THEME)).toExternalForm(); + } +} diff --git a/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java b/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java new file mode 100644 index 00000000..18db2085 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18n.java @@ -0,0 +1,33 @@ +package p.studio.utilities.i18n; + +import lombok.Getter; + +public enum I18n { + APP_TITLE("app.title"), + + MENU_FILE("menu.file"), + MENU_FILE_NEWPROJECT("menu.file.newProject"), + MENU_FILE_OPEN("menu.file.open"), + MENU_FILE_SAVE("menu.file.save"), + + MENU_EDIT("menu.edit"), + MENU_VIEW("menu.view"), + MENU_HELP("menu.help"), + + TOOLBAR_PLAY("toolbar.play"), + TOOLBAR_STOP("toolbar.stop"), + TOOLBAR_EXPORT("toolbar.export"), + + WORKSPACE_CODE("workspace.code"), + WORKSPACE_ASSETS("workspace.assets"), + WORKSPACE_DEBUG("workspace.debug"), + + ; + + @Getter + private final String key; + + I18n(String key) { + this.key = key; + } +} diff --git a/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18nService.java b/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18nService.java new file mode 100644 index 00000000..288287cd --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/utilities/i18n/I18nService.java @@ -0,0 +1,44 @@ +package p.studio.utilities.i18n; + +import javafx.beans.binding.Bindings; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.ResourceBundle; + +public final class I18nService { + private final ObjectProperty locale = new SimpleObjectProperty<>(Locale.ENGLISH); + + public ObjectProperty localeProperty() { return locale; } + public Locale getLocale() { return locale.get(); } + public void setLocale(Locale l) { locale.set(l); } + + private ResourceBundle bundle(Locale l) { + return ResourceBundle.getBundle("i18n.messages", l); + } + + public String text(I18n i18n) { + return bundle(getLocale()).getString(i18n.getKey()); + } + + public String format(I18n i18n, Object... args) { + return new MessageFormat(text(i18n), getLocale()).format(args); + } + + public StringBinding bind(I18n key) { + return Bindings.createStringBinding( + () -> text(key), + locale + ); + } + + public StringBinding bind(I18n key, Object... args) { + return Bindings.createStringBinding( + () -> format(key, args), + locale + ); + } +} diff --git a/prometeu-studio/src/main/java/p/studio/window/MainView.java b/prometeu-studio/src/main/java/p/studio/window/MainView.java new file mode 100644 index 00000000..f52542c5 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/window/MainView.java @@ -0,0 +1,29 @@ +package p.studio.window; + +import javafx.scene.layout.BorderPane; +import p.studio.workspaces.PlaceholderWorkspace; +import p.studio.workspaces.WorkspaceHost; +import p.studio.workspaces.WorkspaceId; +import p.studio.workspaces.editor.EditorWorkspace; + +public final class MainView extends BorderPane { + private static final WorkspaceHost HOST = new WorkspaceHost(); + + public MainView() { + var menubar = new MenuBar(); + setTop(menubar); + + HOST.register(new EditorWorkspace()); + HOST.register(new PlaceholderWorkspace(WorkspaceId.ASSETS, "Assets")); + HOST.register(new PlaceholderWorkspace(WorkspaceId.BUILD, "Build")); + HOST.register(new PlaceholderWorkspace(WorkspaceId.DEVICE, "Device")); + + var bar = new WorkspaceBar(HOST::show); + setLeft(bar); + setCenter(HOST); + + // default + bar.select(WorkspaceId.EDITOR); + HOST.show(WorkspaceId.EDITOR); + } +} diff --git a/prometeu-studio/src/main/java/p/studio/window/MenuBar.java b/prometeu-studio/src/main/java/p/studio/window/MenuBar.java new file mode 100644 index 00000000..12cec360 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/window/MenuBar.java @@ -0,0 +1,36 @@ +package p.studio.window; + +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import p.studio.Container; +import p.studio.utilities.i18n.I18n; + +public final class MenuBar extends javafx.scene.control.MenuBar { + public MenuBar() { + + Menu file = new Menu(); + file.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE)); + + MenuItem newProject = new MenuItem(); + newProject.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE_NEWPROJECT)); + + MenuItem open = new MenuItem(); + open.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE_OPEN)); + + MenuItem save = new MenuItem(); + save.textProperty().bind(Container.i18n().bind(I18n.MENU_FILE_SAVE)); + + file.getItems().addAll(newProject, open, save); + + Menu edit = new Menu(); + edit.textProperty().bind(Container.i18n().bind(I18n.MENU_EDIT)); + + Menu view = new Menu(); + view.textProperty().bind(Container.i18n().bind(I18n.MENU_VIEW)); + + Menu help = new Menu(); + help.textProperty().bind(Container.i18n().bind(I18n.MENU_HELP)); + + getMenus().addAll(file, edit, view, help); + } +} diff --git a/prometeu-studio/src/main/java/p/studio/window/WorkspaceBar.java b/prometeu-studio/src/main/java/p/studio/window/WorkspaceBar.java new file mode 100644 index 00000000..e9232b14 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/window/WorkspaceBar.java @@ -0,0 +1,47 @@ +package p.studio.window; + +import javafx.geometry.Insets; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.VBox; +import p.studio.workspaces.WorkspaceId; + +import java.util.EnumMap; +import java.util.Map; +import java.util.function.Consumer; + +public final class WorkspaceBar extends VBox { + private final ToggleGroup group = new ToggleGroup(); + private final Map buttons = new EnumMap<>(WorkspaceId.class); + + public WorkspaceBar(Consumer onSelect) { + setPadding(new Insets(8)); + setSpacing(8); + setPrefWidth(56); + + addBtn(WorkspaceId.EDITOR, "ðŸ“", "Editor", onSelect); + addBtn(WorkspaceId.ASSETS, "📦", "Assets", onSelect); + addBtn(WorkspaceId.BUILD, "âš™ï¸", "Build", onSelect); + addBtn(WorkspaceId.DEVICE, "🎮", "Device", onSelect); + } + + private void addBtn(WorkspaceId id, String icon, String tooltip, Consumer onSelect) { + ToggleButton b = new ToggleButton(icon); + b.setToggleGroup(group); + b.setFocusTraversable(false); + b.setPrefSize(40, 40); + b.setMinSize(40, 40); + b.setMaxSize(40, 40); + b.setUserData(id); + b.setOnAction(e -> onSelect.accept(id)); + b.setStyle("-fx-font-size: 16px;"); + + buttons.put(id, b); + getChildren().add(b); + } + + public void select(WorkspaceId id) { + ToggleButton b = buttons.get(id); + if (b != null) b.setSelected(true); + } +} diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/PlaceholderWorkspace.java b/prometeu-studio/src/main/java/p/studio/workspaces/PlaceholderWorkspace.java new file mode 100644 index 00000000..aa7f0c09 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/PlaceholderWorkspace.java @@ -0,0 +1,20 @@ +package p.studio.workspaces; + +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import p.studio.utilities.i18n.I18n; + +public final class PlaceholderWorkspace implements Workspace { + private final WorkspaceId id; + private final StackPane root = new StackPane(); + + public PlaceholderWorkspace(WorkspaceId id, String label) { + this.id = id; + root.getChildren().add(new Label(label + " (TODO)")); + } + + @Override public WorkspaceId id() { return id; } + @Override public I18n title() { return I18n.WORKSPACE_ASSETS; } + @Override public Node root() { return root; } +} diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/Workspace.java b/prometeu-studio/src/main/java/p/studio/workspaces/Workspace.java new file mode 100644 index 00000000..ff7eb0b6 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/Workspace.java @@ -0,0 +1,12 @@ +package p.studio.workspaces; + +import p.studio.utilities.i18n.I18n; + +public interface Workspace { + WorkspaceId id(); + I18n title(); + javafx.scene.Node root(); + default void onShow() {} + default void onHide() {} +} + diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/WorkspaceHost.java b/prometeu-studio/src/main/java/p/studio/workspaces/WorkspaceHost.java new file mode 100644 index 00000000..df004b70 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/WorkspaceHost.java @@ -0,0 +1,45 @@ +package p.studio.workspaces; + +import javafx.scene.Node; +import javafx.scene.layout.StackPane; + +import java.util.EnumMap; +import java.util.Map; + +public final class WorkspaceHost extends StackPane { + private final Map workspaces = new EnumMap<>(WorkspaceId.class); + private WorkspaceId active; + + public void register(Workspace ws) { + workspaces.put(ws.id(), ws); + + Node r = ws.root(); + r.setVisible(false); + r.setManaged(false); + getChildren().add(r); + } + + public void show(WorkspaceId id) { + if (active == id) return; + + if (active != null) { + Workspace old = workspaces.get(active); + old.onHide(); + Node n = old.root(); + n.setVisible(false); + n.setManaged(false); + } + + Workspace next = workspaces.get(id); + if (next == null) throw new IllegalStateException("Workspace not registered: " + id); + + Node n = next.root(); + n.setVisible(true); + n.setManaged(true); + next.onShow(); + + active = id; + } + + public WorkspaceId active() { return active; } +} diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/WorkspaceId.java b/prometeu-studio/src/main/java/p/studio/workspaces/WorkspaceId.java new file mode 100644 index 00000000..ba6bc3c0 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/WorkspaceId.java @@ -0,0 +1,8 @@ +package p.studio.workspaces; + +public enum WorkspaceId { + EDITOR, + ASSETS, + BUILD, + DEVICE +} \ No newline at end of file diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/editor/EditorToolbar.java b/prometeu-studio/src/main/java/p/studio/workspaces/editor/EditorToolbar.java new file mode 100644 index 00000000..9a244295 --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/editor/EditorToolbar.java @@ -0,0 +1,40 @@ +package p.studio.workspaces.editor; + +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; + +public final class EditorToolbar extends HBox { + + public EditorToolbar() { + setPadding(new Insets(6, 10, 6, 10)); + setSpacing(8); + getStyleClass().add("main-toolbar"); + + Button newBtn = iconButton("📄", "New"); + Button openBtn = iconButton("📂", "Open"); + Button saveBtn = iconButton("💾", "Save"); + + Button runBtn = iconButton("â–¶", "Run"); + runBtn.getStyleClass().add("accent"); + + Region spacer = new Region(); + HBox.setHgrow(spacer, javafx.scene.layout.Priority.ALWAYS); + + getChildren().addAll( + newBtn, + openBtn, + saveBtn, + spacer, + runBtn + ); + } + + private Button iconButton(String icon, String tooltip) { + Button b = new Button(icon); + b.setFocusTraversable(false); + b.getStyleClass().add("toolbar-button"); + return b; + } +} diff --git a/prometeu-studio/src/main/java/p/studio/workspaces/editor/EditorWorkspace.java b/prometeu-studio/src/main/java/p/studio/workspaces/editor/EditorWorkspace.java new file mode 100644 index 00000000..67fe3bae --- /dev/null +++ b/prometeu-studio/src/main/java/p/studio/workspaces/editor/EditorWorkspace.java @@ -0,0 +1,34 @@ +package p.studio.workspaces.editor; + +import javafx.scene.Node; +import javafx.scene.layout.BorderPane; +import org.fxmisc.richtext.CodeArea; +import org.fxmisc.richtext.LineNumberFactory; +import p.studio.utilities.i18n.I18n; +import p.studio.workspaces.Workspace; +import p.studio.workspaces.WorkspaceId; + +public final class EditorWorkspace implements Workspace { + private final BorderPane root = new BorderPane(); + private final EditorToolbar toolbar = new EditorToolbar(); + private final CodeArea codeArea = new CodeArea(); + + public EditorWorkspace() { + codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea)); + codeArea.replaceText(""" + fn frame(): void + { + // hello Prometeu + } + """); + + root.setTop(toolbar); + root.setCenter(codeArea); + } + + @Override public WorkspaceId id() { return WorkspaceId.EDITOR; } + @Override public I18n title() { return I18n.WORKSPACE_CODE; } + @Override public Node root() { return root; } + + public CodeArea codeArea() { return codeArea; } +} diff --git a/prometeu-studio/src/main/resources/i18n/messages.properties b/prometeu-studio/src/main/resources/i18n/messages.properties new file mode 100644 index 00000000..0d209de0 --- /dev/null +++ b/prometeu-studio/src/main/resources/i18n/messages.properties @@ -0,0 +1,16 @@ +app.title=Prometeu Studio + +menu.file=File +menu.file.newProject=New Project +menu.file.open=Open +menu.file.save=Save +menu.edit=Edit +menu.view=View +menu.help=Help + +toolbar.play=Play +toolbar.stop=Stop +toolbar.export=Export +workspace.code=Code +workspace.assets=Assets +workspace.debug=Debug diff --git a/prometeu-studio/src/main/resources/i18n/messages_pt_BR.properties b/prometeu-studio/src/main/resources/i18n/messages_pt_BR.properties new file mode 100644 index 00000000..4846ace3 --- /dev/null +++ b/prometeu-studio/src/main/resources/i18n/messages_pt_BR.properties @@ -0,0 +1,16 @@ +app.title=Prometeu Studio + +menu.file=Arquivo +menu.file.newProject=Novo Projeto +menu.file.open=Abrir +menu.file.save=Salvar +menu.edit=Editar +menu.view=Visualizar +menu.help=Ajuda + +toolbar.play=Executar +toolbar.stop=Parar +toolbar.export=Exportar +workspace.code=Código +workspace.assets=Assets +workspace.debug=Depurar diff --git a/prometeu-studio/src/main/resources/themes/default-prometeu.css b/prometeu-studio/src/main/resources/themes/default-prometeu.css new file mode 100644 index 00000000..bb061639 --- /dev/null +++ b/prometeu-studio/src/main/resources/themes/default-prometeu.css @@ -0,0 +1,36 @@ +.root { + -fx-font-family: "Inter", "Segoe UI", sans-serif; + -fx-base: #1e1e1e; + -fx-background: #1e1e1e; +} + +.main-toolbar { + -fx-background-color: #252526; + -fx-border-color: #2d2d2d; + -fx-border-width: 0 0 1 0; +} + +.toolbar-button { + -fx-background-color: transparent; + -fx-text-fill: #d4d4d4; + -fx-font-size: 14px; + -fx-padding: 6 10 6 10; + -fx-background-radius: 6; +} + +.toolbar-button:hover { + -fx-background-color: #2a2d2e; +} + +.toolbar-button:pressed { + -fx-background-color: #37373d; +} + +.toolbar-button.accent { + -fx-background-color: #0e639c; + -fx-text-fill: white; +} + +.toolbar-button.accent:hover { + -fx-background-color: #1177bb; +} diff --git a/prometeu-studio/src/test/java/p/studio/app/MessageUtilsTest.java b/prometeu-studio/src/test/java/p/studio/app/MessageUtilsTest.java new file mode 100644 index 00000000..607c04de --- /dev/null +++ b/prometeu-studio/src/test/java/p/studio/app/MessageUtilsTest.java @@ -0,0 +1,14 @@ +/* + * This source file was generated by the Gradle 'init' task + */ +package p.studio.app; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MessageUtilsTest { + @Test void testGetMessage() { + assertEquals("Hello World!", MessageUtils.getMessage()); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..fa0bc71b --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + +rootProject.name = "prometeu-studio" + +include(":prometeu-infra") +include("prometeu-compiler") +include("prometeu-studio") \ No newline at end of file