17 Commits

Author SHA1 Message Date
Xaymar 5b3a47076d Fix try_compile madness 2025-04-20 15:50:00 +02:00
Xaymar e57f229c77 I broke it again 2025-04-18 16:05:46 +02:00
Xaymar cd78d7a42d Remove remaining traces of automatic versioning code 2025-04-18 16:01:18 +02:00
Xaymar c88aa45a93 Fix try_compile failure
For unknown reasons, CMake does not propagate the compiler settings to the try_compile process but instead forwards the toolchain directly. This is weird.
2025-04-18 15:31:59 +02:00
Xaymar 7ce030a181 Remove unnecessary submodules 2025-04-18 15:31:24 +02:00
Xaymar e29c9161c8 Further progress towards building with one click
We've mostly got things to work, but CMake gets stuck trying to figure out the ABI. I'm not entirely sure what its doing.
2025-04-18 15:16:02 +02:00
Xaymar ebab168283 LLVM is mostly self-downloading now 2025-04-17 13:14:57 +02:00
Xaymar dfe1aab2ea Add initial work towards a one-click build setup
We can use some of CMakes built-in systems to ease the required workflow for the whole toolchain. This may eventually allow us to do a single click system where a developer does not need to do anything to get started. If Google can do this (even if slightly scuffed), so can we.
2025-04-17 11:26:57 +02:00
Michael Fabian 'Xaymar' Dirks 15b8ed7690 Even more work 2025-02-12 00:03:19 +01:00
Michael Fabian 'Xaymar' Dirks b61005bcaa More work on getting parsing to be functional 2025-01-25 19:26:49 +01:00
Michael Fabian 'Xaymar' Dirks e191173e7b 2025-01-25 Latest Changes 2025-01-25 16:27:50 +01:00
Michael Fabian 'Xaymar' Dirks f0ffbafee1 Shallow clone submodules
We don't need the entire history of these submodules, only their current content
2024-07-10 06:43:38 +02:00
Michael Fabian 'Xaymar' Dirks d16a36a141 Improve performance, fix file write bug, update headers 2024-07-05 17:10:24 +02:00
Michael Fabian 'Xaymar' Dirks 22c7614e7c Fix CRLF and submodules with auto-generated copyright headers
We no longer add another character to the file every time it is committed, and instead now properly handle CRLF. Additionally submodules are no longer updated when they shouldn't be, without requiring a manual config edit.
2024-07-05 15:19:18 +02:00
Michael Fabian 'Xaymar' Dirks af243a8ce8 Add yet another AST example 2024-07-05 12:56:08 +02:00
Michael Fabian 'Xaymar' Dirks 239c18fece Improve README file 2024-07-05 12:54:52 +02:00
Michael Fabian 'Xaymar' Dirks 2cc9981215 Add another example for AST parsing 2024-07-05 11:51:56 +02:00
47 changed files with 2170 additions and 1285 deletions
+10 -16
View File
@@ -1,17 +1,17 @@
# AUTOGENERATED COPYRIGHT HEADER START # AUTOGENERATED COPYRIGHT HEADER START
# Copyright (C) NaN-NaN undefined # Copyright (C) 2024-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
# Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
# AUTOGENERATED COPYRIGHT HEADER END # AUTOGENERATED COPYRIGHT HEADER END
# Basic Formatting # Basic Formatting
TabWidth: 4 TabWidth: 4
UseTab: ForContinuationAndIndentation UseTab: ForContinuationAndIndentation
ColumnLimit: 65535 ColumnLimit: 65535
LineEnding: LF
#- 0 does not respect the original line breaks! #- 0 does not respect the original line breaks!
# Language # Language
Language: Cpp Language: Cpp
Standard: c++17 Standard: c++20
# Indentation # Indentation
AccessModifierOffset: 0 AccessModifierOffset: 0
@@ -28,18 +28,10 @@ NamespaceIndentation: All
IncludeCategories: IncludeCategories:
- Regex: '^"warning-disable.hpp"$' - Regex: '^"warning-disable.hpp"$'
Priority: 50 Priority: 50
- Regex: '^(<|")(config.hpp|common.hpp|ui-common.hpp|strings.hpp|version.hpp|obs.h)("|>)'
Priority: 100
- Regex: '^<obs-'
Priority: 150
- Regex: '^<' - Regex: '^<'
Priority: 200 Priority: 200
- Regex: '^<Q'
Priority: 250
- Regex: '^"' - Regex: '^"'
Priority: 300 Priority: 300
- Regex: '.moc"$'
Priority: 300
- Regex: '^"warning-enable.hpp"$' - Regex: '^"warning-enable.hpp"$'
Priority: 500 Priority: 500
SortIncludes: true SortIncludes: true
@@ -50,7 +42,8 @@ AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true AlignConsecutiveDeclarations: true
AlignEscapedNewlines: Left AlignEscapedNewlines: Left
AlignOperands: true AlignOperands: true
AlignTrailingComments: false AlignTrailingComments: true
#ArrayInitializerAlignmentStyle: Right
DerivePointerAlignment: false DerivePointerAlignment: false
PointerAlignment: Left PointerAlignment: Left
@@ -78,16 +71,17 @@ BraceWrapping:
SplitEmptyFunction: false SplitEmptyFunction: false
SplitEmptyRecord: false SplitEmptyRecord: false
SplitEmptyNamespace: false SplitEmptyNamespace: false
BinPackArguments: true BinPackArguments: false
BinPackParameters: true BinPackParameters: false
BreakBeforeBinaryOperators: NonAssignment BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: true BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon BreakConstructorInitializers: BeforeColon
#BreakInheritanceList: BeforeColon #BreakInheritanceList: BeforeColon
BreakStringLiterals: true BreakStringLiterals: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerAllOnOneLineOrOnePerLine: false
Cpp11BracedListStyle: true Cpp11BracedListStyle: false
PackConstructorInitializers: NextLineOnly
# Spaces # Spaces
SpaceAfterCStyleCast: false SpaceAfterCStyleCast: false
-1
View File
@@ -1,5 +1,4 @@
# AUTOGENERATED COPYRIGHT HEADER START # AUTOGENERATED COPYRIGHT HEADER START
# Copyright (C) NaN-NaN undefined
# Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> # Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
# AUTOGENERATED COPYRIGHT HEADER END # AUTOGENERATED COPYRIGHT HEADER END
-1
View File
@@ -1,5 +1,4 @@
# AUTOGENERATED COPYRIGHT HEADER START # AUTOGENERATED COPYRIGHT HEADER START
# Copyright (C) NaN-NaN undefined
# Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> # Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
# AUTOGENERATED COPYRIGHT HEADER END # AUTOGENERATED COPYRIGHT HEADER END
+3
View File
@@ -1,3 +1,6 @@
# AUTOGENERATED COPYRIGHT HEADER START
# Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
# AUTOGENERATED COPYRIGHT HEADER END
# Build Directories # Build Directories
/build /build
/build32 /build32
-6
View File
@@ -1,6 +0,0 @@
[submodule "cmake/cmake-version"]
path = cmake/cmake-version
url = https://github.com/Xaymar/cmake-version.git
[submodule "cmake/cmake-clang"]
path = cmake/cmake-clang
url = https://github.com/Xaymar/cmake-clang.git
+5
View File
@@ -0,0 +1,5 @@
{
"cmake.configureArgs": [
"-C ${workspaceFolder}/cmake/generators/ninja.cmake"
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"folders": [
{
"path": "."
}
],
"settings": {}
}
+16 -148
View File
@@ -1,7 +1,14 @@
## AUTOGENERATED COPYRIGHT HEADER START # Copyright 2017-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
# Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> #
# AUTOGENERATED COPYRIGHT HEADER END # Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
cmake_minimum_required(VERSION 3.26...3.29.2 FATAL_ERROR) #
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
cmake_minimum_required(VERSION 4.0 FATAL_ERROR)
################################################################################ ################################################################################
# CMake Setup # CMake Setup
@@ -12,8 +19,6 @@ list(APPEND CMAKE_MESSAGE_INDENT "[${PROJECT_NAME}] ")
# Module Search Paths # Module Search Paths
list(APPEND CMAKE_MODULE_PATH list(APPEND CMAKE_MODULE_PATH
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake-clang"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake-version"
"${CMAKE_CURRENT_SOURCE_DIR}/cmake" "${CMAKE_CURRENT_SOURCE_DIR}/cmake"
) )
@@ -21,157 +26,20 @@ list(APPEND CMAKE_MODULE_PATH
#- Interprocedural optimizations #- Interprocedural optimizations
include("CheckIPOSupported") include("CheckIPOSupported")
#- CMake-based Versioning set_property(GLOBAL PROPERTY USE_FOLDERS ON)
include("version")
#- CMake-based Clang integration
include("clang")
SET_PROPERTY( GLOBAL PROPERTY USE_FOLDERS ON)
################################################################################
# Versioning
################################################################################
version(GENERATE _VERSION COMPRESSED MAJOR 0 MINOR 0 PATCH 0 TWEAK 0 REQUIRE "PATCH;")
version(PARSE _VERSION "${_VERSION}" REQUIRE "PATCH;TWEAK")
# If possible, automatically generate versions from git.
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/.git")
find_program(GIT
NAMES
git
git.exe
)
if(EXISTS "${GIT}")
# Try and calculate the exist version using git.
execute_process(COMMAND "${GIT}" describe --tags --long --abbrev=8 HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} RESULT_VARIABLE GIT_RESULT OUTPUT_VARIABLE GIT_OUTPUT ERROR_VARIABLE GIT_ERROR OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
if((GIT_RESULT EQUAL 0) AND (NOT "${GIT_OUTPUT}" STREQUAL ""))
# Result will be MAJOR.MINOR.PATCH-TWEAK-gHASH
string(REPLACE "-" ";" GIT_OUTPUT "${GIT_OUTPUT}")
string(REPLACE "." ";" GIT_OUTPUT "${GIT_OUTPUT}")
# Split into components
list(GET GIT_OUTPUT 0 GIT_OUTPUT_MAJOR)
list(GET GIT_OUTPUT 1 GIT_OUTPUT_MINOR)
list(GET GIT_OUTPUT 2 GIT_OUTPUT_PATCH)
list(GET GIT_OUTPUT 3 GIT_OUTPUT_TWEAK)
list(GET GIT_OUTPUT 4 GIT_OUTPUT_BUILD)
# Special case: Tag contains prerelease
if(GIT_OUTPUT_PATCH MATCHES "([0-9]+)([a-zA-Z]+)([0-9]*)")
# Patch requires special parsing.
set(GIT_OUTPUT_PATCH "${CMAKE_MATCH_1}")
if(CMAKE_MATCH_3 GREATER 0)
set(GIT_OUTPUT_PRERELEASE "${CMAKE_MATCH_2}")
else()
set(GIT_OUTPUT_PRERELEASE "a")
endif()
MATH(EXPR GIT_OUTPUT_TWEAK "${GIT_OUTPUT_TWEAK} + ${CMAKE_MATCH_3}")
# Modify the global version
version(MODIFY _VERSION "${_VERSION}" COMPRESS
MAJOR "${GIT_OUTPUT_MAJOR}"
MINOR "${GIT_OUTPUT_MINOR}"
PATCH "${GIT_OUTPUT_PATCH}"
TWEAK "${GIT_OUTPUT_TWEAK}"
BUILD "${GIT_OUTPUT_BUILD}"
PRERELEASE "${GIT_OUTPUT_PRERELEASE}"
REQUIRE "PATCH;TWEAK"
)
else()
# Modify the global version
version(MODIFY _VERSION "${_VERSION}" COMPRESS
MAJOR "${GIT_OUTPUT_MAJOR}"
MINOR "${GIT_OUTPUT_MINOR}"
PATCH "${GIT_OUTPUT_PATCH}"
TWEAK "${GIT_OUTPUT_TWEAK}"
BUILD "${GIT_OUTPUT_BUILD}"
PRERELEASE "a"
REQUIRE "PATCH;TWEAK"
)
endif()
else()
execute_process(COMMAND "${GIT}" rev-list --count HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} RESULT_VARIABLE GIT_RESULT OUTPUT_VARIABLE GIT_OUTPUT ERROR_VARIABLE GIT_ERROR OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
if((GIT_RESULT EQUAL 0) AND (NOT "${GIT_OUTPUT}" STREQUAL ""))
version(MODIFY _VERSION "${_VERSION}" COMPRESS
TWEAK "${GIT_OUTPUT}"
PRERELEASE "a"
REQUIRE "PATCH;TWEAK"
)
# Determine the build using git.
execute_process(COMMAND "${GIT}" log -1 "--pretty=format:g%h" --abbrev=8 HEAD WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} RESULT_VARIABLE GIT_RESULT OUTPUT_VARIABLE GIT_OUTPUT ERROR_VARIABLE GIT_ERROR OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
if((GIT_RESULT EQUAL 0) AND (NOT "${GIT_OUTPUT}" STREQUAL ""))
version(MODIFY _VERSION "${_VERSION}" COMPRESS
BUILD "${GIT_OUTPUT}"
REQUIRE "PATCH;TWEAK"
)
else()
message(WARNING "Failed to detect build version with 'git'.")
endif()
else()
message(WARNING "Failed to automatically detect version with 'git'.")
endif()
endif()
else()
message(WARNING "'git' not found, automatic version detection disabled.")
endif()
else()
message(STATUS "Not a git repository, automatic version detection disabled.")
endif()
# Allow manual overrides of the detected version.
if(${PREFIX}VERSION)
version(PARSE _VERSION_CFG "${${PREFIX}VERSION}" REQUIRE "PATCH;TWEAK")
if("${_VERSION_CFG_BUILD}" STREQUAL "")
set(_VERSION_CFG_BUILD "${_VERSION_BUILD}")
endif()
version(GENERATE _VERSION COMPRESS
MAJOR "${_VERSION_CFG_MAJOR}"
MINOR "${_VERSION_CFG_MINOR}"
PATCH "${_VERSION_CFG_PATCH}"
TWEAK "${_VERSION_CFG_TWEAK}"
PRERELEASE "${_VERSION_CFG_PRERELEASE}"
BUILD "${_VERSION_CFG_BUILD}"
)
endif()
# Fix up missing parts of the Version
version(PARSE _VERSION "${_VERSION}" REQUIRE "PATCH;TWEAK")
set(_VERSION_THIN "${_VERSION_MAJOR}.${_VERSION_MINOR}.${_VERSION_PATCH}")
if(NOT (_VERSION_PRERELEASE STREQUAL ""))
set(_VERSION_THIN "${_VERSION_THIN}${_VERSION_PRERELEASE}${_VERSION_TWEAK}")
endif()
if(NOT (VERSION_COMMIT STREQUAL ""))
set(_VERSION_THIN "${_VERSION_THIN}-${_VERSION_BUILD}")
endif()
# Parse & Log the detected version.
message(STATUS "Version ${_VERSION_THIN}")
################################################################################ ################################################################################
# Project # Project
################################################################################ ################################################################################
# Metadata # Metadata
version(GENERATE PROJECT_VERSION
MAJOR "${_VERSION_MAJOR}"
MINOR "${_VERSION_MINOR}"
PATCH "${_VERSION_PATCH}"
TWEAK "${_VERSION_TWEAK}"
REQUIRE "PATCH;TWEAK"
)
project( project(
${PROJECT_NAME} ${PROJECT_NAME}
VERSION ${PROJECT_VERSION}
) )
set(PROJECT_IDENTIFER "com.xaymar.BlitzLLVM") set(PROJECT_IDENTIFER "com.xaymar.blitzllvm")
set(PROJECT_TITLE "BlitzLLVM") set(PROJECT_TITLE "BlitzLLVM")
set(PROJECT_AUTHORS "See AUTHORS file") set(PROJECT_AUTHORS "Xaymar <info@xaymar.com>") # ToDo: Generate from AUTHORS
set(PROJECT_COPYRIGHT "All Rights Reserved. See LICENSE file for more information") set(PROJECT_COPYRIGHT "All Rights Reserved. See LICENSE file for more information") # ToDo: Generate from LICENSE
set(PROJECT_TRADEMARKS "") set(PROJECT_TRADEMARKS "")
function(init_project TARGET) function(init_project TARGET)
@@ -233,4 +101,4 @@ endfunction()
################################################################################ ################################################################################
# Sub-projects # Sub-projects
################################################################################ ################################################################################
ADD_SUBDIRECTORY("code_compiler") add_subdirectory("code_compiler")
+64
View File
@@ -0,0 +1,64 @@
{
"version": 6,
"cmakeMinimumRequired": {
"major": 4,
"minor": 0,
"patch": 0
},
"configurePresets": [
{
"name": "default",
"cacheVariables": {
"CMAKE_INTERPROCEDURAL_OPTIMIZATION": "ON"
},
"hidden": true
},
{
"inherits": "default",
"name": "windows-x64-llvm",
"description": "Windows, x86-64 (LLVM)",
"binaryDir": "build/windows-x64-llvm",
"installDir": "distrib/windows-x64-llvm",
"toolchainFile": "cmake/toolchains/llvm.cmake",
"generator": "Ninja Multi-Config",
"cacheVariables": {
}
},
{
"inherits": "default",
"name": "windows-x64-msvc2022",
"description": "Windows, x86-64 (MSVC 2022)",
"binaryDir": "build/windows-x64-msvc2022",
"installDir": "distrib/windows-x64-msvc2022",
"generator": "Visual Studio 17 2022",
"cacheVariables": {}
}
],
"buildPresets": [
{
"configurePreset": "windows-x64-llvm",
"name": "windows-x64-llvm",
"description": "",
"displayName": "Windows, x64 (LLVM)"
},
{
"configurePreset": "windows-x64-msvc2022",
"name": "windows-x64-msvc2022",
"description": "",
"displayName": "Windows, x64 (MSVC 2022)"
}
],
"workflowPresets": [
{
"name": "windows-x64-llvm",
"description": "",
"displayName": "",
"steps": [
{
"type": "configure",
"name": "windows-x64-llvm"
}
]
}
]
}
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright 2014-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> Copyright 2014-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+91 -1
View File
@@ -1,2 +1,92 @@
== What is BlitzLLVM? == What is BlitzLLVM?
BlitzLLVM is an attempt at a functional transpiler for BlitzBasic (Blitz2D, Blitz3D, and BlitzPlus). BlitzLLVM is an attempt at a BlitzBasic (Blitz2D, Blitz3D, BlitzPlus) compiler using the LLVM compiler backend to enable support for many architectures and platforms. The LLVM backend enables us to take advantage of optimizations and architecture targets that LLVM already provides for other languages, and may even enable further functionality previously considered impossible.
=== Features
++++
<table width="100%">
<colgroup>
<col width="20%">
<col width="60%">
<col width="10%">
<col width="10%">
</colgroup>
<tr>
<th align=left>Feature</th>
<th>Description</th>
<th>Target Version</th>
<th>Status</th>
</tr>
<tr>
<td>Unicode Support</td>
<td>Convert all ASCII functionality to support UTF-8 Unicode instead.</td>
<td>1.0</td>
<td>❌</td>
</tr>
<tr>
<td>User Library Handling</td>
<td>Allow users to provide their own user libraries to load at runtime.</td>
<td>1.0</td>
<td>❌</td>
</tr>
<tr>
<th colspan=2>Lexer / Tokenizer</th>
</tr>
<tr>
<td>Basic Functionality</td>
<td>Handling the necessary basic functionality, like turning text and symbols into tokens</td>
<td>0.1</td>
<td>✅</td>
</tr>
<tr>
<td>Common Types</td>
<td>Lexing for the basic types that BlitzBasic supports: Integers, Reals, Strings.</td>
<td>0.1</td>
<td>✅</td>
</tr>
<tr>
<td>Location Tracking</td>
<td>Properly tracking Line and Character location within a provided file to provide better errors and warnings later on.</td>
<td>0.1</td>
<td>✅</td>
</tr>
<tr>
<td>Error Handling</td>
<td>Throw a proper exception when unexpected behavior occurs, providing necessary information about the error.</td>
<td>0.1</td>
<td>✅</td>
</tr>
<tr>
<th colspan=2>Parser (AST)</th>
</tr>
<tr>
<td>Global Variables</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Local Variables</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Global Functions</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Local Functions</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Math Expressions</td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
++++
+170
View File
@@ -0,0 +1,170 @@
# Copyright (C) 2024-2025 Michael Fabian 'Xaymar' Dirks
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This is a script file that installs the necessary "Ninja" and "Ninja Multi-Config" generators.
# Run with `cmake -P cmake/generators/ninja.cmake` from the source directory.
cmake_minimum_required(VERSION 4.0 FATAL_ERROR)
include_guard(GLOBAL)
list(APPEND CMAKE_MESSAGE_INDENT "[Ninja] ")
# Necessary for propagation into the try_compile CMake subprocesses. It's unclear why this is not the default behavior.
foreach(_T IN ITEMS CMAKE_MAKE_PROGRAM)
if(DEFINED ENV{${_T}})
set(${_T} "$ENV{${_T}}")
endif()
endforeach()
if(NOT CMAKE_MAKE_PROGRAM)
# Try and figure out what processor we need to get binaries for.
if(CMAKE_HOST_SYSTEM_NAME MATCHES "[Ww]indows")
string(TOLOWER "$ENV{PROCESSOR_ARCHITECTURE}" _ARCH)
set(_OS "windows")
elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "[Dd]arwin")
set(_ARCH "multiarch")
set(_OS "macos")
elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "[Oo]pen[Bb][Ss][Dd]")
set(_OS "openbsd")
execute_process(
COMMAND "arch"
OUTPUT_VARIABLE _ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
else()
set(_OS "linux")
execute_process(
COMMAND "uname -p"
OUTPUT_VARIABLE _ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _RES
)
if(_RES EQUAL 0)
execute_process(
COMMAND "uname -m"
OUTPUT_VARIABLE _ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _RES
)
endif()
endif()
set(NINJA_VERSION "1.12.1")
set(NINJA_DIR "${CMAKE_SOURCE_DIR}/extra/ninja-${NINJA_VERSION}-${_OS}-${_ARCH}")
# Check if Ninja is present and if it is up to date.
find_program(NINJA_BIN
NAMES
ninja
HINTS
"${NINJA_DIR}/"
NO_CACHE NO_DEFAULT_PATH NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_INSTALL_PREFIX NO_CMAKE_FIND_ROOT_PATH
)
if(IS_EXECUTABLE "${NINJA_BIN}")
execute_process(
COMMAND ${NINJA_BIN} --version
OUTPUT_VARIABLE NINJA_VERSION_INSTALLED
OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REGEX REPLACE "[\r\n]+" "" NINJA_VERSION_INSTALLED "${NINJA_VERSION_INSTALLED}")
if((EXISTS NINJA_BIN) AND (NINJA_VERSION_INSTALLED VERSION_LESS NINJA_VERSION))
message(STATUS "Found outdated v${NINJA_VERSION_INSTALLED}.")
unset(NINJA_BIN)
else()
#message(STATUS "Found v${NINJA_VERSION_INSTALLED}.")
endif()
endif()
if((NOT IS_EXECUTABLE "${NINJA_BIN}") OR (NINJA_VERSION_INSTALLED VERSION_LESS NINJA_VERSION))
# It isn't up to date or doesn't exist, so try to download the latest version.
# First we'll have to figure out what we need to download, and from where.
if(_OS MATCHES "windows")
# Windows and Windows-like
SET (_FILE_EXT "zip")
if(_ARCH MATCHES "([xX]86)|([aA][mM][dD]64)")
# It's x86, x86-64 or amd64
SET (_FILE_NAME "ninja-win")
else()
# Assume ARM or ARM64
SET (_FILE_NAME "ninja-winarm64")
endif()
elseif(_OS MATCHES "macos")
# MacOS and MacOS-like
SET (_FILE_EXT "zip")
SET (_FILE_NAME "ninja-mac")
else()
# Assume this to be generic Unix-like.
SET (_FILE_EXT "tar.xz")
if(_ARCH MATCHES "([xX]86)|([aA][mM][dD]64)")
# It's x86, x86-64 or amd64
SET (_FILE_NAME "ninja-linux")
else()
# Assume ARM or ARM64
SET (_FILE_NAME "ninja-linux-aarch64")
endif()
endif()
# Download the ideal version.
if(NOT EXISTS "${NINJA_DIR}.${_FILE_EXT}")
message(STATUS "Downloading Ninja v${NINJA_VERSION}...")
file(DOWNLOAD
"https://github.com/ninja-build/ninja/releases/download/v${NINJA_VERSION}/${_FILE_NAME}.${_FILE_EXT}"
"${NINJA_DIR}.${_FILE_EXT}"
SHOW_PROGRESS
)
else()
message(STATUS "Skipping download as file already exists.")
endif()
# Extract it.
message(STATUS "Extracting...")
file(ARCHIVE_EXTRACT
INPUT "${NINJA_DIR}.${_FILE_EXT}"
DESTINATION "${NINJA_DIR}/"
)
# Delete the archive itself.
message(STATUS "Cleaning...")
#file(REMOVE "${NINJA_DIR}.${_FILE_EXT}")
# Check if Ninja is present and if it is up to date.
message(STATUS "Testing...")
find_program(NINJA_BIN
NAMES
ninja
HINTS
"${NINJA_DIR}/"
NO_CACHE NO_DEFAULT_PATH NO_PACKAGE_ROOT_PATH NO_CMAKE_PATH NO_CMAKE_ENVIRONMENT_PATH NO_SYSTEM_ENVIRONMENT_PATH NO_CMAKE_SYSTEM_PATH NO_CMAKE_INSTALL_PREFIX NO_CMAKE_FIND_ROOT_PATH
)
if(IS_EXECUTABLE "${NINJA_BIN}")
execute_process(
COMMAND ${NINJA_BIN} --version
OUTPUT_VARIABLE NINJA_VERSION_INSTALLED
OUTPUT_STRIP_TRAILING_WHITESPACE
)
string(REGEX REPLACE "[\r\n]+" "" NINJA_VERSION_INSTALLED "${NINJA_VERSION_INSTALLED}")
endif()
endif()
if((NOT IS_EXECUTABLE "${NINJA_BIN}") OR (NINJA_VERSION_INSTALLED VERSION_LESS NINJA_VERSION))
message(FATAL_ERROR "Failed to install newer version of Ninja.")
elseif(IS_EXECUTABLE "${NINJA_BIN}")
message(STATUS "Found v${NINJA_VERSION_INSTALLED}.")
endif()
set(CMAKE_MAKE_PROGRAM "${NINJA_BIN}" CACHE STRING "" FORCE)
endif()
list(POP_BACK CMAKE_MESSAGE_INDENT)
+399
View File
@@ -0,0 +1,399 @@
# Copyright (C) 2019-2025 Michael Fabian 'Xaymar' Dirks
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# This is a (mostly) self-contained toolchain file that sets up everything necessary to compile with LLVM/Clang.
# cmake --fresh -C cmake/generators/ninja.cmake --preset windows-x64-llvm
# !BUG: try_compile downloads the whole thing again, which it shouldn't. Why is CMake not passing the compiler stuff on?
# Which version of LLVM do we want to have (or newer)?
set(LLVM_VERSION "19.1.7")
cmake_minimum_required(VERSION 4.0 FATAL_ERROR)
include_guard(GLOBAL)
list(APPEND CMAKE_MESSAGE_INDENT "[LLVM] ")
# CMake 3.6: Force variables to be propagated to try_compile sub-processes.
list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES
LLVM_CLANG
LLVM_CLANGPP
LLVM_AR
LLVM_RANLIB
LLVM_NM
LLVM_READELF
LLVM_OBJCOPY
LLVM_OBJDUMP
LLVM_STRIP
LLVM_DIR
CMAKE_CXX_COMPILER
CMAKE_C_COMPILER
CMAKE_ASM_COMPILER
CMAKE_RC_COMPILER
CMAKE_LINKER
CMAKE_C_COMPILER_LINKER
CMAKE_CXX_COMPILER_LINKER
CMAKE_RC_COMPILER_LINKER
CMAKE_ASM_COMPILER_LINKER
CMAKE_NM
CMAKE_OBJDUMP
CMAKE_OBJCOPY
CMAKE_RANLIB
CMAKE_C_COMPILER_RANLIB
CMAKE_CXX_COMPILER_RANLIB
CMAKE_RC_COMPILER_RANLIB
CMAKE_ASM_COMPILER_RANLIB
CMAKE_AR
CMAKE_C_COMPILER_AR
CMAKE_CXX_COMPILER_AR
CMAKE_RC_COMPILER_AR
CMAKE_ASM_COMPILER_AR
)
set(CMAKE_TRY_COMPILE_NO_PLATFORM_VARIABLES OFF)
# Macro for common stuff
macro(find_llvm)
set(LLVM_FOUND "FALSE")
# - AR
find_program(
LLVM_AR
NAMES
llvm-ar
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
# - Library Randomizer
find_program(
LLVM_RANLIB
NAMES
llvm-ranlib
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
# - Linker
if(_ARCH MATCHES "[xX]64")
find_program(
LLVM_LD
NAMES
ld64.lld
ld.lld
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
else()
find_program(
LLVM_LD
NAMES
ld.lld
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
endif()
# - Object Copy
find_program(
LLVM_OBJCOPY
NAMES
llvm-objcopy
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
# - Object Dump
find_program(
LLVM_OBJDUMP
NAMES
llvm-objdump
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
# - NM
find_program(
LLVM_NM
NAMES
llvm-nm
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
# - ReadELF
find_program(
LLVM_READELF
NAMES
llvm-readelf
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
# - Strip Debug Info
find_program(
LLVM_STRIP
NAMES
llvm-strip
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
# - C Compiler
find_program(
LLVM_CLANG
NAMES
clang
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
# - C++ Compiler
find_program(
LLVM_CLANGPP
NAMES
clang++
PATHS
"${LLVM_DIR}/bin/"
NO_CACHE
)
set(LLVM_FOUND TRUE)
foreach(_TEST IN ITEMS
"LLVM_AR"
"LLVM_LD"
"LLVM_CLANG"
"LLVM_CLANGPP")
if(NOT IS_EXECUTABLE ${${_TEST}})
set(LLVM_FOUND FALSE)
break()
else()
execute_process(
COMMAND "${${_TEST}}" --version
OUTPUT_VARIABLE ${_TEST}_VERSION
RESULT_VARIABLE _RES
)
if(NOT _RES EQUAL 0)
set(LLVM_FOUND FALSE)
break()
endif()
string(REGEX MATCH "[1-9+]?[0-9+]\.[1-9+]?[0-9+]\.[1-9+]?[0-9+]\." "${_TEST}_VERSION" "${${_TEST}_VERSION}")
string(REGEX REPLACE "[\r\n]+" "" "${_TEST}_VERSION" "${${_TEST}_VERSION}")
endif()
endforeach()
endmacro()
if(NOT CMAKE_C_COMPILER OR NOT CMAKE_CXX_COMPILER OR NOT CMAKE_LINKER OR NOT CMAKE_AR)
# Try and figure out what processor we need to get binaries for.
if(CMAKE_HOST_SYSTEM_NAME MATCHES "[Ww]indows")
set(_OS "windows")
string(TOLOWER "$ENV{PROCESSOR_ARCHITECTURE}" _ARCH)
if(_ARCH MATCHES "(amd64)|(AMD64)")
set(_FILE_NAME "LLVM-${LLVM_VERSION}-win64")
elseif(_ARCH MATCHES "(x86)|(X86)")
set(_FILE_NAME "LLVM-${LLVM_VERSION}-win32")
else()
set(_FILE_NAME "LLVM-${LLVM_VERSION}-woa64")
endif()
set(_FILE_EXT "exe")
#Computer\HKEY_CURRENT_USER\SOFTWARE\7-Zip\Path64
cmake_host_system_information(
RESULT 7ZIP_DIR
QUERY WINDOWS_REGISTRY "HKCU/SOFTWARE/7-Zip" VALUE "Path64"
VIEW HOST
)
find_program(7ZIP_BIN
NAMES
7z
7za
HINTS
"${7ZIP_DIR}"
"C:/Program Files/7-Zip"
"C:/Program Files (x86)/7-Zip"
)
elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "[Dd]arwin")
set(_OS "macos")
execute_process(
COMMAND "uname -m"
OUTPUT_VARIABLE _ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _RES
)
if(_ARCH MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
set(_FILE_NAME "LLVM-${LLVM_VERSION}-MacOS-X64")
else()
set(_FILE_NAME "LLVM-${LLVM_VERSION}-MacOS-ARM64")
endif()
set(_FILE_EXT "tar.xz")
elseif(CMAKE_HOST_SYSTEM_NAME MATCHES "[Oo]pen[Bb][Ss][Dd]")
set(_OS "openbsd")
execute_process(
COMMAND "arch"
OUTPUT_VARIABLE _ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(_FILE_EXT "tar.xz")
if(_ARCH MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
set(_FILE_NAME "LLVM-${LLVM_VERSION}-Linux-X64")
else()
set(_FILE_NAME "LLVM-${LLVM_VERSION}-Linux-ARM64")
endif()
else()
set(_OS "linux")
execute_process(
COMMAND "uname -p"
OUTPUT_VARIABLE _ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _RES
)
if(_RES EQUAL 0)
execute_process(
COMMAND "uname -m"
OUTPUT_VARIABLE _ARCH
OUTPUT_STRIP_TRAILING_WHITESPACE
RESULT_VARIABLE _RES
)
endif()
set(_FILE_EXT "tar.xz")
if(_ARCH MATCHES "(x86)|(X86)|(amd64)|(AMD64)")
set(_FILE_NAME "LLVM-${LLVM_VERSION}-Linux-X64")
else()
set(_FILE_NAME "LLVM-${LLVM_VERSION}-Linux-ARM64")
endif()
endif()
set(LLVM_DIR "${CMAKE_SOURCE_DIR}/extra/llvm-${LLVM_VERSION}-${_OS}-${_ARCH}")
# Try and find an existing LLVM installation.
find_llvm()
if(LLVM_CLANG_VERSION AND LLVM_CLANG_VERSION VERSION_LESS LLVM_VERSION)
message(STATUS "Found outdated v${LLVM_CLANG_VERSION}.")
elseif(NOT LLVM_FOUND)
message(STATUS "No installed LLVM found.")
endif()
if((NOT LLVM_FOUND) OR (LLVM_CLANG_VERSION VERSION_LESS LLVM_VERSION))
# It isn't up to date or doesn't exist, so try to download the latest version.
if((_FILE_EXT MATCHES "exe") AND NOT 7ZIP_BIN)
message(FATAL_ERROR "7-Zip is required to continue setting up LLVM. Please provide '7z.exe' in PATH or by installing the latest version from https://www.7-zip.org/.")
endif()
# Download the ideal version.
if(NOT EXISTS "${LLVM_DIR}.${_FILE_EXT}")
message(STATUS "Downloading LLVM v${LLVM_VERSION}...")
file(DOWNLOAD
"https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_VERSION}/${_FILE_NAME}.${_FILE_EXT}"
"${LLVM_DIR}.${_FILE_EXT}"
SHOW_PROGRESS
)
else()
message(STATUS "Skipping download as file already exists.")
endif()
# Extract it.
message(STATUS "Extracting...")
if(_FILE_EXT MATCHES "exe")
execute_process(
COMMAND ${7ZIP_BIN} x -y -aoa "-o${LLVM_DIR}/" "${LLVM_DIR}.${_FILE_EXT}"
COMMAND_ERROR_IS_FATAL ANY
OUTPUT_QUIET
ERROR_QUIET
)
else()
file(ARCHIVE_EXTRACT
INPUT "${LLVM_DIR}.${_FILE_EXT}"
DESTINATION "${LLVM_DIR}/"
)
endif()
# Delete the archive itself.
message(STATUS "Cleaning...")
#file(REMOVE "${LLVM_DIR}.${_FILE_EXT}")
# Final stuff
#message(STATUS "Testing...")
find_llvm()
endif()
if(LLVM_FOUND AND (LLVM_CLANG_VERSION VERSION_GREATER_EQUAL LLVM_VERSION))
message(STATUS "Found v${LLVM_CLANG_VERSION}.")
foreach(_T IN ITEMS CMAKE_AR CMAKE_C_COMPILER_AR CMAKE_CXX_COMPILER_AR CMAKE_RC_COMPILER_AR CMAKE_ASM_COMPILER_AR)
set(${_T} "${LLVM_AR}")
#set(${_T} "${LLVM_AR}" PARENT_SCOPE)
set(${_T} "${LLVM_AR}" CACHE STRING "" FORCE)
set(ENV{${_T}} "${LLVM_AR}")
endforeach()
foreach(_T IN ITEMS CMAKE_RANLIB CMAKE_C_COMPILER_RANLIB CMAKE_CXX_COMPILER_RANLIB CMAKE_RC_COMPILER_RANLIB CMAKE_ASM_COMPILER_RANLIB)
set(${_T} "${LLVM_RANLIB}")
#set(${_T} "${LLVM_RANLIB}" PARENT_SCOPE)
set(${_T} "${LLVM_RANLIB}" CACHE STRING "" FORCE)
set(ENV{${_T}} "${LLVM_RANLIB}")
endforeach()
foreach(_T IN ITEMS CMAKE_OBJCOPY)
set(${_T} "${LLVM_OBJCOPY}")
#set(${_T} "${LLVM_OBJCOPY}" PARENT_SCOPE)
set(${_T} "${LLVM_OBJCOPY}" CACHE STRING "" FORCE)
set(ENV{${_T}} "${LLVM_OBJCOPY}")
endforeach()
foreach(_T IN ITEMS CMAKE_OBJDUMP)
set(${_T} "${LLVM_OBJDUMP}")
#set(${_T} "${LLVM_OBJDUMP}" PARENT_SCOPE)
set(${_T} "${LLVM_OBJDUMP}" CACHE STRING "" FORCE)
set(ENV{${_T}} "${LLVM_OBJDUMP}")
endforeach()
foreach(_T IN ITEMS CMAKE_NM)
set(${_T} "${LLVM_NM}")
#set(${_T} "${LLVM_NM}" PARENT_SCOPE)
set(${_T} "${LLVM_NM}" CACHE STRING "" FORCE)
set(ENV{${_T}} "${LLVM_NM}")
endforeach()
foreach(_T IN ITEMS CMAKE_LINKER CMAKE_C_COMPILER_LINKER CMAKE_CXX_COMPILER_LINKER CMAKE_RC_COMPILER_LINKER CMAKE_ASM_COMPILER_LINKER)
set(${_T} "${LLVM_LD}")
#set(${_T} "${LLVM_LD}" PARENT_SCOPE)
set(${_T} "${LLVM_LD}" CACHE STRING "" FORCE)
set(ENV{${_T}} "${LLVM_LD}")
endforeach()
foreach(_T IN ITEMS CMAKE_C_COMPILER CMAKE_ASM_COMPILER CMAKE_RC_COMPILER)
set(${_T} "${LLVM_CLANG}")
#set(${_T} "${LLVM_CLANG}" PARENT_SCOPE)
set(${_T} "${LLVM_CLANG}" CACHE STRING "" FORCE)
set(ENV{${_T}} "${LLVM_CLANG}")
endforeach()
foreach(_T IN ITEMS CMAKE_CXX_COMPILER)
set(${_T} "${LLVM_CLANGPP}")
#set(${_T} "${LLVM_CLANGPP}" PARENT_SCOPE)
set(${_T} "${LLVM_CLANGPP}" CACHE STRING "" FORCE)
set(ENV{${_T}} "${LLVM_CLANGPP}")
endforeach()
foreach(_T IN ITEMS LLVM_CLANG LLVM_CLANGPP LLVM_AR LLVM_RANLIB LLVM_NM LLVM_READELF LLVM_OBJCOPY LLVM_OBJDUMP LLVM_STRIP LLVM_DIR)
set(${_T} "${${_T}}" CACHE STRING "" FORCE)
set(ENV${_T} "${${_T}}")
endforeach()
else()
message(FATAL_ERROR "Failed to find or provide a compatible LLVM installation.")
endif()
endif()
list(POP_BACK CMAKE_MESSAGE_INDENT)
+7 -11
View File
@@ -1,9 +1,7 @@
## AUTOGENERATED COPYRIGHT HEADER START # AUTOGENERATED COPYRIGHT HEADER START
# Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> # Copyright (C) 2017-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
# AUTOGENERATED COPYRIGHT HEADER END # AUTOGENERATED COPYRIGHT HEADER END
project(code_compiler project(compiler)
VERSION ${PROJECT_VERSION}
)
add_executable(${PROJECT_NAME}) add_executable(${PROJECT_NAME})
init_project(${PROJECT_NAME}) init_project(${PROJECT_NAME})
@@ -15,16 +13,14 @@ target_sources(${PROJECT_NAME} PRIVATE
"source/error.cpp" "source/error.cpp"
"source/parser.hpp" "source/parser.hpp"
"source/parser.cpp" "source/parser.cpp"
"source/util.hpp"
"source/util.cpp"
"source/types.hpp"
"source/types.cpp"
"source/compiler.hpp" "source/compiler.hpp"
"source/compiler.cpp" "source/compiler.cpp"
"source/ast/ast.hpp" "source/ast/ast.hpp"
"source/ast/ast.cpp" "source/ast/ast.cpp"
"source/ast/arithmetic.hpp"
"source/ast/arithmetic.cpp"
"source/ast/function.hpp"
"source/ast/function.cpp"
"source/ast/value.hpp"
"source/ast/value.cpp"
) )
target_include_directories(${PROJECT_NAME} PRIVATE target_include_directories(${PROJECT_NAME} PRIVATE
"${PROJECT_SOURCE_DIR}/source" "${PROJECT_SOURCE_DIR}/source"
-9
View File
@@ -1,9 +0,0 @@
/// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
#include "arithmetic.hpp"
blitz::ast::arithmetic_expression::arithmetic_expression(expression_operator op, std::unique_ptr<expression> left, std::unique_ptr<expression> right)
: m_operator(op), m_left(std::move(left)), m_right(std::move(right)) {}
blitz::ast::arithmetic_expression::~arithmetic_expression() {}
-30
View File
@@ -1,30 +0,0 @@
/// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
#pragma once
#include "ast.hpp"
#include "value.hpp"
namespace blitz {
namespace ast {
enum class expression_operator : int8_t {
Add, /*+*/
Subtract, /*-*/
Multiply, /***/
Divide, /*/*/
Invert, /*~*/
Power, /*^*/
Equal, /*=*/
};
class arithmetic_expression : public expression {
public:
arithmetic_expression(expression_operator op, std::unique_ptr<expression> left, std::unique_ptr<expression> right);
virtual ~arithmetic_expression();
private:
expression_operator m_operator;
std::unique_ptr<expression> m_left, m_right;
};
}
}
+339 -16
View File
@@ -1,29 +1,352 @@
// AUTOGENERATED COPYRIGHT HEADER START // AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> // Copyright (C) 2017-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END // AUTOGENERATED COPYRIGHT HEADER END
#include "ast.hpp" #include "ast.hpp"
#include <cstdlib> #include "util.hpp"
blitz::ast::variable::~variable()
blitz::ast::value_expression::value_expression(blitz::token token) : expression(token) {}
blitz::ast::integer_expression::integer_expression(blitz::token token) : value_expression(token), _value(0)
{ {
if (_token.text.length() > 0) { /* Variable Parsing
_value = atol(_token.text.c_str()); *
} * Declaration:
* - 8bit Signed Integer Variable
* Variable:Byte
* Variable:Int8
* - 8bit Unsigned Integer Variable
* Variable:UByte
* Variable:UInt8
* - 16bit Signed Integer Variable
* Variable:Short
* Variable:Int16
* - 16bit Unsigned Integer Variable
* Variable:UShort
* Variable:UInt16
* - 32bit Signed Integer Variable
* Variable
* Variable%
* Variable:Int
* Variable:Int32
* - 32bit Unsigned Integer Variable
* Variable:UInt
* Variable:UInt32
* - 64bit Signed Integer Variable
* Variable%%
* Variable:Long
* Variable:Int64
* - 64bit Unsigned Integer Variable
* Variable:ULong
* Variable:UInt64
* - 32bit Real Variable
* Variable#
* Variable:Float
* Variable:Float32
* Variable:Real
* Variable:Real32
* - 64bit Real Variable
* Variable##
* Variable:Double
* Variable:Float64
* Variable:Real64
* - UTF-8 String Variable
* Variable$
* Variable:String
* - Struct Variable
* Variable.StructName
* Variable:StructName
*
* Access:
* - Struct Access:
* Variable\Key
* - Array Access:
* Variable[IntegerIndex]
* - Dynamic Array Access:
* Variable(IntegerIndex)
* - Direct Access:
* Variable
*/
} }
blitz::ast::real_expression::real_expression(blitz::token token) : value_expression(token), _value(0.0f) bool blitz::ast::variable::can_parse(std::shared_ptr<blitz::lexer> lexer)
{ {
if (_token.text.length() > 0) { return lexer->current().type == blitz::token::variant::TEXT;
_value = atof(_token.text.c_str());
}
} }
blitz::ast::string_expression::string_expression(blitz::token token) : value_expression(token), _value() std::shared_ptr<blitz::ast::node> blitz::ast::variable::try_parse(std::shared_ptr<blitz::lexer> lexer)
{ {
_value = _token.text; auto file = lexer->file();
auto name_tk = lexer->current();
if (name_tk.type != blitz::token::variant::TEXT) {
throw blitz::error(file, name_tk.location, name_tk.location, blitz::format("Unexpected %s, expected text.", name_tk.to_string().c_str()));
} }
blitz::ast::variable_expression::variable_expression(blitz::token token) : expression(token) {} auto node = std::make_shared<blitz::ast::variable>();
node->tokens.push_back(name_tk);
node->type = blitz::types::type::UNKNOWN;
node->name = name_tk.text;
// Check if this has a type definition
auto symbol_tk = lexer->peek();
if (symbol_tk.type != blitz::token::variant::SYMBOL) {
return node;
} else if (symbol_tk.location.second != (name_tk.location.second + name_tk.text.length())) {
// We only care about these if they're immediately attached to us.
return node;
}
if (symbol_tk.text == ":") {
// :Type
node->tokens.push_back(lexer->next()); // Advance to next token.
auto type_tk = lexer->next();
if (type_tk != blitz::token::variant::TEXT) {
throw blitz::error(file, name_tk.location, type_tk.location, blitz::format("Unexpected %s, expected text.", type_tk.to_string().c_str()));
}
if (type_tk.location.second != (symbol_tk.location.second + 1)) {
throw blitz::error(file, name_tk.location, type_tk.location, blitz::format("Unexpected white space, expected text."));
}
auto type = blitz::types::from_string(type_tk.text);
if (type == blitz::types::type::UNKNOWN) {
throw blitz::error(file, name_tk.location, type_tk.location, blitz::format("Unexpected %s, expected built-in type name.", type_tk.text.c_str()));
}
node->tokens.push_back(type_tk);
node->type = type;
} else if (symbol_tk.text == ".") {
// .Struct
node->tokens.push_back(lexer->next()); // Advance to next token.
auto type_tk = lexer->next();
if (type_tk != blitz::token::variant::TEXT) {
throw blitz::error(file, name_tk.location, type_tk.location, blitz::format("Unexpected %s, expected text.", type_tk.to_string().c_str()));
}
if (type_tk.location.second != (symbol_tk.location.second + 1)) {
throw blitz::error(file, name_tk.location, type_tk.location, blitz::format("Unexpected white space, expected text."));
}
node->tokens.push_back(type_tk);
node->type = blitz::types::type::STRUCT;
node->struct_name = type_tk.text;
} else if (symbol_tk.text == "%") {
// Int32
node->tokens.push_back(lexer->next()); // Advance to next token.
node->type = blitz::types::type::INT32;
} else if (symbol_tk.text == "#") {
// Float
node->tokens.push_back(lexer->next()); // Advance to next token.
node->type = blitz::types::type::FLOAT32;
} else if (symbol_tk.text == "$") {
// String
node->tokens.push_back(lexer->next()); // Advance to next token.
node->type = blitz::types::type::STRING;
}
return node;
}
blitz::ast::value::~value() {}
bool blitz::ast::value::can_parse(std::shared_ptr<blitz::lexer> lexer)
{
auto tk = lexer->current();
switch (tk.type) {
case blitz::token::variant::STRING:
case blitz::token::variant::REAL:
case blitz::token::variant::INTEGER:
return true;
case blitz::token::variant::TEXT: {
// We can only parse True, False, Null
std::string text = tk.text;
std::transform(text.cbegin(), text.cend(), text.begin(), blitz::utility::utf8_safe_tolower);
if (text == "false") {
return true;
} else if (text == "true") {
return true;
} else if (text == "null") {
return true;
}
break;
}
default:
break;
}
return false;
}
std::shared_ptr<blitz::ast::node> blitz::ast::value::try_parse(std::shared_ptr<blitz::lexer> lexer)
{
auto file = lexer->file();
auto tk = lexer->current();
auto node = std::make_shared<blitz::ast::value>();
node->type = variant::UNKNOWN;
if (tk.type == blitz::token::variant::STRING) {
node->type = variant::STRING;
node->text = tk.text;
return node;
} else if (tk.type == blitz::token::variant::REAL) {
node->type = variant::REAL;
node->number.f = strtod(tk.text.c_str(), nullptr);
if (errno == ERANGE) {
throw blitz::error(file, tk.location, tk.location, blitz::format("Real '%s' is not representable on this system.", tk.text.c_str()));
}
} else if (tk.type == blitz::token::variant::INTEGER) {
// Figure out which base this integer is in (and where it starts).
int base = 10;
const char* text = tk.text.c_str();
if ((tk.text.length() > 1) && (text[0] == '0')) {
if (text[1] == 'x') { // Base 16
base = 16;
text = text += 2;
} else if (text[1] == 'b') { // Base 2
base = 2;
text = text += 2;
} else if (isdigit(text[1])) {
base = 8;
text = text += 1;
}
}
if (tk.text[tk.text.length() - 1] == 'u') {
// User wants this to be unsigned.
node->type = variant::UNSIGNED_INTEGER;
node->number.ui = strtoull(text, nullptr, base);
if (errno == ERANGE) {
throw blitz::error(file, tk.location, tk.location, blitz::format("Integer '%s' is not representable on this system.", tk.text.c_str()));
}
} else {
// Try and figure out if it is unsigned.
node->number.i = strtoll(text, nullptr, base);
if (errno == ERANGE) {
node->type = variant::UNSIGNED_INTEGER;
node->number.ui = strtoull(text, nullptr, base);
if (errno == ERANGE) {
throw blitz::error(file, tk.location, tk.location, blitz::format("Integer '%s' is not representable on this system.", tk.text.c_str()));
}
} else {
node->type = variant::INTEGER;
}
}
} else if (tk.type == blitz::token::variant::TEXT) {
std::string text = tk.text;
std::transform(text.cbegin(), text.cend(), text.begin(), blitz::utility::utf8_safe_tolower);
if (text == "false") {
node->type = variant::BOOL;
node->number.b = false;
} else if (text == "true") {
node->type = variant::BOOL;
node->number.b = true;
} else if (text == "null") {
node->type = variant::NULL;
node->number.ui = 0;
}
}
return node;
}
blitz::ast::declare::~declare()
{
/* Variable Declaration
*
* Examples:
* - Local myVar1, myVar2%, myVar3 = "Help", myVar4$ = "Me"
* - Global myVar2g# = 3.147
*
*
*
*/
}
bool blitz::ast::declare::can_parse(std::shared_ptr<blitz::lexer> lexer)
{
auto tk = lexer->current();
if (tk != blitz::token::variant::TEXT) {
return false;
}
std::string text = tk.text;
std::transform(text.cbegin(), text.cend(), text.begin(), blitz::utility::utf8_safe_tolower);
if (text == "local") {
return true;
} else if (text == "global") {
return true;
}
return false;
}
std::shared_ptr<blitz::ast::node> blitz::ast::declare::try_parse(std::shared_ptr<blitz::lexer> lexer)
{
bool is_global;
auto file = lexer->file();
auto tk = lexer->current();
if (tk != blitz::token::variant::TEXT) {
throw blitz::error(file, tk.location, tk.location, blitz::format("Unexpected %s, expected text.", tk.to_string().c_str()));
}
// Check if this is Local or Global.
std::string text = tk.text;
std::transform(text.cbegin(), text.cend(), text.begin(), blitz::utility::utf8_safe_tolower);
if (text == "local") {
is_global = false;
} else if (text == "global") {
is_global = true;
} else {
throw blitz::error(file, tk.location, tk.location, blitz::format("Unexpected %s, expected Local or Global.", tk.to_string().c_str()));
}
auto node = std::make_shared<blitz::ast::declare>();
node->global = is_global;
// Local myVar
// Local myVar, myVar2
// Local myVar = Expression
// Local myVar = Expression, myVar2
// Local myVar = Expression :
// Local myVar : Local MyVar
// Local myVar:Int
// Local myVar.StructType
for (tk = lexer->peek(); tk != blitz::token::variant::ENDOFFILE; tk = lexer->peek()) {
// Declarations require a valid variable name
if (tk.type != blitz::token::variant::TEXT) {
throw blitz::error(file, tk.location, tk.location, blitz::format("Expected variable name, got %s.", tk.to_string().c_str()));
} else if (!blitz::ast::variable::can_parse(lexer)) {
throw blitz::error(file, tk.location, tk.location, blitz::format("Expected variable name, got %s.", tk.to_string().c_str()));
}
// Advance the lexer and parse the variable declaration.
tk = lexer->next();
auto variable_nd = blitz::ast::variable::try_parse(lexer);
node->nodes.push_back(variable_nd);
// Peek at what's coming up and decide on behavior.
tk = lexer->peek();
if ((tk.type == blitz::token::variant::NEWLINE) || ((tk.type == blitz::token::variant::SYMBOL) && (tk.text == ":")) || (tk.type == blitz::token::variant::ENDOFFILE)) {
// Nothing useful, break out here.
break;
} else if ((tk.type == blitz::token::variant::SYMBOL) && (tk.text == ",")) {
// Next variable is being declared.
lexer->next();
continue;
} else if ((tk.type == blitz::token::variant::SYMBOL) && (tk.text == "=")) {
// Assignment, not implemented yet. Skip until next valid symbol.
lexer->next();
do {
if ((tk.type == blitz::token::variant::SYMBOL) && (tk.text == ",")) {
// Next variable is being declared.
break;
} else if ((tk.type == blitz::token::variant::NEWLINE) || ((tk.type == blitz::token::variant::SYMBOL) && (tk.text == ":"))) {
return node;
} else if (tk.type == blitz::token::variant::ENDOFFILE) {
return node;
}
tk = lexer->next();
} while (true);
}
}
return node;
}
+46 -70
View File
@@ -1,5 +1,5 @@
/// AUTOGENERATED COPYRIGHT HEADER START /// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> // Copyright (C) 2017-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END // AUTOGENERATED COPYRIGHT HEADER END
#pragma once #pragma once
#include <list> #include <list>
@@ -7,6 +7,7 @@
#include <optional> #include <optional>
#include <string> #include <string>
#include "../lexer.hpp" #include "../lexer.hpp"
#include "../types.hpp"
// BlitzBasic Built-Ins // BlitzBasic Built-Ins
// - Include: Followed by a String, which is the file to include at this location. // - Include: Followed by a String, which is the file to include at this location.
@@ -28,87 +29,62 @@
// - Function, Return, End Function: Defines a function, and allows returning values. Yes, I know, End itself terminates the program, this is a special case. Thanks younger Sibly. // - Function, Return, End Function: Defines a function, and allows returning values. Yes, I know, End itself terminates the program, this is a special case. Thanks younger Sibly.
// - And, Or, Not: Logical operator, self-explanatory really. // - And, Or, Not: Logical operator, self-explanatory really.
#undef NULL
namespace blitz { namespace blitz {
namespace ast { namespace ast {
class expression { struct node {
public: std::vector<blitz::token> tokens;
virtual ~expression() = default;
virtual ~node() = default;
}; };
// Values struct variable : public node {
class value_expression : public expression { std::string name;
protected: blitz::types::type type;
blitz::token _token; std::string struct_name;
public: virtual ~variable();
virtual ~value_expression() = default;
value_expression(blitz::token token); static bool can_parse(std::shared_ptr<blitz::lexer> lexer);
static std::shared_ptr<blitz::ast::node> try_parse(std::shared_ptr<blitz::lexer> lexer);
}; };
class integer_expression : public value_expression { struct value : public node {
protected: enum class variant {
int32_t _value; UNKNOWN,
NULL,
BOOL,
INTEGER,
UNSIGNED_INTEGER,
REAL,
STRING,
} type;
union {
bool b;
intmax_t i;
uintmax_t ui;
double f;
} number;
std::string text;
public: virtual ~value();
virtual ~integer_expression() = default;
integer_expression(blitz::token token); static bool can_parse(std::shared_ptr<blitz::lexer> lexer);
static std::shared_ptr<blitz::ast::node> try_parse(std::shared_ptr<blitz::lexer> lexer);
}; };
class real_expression : public value_expression { struct declare : public node {
protected: // Local, Global
float _value; bool global;
std::list<std::shared_ptr<blitz::ast::node>> nodes;
public: virtual ~declare();
virtual ~real_expression() = default;
real_expression(blitz::token token); static bool can_parse(std::shared_ptr<blitz::lexer> lexer);
}; static std::shared_ptr<blitz::ast::node> try_parse(std::shared_ptr<blitz::lexer> lexer);
class string_expression : public value_expression {
std::string _value;
public:
virtual ~string_expression() = default;
string_expression(blitz::token token);
};
/** One or more constant values
*
* Const var = Value, var2 = value
*/
class const_expression : public expression {
std::list<std::shared_ptr<variable_expression>> _values;
};
/** One or more local variables
*
* Local var, var2 = value, var3
*/
class local_expression : public expression {
std::list<std::shared_ptr<variable_expression>> _values;
};
/** One or more global variables
*
* Local var, var2 = value, var3
*/
class global_expression : public expression {
std::list<std::shared_ptr<variable_expression>> _values;
};
/** A variable definition
*
*
*/
class variable_expression : public expression {
blitz::token _assign;
std::string _name;
std::shared_ptr<value_expression> _value;
public:
virtual ~variable_expression() = default;
variable_expression(blitz::token token);
}; };
struct expression : public node {};
} // namespace ast } // namespace ast
} // namespace blitz } // namespace blitz
View File
View File
-29
View File
@@ -1,29 +0,0 @@
/// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
#include "function.hpp"
blitz::ast::ScopeExpression::ScopeExpression() {}
blitz::ast::ScopeExpression::~ScopeExpression() {}
void blitz::ast::ScopeExpression::AddExpression(std::unique_ptr<expression> ex) {
m_expressions.push_back(std::move(ex));
}
blitz::ast::FunctionExpression::FunctionExpression(ValueType returnType,
std::string& m_name,
std::list<std::unique_ptr<VariableExpression>> parameters,
std::unique_ptr<ScopeExpression> scope)
: m_returnType(returnType), m_name(m_name), m_parameters(std::move(parameters)), m_content(std::move(scope)) {
}
blitz::ast::FunctionExpression::~FunctionExpression() {}
blitz::ast::CallExpression::CallExpression(std::string& name, std::list<std::unique_ptr<VariableExpression>> arguments)
: m_name(name), m_arguments(std::move(arguments)) {
}
blitz::ast::CallExpression::~CallExpression() {}
-50
View File
@@ -1,50 +0,0 @@
/// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
#pragma once
#include "ast.hpp"
#include "value.hpp"
#include <list>
#include <memory>
#include <string>
namespace blitz {
namespace ast {
class ScopeExpression : public expression {
public:
ScopeExpression();
virtual ~ScopeExpression();
void AddExpression(std::unique_ptr<expression> ex);
private:
std::list<std::unique_ptr<expression>> m_expressions;
};
class FunctionExpression : public ScopeExpression {
public:
FunctionExpression(ValueType returnType,
std::string& m_name,
std::list<std::unique_ptr<VariableExpression>> parameters,
std::unique_ptr<ScopeExpression> scope);
virtual ~FunctionExpression();
private:
ValueType m_returnType;
std::string m_name;
std::list<std::unique_ptr<VariableExpression>> m_parameters;
std::unique_ptr<ScopeExpression> m_content;
};
class CallExpression : public expression {
public:
CallExpression(std::string& name, std::list<std::unique_ptr<VariableExpression>> arguments);
virtual ~CallExpression();
private:
std::string m_name;
std::list<std::unique_ptr<VariableExpression>> m_arguments;
};
}
}
-46
View File
@@ -1,46 +0,0 @@
/// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
#include "value.hpp"
blitz::ast::VariableExpression::VariableExpression(std::string& name, ValueType type /*= ValueType::Number*/)
: m_name(name), m_type(type) {}
blitz::ast::VariableExpression::~VariableExpression() {}
blitz::ast::ValueType blitz::ast::VariableExpression::GetType() {
return m_type;
}
blitz::ast::NumberExpression::NumberExpression(int32_t value) : value(value) {}
blitz::ast::NumberExpression::~NumberExpression() {}
blitz::ast::ValueType blitz::ast::NumberExpression::GetType() {
return ValueType::INTEGER;
}
blitz::ast::DecimalExpression::DecimalExpression(float_t value) : value(value) {}
blitz::ast::DecimalExpression::~DecimalExpression() {}
blitz::ast::ValueType blitz::ast::DecimalExpression::GetType() {
return ValueType::REAL;
}
blitz::ast::StringExpression::StringExpression(std::string value) : value(value) {}
blitz::ast::StringExpression::~StringExpression() {}
blitz::ast::ValueType blitz::ast::StringExpression::GetType() {
return ValueType::STRING;
}
blitz::ast::ConstExpression::ConstExpression(std::string& name, std::unique_ptr<ValueExpression> value)
: m_name(name), m_value(std::move(value)) {}
blitz::ast::ConstExpression::~ConstExpression() {}
blitz::ast::ValueType blitz::ast::ConstExpression::GetType() {
return m_value->GetType();
}
-85
View File
@@ -1,85 +0,0 @@
/// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
#pragma once
#include "ast.hpp"
#include "lexer.hpp"
#include <inttypes.h>
#include <float.h>
#include <math.h>
#include <memory>
#include <string>
namespace blitz {
namespace ast {
enum class ValueType : int8_t {
UNKNOWN,
INTEGER,
REAL,
STRING,
TYPE,
};
class ValueExpression : public expression {
public:
virtual ValueType GetType() = 0;
};
class ConstExpression : public ValueExpression {
public:
ConstExpression(std::string& name, std::unique_ptr<ValueExpression> value);
virtual ~ConstExpression();
virtual ValueType GetType() override;
private:
std::string m_name;
std::unique_ptr<ValueExpression> m_value;
};
class VariableExpression : public ValueExpression {
public:
VariableExpression(std::string& name, ValueType type = ValueType::INTEGER);
virtual ~VariableExpression();
virtual ValueType GetType() override;
private:
std::string m_name;
ValueType m_type;
};
class NumberExpression : public ValueExpression {
public:
NumberExpression(int32_t value);
virtual ~NumberExpression();
virtual ValueType GetType() override;
private:
int32_t value;
};
class DecimalExpression : public ValueExpression {
public:
DecimalExpression(float_t value);
virtual ~DecimalExpression();
virtual ValueType GetType() override;
private:
float_t value;
};
class StringExpression : public ValueExpression {
public:
StringExpression(std::string value);
virtual ~StringExpression();
virtual ValueType GetType() override;
private:
std::string value;
};
}
}
+16 -1
View File
@@ -1,7 +1,8 @@
// AUTOGENERATED COPYRIGHT HEADER START // AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> // Copyright (C) 2024-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END // AUTOGENERATED COPYRIGHT HEADER END
#include "error.hpp" #include "error.hpp"
#include <cstdarg>
blitz::error::~error() {} blitz::error::~error() {}
@@ -26,3 +27,17 @@ std::pair<uint64_t, uint64_t> const& blitz::error::at() const
{ {
return _at; return _at;
} }
std::string blitz::format(const char* format, ...)
{
va_list arg1;
va_list arg2;
va_start(arg1, format);
va_copy(arg2, arg1);
int length = vsnprintf(nullptr, 0, format, arg1);
std::vector<char> buffer(length + 1);
vsnprintf(buffer.data(), buffer.size(), format, arg2);
va_end(arg1);
va_end(arg2);
return {buffer.data(), buffer.data() + length};
}
+3 -1
View File
@@ -1,5 +1,5 @@
// AUTOGENERATED COPYRIGHT HEADER START // AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> // Copyright (C) 2024-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END // AUTOGENERATED COPYRIGHT HEADER END
#pragma once #pragma once
#include <cinttypes> #include <cinttypes>
@@ -24,4 +24,6 @@ namespace blitz {
std::pair<uint64_t, uint64_t> const& at() const; std::pair<uint64_t, uint64_t> const& at() const;
}; };
std::string format(const char* format, ...);
} // namespace blitz } // namespace blitz
+59 -342
View File
@@ -1,24 +1,11 @@
/// AUTOGENERATED COPYRIGHT HEADER START /// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> // Copyright (C) 2017-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END // AUTOGENERATED COPYRIGHT HEADER END
#include "lexer.hpp" #include "lexer.hpp"
#include <codecvt> #include <codecvt>
#include <cstdarg> #include <cstdarg>
#include <sstream> #include <sstream>
#include "util.hpp"
std::string format(const char* format, ...)
{
va_list arg1;
va_list arg2;
va_start(arg1, format);
va_copy(arg2, arg1);
int length = vsnprintf(nullptr, 0, format, arg1);
std::vector<char> buffer(length + 1);
vsnprintf(buffer.data(), buffer.size(), format, arg2);
va_end(arg1);
va_end(arg2);
return {buffer.data(), buffer.data() + length};
}
std::string blitz::token::to_string() std::string blitz::token::to_string()
{ {
@@ -55,17 +42,27 @@ std::string blitz::token::to_string()
name = "Symbol"; name = "Symbol";
break; break;
default: default:
name = "How the fuck?!"; name = "Invalid";
break; break;
} }
if (type == variant::NEWLINE || type == variant::CONTROL) { if (type == variant::NEWLINE || type == variant::CONTROL) {
return format("%s(%llu@%llu, %d)", name.c_str(), location.first, location.second, text[0]); return blitz::format("%s(%llu@%llu, %d)", name.c_str(), location.first, location.second, text[0]);
} else { } else {
return format("%s(%llu@%llu, %s)", name.c_str(), location.first, location.second, text.c_str()); return blitz::format("%s(%llu@%llu, %s)", name.c_str(), location.first, location.second, text.c_str());
} }
} }
bool blitz::token::operator==(variant rhs)
{
return type == rhs;
}
bool blitz::token::operator==(std::string const& rhs)
{
return text == rhs;
}
blitz::lexer::~lexer() {} blitz::lexer::~lexer() {}
blitz::lexer::lexer(std::filesystem::path file) blitz::lexer::lexer(std::filesystem::path file)
@@ -77,14 +74,14 @@ blitz::lexer::lexer(std::filesystem::path file)
_file = file; _file = file;
_stream = std::ifstream(_file, std::ios_base::binary); // We use binary so we can eventually support UTF-8. _stream = std::ifstream(_file, std::ios_base::binary); // We use binary so we can eventually support UTF-8.
if (!_stream.good() || _stream.eof() || _stream.bad() || _stream.fail()) { if (!_stream.good() || _stream.eof() || _stream.bad() || _stream.fail()) {
throw std::runtime_error(format("Reading file '%s' failed.", file.generic_string().c_str())); throw std::runtime_error(blitz::format("Reading file '%s' failed.", file.generic_string().c_str()));
} }
// Initialize token storage to a default token. // Initialize token storage to a default token.
_override = _current = blitz::token{ _next = _current = blitz::token{
.location = { 0, 0 }, .location = { 0, 0 },
.text = "", .text = "",
.type = token::variant::UNKNOWN, .type = token::variant::NONE,
}; };
} }
@@ -95,6 +92,19 @@ blitz::token blitz::lexer::current()
blitz::token blitz::lexer::next() blitz::token blitz::lexer::next()
{ {
_current = peek();
_next = blitz::token{
.location = { 0, 0 },
.text = "",
.type = token::variant::NONE,
};
return _current;
}
blitz::token blitz::lexer::peek()
{
if (_next.type == blitz::token::variant::NONE) {
// ToDo: Optimize
enum class stage { enum class stage {
DEFAULT, DEFAULT,
TEXT, TEXT,
@@ -110,47 +120,8 @@ blitz::token blitz::lexer::next()
.type = blitz::token::variant::UNKNOWN, .type = blitz::token::variant::UNKNOWN,
}; };
auto issymbol = [](int chr) { auto issymbol = [](int chr) { return blitz::utility::is_symbol(chr); };
switch (chr) { auto iswhitespace = [](int chr) { return blitz::utility::is_white_space(chr); };
case ';': // Comment
case ':': // Command Separator
case '=': // Equal
case '<': // Less Than
case '>': // Greater Than
case '~': // Bitwise Not
case '^': // Exponential (X ^ Y = pow(X, Y))
case '+': // Plus
case '-': // Minus
case '*': // Multiply
case '/': // Divide
case ',': // Parameter Separation
case '%': // Integer Type
case '#': // Real Type
case '$': // String Type
case '.': // Structured Type
case '\\': // Structured Type Access
// Blitz Arrays
case '[':
case ']':
// Call, Grouping, Dim
case '(':
case ')':
return true;
default:
return false;
}
return false;
};
auto iswhitespace = [](int chr) {
switch (chr) {
case ' ':
case '\t':
return true;
default:
return false;
}
return false;
};
// ToDo: Figure out why we don't ever hit chr == EOF. // ToDo: Figure out why we don't ever hit chr == EOF.
if (_stream.eof()) { if (_stream.eof()) {
@@ -204,14 +175,14 @@ blitz::token blitz::lexer::next()
complete = true; complete = true;
_stream.get(); _stream.get();
_location.second++; _location.second++;
} else if (chr == ':') { /*} else if (chr == ':') {
// Allows code writers to pretend it's all one line. // Allows code writers to pretend it's all one line.
token.location = _location; token.location = _location;
token.type = blitz::token::variant::SEPARATOR; token.type = blitz::token::variant::SEPARATOR;
token.text = {1, char(chr)}; token.text = {1, char(chr)};
complete = true; complete = true;
_stream.get(); _stream.get();
_location.second++; _location.second++;*/
} else if (chr == ';') { } else if (chr == ';') {
// A comment, which ends at the next new line. // A comment, which ends at the next new line.
state = stage::COMMENT; state = stage::COMMENT;
@@ -284,7 +255,7 @@ blitz::token blitz::lexer::next()
} }
} else { } else {
token.location = _location; token.location = _location;
token.text = {1, char(chr)}; token.text = { char(chr) };
token.type = blitz::token::variant::SYMBOL; token.type = blitz::token::variant::SYMBOL;
complete = true; complete = true;
@@ -300,6 +271,21 @@ blitz::token blitz::lexer::next()
if ((chr == EOF) || (chr < 32) || is_newline || iswhitespace(chr) || (chr == ';')) { if ((chr == EOF) || (chr < 32) || is_newline || iswhitespace(chr) || (chr == ';')) {
// EOF, Control, NL, Whitespace, and Comments should return to default parsing. // EOF, Control, NL, Whitespace, and Comments should return to default parsing.
complete = true; complete = true;
} else if (chr == 'f') {
_stream.get();
token.type = blitz::token::variant::REAL;
complete = true;
} else if (chr == 'u') {
_stream.get();
buffer << (char)chr;
token.type = blitz::token::variant::INTEGER;
complete = true;
} else if ((chr == 'b') || (chr == 'x')) {
_stream.get();
buffer << (char)chr;
if (buffer.tellp() > 2) {
throw blitz::error(_file, token.location, _location, blitz::format("In token %s: Expected [0-9], got '%s' instead.", token.to_string().c_str(), std::string{ 1, (char)chr }.c_str()));
}
} else if (isdigit(chr) || (chr == '.')) { } else if (isdigit(chr) || (chr == '.')) {
_stream.get(); _stream.get();
buffer << (char)chr; buffer << (char)chr;
@@ -308,14 +294,14 @@ blitz::token blitz::lexer::next()
token.type = blitz::token::variant::REAL; token.type = blitz::token::variant::REAL;
} else { } else {
token.text = buffer.str(); token.text = buffer.str();
throw blitz::error(_file, token.location, _location, format("In token %s: Expected [0-9], got '%s' instead.", token.to_string().c_str(), std::string{1, (char)chr}.c_str())); throw blitz::error(_file, token.location, _location, blitz::format("In token %s: Expected [0-9], got '%s' instead.", token.to_string().c_str(), std::string{ 1, (char)chr }.c_str()));
} }
} }
} else if (issymbol(chr)) { } else if (issymbol(chr)) {
complete = true; complete = true;
} else { } else {
token.text = buffer.str(); token.text = buffer.str();
throw blitz::error(_file, token.location, _location, format("In token %s: Expected [0-9.], got '%s' instead.", token.to_string().c_str(), std::string{1, (char)chr}.c_str())); throw blitz::error(_file, token.location, _location, blitz::format("In token %s: Expected ([0](b|x|))[0-9.], got '%s' instead.", token.to_string().c_str(), std::string{ 1, (char)chr }.c_str()));
} }
if (complete) { if (complete) {
@@ -331,7 +317,7 @@ blitz::token blitz::lexer::next()
_location.second++; _location.second++;
} else { } else {
token.text = buffer.str(); token.text = buffer.str();
throw blitz::error(_file, token.location, _location, format("In token %s: Expected [a-zA-Z0-9_], got '%s' instead.", token.to_string().c_str(), std::string{1, (char)chr}.c_str())); throw blitz::error(_file, token.location, _location, blitz::format("In token %s: Expected [a-zA-Z0-9_], got '%s' instead.", token.to_string().c_str(), std::string{ 1, (char)chr }.c_str()));
} }
if (complete) { if (complete) {
@@ -371,282 +357,13 @@ blitz::token blitz::lexer::next()
} }
} }
} }
_next = token;
_current = token;
return _current;
} }
/* return _next;
std::pair<blitz::tokentype, std::string> blitz::lexer::current() {
return _current;
} }
std::pair<blitz::tokentype, std::string> blitz::lexer::next(std::istream& fs) { std::filesystem::path blitz::lexer::file()
std::stringstream buffer; {
blitz::tokentype token; return std::filesystem::path(_file);
enum class parserState {
DEFAULT,
TEXT,
NUMBER,
STRING,
COMMENT,
} state = parserState::DEFAULT;
while ((token == blitz::tokentype::TokenUnknown) && !fs.eof() && fs.good()) {
auto chr = fs.get();
} }
}
/*
std::pair<blitz::lexer::token, std::string> blitz::lexer::next(std::shared_ptr<std::istream> fs) {
std::string buf;
token tkn = token::TokenUnknown;
bool haveResult = false;
// Allow "overriding" the next retrieved Token.
if (m_overrideToken != token::TokenUnknown) {
buf = m_overrideText;
tkn = m_overrideToken;
m_overrideToken = token::TokenUnknown;
haveResult = true;
}
bool m_isTextMode = false;
bool m_isNumberMode = false;
bool m_isStringMode = false;
bool m_isCommentMode = false;
bool m_numberModeHasDecimal = false;
while (((fs->eof() == false) && (fs->good())) && !haveResult) {
char chr = fs->get();
if (chr == '\r' || chr == '\n') {
if (tkn != token::TokenEOF) {
m_overrideToken = token::TokenNewLine;
m_overrideText = "";
} else {
tkn = token::TokenNewLine;
buf = "";
}
m_isStringMode = false;
m_isNumberMode = false;
m_isTextMode = false;
m_isCommentMode = false;
break;
} else if (m_isStringMode) {
if (chr == '\"') {
m_overrideToken = token::TokenDoubleQuote;
m_overrideText = chr;
m_isStringMode = false;
tkn = token::TokenQuotedText;
break;
} else if (iscntrl(chr) || !isprint(chr)) {
fs->putback(chr);
m_isStringMode = false;
break;
} else {
buf += chr;
}
} else if (m_isTextMode) {
if (isalnum(chr) || (chr == '_')) {
buf += chr;
} else {
fs->putback(chr);
m_isTextMode = false;
break;
}
} else if (m_isNumberMode) {
if (isdigit(chr)) {
buf += chr;
} else if (chr == '.') {
if (m_numberModeHasDecimal == false) {
m_numberModeHasDecimal = true;
tkn = token::TokenDecimal;
buf += chr;
} else {
fs->putback(chr);
m_isNumberMode = false;
break;
}
} else {
fs->putback(chr);
m_isNumberMode = false;
break;
}
} else if (m_isCommentMode) {
buf += chr;
tkn = token::TokenComment;
} else {
// Whitespace
if (isspace(chr))
continue;
// Control Code
if (iscntrl(chr)) {
tkn = token::TokenUnknown;
buf = chr;
}
// Special handling for + and -, due to numbers and decimals.
if (chr == '+' || chr == '-') {
char chr2 = fs->get();
if (isdigit(chr2)) {
m_isNumberMode = true;
m_numberModeHasDecimal = false;
tkn = token::TokenNumber;
buf = chr + chr2;
break;
} else if (chr2 == '.') {
m_isNumberMode = true;
m_numberModeHasDecimal = true;
tkn = token::TokenDecimal;
buf = chr + "0" + chr2;
break;
} else {
fs->putback(chr2);
}
}
// Symbol
for (auto v : g_symbolCharacters) {
if (v.first == chr) {
tkn = v.second;
buf = v.first;
break;
}
}
if (tkn != token::TokenEOF) {
haveResult = true;
break;
}
// Strings, Text, Numbers
if (chr == ';') {
m_isCommentMode = true;
tkn = token::TokenSemicolon;
buf = chr;
break;
} else if (chr == '\"') {
m_isStringMode = true;
tkn = token::TokenDoubleQuote;
buf = chr;
break;
} else if (isalpha(chr)) {
m_isTextMode = true;
tkn = token::TokenText;
buf = chr;
} else if (isdigit(chr)) {
m_isNumberMode = true;
m_numberModeHasDecimal = false;
tkn = token::TokenNumber;
buf = chr;
} else if (chr == '.') {
m_isNumberMode = true;
m_numberModeHasDecimal = true;
tkn = token::TokenDecimal;
buf = "0" + chr;
} else {
tkn = token::TokenUnknown;
buf = chr;
break;
}
}
}
// Convert from Text into native Token.
if (tkn == token::TokenText)
tkn = to_token(tkn, buf);
return std::make_pair(tkn, buf);
}
blitz::lexer::token blitz::lexer::to_token(token in, std::string text) {
static std::pair<const char*, token> l_textToTokenList[] = {
// Binary
{ "not", token::TokenNot },
{ "and", token::TokenAnd },
{ "or", token::TokenOr },
{ "xor", token::TokenXor },
{ "shl", token::TokenShl },
{ "shr", token::TokenShr },
{ "sal", token::TokenSal },
{ "sar", token::TokenSar },
{ "false", token::TokenFalse },
{ "true", token::TokenTrue },
// Conversion
{ "float", token::TokenFloat },
{ "string", token::TokenString },
{ "hex", token::TokenHex },
{ "int", token::TokenInt },
// Control
{ "if", token::TokenIf },
{ "then", token::TokenThen },
{ "elseif", token::TokenElseIf },
{ "else", token::TokenElse },
{ "endif", token::TokenEndIf },
{ "select", token::TokenSelect },
{ "case", token::TokenCase },
{ "default", token::TokenDefault },
{ "goto", token::TokenGoto },
{ "gosub", token::TokenGosub },
{ "return", token::TokenReturn },
{ "function", token::TokenFunction },
{ "end", token::TokenEnd },
{ "stop", token::TokenStop },
// Loop
{ "for", token::TokenFor },
{ "to", token::TokenTo },
{ "next", token::TokenNext },
{ "while", token::TokenWhile },
{ "wend", token::TokenWend },
{ "repeat", token::TokenRepeat },
{ "until", token::TokenUntil },
{ "forever", token::TokenForever },
{ "exit", token::TokenExit },
// Math
{ "abs", token::TokenAbs },
{ "sign", token::TokenSign },
{ "cos", token::TokenCos },
{ "sin", token::TokenSin },
{ "tan", token::TokenTan },
{ "acos", token::TokenACos },
{ "asin", token::TokenASin },
{ "atan", token::TokenATan },
{ "atan2", token::TokenATan2 },
{ "log", token::TokenLog },
{ "log10", token::TokenLog10 },
{ "ceil", token::TokenCeil },
{ "floor", token::TokenFloor },
{ "mod", token::TokenMod },
{ "pi", token::TokenPi },
{ "exp", token::TokenExp },
{ "sqr", token::TokenSqr },
// Variables
{ "const", token::TokenConst },
{ "global", token::TokenGlobal },
{ "local", token::TokenLocal },
// Includes
{ "include", token::TokenInclude },
};
for (auto v : l_textToTokenList) {
if (stricmp(text.c_str(), v.first)) {
return v.second;
}
}
return in;
}
*/
+16 -3
View File
@@ -1,5 +1,5 @@
/// AUTOGENERATED COPYRIGHT HEADER START /// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> // Copyright (C) 2017-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END // AUTOGENERATED COPYRIGHT HEADER END
#pragma once #pragma once
#include <cinttypes> #include <cinttypes>
@@ -21,10 +21,11 @@ namespace blitz {
std::pair<uint64_t, uint64_t> location; std::pair<uint64_t, uint64_t> location;
std::string text; std::string text;
enum class variant : uint64_t { enum class variant : uint64_t {
NONE, // There is no token here.
UNKNOWN, // We have absolutely no fucking clue. UNKNOWN, // We have absolutely no fucking clue.
ENDOFFILE, // End of the file. ENDOFFILE, // End of the file.
NEWLINE, // New Line. NEWLINE, // New Line.
SEPARATOR, // Command Separator. //SEPARATOR, // Command Separator.
CONTROL, // All kinds of control signals CONTROL, // All kinds of control signals
SYMBOL, // All kinds of symbols. SYMBOL, // All kinds of symbols.
COMMENT, // ; Whatever COMMENT, // ; Whatever
@@ -35,6 +36,9 @@ namespace blitz {
} type; } type;
std::string to_string(); std::string to_string();
bool operator==(blitz::token::variant rhs);
bool operator==(std::string const& rhs);
}; };
class lexer { class lexer {
@@ -45,7 +49,7 @@ namespace blitz {
std::pair<uint64_t, uint64_t> _location; std::pair<uint64_t, uint64_t> _location;
blitz::token _current; blitz::token _current;
blitz::token _override; blitz::token _next;
public: public:
~lexer(); ~lexer();
@@ -60,5 +64,14 @@ namespace blitz {
* This will replace the current token. * This will replace the current token.
*/ */
blitz::token next(); blitz::token next();
/** Peek at the next token in the given stream.
*
* The current token will remain in-tact.
*/
blitz::token peek();
public:
std::filesystem::path file();
}; };
} // namespace blitz } // namespace blitz
+30 -34
View File
@@ -1,54 +1,49 @@
// AUTOGENERATED COPYRIGHT HEADER START // AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> // Copyright (C) 2017-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END // AUTOGENERATED COPYRIGHT HEADER END
#include <clocale>
#include <iostream> #include <iostream>
#include "compiler.hpp" #include "compiler.hpp"
#include "error.hpp" #include "error.hpp"
#include "lexer.hpp" #include "lexer.hpp"
#include "parser.hpp"
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
std::cout << argv[1] << std::endl;
blitz::lexer lex(argv[1]);
try { try {
for (blitz::token token = lex.next(); (token.type != blitz::token::variant::ENDOFFILE); token = lex.next()) { std::setlocale(LC_ALL, "en_US.UTF-8");
switch (token.type) {
case blitz::token::variant::COMMENT: std::cout << argv[1] << std::endl;
std::cout << token.text; std::list<std::shared_ptr<blitz::ast::node>> nodes;
break;
case blitz::token::variant::SYMBOL: std::shared_ptr<blitz::lexer> lex2 = std::make_shared<blitz::lexer>(argv[1]);
std::cout << token.text << " "; for (blitz::token token = lex2->next(); (token.type != blitz::token::variant::ENDOFFILE); token = lex2->next()) {
break;
case blitz::token::variant::TEXT:
case blitz::token::variant::INTEGER:
case blitz::token::variant::REAL:
std::cout << token.text << " ";
break;
case blitz::token::variant::STRING:
std::cout << "\"" << token.text << "\""
<< " ";
break;
case blitz::token::variant::NEWLINE:
std::cout << std::endl;
break;
default:
std::cout << token.to_string() << " "; std::cout << token.to_string() << " ";
break; if (token.type == blitz::token::variant::NEWLINE) {
std::cout << std::endl;
} }
if (token.type == blitz::token::variant::UNKNOWN) { if (token.type == blitz::token::variant::UNKNOWN) {
std::cin.get(); std::cin.get();
} else if (blitz::ast::declare::can_parse(lex2)) {
nodes.push_back(blitz::ast::declare::try_parse(lex2));
} else if (blitz::ast::value::can_parse(lex2)) {
nodes.push_back(blitz::ast::value::try_parse(lex2));
} else if (blitz::ast::variable::can_parse(lex2)) {
nodes.push_back(blitz::ast::variable::try_parse(lex2));
} }
} }
} catch (blitz::error const& ex) {
std::cout << ex.file() << std::endl;
std::cout << "Line " << ex.at().first << ", Char " << ex.at().second << ": " << ex.what() << std::endl;
} catch (std::runtime_error const& ex) {
std::cout << ex.what() << std::endl;
}
std::cin.get(); //std::cin.get();
return 0; return 0;
} catch (blitz::error const& ex) {
std::cout << std::endl << ex.file() << std::endl;
std::cout << "Line " << ex.at().first << ", Char " << ex.at().second << ": " << ex.what() << std::endl;
return 1;
} catch (std::runtime_error const& ex) {
std::cout << std::endl << ex.what() << std::endl;
return 1;
}
} }
// BlitzBasic is a strange but powerful language in the right hands. While it has // BlitzBasic is a strange but powerful language in the right hands. While it has
@@ -75,6 +70,7 @@ int main(int argc, char** argv)
// //
// 3. Function calls don't always need Parenthesis: // 3. Function calls don't always need Parenthesis:
// ``` // ```
// Local myName
// Function myName() : End Function // Function myName() : End Function
// If myName() Then : EndIf ; <- Calls myName // If myName() Then : EndIf ; <- Calls myName
// myName ; <- Calls myName, because there is no = after it. // myName ; <- Calls myName, because there is no = after it.
@@ -89,4 +85,4 @@ int main(int argc, char** argv)
// Print Int(myName) ; <- Prints the address of the object contained in myName. // Print Int(myName) ; <- Prints the address of the object contained in myName.
// ``` // ```
// //
// As this is a Basic language, there is no concept of undefined or uninitialized anything. Every behavior is well defined. // As this is a Basic language, there is no concept of undefined or uninitialized anything. Every behavior is "well" defined even if confusing.
+149 -105
View File
@@ -1,115 +1,159 @@
/// AUTOGENERATED COPYRIGHT HEADER START /// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> // Copyright (C) 2024-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END // AUTOGENERATED COPYRIGHT HEADER END
/*
#include "parser.hpp" #include "parser.hpp"
#include "ast/function.hpp" #include <algorithm>
#include <iostream> #include <cctype>
#include <vector> #include <cstdarg>
#include <stdarg.h> #include "error.hpp"
blitz::parser::parser(std::string file) { blitz::parser::~parser() {}
// Try and load the file
std::shared_ptr<std::ifstream> instream = std::make_shared<std::ifstream>(file); blitz::parser::parser(std::filesystem::path file) : _file(file), _lexer(), _expr()
if (instream->bad() || !instream->good()) { {
throw std::ios_base::failure("Failed to open file."); _lexer = std::make_shared<blitz::lexer>(file);
}
m_files.push(std::make_pair(file, instream));
} }
blitz::parser::~parser() {
while (m_files.size() > 0) {
std::shared_ptr<std::ifstream> file = std::dynamic_pointer_cast<std::ifstream>(m_files.top().second);
file->close();
m_files.pop();
}
}
std::unique_ptr<blitz::ast::expression> blitz::parser::parse() { //
std::unique_ptr<ast::ScopeExpression> scope = std::make_unique<ast::ScopeExpression>(); //std::shared_ptr<blitz::ast::node> blitz::parser::current()
//{
std::unique_ptr<ast::expression> expr; // return _expr;
while ((expr = std::move(parse_expression())) != nullptr) { //}
scope->AddExpression(std::move(expr)); //
} //std::shared_ptr<blitz::ast::node> blitz::parser::next()
//{
return std::move(scope); // // This should return an entire "line" of expressions in one go, i.e.:
} // // 1. Local a = 1, b = a, c = b+a
// // -> Local(Variable(a, Expression(Integer(1))), Variable(b, Expresssion(Variable(a))), Variable(c, Expression(Add(Variable(b), Variable(a)))
void blitz::parser::log(const char* msg, ...) { // // 2. Include "HelloWorld.bb"
std::vector<char> buf(65535); // // -> Include(String("HelloWorld.bb"))
va_list val; // // 3. Function HelloWorld()
va_start(val, msg); // // -> Function(HelloWorld, ...)(
int rval = vsnprintf(buf.data(), buf.size(), msg, val); // // Not quite sure if the above makes sense, we'd be returning many expressions outside of functions, but only one inside a function? Why even bother with the current/next crap then?
va_end(val); // // Handling Include becomes a problem too. I guess we should actually return expressions on a line by line basis, and let the "compiler" figure out scope and stuff.
std::cout << buf.data() << '\n'; //
} // // Grab the next token to figure out what behavior we should have.
// while (true) {
void blitz::parser::log_error(const char* msg, ...) { // auto token = _lexer->next();
std::vector<char> buf(65535); // try {
va_list val; // switch (token.type) {
va_start(val, msg); // case blitz::token::variant::ENDOFFILE:
int rval = vsnprintf(buf.data(), buf.size(), msg, val); // // End of file means there's nothing left to parse.
va_end(val); // _expr.reset();
std::cerr << buf.data() << '\n'; // return nullptr;
} // case blitz::token::variant::COMMENT:
// case blitz::token::variant::NEWLINE:
std::pair<blitz::lexer::tokentype, std::string> blitz::parser::next() { // case blitz::token::variant::SEPARATOR:
return m_lexer.next(m_files.top().second); // // Ignore some things that aren't very useful right now.
} // continue;
// case blitz::token::variant::TEXT:
std::unique_ptr<blitz::ast::expression> blitz::parser::parse_expression() { // return try_parse(token);
while (true) { // default:
auto tkn = next(); // throw nullptr;
log("%s", tkn.second.c_str()); // }
// } catch (blitz::error const& ex) {
switch (tkn.first) { // throw ex;
case blitz::lexer::tokentype::TokenNewLine: // } catch (std::exception const& ex) {
case blitz::lexer::tokentype::TokenComment: // throw new blitz::error(_file, token.location, token.location, ex.what());
// Skip Comments, since we don't really need them for the AST. // } catch (...) {
continue; // throw new blitz::error(_file, token.location, token.location, blitz::format("Token %s unexpected at this point.", token.to_string().c_str()));
case blitz::lexer::tokentype::TokenPlus: // }
case blitz::lexer::tokentype::TokenMinus: // }
//}
//
//std::shared_ptr<blitz::ast::node> blitz::parser::try_parse(blitz::token token)
//{
// // ToDo: Switch to a proper Unicode library. Maybe Boost?
// std::string ltext;
// std::transform(token.text.begin(), token.text.end(), ltext.begin(), [](std::string::value_type c) { return std::tolower(c); });
//
// if ((ltext == "local") || (ltext == "global")) {
// // Local/Global have the same parsing, but different functionality.
// // Should be:
// // Text Text [Symbol(=) Expression] [Symbol(,) Text [Symbol(=) Expression] [Symbol(,) ...]]
//
// } else if (ltext == "function") {
// //Example:
// // Function FunctionName[$,%,#,:TypeName,.StructName]([Variable[, Variable=Value[, ...]])
// // [Function Content ...]
// // EndFunction
//
//
// } else if (ltext == "select") {
// } else if (ltext == "case") {
// } else if (ltext == "endselect") {
// } else if (ltext == "if") {
// } else if (ltext == "elif") {
// } else if (ltext == "endif") {
//
// } else if (ltext == "end") {
// }
//
// return nullptr;
//}
//
//std::shared_ptr<blitz::ast::node> blitz::parser::try_parse_expression() {
// // () + - / * = <> > < String Integer Float Variable
//}
//
//std::shared_ptr<blitz::ast::node> blitz::parser::try_parse_variable_expression()
//{
// // Text [Symbol(=) Expression(...)] [Symbol(,) [Text [Symbol(=) Expression(...)]]]
//
// auto label = _lexer->next();
// if (label != blitz::token::variant::TEXT) {
// throw new blitz::error(_file, label.location, label.location, blitz::format("Unexpected %s, expected Text.", label.to_string().c_str()));
// }
//
// auto node = std::make_shared<blitz::ast::variable>(label);
//
// auto operand = _lexer->next();
// if (operand == "=") {
// //node->set_value(try_parse_expression());
// } else if (operand == blitz::token::variant::NEWLINE || operand == blitz::token::variant::SEPARATOR || (operand == blitz::token::variant::SYMBOL && operand == ",")) {
// return node;
// } else {
// throw new blitz::error(_file, label.location, operand.location, blitz::format("Unexpected %s, expected Symbol(=), NewLine, Separator, or Symbol(,).", operand.to_string().c_str()));
// }
//
// return node;
//}
default: // End Of File / Unknown /* Expressions
case blitz::lexer::tokentype::TokenUnknown: *
case blitz::lexer::tokentype::TokenEOF: * Example Locations:
return nullptr; * - Local Var = Expression
break; * - Var = Expression
} * - myFunction(Expression, ...)
} * - If Expression Then
} *
* Example Expressions:
std::unique_ptr<blitz::ast::NumberExpression> blitz::parser::parse_number(blitz::lexer::tokentype token, std::string value) { * - 0 + 0, 0 - 0, 0 * 0, 0 / 0, 0 Shr 0, 0 Shl 0, 0 And 0, 0 Or 0, Not 0,
if (token != lexer::tokentype::TokenNumber) { * -
log_error("Unexpected Token during parsing, expected number."); *
return nullptr; *
} *
*
char* endptr = const_cast<char*>(value.c_str() + value.size()); *
int32_t parsed = strtol(value.c_str(), &endptr, 10); *
if (errno == ERANGE) { *
log_error("Number out of range."); *
return nullptr; *
} *
*
return std::make_unique<blitz::ast::NumberExpression>(parsed); *
} *
*
std::unique_ptr<blitz::ast::DecimalExpression> blitz::parser::parse_decimal(blitz::lexer::tokentype token, std::string value) { *
if (token != lexer::tokentype::TokenNumber) { *
log_error("Unexpected Token during parsing, expected number."); *
return nullptr; *
} *
*
char* endptr = const_cast<char*>(value.c_str() + value.size()); *
float_t parsed = strtof(value.c_str(), &endptr); *
if (errno == ERANGE) { *
log_error("Number out of range."); *
return nullptr;
}
return std::make_unique<blitz::ast::DecimalExpression>(parsed);
}
*/ */
+12 -38
View File
@@ -1,54 +1,28 @@
/// AUTOGENERATED COPYRIGHT HEADER START /// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> // Copyright (C) 2017-2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END // AUTOGENERATED COPYRIGHT HEADER END
#pragma once #pragma once
#include <filesystem> #include <filesystem>
#include <memory>
#include "ast/ast.hpp" #include "ast/ast.hpp"
#include "lexer.hpp" #include "lexer.hpp"
namespace blitz { namespace blitz {
class parser { class parser {
std::filesystem::path _file; std::filesystem::path _file;
std::shared_ptr<blitz::lexer> _lexer;
std::shared_ptr<blitz::ast::node> _expr;
public: public:
~parser(); ~parser();
parser(std::filesystem::path file); parser(std::filesystem::path file);
std::shared_ptr<blitz::ast::node> current();
std::shared_ptr<blitz::ast::node> next();
private:
std::shared_ptr<blitz::ast::node> try_parse(blitz::token token);
std::shared_ptr<blitz::ast::node> try_parse_variable();
}; };
} // namespace blitz } // namespace blitz
/*
#include <fstream>
#include <map>
#include <memory>
#include <stack>
#include <string>
#include "ast/value.hpp"
#include "lexer.hpp"
namespace blitz {
class parser {
public:
parser(std::string file);
~parser();
std::unique_ptr<ast::expression> parse();
protected:
void log(const char* msg, ...);
void log_error(const char* msg, ...);
private:
std::pair<blitz::lexer::tokentype, std::string> next();
private:
std::unique_ptr<ast::expression> parse_expression();
std::unique_ptr<ast::NumberExpression> parse_number(blitz::lexer::tokentype token, std::string value);
std::unique_ptr<ast::DecimalExpression> parse_decimal(blitz::lexer::tokentype token, std::string value);
private:
lexer m_lexer;
std::stack<std::pair<std::string, std::shared_ptr<std::istream>>> m_files;
};
}
*/
+70
View File
@@ -0,0 +1,70 @@
// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
#include "types.hpp"
#include <algorithm>
#include <cctype>
const std::pair<const char*, blitz::types::type> _map_to[] = {
{ "byte", blitz::types::type::INT8 }, //
{ "int8", blitz::types::type::INT8 }, //
{ "ubyte", blitz::types::type::UINT8 }, //
{ "uint8", blitz::types::type::UINT8 }, //
{ "short", blitz::types::type::INT16 }, //
{ "int16", blitz::types::type::INT16 }, //
{ "ushort", blitz::types::type::UINT16 }, //
{ "uint16", blitz::types::type::UINT16 }, //
{ "int", blitz::types::type::INT32 }, //
{ "int32", blitz::types::type::INT32 }, //
{ "uint", blitz::types::type::UINT32 }, //
{ "uint32", blitz::types::type::UINT32 }, //
{ "long ", blitz::types::type::INT64 }, //
{ "int64", blitz::types::type::INT64 }, //
{ "ulong", blitz::types::type::UINT64 }, //
{ "uint64", blitz::types::type::UINT64 }, //
{ "half", blitz::types::type::FLOAT16 }, //
{ "float16", blitz::types::type::FLOAT16 }, //
{ "real16", blitz::types::type::FLOAT16 }, //
{ "single", blitz::types::type::FLOAT32 }, //
{ "float", blitz::types::type::FLOAT32 }, //
{ "float32", blitz::types::type::FLOAT32 }, //
{ "real", blitz::types::type::FLOAT32 }, //
{ "real32", blitz::types::type::FLOAT32 }, //
{ "double", blitz::types::type::DOUBLE }, //
{ "float64", blitz::types::type::DOUBLE }, //
{ "real64", blitz::types::type::DOUBLE }, //
{ "string", blitz::types::type::STRING }, //
};
std::string blitz::types::to_string(blitz::types::type type)
{
if (type == type::STRUCT) {
return "struct";
}
for (auto kv : _map_to) {
if (type == kv.second) {
return kv.first;
}
}
return "Unknown";
}
blitz::types::type blitz::types::from_string(std::string text)
{
std::transform(text.cbegin(), text.cend(), text.begin(), [](char from) {
if (from & 0b10000000) { // Exclude Unicode
return from;
}
return (char)std::tolower(from);
});
for (auto kv : _map_to) {
if (text == kv.first) {
return kv.second;
}
}
return blitz::types::type::UNKNOWN;
}
+52
View File
@@ -0,0 +1,52 @@
// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
#pragma once
#include <cinttypes>
#include <string>
namespace blitz {
namespace types {
enum class type : uint8_t {
UNKNOWN,
// 8-bit Integers
INT8,
BYTE = INT8,
UINT8,
UBYTE = UINT8,
// 16-bit Integers
INT16,
SHORT = INT16,
UINT16,
USHORT = UINT16,
// 32-bit Integers
INT32,
INT = INT32,
UINT32,
UINT = UINT32,
// 64-bit Integers
INT64,
LONG = INT64,
UINT64,
ULONG = UINT64,
// 16-bit Float
FLOAT16,
HALF = FLOAT16,
// 32-bit Float
FLOAT32,
FLOAT = FLOAT32,
SINGLE = FLOAT32,
// 64-bit Float
FLOAT64,
DOUBLE = FLOAT64,
// UTF-8 String
STRING,
// User-defined Struct
STRUCT,
};
std::string to_string(blitz::types::type type);
blitz::types::type from_string(std::string text);
};
} // namespace blitz
+67
View File
@@ -0,0 +1,67 @@
// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
#include "util.hpp"
#include <cctype>
bool blitz::utility::is_symbol(int code)
{
switch (code) {
case ';': // Comment
case ':': // Command Separator
case '=': // Equal
case '<': // Less Than
case '>': // Greater Than
case '~': // Bitwise Not
case '^': // Exponential (X ^ Y = pow(X, Y))
case '+': // Plus
case '-': // Minus
case '*': // Multiply
case '/': // Divide
case ',': // Parameter Separation
case '%': // Integer Type
case '#': // Real Type
case '$': // String Type
case '.': // Structured Type
case '\\': // Structured Type Access
case '[': // Blitz Arrays
case ']':
case '(': // Call, Grouping, Dim
case ')':
return true;
default:
return false;
}
return false;
}
bool blitz::utility::is_white_space(int code)
{
switch (code) {
case ' ':
case '\t':
return true;
default:
return false;
}
return false;
}
bool blitz::utility::is_digit(int code)
{
return isdigit(code);
}
bool blitz::utility::is_alpha(int code) {
return isalpha(code);
}
char blitz::utility::utf8_safe_tolower(char code)
{
if (code & 0b10000000) { // Exclude Unicode
return code;
}
return (char)std::tolower(code);
}
+15
View File
@@ -0,0 +1,15 @@
// AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) 2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END
namespace blitz::utility {
bool is_symbol(int code);
bool is_white_space(int code);
bool is_digit(int code);
bool is_alpha(int code);
char utf8_safe_tolower(char code);
} // namespace blitz::utility
+1
View File
@@ -0,0 +1 @@
This directory contains automatically downloaded toolchains and generators.
-1
View File
@@ -1,6 +1,5 @@
#!/bin/bash #!/bin/bash
# AUTOGENERATED COPYRIGHT HEADER START # AUTOGENERATED COPYRIGHT HEADER START
# Copyright (C) NaN-NaN undefined
# Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> # Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
# AUTOGENERATED COPYRIGHT HEADER END # AUTOGENERATED COPYRIGHT HEADER END
-1
View File
@@ -1,5 +1,4 @@
# AUTOGENERATED COPYRIGHT HEADER START # AUTOGENERATED COPYRIGHT HEADER START
# Copyright (C) NaN-NaN undefined
# Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> # Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
# AUTOGENERATED COPYRIGHT HEADER END # AUTOGENERATED COPYRIGHT HEADER END
/stranded2 /stranded2
+3
View File
@@ -1,3 +1,6 @@
; AUTOGENERATED COPYRIGHT HEADER START
; Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
; AUTOGENERATED COPYRIGHT HEADER END
; Ein simpler Lexer Test ; Ein simpler Lexer Test
Local Variable = 1.0 Local Variable = 1.0
Local Variable2$ = "Hallo Welt" Local Variable2$ = "Hallo Welt"
+3
View File
@@ -1,3 +1,6 @@
; AUTOGENERATED COPYRIGHT HEADER START
; Copyright (C) 2017-2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
; AUTOGENERATED COPYRIGHT HEADER END
Graphics 800,600,32,2 Graphics 800,600,32,2
SetBuffer BackBuffer() SetBuffer BackBuffer()
+155
View File
@@ -0,0 +1,155 @@
; AUTOGENERATED COPYRIGHT HEADER START
; Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
; AUTOGENERATED COPYRIGHT HEADER END
Local iValue = 1, iValue2 = 1.2, iValue3 = "Hello World"
; iValue should be 1
; iValue2 should be 1, and print a warning
; iValue3 should be 0 (converted from string), and print a warning
Local fValue# = 1, fValue2# = 1.2, fValue3# = "Hello World"
; fValue should be 1.0
; fValue2 should be 1.2
; fValue3 should be 0.0 (converted from string), and print a warning
Local sValue$ = 1, sValue2$ = 1.2, sValue3$ = "Hello World"
; sValue = "1", print warning
; sValue2 = "1.2", print warning
; sValue3 = "Hello World"
Function iFunction()
Return 1
End Function
Function iFunction2()
Return 1.2
End Function
Function iFunction3()
Return "Hello World"
End Function
; iFunction returns 1
; iFunction2 returns 1, prints warning
; iFunction3 returns 0, prints warning
Function fFunction#()
Return 1
End Function
Function fFunction2#()
Return 1.2
End Function
Function fFunction3#()
Return "Hello World"
End Function
; fFunction returns 1.0
; fFunction2 returns 1.2
; fFunction3 returns 0, prints warning
Function sFunction$()
Return 1
End Function
Function sFunction2$()
Return 1.2
End Function
Function sFunction3$()
Return "Hello World"
End Function
; sFunction returns "1", prints warning
; sFunction2 returns "1.2", prints warning
; sFunction3 returns "Hello World"
;-- AST Representation
;LocalVariables{
; Variable{Int32, iValue, toInt32(Int32(1))},
; Variable{Int32, iValue2, toInt32(Real32(1.2))},
; Variable{Int32, iValue3, toInt32(String(Hello World))},
;}
;LocalVariables{
; Variable{Real32, fValue, toReal32(Int32(1))},
; Variable{Real32, fValue2, toReal32(Real32(1.2))},
; Variable{Real32, fValue3, toReal32(String(Hello World))},
;}
;LocalVariables{
; Variable{String, sValue, toString(Int32(1))},
; Variable{String, sValue2, toString(Real32(1.2))},
; Variable{String, sValue3, toString("Hello World")},
;}
;
;Function{
; Text(iFunction),
; Int32,
; Parameters{
; },
; Content{
; Return(Int32(1))
; }
;}
;Function{
; Text(iFunction2),
; Int32,
; Parameters{
; },
; Content{
; Return(Real32(1.2))
; }
;}
;Function{
; Text(iFunction3),
; Int32,
; Parameters{
; },
; Content{
; Return(String("Hello World"))
; }
;}
;
;Function{
; Text(fFunction),
; Real32,
; Parameters{
; },
; Content{
; Return(Int32(1))
; }
;}
;Function{
; Text(fFunction2),
; Real32,
; Parameters{
; },
; Content{
; Return(Real32(1.2))
; }
;}
;Function{
; Text(fFunction3),
; Real32,
; Parameters{
; },
; Content{
; Return(String("Hello World"))
; }
;}
;
;Function{
; Text(sFunction),
; String,
; Parameters{
; },
; Content{
; Return(Int32(1))
; }
;}
;Function{
; Text(sFunction2),
; String,
; Parameters{
; },
; Content{
; Return(Real32(1.2))
; }
;}
;Function{
; Text(sFunction3),
; String,
; Parameters{
; },
; Content{
; Return(String("Hello World"))
; }
;}
+4
View File
@@ -0,0 +1,4 @@
; AUTOGENERATED COPYRIGHT HEADER START
; Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
; AUTOGENERATED COPYRIGHT HEADER END
Local iValue = 2
+13
View File
@@ -0,0 +1,13 @@
; AUTOGENERATED COPYRIGHT HEADER START
; Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
; AUTOGENERATED COPYRIGHT HEADER END
Include "004-incl.bb"
; Should be parsed as:
; Include(String("test.bb"))
; which should then jump to parsing "test.bb" relative to the current file.
iValue = iValue + 4
; Should be parsed as Assign(Variable(iValue), Add(Variable(iValue), Int32(4)))
Print iValue
; Should be parsed as Call(Function(Print), Variable(iValue))
+50
View File
@@ -0,0 +1,50 @@
; AUTOGENERATED COPYRIGHT HEADER START
; Copyright (C) 2025 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
; AUTOGENERATED COPYRIGHT HEADER END
Variable
Variable%
Variable%%
Variable#
Variable##
Variable$
Variable$$
Variable:Int
Variable:Float
Variable:String
Variable:Int8
Variable:Int16
Variable:Int32
Variable:Int64
Variable:UInt8
Variable:UInt16
Variable:UInt32
Variable:UInt64
Variable:Half
Variable:Single
Variable:Double
Variable:Real
Variable:Float16
Variable:Float32
Variable:Float64
Variable:Real16
Variable:Real32
Variable:Real64
0
0x
0b
0b100
0b100u
0x100
0x100u
255
255u
65535
65535u
"Level Up"
"Hello World"
True
False
Null
Local myVar%, myVar2#
Global gVar$, gVar15:Int32
Global gHelloWorld$ = "Hello World", localVarThatIsntLocal
+82 -26
View File
@@ -1,8 +1,6 @@
// AUTOGENERATED COPYRIGHT HEADER START // AUTOGENERATED COPYRIGHT HEADER START
// Copyright (C) NaN-NaN undefined
// Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com> // Copyright (C) 2024 Michael Fabian 'Xaymar' Dirks <info@xaymar.com>
// AUTOGENERATED COPYRIGHT HEADER END // AUTOGENERATED COPYRIGHT HEADER END
const ignoreList = [ const ignoreList = [
/^\.git$/gi, /^\.git$/gi,
/^cmake\/clang$/gi, /^cmake\/clang$/gi,
@@ -39,6 +37,10 @@ const formatStyleList = {
], exts: [ ], exts: [
".iss", ".iss",
".iss.in", ".iss.in",
".bb",
".b3d",
".b2d",
".bpl"
], ],
prepend: [ prepend: [
`; ${sectionStart}`, `; ${sectionStart}`,
@@ -111,6 +113,7 @@ const PATH = require("node:path");
const FS = require("node:fs"); const FS = require("node:fs");
const FSPROMISES = require("node:fs/promises"); const FSPROMISES = require("node:fs/promises");
const OS = require("os"); const OS = require("os");
const READLINE = require('node:readline');
if (!debug) if (!debug)
console.debug = function() {} console.debug = function() {}
@@ -191,11 +194,12 @@ class RateLimiter {
} }
let abortAllWork = false; let abortAllWork = false;
let gitRL = new RateLimiter(1); let gitRL = new RateLimiter(2);
let workRL = new RateLimiter(); let workRL = new RateLimiter();
let gitCurrentFiles; let gitCurrentFiles;
let gitUserName; let gitUserName;
let gitUserMail; let gitUserMail;
let gitSubmodules;
let gitDate = (new Date()).toISOString(); let gitDate = (new Date()).toISOString();
/** Run a process asynchronously, returning an array of messages. /** Run a process asynchronously, returning an array of messages.
@@ -265,6 +269,8 @@ async function runProcessAsync(path, args, options) {
async function git_isIgnored(path) { async function git_isIgnored(path) {
console.debug(arguments.callee.name, Array.from(arguments)); console.debug(arguments.callee.name, Array.from(arguments));
let rpath = PATH.relative(process.cwd(), path).replaceAll(PATH.sep, PATH.posix.sep); let rpath = PATH.relative(process.cwd(), path).replaceAll(PATH.sep, PATH.posix.sep);
// Check manual ignore list.
for (let ignore of ignoreList) { for (let ignore of ignoreList) {
if (ignore instanceof RegExp) { if (ignore instanceof RegExp) {
if (ignore.global) { if (ignore.global) {
@@ -282,6 +288,15 @@ async function git_isIgnored(path) {
} }
} }
// Check if this happens to be (in) a submodule.
let modules = await git_subModules()
for (let module of modules) {
if (rpath.startsWith(module)) {
return true;
}
}
// Finally check if git ignores this file.
let result = await gitRL.queue(runProcessAsync, "git", ["check-ignore", path], {}); let result = await gitRL.queue(runProcessAsync, "git", ["check-ignore", path], {});
return (result[0] == 0) return (result[0] == 0)
} }
@@ -299,6 +314,31 @@ async function git_getCurrentAuthor() {
return commitFormat.replace("%aI", gitDate).replace("%aN", gitUserName).replace("%aE", gitUserMail); return commitFormat.replace("%aI", gitDate).replace("%aN", gitUserName).replace("%aE", gitUserMail);
} }
async function git_subModules() {
if (!gitSubmodules) {
gitSubmodules = new Set();
let gitmodules = PATH.join(process.cwd(), ".gitmodules");
if ((await FSPROMISES.stat(gitmodules)).isFile()) {
let gmfs = FS.createReadStream(gitmodules);
const rl = READLINE.createInterface({
input: gmfs,
crlfDelay: Infinity
});
for await(const line of rl) {
let parts = line.trim().split("=");
if (parts.length > 1) {
if(parts[0].trim() == "path") {
gitSubmodules.add(parts[1].trim());
}
}
}
}
}
return gitSubmodules;
}
async function git_isInCurrentCommit(file) { async function git_isInCurrentCommit(file) {
console.debug(arguments.callee.name, Array.from(arguments)); console.debug(arguments.callee.name, Array.from(arguments));
if (!gitCurrentFiles) { if (!gitCurrentFiles) {
@@ -444,39 +484,44 @@ async function updateFile(file) {
} }
console.log(`Updating file '${file}'...`); console.log(`Updating file '${file}'...`);
// ToDo: Do we actually need to read the file first, or can we use the same rw stream?
// File contents. // File contents.
let content = await FSPROMISES.readFile(file); let contentBuf = await FSPROMISES.readFile(file);
let eol = (content.indexOf("\r\n") != -1 ? OS.EOL : "\n"); let eol = contentBuf.indexOf("\r\n") != -1 ? "\r\n" : "\n";
let insert = Buffer.from(header.join(eol) + eol); let headerBuf = Buffer.from(header.join(eol) + eol, "utf8");
// Find the starting point. // Find the starting point.
let startHeader = content.indexOf(sectionStart); let startHeader = contentBuf.indexOf(Buffer.from(header[0], "utf8"));
startHeader = content.lastIndexOf(eol, startHeader); if (startHeader != -1) {
startHeader += Buffer.from(eol).byteLength; //startHeader = contentBuf.lastIndexOf(eolBuf, startHeader);
//startHeader += eolb.byteLength;
}
// Find the ending point. // Find the ending point.
let endHeader = content.indexOf(sectionEnd); let endHeader = contentBuf.lastIndexOf(Buffer.from(header[header.length - 1], "utf8"));
endHeader = content.indexOf(eol, endHeader); if (endHeader != -1) {
endHeader += Buffer.from(eol).byteLength; endHeader += Buffer.from(header[header.length - 1], "utf8").byteLength;
endHeader += Buffer.byteLength(eol, "utf8");
}
// Last check for early-exit here.
if (abortAllWork) { if (abortAllWork) {
return; return;
} }
let fd = await FSPROMISES.open(file, "w"); let fd = await FSPROMISES.open(file, "w");
let fp = []; if (startHeader == -1 || (endHeader < startHeader)) {
if ((startHeader >= 0) && (endHeader > startHeader)) { await fd.write(headerBuf, 0, null, 0);
await fd.write(contentBuf, 0, null, headerBuf.byteLength);
} else {
let pos = 0; let pos = 0;
if (startHeader > 0) { if (startHeader > 0) {
fd.write(content, 0, startHeader, 0); await fd.write(contentBuf, 0, startHeader, 0);
pos += startHeader; pos += startHeader;
} }
fd.write(insert, 0, undefined, pos); await fd.write(headerBuf, 0, null, pos); pos += headerBuf.byteLength;
pos += insert.byteLength; await fd.write(contentBuf, endHeader, null, pos);
fd.write(content, endHeader, undefined, pos);
} else {
fd.write(insert, 0, undefined, 0);
fd.write(content, 0, undefined, insert.byteLength);
} }
await fd.close(); await fd.close();
} catch (ex) { } catch (ex) {
@@ -490,11 +535,14 @@ async function updateFile(file) {
async function scanPath(path) { async function scanPath(path) {
console.debug(arguments.callee.name, Array.from(arguments)); console.debug(arguments.callee.name, Array.from(arguments));
// Abort here if the user aborted the process, or if the path is ignored. // Abort here if the user aborted the process, or if the path is ignored.
if (abortAllWork) { if (abortAllWork) {
return; return;
} }
console.log(`Scanning path '${path}'...`);
let promises = []; let promises = [];
await workRL.queue(async () => { await workRL.queue(async () => {
@@ -511,7 +559,6 @@ async function scanPath(path) {
} }
if (file.isDirectory()) { if (file.isDirectory()) {
console.log(`Scanning path '${fullname}'...`);
promises.push(scanPath(fullname)); promises.push(scanPath(fullname));
} else { } else {
promises.push(updateFile(fullname)); promises.push(updateFile(fullname));
@@ -535,6 +582,7 @@ async function scanPath(path) {
console.debug(root_path, PROCESS.argv, PROCESS.execArgv); console.debug(root_path, PROCESS.argv, PROCESS.execArgv);
var args = PROCESS.argv.slice(2); var args = PROCESS.argv.slice(2);
let promises = new Array();
while (args.length > 0) { while (args.length > 0) {
// Try to place ourselves where git actually is. // Try to place ourselves where git actually is.
while (!is_git_directory) { while (!is_git_directory) {
@@ -543,7 +591,7 @@ async function scanPath(path) {
} }
let entries = await FSPROMISES.readdir(PROCESS.cwd()); let entries = await FSPROMISES.readdir(PROCESS.cwd());
if (entries.includes(".git")) { if (entries.includes(".git") && ((await FSPROMISES.stat(PATH.join(PROCESS.cwd(), ".git"))).isDirectory())) {
console.log(`Found .git at '${process.cwd()}'.`); console.log(`Found .git at '${process.cwd()}'.`);
is_git_directory = true; is_git_directory = true;
} else { } else {
@@ -558,6 +606,12 @@ async function scanPath(path) {
return; return;
} }
if (abortAllWork) {
return;
}
git_getCurrentAuthor();
// Then proceed with normal work. // Then proceed with normal work.
let path = PATH.normalize(PATH.relative(process.cwd(), PATH.resolve(root_path, args[0]))); let path = PATH.normalize(PATH.relative(process.cwd(), PATH.resolve(root_path, args[0])));
@@ -567,15 +621,17 @@ async function scanPath(path) {
if (await git_isIgnored(path)) { if (await git_isIgnored(path)) {
console.log(`Ignoring path '${path}'...`); console.log(`Ignoring path '${path}'...`);
} else if(pathStat.isDirectory()) { } else if(pathStat.isDirectory()) {
console.log(`Scanning path '${path}'...`); //await scanPath(path);
await scanPath(path); promises.push(scanPath(path));
} else { } else {
await updateFile(path); //await updateFile(path);
promises.push(updateFile(path));
} }
} }
// Slice off the first argument and continue. // Slice off the first argument and continue.
args = args.slice(1); args = args.slice(1);
} }
await Promise.all(promises);
console.log("Done"); console.log("Done");
})(); })();