Compare commits
149 Commits
Author | SHA1 | Date |
---|---|---|
Kelvin Sherlock | 91590e92aa | |
Kelvin Sherlock | 6199944350 | |
Kelvin Sherlock | f799eb6d81 | |
Kelvin Sherlock | 298c601300 | |
Kelvin Sherlock | ce1a36eba5 | |
Kelvin Sherlock | a90ca3c849 | |
Kelvin Sherlock | fdf33c69b7 | |
Kelvin Sherlock | 6f2b59c4d6 | |
ksherlock | fd94247aec | |
Uli Kusterer | 8e54c6519c | |
ksherlock | 0c1419ec98 | |
Kelvin Sherlock | 225c3b8ebd | |
ksherlock | da24b85f68 | |
ksherlock | f25a30edc7 | |
ksherlock | e8c0080f77 | |
Kelvin Sherlock | b8cada73e8 | |
Kelvin Sherlock | be1c6c14fa | |
Kelvin Sherlock | a15d2bf257 | |
ksherlock | 87b2cc0902 | |
Kelvin Sherlock | 5d95f10dd8 | |
ksherlock | 9d4340b3ac | |
C.W. Betts | 45df4524ea | |
Kelvin Sherlock | 474b10ccaa | |
Kelvin Sherlock | ed96470e18 | |
Kelvin Sherlock | 7b99997f28 | |
Kelvin Sherlock | 42f9552352 | |
Kelvin Sherlock | fa804e91f0 | |
Kelvin Sherlock | 0ec15bcd63 | |
Kelvin Sherlock | 06e2e1f309 | |
Kelvin Sherlock | 32c72cb89a | |
Kelvin Sherlock | f125b533f7 | |
Kelvin Sherlock | f6c5478063 | |
Kelvin Sherlock | efd51bcb48 | |
Kelvin Sherlock | 33dd3228f8 | |
Kelvin Sherlock | 548880a517 | |
Kelvin Sherlock | 1da6c3e9da | |
Kelvin Sherlock | bdba86249e | |
Kelvin Sherlock | 87f5398649 | |
Kelvin Sherlock | 811c8b976a | |
Kelvin Sherlock | 0422976719 | |
Kelvin Sherlock | 2893f7fe79 | |
Kelvin Sherlock | 34a4f431c0 | |
Kelvin Sherlock | f3db9b7cc0 | |
Kelvin Sherlock | a4e724a1a6 | |
Kelvin Sherlock | fbcbfffcb5 | |
Kelvin Sherlock | 50ac7355bd | |
Kelvin Sherlock | 827f49c48b | |
Kelvin Sherlock | e2affa1bdd | |
Kelvin Sherlock | 76980a6e06 | |
Kelvin Sherlock | ba0fe6268f | |
Kelvin Sherlock | 47b734a5fa | |
Kelvin Sherlock | f9bbf7a2f8 | |
Kelvin Sherlock | 8c8a768530 | |
Kelvin Sherlock | 27c4eadf93 | |
Kelvin Sherlock | ca54485061 | |
Kelvin Sherlock | f8c596668f | |
Kelvin Sherlock | 469f0a23c1 | |
Kelvin Sherlock | 9d5d3ca9e8 | |
Kelvin Sherlock | 5b343cc7dd | |
Kelvin Sherlock | 56f945ce29 | |
Kelvin Sherlock | b9782a0926 | |
Kelvin Sherlock | ed341db9fa | |
Kelvin Sherlock | edcb832c13 | |
Kelvin Sherlock | c2c41f3a52 | |
Kelvin Sherlock | 413b9a805b | |
Kelvin Sherlock | a2a48fcba7 | |
Kelvin Sherlock | ad523f258e | |
Kelvin Sherlock | 544f3a994c | |
Kelvin Sherlock | 683b06b3b5 | |
Kelvin Sherlock | 9b16e98133 | |
Kelvin Sherlock | fe76877693 | |
Kelvin Sherlock | 47af010ba5 | |
Kelvin Sherlock | 1a98acb756 | |
Kelvin Sherlock | 933f23de91 | |
Kelvin Sherlock | 62612a10d3 | |
Kelvin Sherlock | 05f48c6a3c | |
Kelvin Sherlock | 0524d10590 | |
Kelvin Sherlock | 2fdca6ea9d | |
Kelvin Sherlock | 9b577bdbb1 | |
Kelvin Sherlock | 84737e1cf7 | |
Kelvin Sherlock | f97625eba7 | |
Kelvin Sherlock | a366bbf2cb | |
Kelvin Sherlock | 76d5c9a474 | |
Kelvin Sherlock | db8e9af504 | |
Kelvin Sherlock | fac76f1b54 | |
Kelvin Sherlock | 5c333cbedd | |
Kelvin Sherlock | 34900a00b8 | |
Kelvin Sherlock | da92eb4e36 | |
Kelvin Sherlock | c28cfb0710 | |
Kelvin Sherlock | 31f33096cb | |
Kelvin Sherlock | 6d929aa87f | |
Kelvin Sherlock | 80aaaa208d | |
Kelvin Sherlock | fed90b3753 | |
Kelvin Sherlock | 0ba9574d2d | |
Kelvin Sherlock | 40a92c7976 | |
Kelvin Sherlock | 5f9293a9e2 | |
Kelvin Sherlock | 7984ccca54 | |
Kelvin Sherlock | 42806cdd9b | |
Kelvin Sherlock | 686bee2578 | |
Kelvin Sherlock | adbf776d31 | |
Kelvin Sherlock | b4db751cbe | |
Kelvin Sherlock | 84b24e6379 | |
Kelvin Sherlock | e51e757556 | |
Kelvin Sherlock | bc2381a360 | |
Kelvin Sherlock | a44d2d3e4d | |
Kelvin Sherlock | 45eade7af5 | |
Kelvin Sherlock | 6bfad57a35 | |
Kelvin Sherlock | ca6d8a453e | |
Kelvin Sherlock | a15c6fbd65 | |
Kelvin Sherlock | ef99bb40de | |
Kelvin Sherlock | d56d689f98 | |
Kelvin Sherlock | 8a2b9ec3cd | |
Kelvin Sherlock | c8f1e370dc | |
Kelvin Sherlock | 92ddf18766 | |
Kelvin Sherlock | 701786277b | |
Kelvin Sherlock | 97bcf8259d | |
Kelvin Sherlock | 322a32af65 | |
Kelvin Sherlock | edd80fc3c5 | |
Kelvin Sherlock | 7724ca0d7c | |
Kelvin Sherlock | 6ff7b50a7d | |
Kelvin Sherlock | fafb08b90a | |
Kelvin Sherlock | 56b0c93fd7 | |
Kelvin Sherlock | 0d0367ca08 | |
Kelvin Sherlock | ca4fafb62c | |
Kelvin Sherlock | 7d18720162 | |
Kelvin Sherlock | 034321830f | |
Kelvin Sherlock | beb3e3813a | |
Kelvin Sherlock | a6913f7c46 | |
Kelvin Sherlock | a7dce37fe6 | |
Kelvin Sherlock | 50f171d5c8 | |
Kelvin Sherlock | 446e3e5e1e | |
Kelvin Sherlock | 9316311a1d | |
Kelvin Sherlock | 4131616420 | |
Kelvin Sherlock | 2485f08172 | |
Kelvin Sherlock | a8920e2f53 | |
Kelvin Sherlock | 1339c0891f | |
Kelvin Sherlock | 6f53faeae7 | |
Kelvin Sherlock | 3fea71d1e8 | |
Kelvin Sherlock | acf616845e | |
Kelvin Sherlock | b52565bc37 | |
Kelvin Sherlock | a65cdfead0 | |
Kelvin Sherlock | d98247bd12 | |
Kelvin Sherlock | 41e1424644 | |
Kelvin Sherlock | 0dfecbd520 | |
Kelvin Sherlock | f58f6d4115 | |
Kelvin Sherlock | 353832ca3a | |
Kelvin Sherlock | e2b1306b30 | |
Kelvin Sherlock | 371ce08ef6 | |
Kelvin Sherlock | 449595c56b |
|
@ -0,0 +1,36 @@
|
|||
name: CMake MacOS
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Brew
|
||||
run: brew install ragel
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64"
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
shell: bash
|
||||
# Execute the build. You can specify a specific target with "--target <NAME>"
|
||||
run: cmake --build . --config $BUILD_TYPE
|
||||
|
||||
- name: Archive
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: mpw fat
|
||||
path: ${{runner.workspace}}/build/mpw-shell
|
|
@ -0,0 +1,29 @@
|
|||
name: CMake Ubuntu
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
BUILD_TYPE: Release
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: apt-get
|
||||
run: sudo apt-get install ragel
|
||||
|
||||
- name: Create Build Environment
|
||||
run: cmake -E make_directory ${{runner.workspace}}/build
|
||||
|
||||
- name: Configure CMake
|
||||
shell: bash
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE
|
||||
|
||||
- name: Build
|
||||
working-directory: ${{runner.workspace}}/build
|
||||
shell: bash
|
||||
run: cmake --build . --config $BUILD_TYPE
|
|
@ -14,5 +14,5 @@ script: make
|
|||
before_install:
|
||||
- brew update
|
||||
- brew install ragel
|
||||
- brew install cmake
|
||||
- brew install "https://raw.githubusercontent.com/ksherlock/homebrew-ksherlock/master/lemonxx.rb"
|
||||
|
||||
|
|
159
CMakeLists.txt
159
CMakeLists.txt
|
@ -1,23 +1,50 @@
|
|||
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
# CMAKE_INSTALL_PREFIX defaults to /usr/local.
|
||||
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
project("mpw-shell")
|
||||
set (PROJECT_TYPE "CXX")
|
||||
set (PROJECT_NAME "MPW Shell")
|
||||
|
||||
set(CMAKE_CXX_FLAGS "-g -Wall -Wno-unused-variable -Wno-multichar")
|
||||
|
||||
# -std=c++14
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
|
||||
set(CMAKE_CXX_EXTENSIONS FALSE)
|
||||
|
||||
|
||||
#
|
||||
# build config.h
|
||||
#
|
||||
|
||||
INCLUDE (CheckFunctionExists)
|
||||
INCLUDE (CheckLibraryExists)
|
||||
|
||||
SET(CMAKE_EXTRA_INCLUDE_FILES stdio.h)
|
||||
CHECK_FUNCTION_EXISTS(dprintf HAVE_DPRINTF)
|
||||
SET(CMAKE_EXTRA_INCLUDE_FILES)
|
||||
|
||||
CHECK_LIBRARY_EXISTS(edit readline "" HAVE_LIBEDIT)
|
||||
CHECK_LIBRARY_EXISTS(readline readline "" HAVE_LIBREADLINE)
|
||||
CHECK_LIBRARY_EXISTS(history add_history "" HAVE_LIBHISTORY)
|
||||
|
||||
CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||
|
||||
|
||||
add_compile_options(-g -Wall -Wno-unused-variable -Wno-multichar -O1)
|
||||
|
||||
if(${CMAKE_CXX_COMPILER_ID} MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-const-variable ")
|
||||
add_compile_options(-Wno-unused-const-variable)
|
||||
endif()
|
||||
|
||||
|
||||
if(${CMAKE_CXX_COMPILER_ID} MATCHES "GNU")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-but-set-variable")
|
||||
add_compile_options(-Wno-unused-but-set-variable)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
add_definitions(-I ${CMAKE_SOURCE_DIR}/)
|
||||
include_directories(${CMAKE_SOURCE_DIR})
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# from https://github.com/gsauthof/cmake-ragel
|
||||
macro(RAGEL_TARGET Name Input Output)
|
||||
|
@ -52,70 +79,74 @@ macro(RAGEL_TARGET Name Input Output)
|
|||
endmacro()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#add_custom_command(
|
||||
# OUTPUT mpw-shell-read.cpp
|
||||
# COMMAND ragel -p -G2 -o mpw-shell-read.cpp "${CMAKE_CURRENT_SOURCE_DIR}/mpw-shell-read.rl"
|
||||
# MAIN_DEPENDENCY mpw-shell-read.rl
|
||||
#)
|
||||
|
||||
RAGEL_TARGET(mpw-shell-read mpw-shell-read.rl mpw-shell-read.cpp COMPILE_FLAGS "-p -G2")
|
||||
RAGEL_TARGET(phase1 phase1.rl phase1.cpp COMPILE_FLAGS "-p -G2")
|
||||
#RAGEL_TARGET(phase1 phase1.rl phase1.cpp COMPILE_FLAGS "-p -G2")
|
||||
RAGEL_TARGET(phase2 phase2.rl phase2.cpp COMPILE_FLAGS "-p -G2")
|
||||
RAGEL_TARGET(pathnames pathnames.rl pathnames.cpp COMPILE_FLAGS "-p -G2")
|
||||
RAGEL_TARGET(mpw-shell-token mpw-shell-token.rl mpw-shell-token.cpp COMPILE_FLAGS "-p -G2")
|
||||
RAGEL_TARGET(mpw-shell-expand mpw-shell-expand.rl mpw-shell-expand.cpp COMPILE_FLAGS "-p -G2")
|
||||
RAGEL_TARGET(mpw-shell-quote mpw-shell-quote.rl mpw-shell-quote.cpp COMPILE_FLAGS "-p -G2")
|
||||
RAGEL_TARGET(value value.rl value.cpp COMPILE_FLAGS "-p -G2")
|
||||
|
||||
|
||||
# need to copy all OUTPUT file to the build dir
|
||||
add_custom_command(
|
||||
OUTPUT phase2-parser.cpp phase2-parser.h
|
||||
COMMAND lemon++ -Tlempar.cxx phase2-parser.lemon
|
||||
COMMAND cp -p phase2-parser.cpp phase2-parser.h ${CMAKE_CURRENT_BINARY_DIR}/
|
||||
MAIN_DEPENDENCY phase2-parser.lemon
|
||||
DEPENDS lempar.cxx
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
# add_custom_command(
|
||||
# OUTPUT phase3.cpp phase3.h phase3.out
|
||||
# COMMAND lemon++ phase3.lemon
|
||||
# COMMAND mv -f phase3.cpp phase3.h phase3.out ${CMAKE_CURRENT_BINARY_DIR}/
|
||||
# MAIN_DEPENDENCY phase3.lemon
|
||||
# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
|
||||
# )
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT mpw-shell-expand.cpp
|
||||
COMMAND ragel -p -G2 -o mpw-shell-expand.cpp "${CMAKE_CURRENT_SOURCE_DIR}/mpw-shell-expand.rl"
|
||||
MAIN_DEPENDENCY mpw-shell-expand.rl
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT mpw-shell-token.cpp
|
||||
COMMAND ragel -p -G2 -o mpw-shell-token.cpp "${CMAKE_CURRENT_SOURCE_DIR}/mpw-shell-token.rl"
|
||||
MAIN_DEPENDENCY mpw-shell-token.rl
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT mpw-shell-command.cpp
|
||||
COMMAND ragel -p -G2 -o mpw-shell-command.cpp "${CMAKE_CURRENT_SOURCE_DIR}/mpw-shell-command.rl"
|
||||
MAIN_DEPENDENCY mpw-shell-command.rl
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT value.cpp
|
||||
COMMAND ragel -p -G2 -o value.cpp "${CMAKE_CURRENT_SOURCE_DIR}/value.rl"
|
||||
MAIN_DEPENDENCY value.rl
|
||||
)
|
||||
find_program(LEMON_PLUSPLUS NAMES lemon++)
|
||||
if (LEMON_PLUSPLUS)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/phase3.cpp ${CMAKE_CURRENT_SOURCE_DIR}/phase3.h
|
||||
#COMMAND cp -f "${CMAKE_CURRENT_SOURCE_DIR}/parser.lemon" "parser.lemon"
|
||||
COMMAND lemon++ phase3.lemon
|
||||
MAIN_DEPENDENCY phase3.lemon
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT mpw-shell-quote.cpp
|
||||
COMMAND ragel -p -G2 -o mpw-shell-quote.cpp "${CMAKE_CURRENT_SOURCE_DIR}/mpw-shell-quote.rl"
|
||||
MAIN_DEPENDENCY mpw-shell-quote.rl
|
||||
)
|
||||
|
||||
|
||||
# mpw-shell-execute.cpp mpw-shell-builtins.cpp mpw-shell-read.cpp
|
||||
add_executable(mpw-shell mpw-shell.cpp mpw-shell-token.cpp mpw-shell-expand.cpp
|
||||
mpw-shell-parser.cpp value.cpp mpw-shell-quote.cpp
|
||||
phase1.cpp phase2.cpp phase2-parser.cpp command.cpp environment.cpp builtins.cpp mapped_file.cpp)
|
||||
mpw-shell-parser.cpp mpw_parser.cpp value.cpp mpw-shell-quote.cpp mpw-regex.cpp
|
||||
phase1.cpp phase2.cpp phase3.cpp command.cpp environment.cpp builtins.cpp
|
||||
pathnames.cpp
|
||||
macroman.cpp
|
||||
cxx/mapped_file.cpp
|
||||
cxx/filesystem.cpp
|
||||
cxx/path.cpp
|
||||
cxx/directory_iterator.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(mpw-shell edit)
|
||||
#
|
||||
# -ledit includes history stuff. gnu -lreadline does not.
|
||||
#
|
||||
if(HAVE_LIBEDIT)
|
||||
target_link_libraries(mpw-shell edit)
|
||||
elseif(HAVE_LIBREADLINE)
|
||||
target_link_libraries(mpw-shell readline)
|
||||
if (HAVE_LIBHISTORY)
|
||||
target_link_libraries(mpw-shell history)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# all this for -std=c++14
|
||||
set_property (TARGET mpw-shell PROPERTY CXX_STANDARD 14)
|
||||
set_property (TARGET mpw-shell PROPERTY CXX_STANDARD_REQUIRED TRUE)
|
||||
set_property (TARGET mpw-shell PROPERTY CXX_EXTENSIONS FALSE)
|
||||
|
||||
|
||||
# create a symlink for mpw-make
|
||||
add_custom_command(
|
||||
TARGET mpw-shell
|
||||
POST_BUILD
|
||||
COMMAND ln;-sf;mpw-shell;mpw-make
|
||||
COMMENT "ln -s mpw-shell mpw-make"
|
||||
)
|
||||
|
||||
# install...
|
||||
|
||||
install(
|
||||
PROGRAMS
|
||||
${CMAKE_CURRENT_BINARY_DIR}/mpw-shell
|
||||
${CMAKE_CURRENT_BINARY_DIR}/mpw-make
|
||||
DESTINATION bin
|
||||
)
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
MPW Shell
|
||||
---------
|
||||
|
||||
MPW Shell is a re-implementation of the Macintosh Programmer's Workshop shell.
|
||||
The primary reason is to support MPW Make (which generated shell script). It
|
||||
may also be useful for other things.
|
||||
|
||||
Supported features
|
||||
------------------
|
||||
* If ... [Else If] ... [Else] ... End
|
||||
* Begin ... End
|
||||
* Loop ... End
|
||||
* For name In [word...] ... End
|
||||
* Break [If], Continue [If], Exit [If]
|
||||
* ( ... )
|
||||
* ||
|
||||
* &&
|
||||
* Redirection
|
||||
* | "pipes" (via a temporary file. Presumably, that's what MPW did as well.)
|
||||
* Subshells (`...`, ``...``)
|
||||
|
||||
|
||||
Not (yet) supported
|
||||
-------------
|
||||
* aliases
|
||||
* regular expressions
|
||||
* text-editing commands (search forward/backward, et cetera)
|
||||
|
||||
Builtin Commands
|
||||
----------------
|
||||
* AboutBox
|
||||
* Alias
|
||||
* Catenate
|
||||
* Directory
|
||||
* Echo
|
||||
* Evaluate
|
||||
* Execute
|
||||
* Exists
|
||||
* Export
|
||||
* Parameters
|
||||
* Quit
|
||||
* Quote
|
||||
* Set
|
||||
* Shift
|
||||
* Unalias
|
||||
* Unexport
|
||||
* Unset
|
||||
* Version
|
||||
* Which
|
||||
|
||||
|
||||
Setup
|
||||
-----
|
||||
1. Install MPW. The mpw binary should be somewhere in your `$PATH`.
|
||||
It also checks `/usr/local/bin/mpw` and `$HOME/mpw/bin/mpw`. You can
|
||||
use mpw-shell without it but only with builtin commands.
|
||||
2. Copy the `Startup` script to `$HOME/mpw/`. This script is executed
|
||||
when mpw-shell (or mpw-make) starts up (imagine that) and should
|
||||
be used to set environment variables.
|
||||
|
||||
|
||||
Command Line Flags
|
||||
------------------
|
||||
|
||||
-D name=value Define environment variable
|
||||
-v Be verbose (equivalent to -Decho=1)
|
||||
-f Ignore the Startup script
|
||||
-c string Execute string
|
||||
-h Display help
|
||||
|
||||
|
||||
Build
|
||||
-----
|
||||
|
||||
Standard CMake build sequence:
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
```
|
||||
|
||||
After that, do the standard CMake install sequence in the same folder:
|
||||
|
||||
```bash
|
||||
cmake --install
|
||||
```
|
||||
|
||||
to install `mpw-shell` and `mpw-make` in `/usr/bin/local`.
|
933
builtins.cpp
933
builtins.cpp
File diff suppressed because it is too large
Load Diff
19
builtins.h
19
builtins.h
|
@ -8,14 +8,29 @@ class Environment;
|
|||
class fdmask;
|
||||
class token;
|
||||
|
||||
int builtin_aboutbox(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_catenate(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_directory(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_echo(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_exists(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_export(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_help(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_parameters(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_quote(Environment &e, const std::vector<std::string> &tokens, const fdmask &);
|
||||
int builtin_set(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_unset(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_export(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_shift(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_unexport(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_unset(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_version(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_which(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_alias(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_unalias(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
|
||||
int builtin_execute(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_true(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_false(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
int builtin_quit(Environment &e, const std::vector<std::string> &, const fdmask &);
|
||||
|
||||
|
||||
int builtin_evaluate(Environment &e, std::vector<token> &&, const fdmask &);
|
||||
|
||||
|
|
856
command.cpp
856
command.cpp
File diff suppressed because it is too large
Load Diff
73
command.h
73
command.h
|
@ -6,7 +6,7 @@
|
|||
#include <vector>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include "phase2-parser.h"
|
||||
#include "phase3.h"
|
||||
|
||||
typedef std::unique_ptr<struct command> command_ptr;
|
||||
typedef std::vector<command_ptr> command_ptr_vector;
|
||||
|
@ -22,7 +22,7 @@ struct command {
|
|||
{}
|
||||
|
||||
virtual bool terminal() const noexcept {
|
||||
return type == EVALUATE || type == COMMAND;
|
||||
return type == EVALUATE || type == COMMAND || type == BREAK || type == CONTINUE || type == ERROR || type == EXIT;
|
||||
}
|
||||
|
||||
int type = 0;
|
||||
|
@ -60,6 +60,38 @@ struct evaluate_command : public command {
|
|||
virtual int execute(Environment &e, const fdmask &fds, bool throwup = true) final override;
|
||||
};
|
||||
|
||||
|
||||
struct break_command : public command {
|
||||
|
||||
template<class S>
|
||||
break_command(S &&s) : command(BREAK), text(std::forward<S>(s))
|
||||
{}
|
||||
|
||||
std::string text;
|
||||
virtual int execute(Environment &e, const fdmask &fds, bool throwup) final override;
|
||||
};
|
||||
|
||||
struct continue_command : public command {
|
||||
|
||||
template<class S>
|
||||
continue_command(S &&s) : command(CONTINUE), text(std::forward<S>(s))
|
||||
{}
|
||||
|
||||
std::string text;
|
||||
virtual int execute(Environment &e, const fdmask &fds, bool throwup) final override;
|
||||
};
|
||||
|
||||
struct exit_command : public command {
|
||||
|
||||
template<class S>
|
||||
exit_command(S &&s) : command(EXIT), text(std::forward<S>(s))
|
||||
{}
|
||||
|
||||
std::string text;
|
||||
virtual int execute(Environment &e, const fdmask &fds, bool throwup) final override;
|
||||
};
|
||||
|
||||
|
||||
struct binary_command : public command {
|
||||
|
||||
binary_command(int t, command_ptr &&a, command_ptr &&b) :
|
||||
|
@ -91,15 +123,13 @@ struct and_command : public binary_command {
|
|||
};
|
||||
|
||||
|
||||
#if 0
|
||||
struct pipe_command : public binary_command {
|
||||
and_command(command_ptr &&a, command_ptr &&b) :
|
||||
pipe_command(command_ptr &&a, command_ptr &&b) :
|
||||
binary_command(PIPE, std::move(a), std::move(b))
|
||||
{}
|
||||
|
||||
virtual int execute(Environment &e) final override;
|
||||
virtual int execute(Environment &e, const fdmask &fds, bool throwup) final override;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
struct vector_command : public command {
|
||||
|
@ -122,6 +152,33 @@ struct begin_command : public vector_command {
|
|||
virtual int execute(Environment &e, const fdmask &fds, bool throwup) final override;
|
||||
};
|
||||
|
||||
struct loop_command : public vector_command {
|
||||
|
||||
template<class S1, class S2>
|
||||
loop_command(int t, command_ptr_vector &&v, S1 &&b, S2 &&e) :
|
||||
vector_command(t, std::move(v)), begin(std::forward<S1>(b)), end(std::forward<S2>(e))
|
||||
{}
|
||||
|
||||
std::string begin;
|
||||
std::string end;
|
||||
|
||||
virtual int execute(Environment &e, const fdmask &fds, bool throwup) final override;
|
||||
};
|
||||
|
||||
struct for_command : public vector_command {
|
||||
|
||||
template<class S1, class S2>
|
||||
for_command(int t, command_ptr_vector &&v, S1 &&b, S2 &&e) :
|
||||
vector_command(t, std::move(v)), begin(std::forward<S1>(b)), end(std::forward<S2>(e))
|
||||
{}
|
||||
|
||||
std::string begin;
|
||||
std::string end;
|
||||
|
||||
virtual int execute(Environment &e, const fdmask &fds, bool throwup) final override;
|
||||
};
|
||||
|
||||
|
||||
typedef std::unique_ptr<struct if_else_clause> if_else_clause_ptr;
|
||||
struct if_command : public command {
|
||||
|
||||
|
@ -151,4 +208,6 @@ struct if_else_clause : public vector_command {
|
|||
//bool evaluate(const Environment &e);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef __mpw_shell_config_h__
|
||||
#define __mpw_shell_config_h__
|
||||
|
||||
#cmakedefine HAVE_DPRINTF
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
#include "filesystem.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
|
||||
|
||||
namespace filesystem {
|
||||
|
||||
|
||||
#pragma mark - directory_entry
|
||||
|
||||
directory_entry::directory_entry(const class path& p, file_status st, file_status symlink_st) :
|
||||
_path(p), _st(st), _lst(symlink_st)
|
||||
{}
|
||||
|
||||
void directory_entry::assign(const class path& p, file_status st, file_status symlink_st)
|
||||
{
|
||||
_path = p;
|
||||
_st = st;
|
||||
_lst = symlink_st;
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
void directory_entry::replace_filename(const path& p, file_status st, file_status symlink_st)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
file_status directory_entry::status() const {
|
||||
|
||||
if (!status_known(_st))
|
||||
{
|
||||
if (status_known(_lst) && ! is_symlink(_lst)) {
|
||||
_st = _lst;
|
||||
} else {
|
||||
_st = filesystem::status(_path);
|
||||
}
|
||||
}
|
||||
|
||||
return _st;
|
||||
}
|
||||
|
||||
file_status directory_entry::status(error_code& ec) const noexcept {
|
||||
|
||||
if (!status_known(_st))
|
||||
{
|
||||
if (status_known(_lst) && ! is_symlink(_lst)) {
|
||||
_st = _lst;
|
||||
} else {
|
||||
_st = filesystem::status(_path, ec);
|
||||
}
|
||||
}
|
||||
|
||||
return _st;
|
||||
}
|
||||
|
||||
file_status directory_entry::symlink_status() const {
|
||||
|
||||
if (!status_known(_lst))
|
||||
_lst = filesystem::symlink_status(_path);
|
||||
|
||||
return _lst;
|
||||
}
|
||||
|
||||
file_status directory_entry::symlink_status(error_code& ec) const noexcept {
|
||||
|
||||
ec.clear();
|
||||
|
||||
if (!status_known(_lst))
|
||||
_lst = filesystem::symlink_status(_path, ec);
|
||||
|
||||
return _lst;
|
||||
}
|
||||
|
||||
|
||||
bool directory_entry::operator< (const directory_entry& rhs) const noexcept {
|
||||
return _path < rhs._path;
|
||||
}
|
||||
bool directory_entry::operator==(const directory_entry& rhs) const noexcept {
|
||||
return _path == rhs._path;
|
||||
}
|
||||
bool directory_entry::operator!=(const directory_entry& rhs) const noexcept {
|
||||
return _path != rhs._path;
|
||||
}
|
||||
bool directory_entry::operator<=(const directory_entry& rhs) const noexcept {
|
||||
return _path <= rhs._path;
|
||||
}
|
||||
bool directory_entry::operator> (const directory_entry& rhs) const noexcept {
|
||||
return _path > rhs._path;
|
||||
}
|
||||
bool directory_entry::operator>=(const directory_entry& rhs) const noexcept {
|
||||
return _path >= rhs._path;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - directory_iterator
|
||||
|
||||
directory_iterator::directory_iterator(const path& p) {
|
||||
|
||||
|
||||
error_code ec;
|
||||
|
||||
open_dir(p, ec);
|
||||
if (ec) throw filesystem_error("directory_iterator::directory_iterator", p, ec);
|
||||
|
||||
increment(ec);
|
||||
if (ec) throw filesystem_error("directory_iterator::directory_iterator", p, ec);
|
||||
}
|
||||
|
||||
directory_iterator::directory_iterator(const path& p, error_code& ec) noexcept {
|
||||
ec.clear();
|
||||
|
||||
open_dir(p, ec);
|
||||
if (!ec) increment(ec);
|
||||
}
|
||||
|
||||
void directory_iterator::open_dir(const path &p, error_code &ec) noexcept {
|
||||
|
||||
DIR *dp = opendir(p.c_str());
|
||||
if (!dp)
|
||||
{
|
||||
ec = error_code(errno, std::system_category());
|
||||
return;
|
||||
}
|
||||
|
||||
_imp = std::make_shared<imp>(p, dp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
directory_iterator& directory_iterator::operator++() {
|
||||
|
||||
error_code ec;
|
||||
|
||||
increment(ec);
|
||||
if (ec) throw filesystem_error("directory_iterator::operator++", ec);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
directory_iterator& directory_iterator::increment(error_code& ec) noexcept {
|
||||
|
||||
ec.clear();
|
||||
|
||||
if (_imp) {
|
||||
|
||||
struct dirent entry;
|
||||
struct dirent *result = nullptr;
|
||||
int rv;
|
||||
|
||||
DIR *dp = _imp->_dp;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
|
||||
|
||||
rv = readdir_r(dp, &entry, &result);
|
||||
if (rv != 0)
|
||||
{
|
||||
ec = error_code(errno, std::system_category());
|
||||
_imp.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
_imp.reset();
|
||||
break; // end of directory.
|
||||
}
|
||||
|
||||
std::string s(entry.d_name);
|
||||
if (s == ".") continue;
|
||||
if (s == "..") continue;
|
||||
|
||||
|
||||
path p = _imp->_path / s;
|
||||
|
||||
_imp->_entry.assign(p);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
#include "filesystem.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <unistd.h>
|
||||
#include <sys/param.h>
|
||||
#include <limits.h>
|
||||
|
||||
#ifndef PATH_MAX
|
||||
#define PATH_MAX 4096
|
||||
#endif
|
||||
|
||||
namespace filesystem {
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
template<class FX, class... Args>
|
||||
auto syscall(error_code &ec, FX fx, Args&&... args) -> decltype(fx(std::forward<Args>(args)...))
|
||||
{
|
||||
auto rv = fx(std::forward<Args>(args)...);
|
||||
if (rv < 0) {
|
||||
ec = error_code(errno, std::system_category());
|
||||
} else {
|
||||
ec.clear();
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int fs_stat(const path &p, struct stat *buf, error_code &ec) {
|
||||
int rv = stat(p.c_str(), buf);
|
||||
if (rv < 0) {
|
||||
ec = error_code(errno, std::system_category());
|
||||
}
|
||||
else {
|
||||
ec.clear();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
int fs_lstat(const path &p, struct stat *buf, error_code &ec) {
|
||||
int rv = lstat(p.c_str(), buf);
|
||||
if (rv < 0) {
|
||||
ec = error_code(errno, std::system_category());
|
||||
}
|
||||
else {
|
||||
ec.clear();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
template<class FX>
|
||||
file_status status_common(FX fx, const path& p, error_code& ec) noexcept {
|
||||
|
||||
|
||||
struct stat st;
|
||||
int rv = fx(p, &st, ec);
|
||||
if (rv < 0)
|
||||
{
|
||||
switch (ec.value())
|
||||
{
|
||||
case ENOENT:
|
||||
case ENOTDIR:
|
||||
return file_status(file_type::not_found);
|
||||
|
||||
case EOVERFLOW:
|
||||
return file_status(file_type::unknown);
|
||||
|
||||
//case ENAMETOOLONG: ???
|
||||
// case ELOOP ?
|
||||
|
||||
default:
|
||||
return file_status(file_type::none);
|
||||
}
|
||||
}
|
||||
|
||||
ec.clear();
|
||||
perms prms = static_cast<perms>(st.st_mode & perms::mask);
|
||||
|
||||
if (S_ISREG(st.st_mode))
|
||||
return file_status(file_type::regular, prms);
|
||||
|
||||
if (S_ISDIR(st.st_mode))
|
||||
return file_status(file_type::directory, prms);
|
||||
|
||||
if (S_ISBLK(st.st_mode))
|
||||
return file_status(file_type::block, prms);
|
||||
|
||||
if (S_ISFIFO(st.st_mode))
|
||||
return file_status(file_type::fifo, prms);
|
||||
|
||||
if (S_ISSOCK(st.st_mode))
|
||||
return file_status(file_type::socket, prms);
|
||||
|
||||
return file_status(file_type::unknown, prms);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
file_status status(const path& p) {
|
||||
error_code ec;
|
||||
file_status result = status(p, ec);
|
||||
if (result.type() == file_type::none)
|
||||
throw filesystem_error("filesystem::file_status", p, ec);
|
||||
return result;
|
||||
}
|
||||
|
||||
file_status status(const path& p, error_code& ec) noexcept {
|
||||
|
||||
return status_common(fs_stat, p, ec);
|
||||
/*
|
||||
struct stat st;
|
||||
int rv = stat(p.c_str(), &st);
|
||||
if (rv < 0) {
|
||||
int e = errno;
|
||||
ec = error_code(e, std::system_category());
|
||||
|
||||
switch(e){
|
||||
case ENOENT:
|
||||
case ENOTDIR:
|
||||
return file_status(file_type::not_found);
|
||||
|
||||
case EOVERFLOW:
|
||||
return file_status(file_type::unknown);
|
||||
|
||||
//case ENAMETOOLONG: ???
|
||||
// case ELOOP ?
|
||||
|
||||
default:
|
||||
return file_status(file_type::none);
|
||||
}
|
||||
}
|
||||
|
||||
ec.clear();
|
||||
perms prms = static_cast<perms>(st.st_mode & perms::mask);
|
||||
|
||||
if (S_ISREG(st.st_mode))
|
||||
return file_status(file_type::regular, prms);
|
||||
|
||||
if (S_ISDIR(st.st_mode))
|
||||
return file_status(file_type::directory, prms);
|
||||
|
||||
if (S_ISBLK(st.st_mode))
|
||||
return file_status(file_type::block, prms);
|
||||
|
||||
if (S_ISFIFO(st.st_mode))
|
||||
return file_status(file_type::fifo, prms);
|
||||
|
||||
if (S_ISSOCK(st.st_mode))
|
||||
return file_status(file_type::socket, prms);
|
||||
|
||||
return file_status(file_type::unknown, prms);
|
||||
*/
|
||||
}
|
||||
|
||||
file_status symlink_status(const path& p) {
|
||||
error_code ec;
|
||||
file_status result = symlink_status(p, ec);
|
||||
if (result.type() == file_type::none)
|
||||
throw filesystem_error("filesystem::symlink_status", p, ec);
|
||||
return result;
|
||||
}
|
||||
|
||||
file_status symlink_status(const path& p, error_code& ec) noexcept {
|
||||
|
||||
return status_common(fs_lstat, p, ec);
|
||||
}
|
||||
|
||||
|
||||
uintmax_t file_size(const path& p) {
|
||||
error_code ec;
|
||||
|
||||
struct stat st;
|
||||
//if (fs_stat(p, &st, ec) < 0)
|
||||
if (syscall(ec, ::stat, p.c_str(), &st) < 0)
|
||||
throw filesystem_error("filesystem::file_size", p, ec);
|
||||
|
||||
return st.st_size;
|
||||
|
||||
}
|
||||
|
||||
uintmax_t file_size(const path& p, error_code& ec) noexcept {
|
||||
|
||||
struct stat st;
|
||||
|
||||
//if (fs_stat(p, &st, ec) < 0)
|
||||
if (syscall(ec, ::stat, p.c_str(), &st) < 0)
|
||||
return static_cast<uintmax_t>(-1);
|
||||
|
||||
return st.st_size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool create_directory(const path& p) {
|
||||
error_code ec;
|
||||
bool rv = create_directory(p, ec);
|
||||
if (ec)
|
||||
throw filesystem_error("filesystem::create_directory", p, ec);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool create_directory(const path& p, error_code& ec) noexcept {
|
||||
|
||||
int rv = ::mkdir(p.c_str(), S_IRWXU|S_IRWXG|S_IRWXO);
|
||||
if (rv == 0) {
|
||||
ec.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
int e = errno;
|
||||
error_code tmp;
|
||||
|
||||
// special case -- not an error if the directory already exists.
|
||||
|
||||
if (e == EEXIST && is_directory(p, tmp)) {
|
||||
ec.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
ec = error_code(e, std::system_category());
|
||||
return false;
|
||||
}
|
||||
|
||||
void resize_file(const path& p, uintmax_t new_size) {
|
||||
error_code ec;
|
||||
|
||||
if (syscall(ec, ::truncate, p.c_str(), new_size) < 0)
|
||||
throw filesystem_error("filesystem::create_directory", p, ec);
|
||||
}
|
||||
|
||||
void resize_file(const path& p, uintmax_t new_size, error_code& ec) noexcept {
|
||||
|
||||
syscall(ec, ::truncate, p.c_str(), new_size);
|
||||
|
||||
}
|
||||
|
||||
bool remove(const path& p) {
|
||||
error_code ec;
|
||||
|
||||
if (syscall(ec, ::remove, p.c_str()) < 0)
|
||||
{
|
||||
throw filesystem_error("filesystem::remove", p, ec);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool remove(const path& p, error_code& ec) noexcept {
|
||||
if (syscall(ec, ::remove, p.c_str()) < 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
path current_path() {
|
||||
error_code ec;
|
||||
path p = current_path(ec);
|
||||
|
||||
if (ec)
|
||||
throw filesystem_error("filesystem::current_path", ec);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
path current_path(error_code& ec) {
|
||||
|
||||
char *cp;
|
||||
char buffer[PATH_MAX+1];
|
||||
|
||||
ec.clear();
|
||||
cp = ::getcwd(buffer, PATH_MAX);
|
||||
if (cp) return path(cp);
|
||||
|
||||
ec = error_code(errno, std::system_category());
|
||||
return path();
|
||||
}
|
||||
|
||||
void current_path(const path& p) {
|
||||
error_code ec;
|
||||
syscall(ec, ::chdir, p.c_str());
|
||||
if (ec)
|
||||
throw filesystem_error("filesystem::current_path", p, ec);
|
||||
}
|
||||
|
||||
void current_path(const path& p, error_code& ec) noexcept {
|
||||
|
||||
syscall(ec, ::chdir, p.c_str());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
path canonical(const path& p, const path& base) {
|
||||
error_code ec;
|
||||
path rv = canonical(p, base, ec);
|
||||
if (ec)
|
||||
throw filesystem_error("filesystem::canonical", p, ec);
|
||||
return rv;
|
||||
}
|
||||
|
||||
path canonical(const path& p, error_code& ec) {
|
||||
char *cp;
|
||||
char buffer[PATH_MAX+1];
|
||||
|
||||
ec.clear();
|
||||
cp = realpath(p.c_str(), buffer);
|
||||
if (cp) return path(cp);
|
||||
ec = error_code(errno, std::system_category());
|
||||
return path();
|
||||
}
|
||||
|
||||
path canonical(const path& p, const path& base, error_code& ec) {
|
||||
|
||||
char *cp;
|
||||
char buffer[PATH_MAX+1];
|
||||
|
||||
ec.clear();
|
||||
|
||||
if (p.is_absolute()) cp = realpath(p.c_str(), buffer);
|
||||
else {
|
||||
path tmp = base;
|
||||
tmp /= p;
|
||||
cp = realpath(tmp.c_str(), buffer);
|
||||
}
|
||||
if (cp) return path(cp);
|
||||
ec = error_code(errno, std::system_category());
|
||||
return path();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,849 @@
|
|||
#ifndef __filesystem_h__
|
||||
#define __filesystem_h__
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <locale>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <memory>
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
/*
|
||||
*
|
||||
* light-weight implementation of n3803
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
namespace filesystem {
|
||||
|
||||
class path;
|
||||
class filesystem_error;
|
||||
|
||||
using std::error_code;
|
||||
using std::system_error;
|
||||
|
||||
class path {
|
||||
|
||||
public:
|
||||
|
||||
typedef char value_type;
|
||||
typedef std::basic_string<value_type> string_type;
|
||||
static constexpr value_type preferred_separator = '/';
|
||||
|
||||
|
||||
// constructors and destructor
|
||||
path() = default;
|
||||
path(const path& p) = default;
|
||||
path(path&& p) noexcept :
|
||||
_path(std::move(p._path)) {
|
||||
p.invalidate();
|
||||
}
|
||||
|
||||
template<class Source>
|
||||
path(Source const& source) :
|
||||
_path(source) {}
|
||||
|
||||
template <class InputIterator>
|
||||
path(InputIterator begin, InputIterator end) :
|
||||
_path(begin, end) {}
|
||||
|
||||
template <class Source>
|
||||
path(Source const& source, const std::locale& loc);
|
||||
|
||||
template <class InputIterator>
|
||||
path(InputIterator begin, InputIterator end, const std::locale& loc);
|
||||
|
||||
~path() = default;
|
||||
|
||||
// assignments
|
||||
path& operator=(const path& p) {
|
||||
if (this != &p) {
|
||||
_path = p._path;
|
||||
_info = p._info;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
path& operator=(path&& p) noexcept {
|
||||
if (this != &p) {
|
||||
_path = std::move(p._path);
|
||||
_info = p._info;
|
||||
p.invalidate();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Source>
|
||||
path& operator=(Source const& source) {
|
||||
invalidate();
|
||||
_path = source;
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& assign(const path &p) {
|
||||
return (*this = p);
|
||||
}
|
||||
|
||||
template <class Source>
|
||||
path& assign(Source const& source) {
|
||||
invalidate();
|
||||
_path.assign(source);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class InputIterator>
|
||||
path& assign(InputIterator begin, InputIterator end) {
|
||||
invalidate();
|
||||
_path.assign(begin, end);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
// appends
|
||||
path& operator/=(const path& p) {
|
||||
return append(p);
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <class Source>
|
||||
path& operator/=(Source const& source) {
|
||||
return append(source);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// consider this a specialization of append.
|
||||
path& append(const path &p);
|
||||
|
||||
path& append(const string_type &s);
|
||||
|
||||
|
||||
template <class Source>
|
||||
path& append(Source const& source) {
|
||||
return append(path(source));
|
||||
}
|
||||
|
||||
template <class InputIterator>
|
||||
path& append(InputIterator begin, InputIterator end) {
|
||||
return append(path(begin, end));
|
||||
}
|
||||
|
||||
|
||||
// concatenation
|
||||
path& operator+=(const path& x) {
|
||||
invalidate();
|
||||
_path += x._path;
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& operator+=(const string_type& x) {
|
||||
invalidate();
|
||||
_path += x;
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& operator+=(const value_type* x) {
|
||||
invalidate();
|
||||
_path += x;
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& operator+=(value_type x) {
|
||||
invalidate();
|
||||
_path += x;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Source>
|
||||
path& operator+=(Source const& x) {
|
||||
invalidate();
|
||||
_path += x;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class charT>
|
||||
path& operator+=(charT x){
|
||||
invalidate();
|
||||
_path += x;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class Source>
|
||||
path& concat(Source const& x) {
|
||||
invalidate();
|
||||
_path += x;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class InputIterator>
|
||||
path& concat(InputIterator begin, InputIterator end) {
|
||||
invalidate();
|
||||
_path.append(begin, end);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
// modifiers
|
||||
void clear() noexcept {
|
||||
invalidate();
|
||||
_path.clear();
|
||||
}
|
||||
|
||||
path& make_preferred() {
|
||||
return *this;
|
||||
}
|
||||
path& remove_filename();
|
||||
path& replace_filename(const path& replacement);
|
||||
path& replace_extension(/* const path& replacement = path() */);
|
||||
path& replace_extension(const path& replacement);
|
||||
|
||||
void swap(path& rhs) noexcept {
|
||||
std::swap(_path, rhs._path);
|
||||
std::swap(_info, rhs._info);
|
||||
}
|
||||
|
||||
// native format observers
|
||||
const string_type& native() const noexcept {
|
||||
return _path;
|
||||
}
|
||||
|
||||
const value_type* c_str() const noexcept {
|
||||
return _path.c_str();
|
||||
}
|
||||
|
||||
operator string_type() const {
|
||||
return _path;
|
||||
}
|
||||
|
||||
template <class charT, class traits = std::char_traits<charT>,
|
||||
class Allocator = std::allocator<charT> >
|
||||
std::basic_string<charT, traits, Allocator> string(const Allocator& a = Allocator()) const;
|
||||
|
||||
std::string string() const {
|
||||
return _path;
|
||||
}
|
||||
|
||||
std::wstring wstring() const;
|
||||
std::string u8string() const;
|
||||
std::u16string u16string() const;
|
||||
std::u32string u32string() const;
|
||||
|
||||
// generic format observers
|
||||
template <class charT, class traits = std::char_traits<charT>,
|
||||
class Allocator = std::allocator<charT> >
|
||||
std::basic_string<charT, traits, Allocator>
|
||||
generic_string(const Allocator& a = Allocator()) const;
|
||||
std::string generic_string() const {
|
||||
return _path;
|
||||
}
|
||||
std::wstring generic_wstring() const;
|
||||
std::string generic_u8string() const;
|
||||
std::u16string generic_u16string() const;
|
||||
std::u32string generic_u32string() const;
|
||||
|
||||
// compare
|
||||
int compare(const path& p) const noexcept;
|
||||
int compare(const std::string& s) const;
|
||||
int compare(const value_type* s) const;
|
||||
|
||||
// decomposition
|
||||
path root_name() const;
|
||||
path root_directory() const;
|
||||
path root_path() const;
|
||||
path relative_path() const;
|
||||
path parent_path() const;
|
||||
path filename() const;
|
||||
path stem() const;
|
||||
path extension() const;
|
||||
|
||||
// query
|
||||
bool empty() const noexcept {
|
||||
return _path.empty();
|
||||
}
|
||||
|
||||
bool has_root_name() const;
|
||||
bool has_root_directory() const;
|
||||
bool has_root_path() const;
|
||||
bool has_relative_path() const;
|
||||
bool has_parent_path() const;
|
||||
bool has_filename() const;
|
||||
bool has_stem() const;
|
||||
bool has_extension() const;
|
||||
|
||||
bool is_absolute() const {
|
||||
return !empty() && _path[0] == '/';
|
||||
}
|
||||
|
||||
bool is_relative() const {
|
||||
return empty() || _path[0] != '/';
|
||||
}
|
||||
|
||||
// iterators
|
||||
class iterator;
|
||||
typedef iterator const_iterator;
|
||||
iterator begin() const;
|
||||
iterator end() const;
|
||||
|
||||
private:
|
||||
|
||||
void invalidate() const {
|
||||
_info.valid = false;
|
||||
}
|
||||
|
||||
void study() const;
|
||||
path &append_common(const std::string &s);
|
||||
|
||||
|
||||
|
||||
|
||||
string_type _path;
|
||||
|
||||
mutable struct {
|
||||
bool valid = false;
|
||||
value_type special;
|
||||
int stem;
|
||||
int extension;
|
||||
} _info;
|
||||
|
||||
};
|
||||
|
||||
|
||||
inline void swap(path& lhs, path& rhs) noexcept {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
/*
|
||||
// ugh, this is boost, not stl.
|
||||
inline std::size_t hash_value(const path& p) noexcept {
|
||||
return std::hash_value(p._path);
|
||||
}
|
||||
*/
|
||||
inline bool operator==(const path& lhs, const path& rhs) noexcept {
|
||||
return lhs.compare(rhs) == 0;
|
||||
}
|
||||
|
||||
inline bool operator!=(const path& lhs, const path& rhs) noexcept {
|
||||
return lhs.compare(rhs) != 0;
|
||||
}
|
||||
|
||||
inline bool operator< (const path& lhs, const path& rhs) noexcept {
|
||||
return lhs.compare(rhs) < 0;
|
||||
}
|
||||
inline bool operator<=(const path& lhs, const path& rhs) noexcept {
|
||||
return lhs.compare(rhs) <= 0;
|
||||
}
|
||||
inline bool operator> (const path& lhs, const path& rhs) noexcept {
|
||||
return lhs.compare(rhs) > 0;
|
||||
}
|
||||
inline bool operator>=(const path& lhs, const path& rhs) noexcept {
|
||||
return lhs.compare(rhs) >= 0;
|
||||
}
|
||||
inline path operator/ (const path& lhs, const path& rhs) {
|
||||
path tmp = lhs;
|
||||
return tmp.append(rhs);
|
||||
}
|
||||
|
||||
//template <class charT, class traits>
|
||||
//basic_ostream<charT, traits>&
|
||||
//operator<<(basic_ostream<charT, traits>& os, const path& p);
|
||||
//template <class charT, class traits>
|
||||
//basic_istream<charT, traits>&
|
||||
///operator>>(basic_istream<charT, traits>& is, path& p);
|
||||
template <class Source>
|
||||
path u8path(Source const& source);
|
||||
template <class InputIterator>
|
||||
path u8path(InputIterator begin, InputIterator end);
|
||||
|
||||
|
||||
class filesystem_error : public system_error
|
||||
{
|
||||
public:
|
||||
filesystem_error(const std::string& what_arg, error_code ec) :
|
||||
system_error(ec, what_arg)
|
||||
{}
|
||||
|
||||
filesystem_error(const std::string& what_arg, const path& p1, error_code ec) :
|
||||
system_error(ec, what_arg), _p1(p1)
|
||||
{}
|
||||
|
||||
filesystem_error(const std::string& what_arg, const path& p1, const path& p2, error_code ec) :
|
||||
system_error(ec, what_arg), _p1(p1), _p2(p2)
|
||||
{}
|
||||
|
||||
const path& path1() const noexcept {
|
||||
return _p1;
|
||||
}
|
||||
const path& path2() const noexcept {
|
||||
return _p2;
|
||||
}
|
||||
|
||||
//const char* what() const noexcept;
|
||||
|
||||
private:
|
||||
path _p1;
|
||||
path _p2;
|
||||
};
|
||||
|
||||
|
||||
|
||||
enum class file_type {
|
||||
|
||||
none = 0,
|
||||
not_found = -1,
|
||||
regular = 1,
|
||||
directory = 2,
|
||||
symlink = 3,
|
||||
block = 4,
|
||||
character = 5,
|
||||
fifo = 6,
|
||||
socket = 7,
|
||||
unknown = 8
|
||||
};
|
||||
|
||||
enum perms {
|
||||
|
||||
none = 0,
|
||||
owner_read = 0400,
|
||||
owner_write = 0200,
|
||||
owner_exec = 0100,
|
||||
owner_all = 0700,
|
||||
|
||||
group_read = 040,
|
||||
group_write = 020,
|
||||
group_exec = 010,
|
||||
group_all = 070,
|
||||
|
||||
others_read = 04,
|
||||
others_write = 02,
|
||||
others_exec = 01,
|
||||
others_all = 07,
|
||||
|
||||
all = 0777,
|
||||
set_uid = 04000,
|
||||
set_gid = 02000,
|
||||
sticky_bit = 01000,
|
||||
mask = 07777,
|
||||
|
||||
unknown = 0xffff,
|
||||
|
||||
add_perms = 0x10000,
|
||||
remove_perms = 0x20000,
|
||||
resolve_symlinks = 0x40000,
|
||||
|
||||
};
|
||||
|
||||
|
||||
class file_status
|
||||
{
|
||||
public:
|
||||
// constructors
|
||||
explicit file_status(file_type ft = file_type::none, perms prms = perms::unknown) noexcept:
|
||||
_ft(ft), _prms(prms) {}
|
||||
|
||||
file_status(const file_status&) noexcept = default;
|
||||
file_status(file_status&&) noexcept = default;
|
||||
~file_status() = default;
|
||||
|
||||
file_status& operator=(const file_status&) noexcept = default;
|
||||
file_status& operator=(file_status&&) noexcept = default;
|
||||
|
||||
// observers
|
||||
file_type type() const noexcept {
|
||||
return _ft;
|
||||
}
|
||||
|
||||
perms permissions() const noexcept {
|
||||
return _prms;
|
||||
}
|
||||
|
||||
// modifiers
|
||||
void type(file_type ft) noexcept {
|
||||
_ft = ft;
|
||||
}
|
||||
void permissions(perms prms) noexcept {
|
||||
_prms = prms;
|
||||
}
|
||||
|
||||
private:
|
||||
file_type _ft = file_type::none;
|
||||
perms _prms = perms::unknown;
|
||||
};
|
||||
|
||||
|
||||
struct space_info // returned by space function
|
||||
{
|
||||
uintmax_t capacity;
|
||||
uintmax_t free;
|
||||
uintmax_t available; // free space available to a non-privileged process
|
||||
};
|
||||
|
||||
enum class directory_options
|
||||
{
|
||||
none,
|
||||
follow_directory_symlink,
|
||||
skip_permission_denied
|
||||
};
|
||||
|
||||
//typedef std::chrono::time_point<trivial-clock> file_time_type;
|
||||
typedef std::chrono::time_point<std::chrono::system_clock> file_time_type;
|
||||
|
||||
enum class copy_options
|
||||
{
|
||||
none = 0,
|
||||
skip_existing = 1,
|
||||
overwrite_existing = 2,
|
||||
update_existing = 4,
|
||||
recurive = 8,
|
||||
copy_symlinks = 16,
|
||||
skip_symlinks = 32,
|
||||
directories_only = 64,
|
||||
create_symlinks = 128,
|
||||
create_hard_links = 256
|
||||
};
|
||||
|
||||
path current_path();
|
||||
path current_path(error_code& ec);
|
||||
void current_path(const path& p);
|
||||
void current_path(const path& p, error_code& ec) noexcept;
|
||||
|
||||
path absolute(const path& p, const path& base=current_path());
|
||||
|
||||
path canonical(const path& p, const path& base = current_path());
|
||||
path canonical(const path& p, error_code& ec);
|
||||
path canonical(const path& p, const path& base, error_code& ec);
|
||||
|
||||
void copy(const path& from, const path& to);
|
||||
void copy(const path& from, const path& to, error_code& ec) noexcept;
|
||||
void copy(const path& from, const path& to, copy_options options);
|
||||
void copy(const path& from, const path& to, copy_options options, error_code& ec) noexcept;
|
||||
|
||||
bool copy_file(const path& from, const path& to);
|
||||
bool copy_file(const path& from, const path& to, error_code& ec) noexcept;
|
||||
bool copy_file(const path& from, const path& to, copy_options option);
|
||||
bool copy_file(const path& from, const path& to, copy_options option, error_code& ec) noexcept;
|
||||
void copy_symlink(const path& existing_symlink, const path& new_symlink);
|
||||
|
||||
void copy_symlink(const path& existing_symlink, const path& new_symlink, error_code& ec) noexcept;
|
||||
|
||||
bool create_directories(const path& p);
|
||||
bool create_directories(const path& p, error_code& ec) noexcept;
|
||||
|
||||
bool create_directory(const path& p);
|
||||
bool create_directory(const path& p, error_code& ec) noexcept;
|
||||
bool create_directory(const path& p, const path& attributes);
|
||||
bool create_directory(const path& p, const path& attributes, error_code& ec) noexcept;
|
||||
|
||||
void create_directory_symlink(const path& to, const path& new_symlink);
|
||||
void create_directory_symlink(const path& to, const path& new_symlink, error_code& ec) noexcept;
|
||||
|
||||
void create_hard_link(const path& to, const path& new_hard_link);
|
||||
void create_hard_link(const path& to, const path& new_hard_link, error_code& ec) noexcept;
|
||||
|
||||
void create_symlink(const path& to, const path& new_symlink);
|
||||
void create_symlink(const path& to, const path& new_symlink, error_code& ec) noexcept;
|
||||
|
||||
|
||||
|
||||
|
||||
file_status status(const path& p);
|
||||
file_status status(const path& p, error_code& ec) noexcept;
|
||||
|
||||
file_status symlink_status(const path& p);
|
||||
file_status symlink_status(const path& p, error_code& ec) noexcept;
|
||||
|
||||
inline bool status_known(file_status s) noexcept {
|
||||
return s.type() != file_type::none;
|
||||
}
|
||||
|
||||
inline bool exists(file_status s) noexcept {
|
||||
return status_known(s) && s.type() != file_type::not_found;
|
||||
}
|
||||
|
||||
inline bool exists(const path& p) {
|
||||
return exists(status(p));
|
||||
}
|
||||
inline bool exists(const path& p, error_code& ec) noexcept {
|
||||
return exists(status(p, ec));
|
||||
}
|
||||
|
||||
|
||||
bool equivalent(const path& p1, const path& p2);
|
||||
bool equivalent(const path& p1, const path& p2, error_code& ec) noexcept;
|
||||
|
||||
uintmax_t file_size(const path& p);
|
||||
uintmax_t file_size(const path& p, error_code& ec) noexcept;
|
||||
|
||||
uintmax_t hard_link_count(const path& p);
|
||||
uintmax_t hard_link_count(const path& p, error_code& ec) noexcept;
|
||||
|
||||
inline bool is_block_file(file_status s) noexcept {
|
||||
return s.type() == file_type::block;
|
||||
}
|
||||
inline bool is_block_file(const path& p) {
|
||||
return is_block_file(status(p));
|
||||
}
|
||||
inline bool is_block_file(const path& p, error_code& ec) noexcept {
|
||||
return is_block_file(status(p, ec));
|
||||
}
|
||||
|
||||
inline bool is_character_file(file_status s) noexcept {
|
||||
return s.type() == file_type::character;
|
||||
}
|
||||
inline bool is_character_file(const path& p) {
|
||||
return is_character_file(status(p));
|
||||
}
|
||||
inline bool is_character_file(const path& p, error_code& ec) noexcept {
|
||||
return is_character_file(status(p, ec));
|
||||
}
|
||||
|
||||
inline bool is_directory(file_status s) noexcept {
|
||||
return s.type() == file_type::directory;
|
||||
}
|
||||
inline bool is_directory(const path& p) {
|
||||
return is_directory(status(p));
|
||||
}
|
||||
inline bool is_directory(const path& p, error_code& ec) noexcept {
|
||||
return is_directory(status(p, ec));
|
||||
}
|
||||
|
||||
bool is_empty(const path& p);
|
||||
bool is_empty(const path& p, error_code& ec) noexcept;
|
||||
|
||||
|
||||
inline bool is_fifo(file_status s) noexcept {
|
||||
return s.type() == file_type::fifo;
|
||||
}
|
||||
inline bool is_fifo(const path& p) {
|
||||
return is_fifo(status(p));
|
||||
}
|
||||
inline bool is_fifo(const path& p, error_code& ec) noexcept {
|
||||
return is_fifo(status(p, ec));
|
||||
}
|
||||
|
||||
inline bool is_socket(file_status s) noexcept {
|
||||
return s.type() == file_type::socket;
|
||||
}
|
||||
inline bool is_socket(const path& p) {
|
||||
return is_socket(status(p));
|
||||
}
|
||||
inline bool is_socket(const path& p, error_code& ec) noexcept {
|
||||
return is_socket(status(p, ec));
|
||||
}
|
||||
|
||||
|
||||
inline bool is_symlink(file_status s) noexcept {
|
||||
return s.type() == file_type::symlink;
|
||||
}
|
||||
inline bool is_symlink(const path& p) {
|
||||
return is_symlink(status(p));
|
||||
}
|
||||
inline bool is_symlink(const path& p, error_code& ec) noexcept {
|
||||
return is_symlink(status(p, ec));
|
||||
}
|
||||
|
||||
|
||||
inline bool is_regular_file(file_status s) noexcept {
|
||||
return s.type() == file_type::regular;
|
||||
}
|
||||
|
||||
inline bool is_regular_file(const path& p) {
|
||||
return is_regular_file(status(p));
|
||||
}
|
||||
|
||||
inline bool is_regular_file(const path& p, error_code& ec) noexcept {
|
||||
return is_regular_file(status(p, ec));
|
||||
}
|
||||
|
||||
inline bool is_other(file_status s) noexcept {
|
||||
return exists(s) && !is_regular_file(s) && ! is_directory(s) && !is_symlink(s);
|
||||
}
|
||||
|
||||
inline bool is_other(const path& p) {
|
||||
return is_other(status(p));
|
||||
}
|
||||
|
||||
inline bool is_other(const path& p, error_code& ec) noexcept {
|
||||
return is_other(status(p, ec));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
file_time_type last_write_time(const path& p);
|
||||
file_time_type last_write_time(const path& p, error_code& ec) noexcept;
|
||||
void last_write_time(const path& p, file_time_type new_time);
|
||||
void last_write_time(const path& p, file_time_type new_time, error_code& ec) noexcept;
|
||||
|
||||
void permissions(const path& p, perms prms);
|
||||
void permissions(const path& p, perms prms, error_code& ec) noexcept;
|
||||
|
||||
path read_symlink(const path& p);
|
||||
path read_symlink(const path& p, error_code& ec);
|
||||
bool remove(const path& p);
|
||||
bool remove(const path& p, error_code& ec) noexcept;
|
||||
uintmax_t remove_all(const path& p);
|
||||
uintmax_t remove_all(const path& p, error_code& ec) noexcept;
|
||||
void rename(const path& from, const path& to);
|
||||
void rename(const path& from, const path& to, error_code& ec) noexcept;
|
||||
void resize_file(const path& p, uintmax_t size);
|
||||
void resize_file(const path& p, uintmax_t size, error_code& ec) noexcept;
|
||||
space_info space(const path& p);
|
||||
space_info space(const path& p, error_code& ec) noexcept;
|
||||
file_status status(const path& p);
|
||||
file_status status(const path& p, error_code& ec) noexcept;
|
||||
bool status_known(file_status s) noexcept;
|
||||
file_status symlink_status(const path& p);
|
||||
file_status symlink_status(const path& p, error_code& ec) noexcept;
|
||||
path system_complete(const path& p);
|
||||
path system_complete(const path& p, error_code& ec);
|
||||
path temp_directory_path();
|
||||
path temp_directory_path(error_code& ec);
|
||||
|
||||
|
||||
|
||||
|
||||
class directory_entry {
|
||||
|
||||
public:
|
||||
|
||||
// constructors and destructor
|
||||
directory_entry() = default;
|
||||
directory_entry(const directory_entry&) = default;
|
||||
directory_entry(directory_entry&&) noexcept = default;
|
||||
explicit directory_entry(const path& p, file_status st=file_status(), file_status symlink_st=file_status());
|
||||
|
||||
~directory_entry() = default;
|
||||
|
||||
// modifiers
|
||||
directory_entry& operator=(const directory_entry&) = default;
|
||||
directory_entry& operator=(directory_entry&&) noexcept = default;
|
||||
void assign(const path& p, file_status st=file_status(),
|
||||
file_status symlink_st=file_status());
|
||||
void replace_filename(const path& p, file_status st=file_status(),
|
||||
file_status symlink_st=file_status());
|
||||
|
||||
|
||||
// observers
|
||||
const filesystem::path& path() const noexcept {
|
||||
return _path;
|
||||
}
|
||||
|
||||
file_status status() const;
|
||||
file_status status(error_code& ec) const noexcept;
|
||||
file_status symlink_status() const;
|
||||
file_status symlink_status(error_code& ec) const noexcept;
|
||||
|
||||
|
||||
bool operator< (const directory_entry& rhs) const noexcept;
|
||||
bool operator==(const directory_entry& rhs) const noexcept;
|
||||
bool operator!=(const directory_entry& rhs) const noexcept;
|
||||
bool operator<=(const directory_entry& rhs) const noexcept;
|
||||
bool operator> (const directory_entry& rhs) const noexcept;
|
||||
bool operator>=(const directory_entry& rhs) const noexcept;
|
||||
|
||||
|
||||
private:
|
||||
class path _path;
|
||||
mutable file_status _st;
|
||||
mutable file_status _lst;
|
||||
};
|
||||
|
||||
|
||||
class directory_iterator : public std::iterator<std::input_iterator_tag, directory_entry>
|
||||
{
|
||||
public:
|
||||
// member functions
|
||||
directory_iterator() noexcept = default;
|
||||
explicit directory_iterator(const path& p);
|
||||
directory_iterator(const path& p, error_code& ec) noexcept;
|
||||
|
||||
directory_iterator(const directory_iterator&) = default;
|
||||
|
||||
directory_iterator(directory_iterator&&) = default;
|
||||
~directory_iterator() = default;
|
||||
|
||||
directory_iterator& operator=(const directory_iterator&) = default;
|
||||
directory_iterator& operator=(directory_iterator&&) = default;
|
||||
|
||||
const directory_entry& operator*() const {
|
||||
return _imp->_entry;
|
||||
}
|
||||
const directory_entry* operator->() const {
|
||||
return &_imp->_entry;
|
||||
}
|
||||
directory_iterator& operator++();
|
||||
directory_iterator& increment(error_code& ec) noexcept;
|
||||
|
||||
bool operator == (const directory_iterator &rhs) const noexcept {
|
||||
return _imp == rhs._imp;
|
||||
}
|
||||
bool operator != (const directory_iterator &rhs) const noexcept {
|
||||
return _imp != rhs._imp;
|
||||
}
|
||||
|
||||
// other members as required by
|
||||
// C++ Std, 24.1.1 Input iterators [input.iterators]
|
||||
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void open_dir(const path &p, error_code &ec) noexcept;
|
||||
|
||||
struct imp {
|
||||
path _path;
|
||||
directory_entry _entry;
|
||||
DIR *_dp = nullptr;
|
||||
|
||||
imp() = default;
|
||||
imp(const path &p, DIR *dp) : _path(p), _dp(dp)
|
||||
{}
|
||||
|
||||
~imp() {
|
||||
if (_dp) closedir(_dp);
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<imp> _imp;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
directory_iterator non-member functions
|
||||
*/
|
||||
inline const directory_iterator& begin(const directory_iterator& iter) noexcept {
|
||||
return iter;
|
||||
}
|
||||
|
||||
inline directory_iterator end(const directory_iterator&) noexcept {
|
||||
return directory_iterator();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
namespace std
|
||||
{
|
||||
|
||||
inline void swap(filesystem::path& lhs, filesystem::path& rhs) noexcept {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
template<>
|
||||
struct hash<filesystem::path>
|
||||
{
|
||||
std::size_t operator()(const filesystem::path &p) const
|
||||
{
|
||||
std::hash<filesystem::path::string_type> hasher;
|
||||
return hasher(p.native());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif
|
|
@ -23,17 +23,12 @@ namespace {
|
|||
FX _fx;
|
||||
};
|
||||
|
||||
void throw_error(int error) {
|
||||
throw std::system_error(error, std::system_category());
|
||||
|
||||
void set_or_throw_error(std::error_code *ec, int error, const std::string &what) {
|
||||
if (ec) *ec = std::error_code(error, std::system_category());
|
||||
else throw std::system_error(error, std::system_category(), what);
|
||||
}
|
||||
|
||||
|
||||
void throw_error(int error, const std::string &what) {
|
||||
throw std::system_error(error, std::system_category(), what);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -41,12 +36,8 @@ namespace {
|
|||
|
||||
namespace {
|
||||
|
||||
void throw_error() {
|
||||
throw_error(GetLastError());
|
||||
}
|
||||
|
||||
void throw_error(const std::string &what) {
|
||||
throw_error(GetLastError(), what);
|
||||
void set_or_throw_error(std::error_code *ec, const std::string &what) {
|
||||
set_or_throw_error(ec, GetLastError(), what);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -61,19 +52,16 @@ void mapped_file_base::close() {
|
|||
}
|
||||
}
|
||||
|
||||
void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, size_t offset) {
|
||||
void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, size_t offset, std::error_code *ec) {
|
||||
|
||||
|
||||
HANDLE fh;
|
||||
HANDLE mh;
|
||||
LARGE_INTEGER file_size;
|
||||
|
||||
// length of 0 in CreateFileMapping / MapViewOfFile
|
||||
// means map the entire file.
|
||||
|
||||
if (is_open()) {
|
||||
throw std::runtime_error("mapped_file_base::open - file already open");
|
||||
}
|
||||
if (is_open()) close();
|
||||
|
||||
fh = CreateFile(p.c_str(),
|
||||
flags == readonly ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
|
||||
|
@ -84,15 +72,19 @@ void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, si
|
|||
nullptr
|
||||
);
|
||||
if (fh == INVALID_HANDLE_VALUE) {
|
||||
throw_error();
|
||||
return set_or_throw_error(ec, "CreateFile");
|
||||
}
|
||||
|
||||
auto fh_close = make_unique_resource(fh, CloseHandle);
|
||||
|
||||
GetFileSizeEx(fh, &file_size);
|
||||
|
||||
if (length == -1)
|
||||
if (length == -1) {
|
||||
LARGE_INTEGER file_size;
|
||||
GetFileSizeEx(fh, &file_size);
|
||||
length = file_size.QuadPart;
|
||||
}
|
||||
|
||||
if (length == 0) return;
|
||||
|
||||
DWORD protect = 0;
|
||||
DWORD access = 0;
|
||||
|
@ -113,7 +105,7 @@ void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, si
|
|||
|
||||
mh = CreateFileMapping(fh, nullptr, protect, 0, 0, 0);
|
||||
if (mh == INVALID_HANDLE_VALUE) {
|
||||
throw_error();
|
||||
return set_or_throw_error(ec, "CreateFileMapping");
|
||||
}
|
||||
|
||||
auto mh_close = make_unique_resource(mh, CloseHandle);
|
||||
|
@ -126,7 +118,7 @@ void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, si
|
|||
length,
|
||||
nullptr);
|
||||
if (!_data) {
|
||||
throw_error();
|
||||
return set_or_throw_error(ec, "MapViewOfFileEx");
|
||||
}
|
||||
|
||||
|
||||
|
@ -149,10 +141,7 @@ void mapped_file_base::create(const path_type& p, size_t length) {
|
|||
const DWORD access = FILE_MAP_WRITE;
|
||||
|
||||
|
||||
if (is_open()) {
|
||||
throw std::runtime_error("mapped_file_base::create - file already open");
|
||||
}
|
||||
|
||||
if (is_open()) close();
|
||||
|
||||
fh = CreateFile(p.c_str(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
|
@ -163,7 +152,7 @@ void mapped_file_base::create(const path_type& p, size_t length) {
|
|||
nullptr
|
||||
);
|
||||
if (fh == INVALID_HANDLE_VALUE) {
|
||||
throw_error();
|
||||
return set_or_throw_error(nullptr, "CreateFile");
|
||||
}
|
||||
|
||||
auto fh_close = make_unique_resource(fh, CloseHandle);
|
||||
|
@ -171,11 +160,11 @@ void mapped_file_base::create(const path_type& p, size_t length) {
|
|||
|
||||
file_size.QuadPart = length;
|
||||
if (!SetFilePointerEx(fh, file_size, nullptr, FILE_BEGIN));
|
||||
if (!SetEndOfFile(fh)) throw_error();
|
||||
if (!SetEndOfFile(fh)) return set_or_throw_error(nullptr, "SetEndOfFile");
|
||||
|
||||
mh = CreateFileMapping(fh, nullptr, protect, 0, 0, 0);
|
||||
if (mh == INVALID_HANDLE_VALUE) {
|
||||
throw_error();
|
||||
return set_or_throw_error(nullptr, "CreateFileMapping");
|
||||
}
|
||||
|
||||
auto mh_close = make_unique_resource(mh, CloseHandle);
|
||||
|
@ -188,7 +177,7 @@ void mapped_file_base::create(const path_type& p, size_t length) {
|
|||
nullptr);
|
||||
|
||||
if (!_data) {
|
||||
throw_error();
|
||||
return set_or_throw_error(nullptr, "MapViewOfFileEx");
|
||||
}
|
||||
|
||||
_file_handle = fh_close.release();
|
||||
|
@ -210,13 +199,10 @@ void mapped_file_base::create(const path_type& p, size_t length) {
|
|||
|
||||
namespace {
|
||||
|
||||
void throw_error() {
|
||||
throw_error(errno);
|
||||
void set_or_throw_error(std::error_code *ec, const std::string &what) {
|
||||
set_or_throw_error(ec, errno, what);
|
||||
}
|
||||
|
||||
void throw_error(const std::string &what) {
|
||||
throw_error(errno, what);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -229,16 +215,15 @@ void mapped_file_base::close() {
|
|||
}
|
||||
|
||||
|
||||
void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, size_t offset) {
|
||||
void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, size_t offset, std::error_code *ec) {
|
||||
|
||||
if (ec) ec->clear();
|
||||
|
||||
int fd;
|
||||
struct stat st;
|
||||
|
||||
int oflags = 0;
|
||||
|
||||
if (is_open()) {
|
||||
throw std::runtime_error("mapped_file_base::open - file already open");
|
||||
}
|
||||
if (is_open()) close();
|
||||
|
||||
switch (flags) {
|
||||
case readonly:
|
||||
|
@ -251,18 +236,25 @@ void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, si
|
|||
|
||||
fd = ::open(p.c_str(), oflags);
|
||||
if (fd < 0) {
|
||||
throw_error(errno);
|
||||
return set_or_throw_error(ec, "open");
|
||||
}
|
||||
|
||||
//defer([fd](){::close(fd); });
|
||||
auto close_fd = make_unique_resource(fd, ::close);
|
||||
|
||||
if (::fstat(fd, &st) < 0) {
|
||||
throw_error(errno);
|
||||
|
||||
|
||||
if (length == -1) {
|
||||
struct stat st;
|
||||
|
||||
if (::fstat(fd, &st) < 0) {
|
||||
set_or_throw_error(ec, "stat");
|
||||
return;
|
||||
}
|
||||
length = st.st_size;
|
||||
}
|
||||
|
||||
|
||||
if (length == -1) length = st.st_size;
|
||||
if (length == 0) return;
|
||||
|
||||
_data = ::mmap(0, length,
|
||||
flags == readonly ? PROT_READ : PROT_READ | PROT_WRITE,
|
||||
|
@ -271,7 +263,7 @@ void mapped_file_base::open(const path_type& p, mapmode flags, size_t length, si
|
|||
|
||||
if (_data == MAP_FAILED) {
|
||||
_data = nullptr;
|
||||
throw_error(errno);
|
||||
return set_or_throw_error(ec, "mmap");
|
||||
}
|
||||
|
||||
_fd = close_fd.release();
|
||||
|
@ -284,15 +276,11 @@ void mapped_file_base::create(const path_type& p, size_t length) {
|
|||
int fd;
|
||||
const size_t offset = 0;
|
||||
|
||||
if (is_open()) {
|
||||
throw std::runtime_error("mapped_file_base::create - file already open");
|
||||
}
|
||||
if (is_open()) close();
|
||||
|
||||
|
||||
|
||||
fd = ::open(p.c_str(), O_RDWR | O_CREAT | O_TRUNC);
|
||||
fd = ::open(p.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
|
||||
if (fd < 0) {
|
||||
throw_error(errno);
|
||||
return set_or_throw_error(nullptr, "open");
|
||||
}
|
||||
|
||||
//defer([fd](){::close(fd); });
|
||||
|
@ -301,7 +289,7 @@ void mapped_file_base::create(const path_type& p, size_t length) {
|
|||
|
||||
|
||||
if (::ftruncate(fd, length) < 0) {
|
||||
throw_error(errno);
|
||||
return set_or_throw_error(nullptr, "ftruncate");
|
||||
}
|
||||
|
||||
|
||||
|
@ -312,7 +300,7 @@ void mapped_file_base::create(const path_type& p, size_t length) {
|
|||
|
||||
if (_data == MAP_FAILED) {
|
||||
_data = nullptr;
|
||||
throw_error(errno);
|
||||
return set_or_throw_error(nullptr, "mmap");
|
||||
}
|
||||
|
||||
_fd = close_fd.release();
|
|
@ -8,7 +8,7 @@
|
|||
#endif
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <system_error>
|
||||
|
||||
class mapped_file_base {
|
||||
public:
|
||||
|
@ -40,7 +40,7 @@ protected:
|
|||
|
||||
void swap(mapped_file_base &rhs);
|
||||
|
||||
void open(const path_type& p, mapmode flags, size_t length, size_t offset);
|
||||
void open(const path_type& p, mapmode flags, size_t length, size_t offset, std::error_code *ec);
|
||||
void create(const path_type &p, size_t new_size); // always creates readwrite.
|
||||
void reset();
|
||||
|
||||
|
@ -79,6 +79,22 @@ public:
|
|||
open(p, flags, length, offset);
|
||||
}
|
||||
|
||||
mapped_file(const path_type &p, std::error_code &ec) noexcept {
|
||||
open(p, readonly, -1, 0, ec);
|
||||
}
|
||||
mapped_file(const path_type &p, mapmode flags, std::error_code &ec) noexcept {
|
||||
open(p, flags, -1, 0, ec);
|
||||
}
|
||||
|
||||
mapped_file(const path_type &p, mapmode flags, size_t length, std::error_code &ec) noexcept {
|
||||
open(p, flags, length, 0, ec);
|
||||
}
|
||||
|
||||
mapped_file(const path_type &p, mapmode flags, size_t length, size_t offset, std::error_code &ec) noexcept {
|
||||
open(p, flags, length, offset, ec);
|
||||
}
|
||||
|
||||
|
||||
mapped_file(mapped_file &&);
|
||||
mapped_file(const mapped_file &) = delete;
|
||||
|
||||
|
@ -87,7 +103,20 @@ public:
|
|||
|
||||
|
||||
void open(const path_type& p, mapmode flags, size_t length = -1, size_t offset = 0) {
|
||||
base::open(p, flags, length, offset);
|
||||
base::open(p, flags, length, offset, nullptr);
|
||||
}
|
||||
|
||||
void open(const path_type &p, std::error_code &ec) noexcept {
|
||||
base::open(p, readonly, -1, 0, &ec);
|
||||
}
|
||||
void open(const path_type &p, mapmode flags, std::error_code &ec) noexcept {
|
||||
base::open(p, flags, -1, 0, &ec);
|
||||
}
|
||||
void open(const path_type &p, mapmode flags, size_t length, std::error_code &ec) noexcept {
|
||||
base::open(p, flags, length, 0, &ec);
|
||||
}
|
||||
void open(const path_type &p, mapmode flags, size_t length, size_t offset, std::error_code &ec) noexcept {
|
||||
base::open(p, flags, length, offset, &ec);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
|
||||
#include "filesystem.h"
|
||||
|
||||
|
||||
namespace filesystem {
|
||||
|
||||
namespace {
|
||||
const path::value_type separator = '/';
|
||||
|
||||
// hmmm... these could be pre-studied since they're constant?
|
||||
const path path_dot = ".";
|
||||
const path path_dotdot = "..";
|
||||
const path path_sep = "/";
|
||||
}
|
||||
|
||||
void path::study() const {
|
||||
|
||||
if (_info.valid) return;
|
||||
int length = _path.length();
|
||||
if (length == 0)
|
||||
{
|
||||
_info.valid = true;
|
||||
_info.special = 0;
|
||||
_info.stem = _info.extension = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
auto back = _path[length - 1];
|
||||
|
||||
// check for special cases (part 1)
|
||||
// if the path is all /s, filename is /
|
||||
// if the path ends with a / (but contains a non-/ char),
|
||||
// the filename is .
|
||||
|
||||
if (back == separator) {
|
||||
value_type special = '.';
|
||||
if (_path.find_first_not_of(separator) == _path.npos)
|
||||
special = separator;
|
||||
|
||||
_info.valid = true;
|
||||
_info.extension = length;
|
||||
_info.stem = length;
|
||||
_info.special = special;
|
||||
return;
|
||||
}
|
||||
|
||||
int stem = 0;
|
||||
int extension = length;
|
||||
for (int i = length; i; ) {
|
||||
auto c = _path[--i];
|
||||
if (c == '.' && extension == length)
|
||||
extension = i;
|
||||
if (c == '/') {
|
||||
stem = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check for special cases (part 2)
|
||||
// ".." and "." are not extensions.
|
||||
if (back == '.') {
|
||||
int xlength = length - stem;
|
||||
if (xlength == 1)
|
||||
extension = length;
|
||||
if (xlength == 2 && _path[stem] == '.')
|
||||
extension = length;
|
||||
}
|
||||
|
||||
_info.valid = true;
|
||||
_info.stem = stem;
|
||||
_info.extension = extension;
|
||||
_info.special = 0;
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
path::path(const path &rhs) :
|
||||
_path(rhs._path), _info(rhs._info)
|
||||
{}
|
||||
|
||||
path::path(path &&rhs) noexcept :
|
||||
_path(std::move(rhs._path))
|
||||
{
|
||||
rhs.invalidate();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// private. neither this->_path nor s are empty.
|
||||
path &path::append_common(const std::string &s)
|
||||
{
|
||||
invalidate();
|
||||
if (_path.back() != separator && s.front() != separator)
|
||||
_path.push_back(separator);
|
||||
|
||||
_path.append(s);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
path& path::append(const path& p)
|
||||
{
|
||||
if (p.empty()) return *this;
|
||||
|
||||
if (empty()) {
|
||||
return (*this = p);
|
||||
}
|
||||
|
||||
invalidate();
|
||||
|
||||
// check for something stupid like xx.append(xx);
|
||||
if (&p == this) {
|
||||
return append_common(string_type(p._path));
|
||||
}
|
||||
|
||||
return append_common(p._path);
|
||||
}
|
||||
|
||||
path& path::append(const string_type &s)
|
||||
{
|
||||
if (s.empty()) return *this;
|
||||
invalidate();
|
||||
|
||||
if (empty()) {
|
||||
_path = s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
if (&s == &_path) {
|
||||
string_type tmp(s);
|
||||
if (_path.back() != separator && tmp[0] != separator)
|
||||
_path.push_back(separator);
|
||||
_path.append(tmp);
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (_path.back() != separator && s[0] != separator)
|
||||
_path.push_back(separator);
|
||||
|
||||
_path.append(s);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
path path::filename() const {
|
||||
|
||||
if (empty()) return *this;
|
||||
if (!_info.valid) study();
|
||||
|
||||
if (_info.special == separator) return path_sep;
|
||||
if (_info.special == '.') return path_dot;
|
||||
|
||||
if (_info.stem == 0) return *this;
|
||||
return _path.substr(_info.stem);
|
||||
}
|
||||
|
||||
path path::stem() const {
|
||||
|
||||
// filename without the extension.
|
||||
|
||||
if (empty()) return *this;
|
||||
if (!_info.valid) study();
|
||||
|
||||
if (_info.special == separator) return path_sep;
|
||||
if (_info.special == '.') return path_dot;
|
||||
|
||||
return _path.substr(_info.stem, _info.extension - _info.stem);
|
||||
}
|
||||
|
||||
path path::extension() const {
|
||||
|
||||
if (empty()) return *this;
|
||||
if (!_info.valid) study();
|
||||
|
||||
return _path.substr(_info.extension);
|
||||
}
|
||||
|
||||
|
||||
bool path::has_parent_path() const
|
||||
{
|
||||
// if there is a /, it has a parent path.
|
||||
// ... unless it's /.
|
||||
|
||||
if (empty()) return false;
|
||||
|
||||
if (!_info.valid) study();
|
||||
|
||||
if (_info.special == '/') return false;
|
||||
|
||||
return _path.find(separator) != _path.npos;
|
||||
}
|
||||
|
||||
path path::parent_path() const {
|
||||
|
||||
/*
|
||||
* special cases:
|
||||
* /abc -> /
|
||||
* /abc/ -> /abc
|
||||
* all trailing /s are removed.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
if (empty()) return *this;
|
||||
|
||||
if (!_info.valid) study();
|
||||
|
||||
// "/" is a file of "/" with a parent of ""
|
||||
if (_info.special == separator) return path();
|
||||
|
||||
// stem starts at 0, eg "abc"
|
||||
if (!_info.stem) return path();
|
||||
|
||||
|
||||
auto tmp = _path.substr(0, _info.stem - 1);
|
||||
|
||||
// remove trailing slashes, but return "/" if nothing BUT /s.
|
||||
while (!tmp.empty() && tmp.back() == separator) tmp.pop_back();
|
||||
if (tmp.empty()) return path_sep;
|
||||
|
||||
return path(tmp);
|
||||
}
|
||||
|
||||
path path::root_directory() const {
|
||||
// for unix, root directory is / or "".
|
||||
if (empty()) return *this;
|
||||
|
||||
return _path.front() == '/' ? path_sep : path();
|
||||
}
|
||||
|
||||
path path::root_name() const {
|
||||
/*
|
||||
* boost (unix) considers // or //component
|
||||
* to be a root name (and only those cases).
|
||||
*
|
||||
* I do not.
|
||||
*/
|
||||
|
||||
return path();
|
||||
}
|
||||
|
||||
path path::root_path() const {
|
||||
// root_name + root_directory.
|
||||
// since root_name is always empty...
|
||||
|
||||
return root_directory();
|
||||
}
|
||||
|
||||
|
||||
path path::relative_path() const {
|
||||
// first pathname *after* the root path
|
||||
// root_path is first / in this implementation.
|
||||
|
||||
if (is_relative()) return *this;
|
||||
|
||||
auto pos = _path.find_first_not_of(separator);
|
||||
if (pos == _path.npos) return path();
|
||||
|
||||
return path(_path.substr(pos));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// compare
|
||||
int path::compare(const path& p) const noexcept {
|
||||
if (&p == this) return 0;
|
||||
return _path.compare(p._path);
|
||||
}
|
||||
|
||||
int path::compare(const std::string& s) const {
|
||||
if (&s == &_path) return 0;
|
||||
return _path.compare(s);
|
||||
}
|
||||
|
||||
int path::compare(const value_type* s) const {
|
||||
return _path.compare(s);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
#ifndef __string_splitter__
|
||||
#define __string_splitter__
|
||||
|
||||
#include <string>
|
||||
|
||||
class string_splitter {
|
||||
public:
|
||||
string_splitter(const std::string &str, char sep) :
|
||||
_parent(str), _sep(sep)
|
||||
{
|
||||
_begin = 0;
|
||||
_end = _parent.find(_sep);
|
||||
_str = _parent.substr(_begin, _end);
|
||||
// _begin is 0, _end is either npos or offset from 0,
|
||||
// so no need to calculate a count.
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return _begin != npos;
|
||||
}
|
||||
|
||||
string_splitter &operator++() {
|
||||
increment();
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::string &operator *() const {
|
||||
return _str;
|
||||
}
|
||||
|
||||
const std::string *operator ->() const {
|
||||
return &_str;
|
||||
}
|
||||
|
||||
private:
|
||||
void increment() {
|
||||
_str.clear();
|
||||
if (_begin == npos) return;
|
||||
if (_end == npos) { _begin = _end; return; }
|
||||
|
||||
_begin = _end + 1;
|
||||
_end = _parent.find(_sep, _begin);
|
||||
auto count = _end == npos ? _end : _end - _begin;
|
||||
_str = _parent.substr(_begin, count);
|
||||
}
|
||||
|
||||
const static auto npos = std::string::npos;
|
||||
std::string _str;
|
||||
const std::string &_parent;
|
||||
char _sep;
|
||||
std::string::size_type _begin = 0;
|
||||
std::string::size_type _end = 0;
|
||||
};
|
||||
|
||||
|
||||
#ifdef TEST
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: %s sep string\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
if (strlen(argv[1]) != 1) {
|
||||
fprintf(stderr, "Separator must be a single character\n");
|
||||
return 1;
|
||||
}
|
||||
char sep = argv[1][0];
|
||||
std::string str(argv[2]);
|
||||
|
||||
for (auto iter = string_splitter(str, sep); iter; ++iter) {
|
||||
printf("%s\n", iter->c_str());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,95 @@
|
|||
#ifndef __unique_resource_h__
|
||||
#define __unique_resource_h__
|
||||
|
||||
#include <utility>
|
||||
|
||||
template <class T, class D>
|
||||
class unique_resource {
|
||||
public:
|
||||
typedef T element_type;
|
||||
typedef D deleter_type;
|
||||
|
||||
unique_resource() = default;
|
||||
unique_resource(const unique_resource &) = delete;
|
||||
unique_resource(unique_resource &&rhs) {
|
||||
swap(rhs);
|
||||
}
|
||||
|
||||
unique_resource(T t, D d): _pair(t,d), _active(true)
|
||||
{}
|
||||
|
||||
~unique_resource() {
|
||||
reset();
|
||||
}
|
||||
|
||||
unique_resource &operator=(const unique_resource &) = delete;
|
||||
unique_resource &operator=(unique_resource &&rhs) {
|
||||
if (this != std::addressof(rhs)) {
|
||||
reset();
|
||||
swap(rhs);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void swap(unique_resource & rhs) {
|
||||
if (this != std::addressof(rhs)) {
|
||||
std::swap(_active, rhs._active);
|
||||
std::swap(_pair, rhs._pair);
|
||||
}
|
||||
}
|
||||
|
||||
void reset(T t) {
|
||||
reset();
|
||||
_active = true;
|
||||
_pair.first = t;
|
||||
}
|
||||
|
||||
void reset(T t, D d) {
|
||||
reset();
|
||||
_active = true;
|
||||
_pair = std::make_pair(t, d);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if (_active) {
|
||||
(*_pair.second)(_pair.first);
|
||||
_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
T release() {
|
||||
_active = false;
|
||||
return _pair.first;;
|
||||
}
|
||||
|
||||
T get() {
|
||||
return _pair.first;
|
||||
}
|
||||
|
||||
operator bool() const {
|
||||
return _active;
|
||||
}
|
||||
|
||||
D& get_deleter() {
|
||||
return _pair.second;
|
||||
}
|
||||
|
||||
const D& get_deleter() const {
|
||||
return _pair.second;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
std::pair<T, D> _pair;
|
||||
bool _active = false;
|
||||
};
|
||||
|
||||
#define MAKE_UNIQUE_RESOURCE(T, D) \
|
||||
unique_resource<decltype(T), decltype(D) *>(T, D)
|
||||
|
||||
template<class T, class D>
|
||||
unique_resource<T, D> make_unique_resource(T t, D d) {
|
||||
return unique_resource<T, D>(t, d);
|
||||
}
|
||||
|
||||
#endif
|
198
environment.cpp
198
environment.cpp
|
@ -2,6 +2,8 @@
|
|||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "error.h"
|
||||
|
||||
namespace {
|
||||
|
@ -18,9 +20,57 @@ namespace {
|
|||
if (s.size() == 1 && s == "0") return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tf(long v) { return v; }
|
||||
|
||||
bool tf(const EnvironmentEntry &e) {
|
||||
return tf(static_cast<std::string>(e));
|
||||
}
|
||||
|
||||
// used for #. base 10 only, extra chars ignored.
|
||||
int to_pound_int(const std::string &s) {
|
||||
if (s.empty()) return 0;
|
||||
try {
|
||||
int n = stoi(s);
|
||||
return std::max(n, (int)0);
|
||||
}
|
||||
catch(std::exception e) {}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int to_pound_int(long n) { return std::max(n, (long)0); }
|
||||
|
||||
}
|
||||
|
||||
|
||||
Environment Environment::subshell_environment() {
|
||||
/* clone the current environment, do not include local variables */
|
||||
Environment env;
|
||||
env._alias_table = _alias_table;
|
||||
|
||||
auto &table = env._table;
|
||||
for (const auto &kv : _table) {
|
||||
const auto &k = kv.first;
|
||||
const auto &value = kv.second;
|
||||
if (!value) continue;
|
||||
|
||||
if (k == "echo") env._echo = tf(value);
|
||||
if (k == "exit") env._exit = tf(value);
|
||||
if (k == "test") env._test = tf(value);
|
||||
|
||||
table.emplace_hint(table.end(), k, value);
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
|
||||
std::string Environment::get(const std::string & key) const {
|
||||
auto iter = find(key);
|
||||
if (iter == end()) return "";
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
Environment::iterator Environment::find( const std::string & key ) {
|
||||
std::string k(key);
|
||||
lowercase(k);
|
||||
|
@ -33,6 +83,7 @@ namespace {
|
|||
return _table.find(k);
|
||||
}
|
||||
|
||||
|
||||
void Environment::set(const std::string &key, const std::string &value, bool exported) {
|
||||
std::string k(key);
|
||||
lowercase(k);
|
||||
|
@ -40,10 +91,81 @@ namespace {
|
|||
if (k == "echo") _echo = tf(value);
|
||||
if (k == "exit") _exit = tf(value);
|
||||
if (k == "test") _test = tf(value);
|
||||
if (k == "#") _pound = to_pound_int(value);
|
||||
|
||||
// don't need to check {status} because that will be clobbered
|
||||
// by the return value.
|
||||
set_common(k, value, exported);
|
||||
}
|
||||
|
||||
void Environment::set(const std::string &key, long value, bool exported) {
|
||||
std::string k(key);
|
||||
lowercase(k);
|
||||
|
||||
if (k == "echo") _echo = tf(value);
|
||||
if (k == "exit") _exit = tf(value);
|
||||
if (k == "test") _test = tf(value);
|
||||
if (k == "#") _pound = to_pound_int(value);
|
||||
|
||||
// don't need to check {status} because that will be clobbered
|
||||
// by the return value.
|
||||
set_common(k, std::to_string(value), exported);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void Environment::set_argv(const std::string &argv0, const std::vector<std::string>& argv) {
|
||||
set_common("0", argv0, false);
|
||||
set_argv(argv);
|
||||
}
|
||||
#endif
|
||||
void Environment::set_argv(const std::vector<std::string>& argv) {
|
||||
_pound = argv.size() - 1;
|
||||
set_common("#", std::to_string(_pound), false);
|
||||
|
||||
int n = 0;
|
||||
for (const auto &s : argv) {
|
||||
set_common(std::to_string(n++), s, false);
|
||||
}
|
||||
|
||||
// parameters, "parameters" ...
|
||||
std::string p;
|
||||
for (const auto &s : argv) {
|
||||
p.push_back('"');
|
||||
p += s;
|
||||
p.push_back('"');
|
||||
p.push_back(' ');
|
||||
}
|
||||
p.pop_back();
|
||||
set_common("\"parameters\"", p, false);
|
||||
p.clear();
|
||||
|
||||
for (const auto &s : argv) {
|
||||
p += s;
|
||||
p.push_back(' ');
|
||||
}
|
||||
p.pop_back();
|
||||
set_common("parameters", p, false);
|
||||
}
|
||||
|
||||
void Environment::shift(int n) {
|
||||
if (n < 0) return;
|
||||
if (_pound < 1) return;
|
||||
|
||||
std::vector<std::string> argv;
|
||||
argv.push_back(get("0"));
|
||||
|
||||
for (int i = 1 + n; i <= _pound; ++i) {
|
||||
argv.push_back(get(std::to_string(i)));
|
||||
}
|
||||
for (int i = 0; i < n; ++i) {
|
||||
unset(std::to_string(_pound - i));
|
||||
}
|
||||
set_argv(argv);
|
||||
}
|
||||
|
||||
|
||||
void Environment::set_common(const std::string &k, const std::string &value, bool exported)
|
||||
{
|
||||
EnvironmentEntry v(value, exported);
|
||||
|
||||
auto iter = _table.find(k);
|
||||
|
@ -54,15 +176,20 @@ namespace {
|
|||
// if previously exported, keep exported.
|
||||
if (iter->second) v = true;
|
||||
iter->second = std::move(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void Environment::unset(const std::string &key) {
|
||||
std::string k(key);
|
||||
lowercase(k);
|
||||
if (k == "echo") _echo = false;
|
||||
if (k == "exit") _exit = false;
|
||||
if (k == "test") _test = false;
|
||||
if (k == "#") _pound = 0;
|
||||
_table.erase(k);
|
||||
}
|
||||
|
||||
|
@ -93,18 +220,81 @@ namespace {
|
|||
return i;
|
||||
}
|
||||
|
||||
void Environment::echo(const char *fmt, ...) {
|
||||
void Environment::echo(const char *fmt, ...) const {
|
||||
if (_echo && !_startup) {
|
||||
for (unsigned i = 0; i < _indent; ++i) {
|
||||
for (int i = 0; i <= _indent; ++i) {
|
||||
fputc(' ', stderr);
|
||||
fputc(' ', stderr);
|
||||
}
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
va_end(ap);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Environment::rebuild_aliases() {
|
||||
std::string as;
|
||||
for (const auto &p : _alias_table) {
|
||||
as += p.first;
|
||||
as.push_back(',');
|
||||
}
|
||||
as.pop_back();
|
||||
set_common("aliases", as, true);
|
||||
}
|
||||
|
||||
void Environment::remove_alias() {
|
||||
_alias_table.clear();
|
||||
set_common("aliases", "", true);
|
||||
}
|
||||
|
||||
void Environment::remove_alias(const std::string &name) {
|
||||
|
||||
std::string k(name);
|
||||
lowercase(k);
|
||||
|
||||
auto iter = std::remove_if(_alias_table.begin(), _alias_table.end(), [&k](const auto &p){
|
||||
return k == p.first;
|
||||
});
|
||||
_alias_table.erase(iter, _alias_table.end());
|
||||
rebuild_aliases();
|
||||
}
|
||||
|
||||
const std::string &Environment::find_alias(const std::string &name) const {
|
||||
|
||||
std::string k(name);
|
||||
lowercase(k);
|
||||
|
||||
auto iter = std::find_if(_alias_table.begin(), _alias_table.end(), [&k](const auto &p){
|
||||
return k == p.first;
|
||||
});
|
||||
|
||||
if (iter == _alias_table.end()) {
|
||||
static std::string empty;
|
||||
return empty;
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void Environment::add_alias(std::string &&name, std::string &&value) {
|
||||
|
||||
lowercase(name);
|
||||
|
||||
auto iter = std::find_if(_alias_table.begin(), _alias_table.end(), [&name](const auto &p){
|
||||
return name == p.first;
|
||||
});
|
||||
|
||||
if (iter == _alias_table.end()) {
|
||||
_alias_table.emplace_back(std::make_pair(std::move(name), std::move(value)));
|
||||
} else {
|
||||
iter->second = std::move(value);
|
||||
}
|
||||
rebuild_aliases();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
#ifndef __environment_h__
|
||||
#define __environment_h__
|
||||
|
||||
#include <map>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <new>
|
||||
#include <vector>
|
||||
|
||||
|
||||
|
||||
|
@ -46,32 +48,40 @@ private:
|
|||
class Environment {
|
||||
|
||||
public:
|
||||
typedef std::unordered_map<std::string, EnvironmentEntry> mapped_type;
|
||||
typedef std::map<std::string, EnvironmentEntry> mapped_type;
|
||||
typedef mapped_type::iterator iterator;
|
||||
typedef mapped_type::const_iterator const_iterator;
|
||||
|
||||
//const EnvironmentEntry & lookup(const std::string &s);
|
||||
|
||||
typedef std::vector<std::pair<std::string, std::string>> alias_table_type;
|
||||
typedef alias_table_type::const_iterator const_alias_iterator;
|
||||
|
||||
|
||||
Environment subshell_environment();
|
||||
|
||||
// void set_argv(const std::string &argv0, const std::vector<std::string>& argv);
|
||||
void set_argv(const std::vector<std::string>& argv);
|
||||
|
||||
void shift(int n);
|
||||
|
||||
void set(const std::string &k, const std::string &value, bool exported = false);
|
||||
void set(const std::string &k, long l, bool exported = false);
|
||||
void unset(const std::string &k);
|
||||
void unset();
|
||||
|
||||
constexpr bool echo() const noexcept { return _echo; }
|
||||
constexpr bool test() const noexcept { return _test; }
|
||||
constexpr bool exit() const noexcept { return _and_or ? false : _exit; }
|
||||
constexpr int status() const noexcept { return _status; }
|
||||
std::string get(const std::string &k) const;
|
||||
|
||||
bool and_or(bool v) { std::swap(v, _and_or); return v; }
|
||||
bool echo() const noexcept { return _echo; }
|
||||
bool test() const noexcept { return _test; }
|
||||
bool exit() const noexcept { return _exit; }
|
||||
int status() const noexcept { return _status; }
|
||||
int pound() const noexcept { return _pound; }
|
||||
|
||||
int status(int i, bool throw_up = true);
|
||||
int status(int i, const std::nothrow_t &);
|
||||
|
||||
constexpr bool startup() const noexcept { return _startup; }
|
||||
constexpr void startup(bool tf) noexcept { _startup = tf; }
|
||||
|
||||
|
||||
constexpr bool passthrough() const noexcept { return _passthrough; }
|
||||
constexpr void passthrough(bool tf) noexcept { _passthrough = tf; }
|
||||
bool startup() const noexcept { return _startup; }
|
||||
void startup(bool tf) noexcept { _startup = tf; }
|
||||
|
||||
template<class FX>
|
||||
void foreach(FX && fx) { for (const auto &kv : _table) { fx(kv.first, kv.second); }}
|
||||
|
@ -89,36 +99,56 @@ public:
|
|||
const_iterator find( const std::string & key ) const;
|
||||
|
||||
|
||||
void echo(const char *format, ...);
|
||||
void echo(const char *format, ...) const;
|
||||
|
||||
template<class FX>
|
||||
void indent_and(FX &&fx) {
|
||||
int i = _indent++;
|
||||
try { fx(); _indent = i; }
|
||||
catch (...) { _indent = i; throw; }
|
||||
}
|
||||
|
||||
template<class FX>
|
||||
void loop_indent_and(FX &&fx) {
|
||||
int i = _indent++;
|
||||
int j = _loop++;
|
||||
try { fx(); _indent = i; _loop = j; }
|
||||
catch (...) { _indent = i; _loop = j; throw; }
|
||||
}
|
||||
|
||||
bool loop() const noexcept { return _loop; }
|
||||
|
||||
const alias_table_type &aliases() const { return _alias_table; }
|
||||
|
||||
void add_alias(std::string &&name, std::string &&value);
|
||||
const std::string &find_alias(const std::string &s) const;
|
||||
|
||||
void remove_alias(const std::string &name);
|
||||
void remove_alias();
|
||||
|
||||
const_alias_iterator alias_begin() const { return _alias_table.begin(); }
|
||||
const_alias_iterator alias_end() const { return _alias_table.end(); }
|
||||
|
||||
private:
|
||||
// magic variables.
|
||||
|
||||
friend class indent_helper;
|
||||
|
||||
int _indent = 0;
|
||||
int _loop = 0;
|
||||
|
||||
bool _exit = false;
|
||||
bool _test = false;
|
||||
|
||||
bool _and_or = false;
|
||||
|
||||
bool _echo = false;
|
||||
int _status = 0;
|
||||
int _pound = 0;
|
||||
bool _startup = false;
|
||||
bool _passthrough = false;
|
||||
|
||||
std::unordered_map<std::string, EnvironmentEntry> _table;
|
||||
};
|
||||
void set_common(const std::string &, const std::string &, bool);
|
||||
void rebuild_aliases();
|
||||
|
||||
class indent_helper {
|
||||
public:
|
||||
indent_helper(Environment &e) : env(e) { env._indent++; }
|
||||
void release() { if (active) { active = false; env._indent--; }}
|
||||
~indent_helper() { if (active) env._indent--; }
|
||||
private:
|
||||
Environment &env;
|
||||
bool active = true;
|
||||
mapped_type _table;
|
||||
|
||||
alias_table_type _alias_table;
|
||||
};
|
||||
|
||||
|
||||
|
|
96
error.h
96
error.h
|
@ -4,14 +4,100 @@
|
|||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
|
||||
class execution_of_input_terminated : public std::runtime_error {
|
||||
class mpw_error : public std::runtime_error {
|
||||
public:
|
||||
execution_of_input_terminated(int status) :
|
||||
std::runtime_error("MPW Shell - Execution of input Terminated."), _status(status)
|
||||
mpw_error(int status, const std::string &s) : std::runtime_error(s), _status(status)
|
||||
{}
|
||||
constexpr int status() const noexcept { return _status; }
|
||||
|
||||
mpw_error(int status, const char *s) : std::runtime_error(s), _status(status)
|
||||
{}
|
||||
|
||||
int status() const noexcept { return _status; }
|
||||
private:
|
||||
int _status;
|
||||
};
|
||||
|
||||
#endif
|
||||
class execution_of_input_terminated : public mpw_error {
|
||||
public:
|
||||
execution_of_input_terminated(int status = -9) :
|
||||
mpw_error(status, "MPW Shell - Execution of input Terminated.")
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
class break_error : public mpw_error {
|
||||
public:
|
||||
break_error(int status = -3) :
|
||||
mpw_error(status, "MPW Shell - Break must be within for or loop.")
|
||||
{}
|
||||
};
|
||||
|
||||
class continue_error : public mpw_error {
|
||||
public:
|
||||
continue_error(int status = -3) :
|
||||
mpw_error(status, "MPW Shell - Continue must be within for or loop.")
|
||||
{}
|
||||
};
|
||||
|
||||
class estring_error: public mpw_error {
|
||||
public:
|
||||
estring_error(int status = -3) :
|
||||
mpw_error(status, "MPW Shell - `s must occur in pairs.")
|
||||
{}
|
||||
};
|
||||
|
||||
class vstring_error: public mpw_error {
|
||||
public:
|
||||
vstring_error(int status = -3) :
|
||||
mpw_error(status, "MPW Shell - {s must occur in pairs.")
|
||||
{}
|
||||
};
|
||||
|
||||
class sstring_error: public mpw_error {
|
||||
public:
|
||||
sstring_error(int status = -3) :
|
||||
mpw_error(status, "MPW Shell - 's must occur in pairs.")
|
||||
{}
|
||||
};
|
||||
|
||||
class dstring_error: public mpw_error {
|
||||
public:
|
||||
dstring_error(int status = -3) :
|
||||
mpw_error(status, "MPW Shell - \"s must occur in pairs.")
|
||||
{}
|
||||
};
|
||||
|
||||
class fsstring_error: public mpw_error {
|
||||
public:
|
||||
fsstring_error(int status = -3) :
|
||||
mpw_error(status, "MPW Shell - /s must occur in pairs.")
|
||||
{}
|
||||
};
|
||||
|
||||
class bsstring_error: public mpw_error {
|
||||
public:
|
||||
bsstring_error(int status = -3) :
|
||||
mpw_error(status, "MPW Shell - \\s must occur in pairs.")
|
||||
{}
|
||||
};
|
||||
|
||||
class regex_error : public mpw_error {
|
||||
public:
|
||||
regex_error(int status = -2) :
|
||||
mpw_error(status, "MPW Shell - File name pattern is incorrect")
|
||||
{}
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
these are used for flow-control.
|
||||
they do not inherit from std::exception to prevent being caught
|
||||
by normal handlers.
|
||||
*/
|
||||
|
||||
struct break_command_t {};
|
||||
struct continue_command_t {};
|
||||
struct exit_command_t { int value = 0; };
|
||||
struct quit_command_t {};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CharacterEncoding</key>
|
||||
<integer>30</integer>
|
||||
<key>CommandString</key>
|
||||
<string>/usr/local/bin/mpw-shell</string>
|
||||
<key>CursorBlink</key>
|
||||
<true/>
|
||||
<key>CursorType</key>
|
||||
<integer>2</integer>
|
||||
<key>Font</key>
|
||||
<data>
|
||||
YnBsaXN0MDDUAQIDBAUGGBlYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
|
||||
AAGGoKQHCBESVSRudWxs1AkKCwwNDg8QVk5TU2l6ZVhOU2ZGbGFnc1ZOU05hbWVWJGNs
|
||||
YXNzI0AsAAAAAAAAEBCAAoADVk1vbmFjb9ITFBUWWiRjbGFzc25hbWVYJGNsYXNzZXNW
|
||||
TlNGb250ohUXWE5TT2JqZWN0XxAPTlNLZXllZEFyY2hpdmVy0RobVHJvb3SAAQgRGiMt
|
||||
Mjc8QktSW2JpcnR2eH+Ej5ifoqu9wMUAAAAAAAABAQAAAAAAAAAcAAAAAAAAAAAAAAAA
|
||||
AAAAxw==
|
||||
</data>
|
||||
<key>FontAntialias</key>
|
||||
<true/>
|
||||
<key>ProfileCurrentVersion</key>
|
||||
<real>2.04</real>
|
||||
<key>RunCommandAsShell</key>
|
||||
<true/>
|
||||
<key>ShowRepresentedURLInTitle</key>
|
||||
<false/>
|
||||
<key>ShowRepresentedURLPathInTitle</key>
|
||||
<false/>
|
||||
<key>ShowShellCommandInTitle</key>
|
||||
<false/>
|
||||
<key>TextBoldColor</key>
|
||||
<data>
|
||||
YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
|
||||
AAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NGMCAw
|
||||
IDAAEAGAAtIQERITWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xvcqISFFhOU09iamVj
|
||||
dF8QD05TS2V5ZWRBcmNoaXZlctEXGFRyb290gAEIERojLTI3O0FITltiaWttcn2GjpGa
|
||||
rK+0AAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAALY=
|
||||
</data>
|
||||
<key>TextColor</key>
|
||||
<data>
|
||||
YnBsaXN0MDDUAQIDBAUGFRZYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
|
||||
AAGGoKMHCA9VJG51bGzTCQoLDA0OVU5TUkdCXE5TQ29sb3JTcGFjZVYkY2xhc3NGMCAw
|
||||
IDEAEAGAAtIQERITWiRjbGFzc25hbWVYJGNsYXNzZXNXTlNDb2xvcqISFFhOU09iamVj
|
||||
dF8QD05TS2V5ZWRBcmNoaXZlctEXGFRyb290gAEIERojLTI3O0FITltiaWttcn2GjpGa
|
||||
rK+0AAAAAAAAAQEAAAAAAAAAGQAAAAAAAAAAAAAAAAAAALY=
|
||||
</data>
|
||||
<key>columnCount</key>
|
||||
<integer>100</integer>
|
||||
<key>name</key>
|
||||
<string>MPW Shell</string>
|
||||
<key>noWarnProcesses</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>ProcessName</key>
|
||||
<string>screen</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ProcessName</key>
|
||||
<string>tmux</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>ProcessName</key>
|
||||
<string>mpw-shell</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>rowCount</key>
|
||||
<integer>50</integer>
|
||||
<key>type</key>
|
||||
<string>Window Settings</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,57 @@
|
|||
# MPW Shell start up script.
|
||||
# only built-in commands are executed.
|
||||
#
|
||||
# {MPW} is pre-defined.
|
||||
|
||||
if {MPWVersion}==""
|
||||
set -e MPWVersion 3.5
|
||||
end
|
||||
|
||||
set -e ShellDirectory "{MPW}{MPWVersion}:"
|
||||
set -e Commands "{ShellDirectory}Tools:,{MPW}Tools:,:"
|
||||
|
||||
set -e SysTempFolder "/tmp/"
|
||||
set -e TempFolder "/tmp/"
|
||||
|
||||
# MPW IIgs
|
||||
set -e AIIGSIncludes "{MPW}Interfaces:AIIGSIncludes:"
|
||||
set -e RIIGSIncludes "{MPW}Interfaces:RIIGSIncludes:"
|
||||
set -e CIIGSIncludes "{MPW}Interfaces:CIIGSIncludes:"
|
||||
set -e CIIGSLibraries "{MPW}Libraries:CIIGSLibraries:"
|
||||
set -e PIIGSIncludes "{MPW}Interfaces:PIIGSIncludes:"
|
||||
set -e PIIGSLibraries "{MPW}Libraries:PIIGSLibraries:"
|
||||
|
||||
# MPW Macintosh compilers
|
||||
set -e SCIncludes "{ShellDirectory}:Interfaces:CIncludes:"
|
||||
set -e CIncludes "{ShellDirectory}:Interfaces:CIncludes:"
|
||||
set -e AIncludes "{ShellDirectory}Interfaces:AIncludes:"
|
||||
set -e RIncludes "{ShellDirectory}Interfaces:RIncludes:"
|
||||
set -e PInterfaces "{ShellDirectory}Interfaces:PInterfaces:"
|
||||
|
||||
set -e Libraries "{ShellDirectory}Libraries:Libraries:"
|
||||
set -e PLibraries "{ShellDirectory}Libraries:PLibraries:"
|
||||
set -e CLibraries "{ShellDirectory}Libraries:CLibraries:"
|
||||
|
||||
set -e PPCLibraries "{ShellDirectory}Libraries:PPCLibraries:"
|
||||
set -e SharedLibraries "{ShellDirectory}Libraries:SharedLibraries:"
|
||||
set -e CFM68KLibraries "{ShellDirectory}Libraries:CFM68KLibraries:"
|
||||
|
||||
#MetroWerks
|
||||
set -e MW68KLibraries "{MPW}Libraries:MW68KLibraries:"
|
||||
set -e MWPPCLibraries "{MPW}Libraries:MWPPCLibraries:"
|
||||
|
||||
set -e CWCIncludes "{MPW}Interfaces:CWCIncludes:"
|
||||
set -e CWANSIIncludes "{MPW}Interfaces:CWANSIIncludes:"
|
||||
set -e MWCIncludes "{CIncludes}"
|
||||
|
||||
|
||||
#Newton Tools
|
||||
set -e ARMCIncludes "{MPW}Interfaces:ARMCIncludes:"
|
||||
|
||||
set -e AsmMatOpts "-t 4"
|
||||
|
||||
# customizations can go in the UserStartup file.
|
||||
if `exists {MPW}UserStartup` != ""
|
||||
execute {MPW}UserStartup
|
||||
end
|
||||
|
7
fdset.h
7
fdset.h
|
@ -25,6 +25,9 @@ class fdmask {
|
|||
fdmask(const std::array<int, 3> &rhs) : _fds(rhs)
|
||||
{}
|
||||
|
||||
fdmask(int a, int b, int c) : _fds{{ a, b, c }}
|
||||
{}
|
||||
|
||||
#if 0
|
||||
fdmask(std::initializer_list<int> rhs) : _fds(rhs)
|
||||
{}
|
||||
|
@ -129,6 +132,10 @@ class fdset {
|
|||
return fdmask(_fds);
|
||||
}
|
||||
|
||||
void swap_in_out() {
|
||||
std::swap(_fds[0], _fds[1]);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void reset() {
|
||||
|
|
11
lemon_base.h
11
lemon_base.h
|
@ -9,14 +9,13 @@ public:
|
|||
|
||||
virtual ~lemon_base() = default;
|
||||
|
||||
#if 0
|
||||
virtual typename std::enable_if<std::is_copy_constructible<TokenType>::value, void>::type
|
||||
parse(int yymajor, const TokenType &yyminor) = 0;
|
||||
#endif
|
||||
|
||||
//virtual typename std::enable_if<std::is_move_constructible<TokenType>::value, void>::type
|
||||
virtual void parse(int yymajor, TokenType &&yyminor) = 0;
|
||||
virtual void trace(FILE *, const char *) = 0;
|
||||
|
||||
virtual void trace(FILE *, const char *) {}
|
||||
|
||||
virtual bool will_accept() const = 0;
|
||||
virtual void reset() {}
|
||||
|
||||
protected:
|
||||
virtual void parse_accept() {}
|
||||
|
|
1063
lempar.cxx
1063
lempar.cxx
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,163 @@
|
|||
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <stdint.h>
|
||||
#include <array>
|
||||
|
||||
|
||||
struct mu_pair {
|
||||
uint16_t unicode;
|
||||
uint8_t macroman;
|
||||
|
||||
constexpr bool operator<(const mu_pair &rhs) const noexcept {
|
||||
return unicode < rhs.unicode;
|
||||
}
|
||||
|
||||
constexpr bool operator==(const mu_pair &rhs) const noexcept {
|
||||
return unicode == rhs.unicode;
|
||||
}
|
||||
|
||||
constexpr bool operator<(uint16_t c) const noexcept {
|
||||
return unicode < c;
|
||||
}
|
||||
|
||||
constexpr bool operator==(uint16_t c) const noexcept {
|
||||
return unicode == c;
|
||||
}
|
||||
};
|
||||
|
||||
uint8_t unicode_to_macroman(uint16_t c){
|
||||
|
||||
|
||||
static std::array< mu_pair, 0x80> table = {{
|
||||
#undef _
|
||||
#define _(macroman, unicode, comment) mu_pair{ unicode, macroman } ,
|
||||
#include "macroman.x"
|
||||
#undef _
|
||||
}};
|
||||
|
||||
static bool init = false;
|
||||
|
||||
if (c < 0x80) return c;
|
||||
|
||||
if (!init) {
|
||||
init = true;
|
||||
std::sort(table.begin(), table.end());
|
||||
init = true;
|
||||
}
|
||||
|
||||
auto iter = std::lower_bound(table.begin(), table.end(), c);
|
||||
if (iter != table.end() && iter->unicode == c) return iter->macroman;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
uint16_t macroman_to_unicode(uint8_t c) {
|
||||
|
||||
static std::array<uint16_t, 0x80> table = {{
|
||||
#undef _
|
||||
#define _(macroman,unicode,comment) unicode ,
|
||||
#include "macroman.x"
|
||||
#undef _
|
||||
}};
|
||||
|
||||
if (c < 0x80) return c;
|
||||
return table[c - 0x80];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
std::string utf8_to_macroman(const std::string &s) {
|
||||
|
||||
|
||||
if (std::find_if(s.begin(), s.end(), [](unsigned char c){ return c & 0x80; }) == s.end())
|
||||
return s;
|
||||
|
||||
std::string rv;
|
||||
rv.reserve(s.size());
|
||||
|
||||
unsigned cs = 0;
|
||||
uint16_t tmp = 0;
|
||||
|
||||
for (unsigned char c : s) {
|
||||
switch(cs) {
|
||||
case 0:
|
||||
if (c <= 0x7f) {
|
||||
rv.push_back(c);
|
||||
continue;
|
||||
}
|
||||
if ((c & 0b11100000) == 0b11000000) {
|
||||
(tmp = c & 0b00011111);
|
||||
cs = 1;
|
||||
continue;
|
||||
}
|
||||
if ((c & 0b11110000) == 0b11100000) {
|
||||
(tmp = c & 0b00001111);
|
||||
cs = 2;
|
||||
continue;
|
||||
}
|
||||
if ((c & 0b11111000) == 0b11110000) {
|
||||
(tmp = c & 0b00000111);
|
||||
cs = 3;
|
||||
continue;
|
||||
}
|
||||
// not utf8...
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
if ((c & 0b11000000) != 0b10000000) {
|
||||
//not utf8...
|
||||
}
|
||||
tmp = (tmp << 6) + (c & 0b00111111);
|
||||
if (--cs == 0) {
|
||||
c = unicode_to_macroman(tmp);
|
||||
if (c) rv.push_back(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
std::string macroman_to_utf8(const std::string &s) {
|
||||
|
||||
if (std::find_if(s.begin(), s.end(), [](unsigned char c){ return c & 0x80; }) == s.end())
|
||||
return s;
|
||||
|
||||
std::string rv;
|
||||
rv.reserve(s.size());
|
||||
|
||||
for (uint8_t c : s) {
|
||||
if (c <= 0x7f) { rv.push_back(c); continue; }
|
||||
|
||||
uint16_t tmp = macroman_to_unicode(c);
|
||||
|
||||
if (tmp <= 0x7f) {
|
||||
rv.push_back(tmp);
|
||||
} else if (tmp <= 0x07ff) {
|
||||
uint8_t a,b;
|
||||
b = tmp & 0b00111111; tmp >>= 6;
|
||||
a = tmp;
|
||||
rv.push_back(0b11000000 | a);
|
||||
rv.push_back(0b10000000 | b);
|
||||
} else { // tmp <= 0xffff
|
||||
uint8_t a,b,c;
|
||||
c = tmp & 0b00111111; tmp >>= 6;
|
||||
b = tmp & 0b00111111; tmp >>= 6;
|
||||
a = tmp;
|
||||
rv.push_back(0b11100000 | a);
|
||||
rv.push_back(0b10000000 | b);
|
||||
rv.push_back(0b10000000 | c);
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef __macroman_h__
|
||||
#define __macroman_h__
|
||||
|
||||
std::string utf8_to_macroman(const std::string &s);
|
||||
std::string macroman_to_utf8(const std::string &s);
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,130 @@
|
|||
|
||||
// http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/ROMAN.TXT
|
||||
_(0x80, 0x00C4, "# LATIN CAPITAL LETTER A WITH DIAERESIS")
|
||||
_(0x81, 0x00C5, "# LATIN CAPITAL LETTER A WITH RING ABOVE")
|
||||
_(0x82, 0x00C7, "# LATIN CAPITAL LETTER C WITH CEDILLA")
|
||||
_(0x83, 0x00C9, "# LATIN CAPITAL LETTER E WITH ACUTE")
|
||||
_(0x84, 0x00D1, "# LATIN CAPITAL LETTER N WITH TILDE")
|
||||
_(0x85, 0x00D6, "# LATIN CAPITAL LETTER O WITH DIAERESIS")
|
||||
_(0x86, 0x00DC, "# LATIN CAPITAL LETTER U WITH DIAERESIS")
|
||||
_(0x87, 0x00E1, "# LATIN SMALL LETTER A WITH ACUTE")
|
||||
_(0x88, 0x00E0, "# LATIN SMALL LETTER A WITH GRAVE")
|
||||
_(0x89, 0x00E2, "# LATIN SMALL LETTER A WITH CIRCUMFLEX")
|
||||
_(0x8A, 0x00E4, "# LATIN SMALL LETTER A WITH DIAERESIS")
|
||||
_(0x8B, 0x00E3, "# LATIN SMALL LETTER A WITH TILDE")
|
||||
_(0x8C, 0x00E5, "# LATIN SMALL LETTER A WITH RING ABOVE")
|
||||
_(0x8D, 0x00E7, "# LATIN SMALL LETTER C WITH CEDILLA")
|
||||
_(0x8E, 0x00E9, "# LATIN SMALL LETTER E WITH ACUTE")
|
||||
_(0x8F, 0x00E8, "# LATIN SMALL LETTER E WITH GRAVE")
|
||||
_(0x90, 0x00EA, "# LATIN SMALL LETTER E WITH CIRCUMFLEX")
|
||||
_(0x91, 0x00EB, "# LATIN SMALL LETTER E WITH DIAERESIS")
|
||||
_(0x92, 0x00ED, "# LATIN SMALL LETTER I WITH ACUTE")
|
||||
_(0x93, 0x00EC, "# LATIN SMALL LETTER I WITH GRAVE")
|
||||
_(0x94, 0x00EE, "# LATIN SMALL LETTER I WITH CIRCUMFLEX")
|
||||
_(0x95, 0x00EF, "# LATIN SMALL LETTER I WITH DIAERESIS")
|
||||
_(0x96, 0x00F1, "# LATIN SMALL LETTER N WITH TILDE")
|
||||
_(0x97, 0x00F3, "# LATIN SMALL LETTER O WITH ACUTE")
|
||||
_(0x98, 0x00F2, "# LATIN SMALL LETTER O WITH GRAVE")
|
||||
_(0x99, 0x00F4, "# LATIN SMALL LETTER O WITH CIRCUMFLEX")
|
||||
_(0x9A, 0x00F6, "# LATIN SMALL LETTER O WITH DIAERESIS")
|
||||
_(0x9B, 0x00F5, "# LATIN SMALL LETTER O WITH TILDE")
|
||||
_(0x9C, 0x00FA, "# LATIN SMALL LETTER U WITH ACUTE")
|
||||
_(0x9D, 0x00F9, "# LATIN SMALL LETTER U WITH GRAVE")
|
||||
_(0x9E, 0x00FB, "# LATIN SMALL LETTER U WITH CIRCUMFLEX")
|
||||
_(0x9F, 0x00FC, "# LATIN SMALL LETTER U WITH DIAERESIS")
|
||||
_(0xA0, 0x2020, "# DAGGER")
|
||||
_(0xA1, 0x00B0, "# DEGREE SIGN")
|
||||
_(0xA2, 0x00A2, "# CENT SIGN")
|
||||
_(0xA3, 0x00A3, "# POUND SIGN")
|
||||
_(0xA4, 0x00A7, "# SECTION SIGN")
|
||||
_(0xA5, 0x2022, "# BULLET")
|
||||
_(0xA6, 0x00B6, "# PILCROW SIGN")
|
||||
_(0xA7, 0x00DF, "# LATIN SMALL LETTER SHARP S")
|
||||
_(0xA8, 0x00AE, "# REGISTERED SIGN")
|
||||
_(0xA9, 0x00A9, "# COPYRIGHT SIGN")
|
||||
_(0xAA, 0x2122, "# TRADE MARK SIGN")
|
||||
_(0xAB, 0x00B4, "# ACUTE ACCENT")
|
||||
_(0xAC, 0x00A8, "# DIAERESIS")
|
||||
_(0xAD, 0x2260, "# NOT EQUAL TO")
|
||||
_(0xAE, 0x00C6, "# LATIN CAPITAL LETTER AE")
|
||||
_(0xAF, 0x00D8, "# LATIN CAPITAL LETTER O WITH STROKE")
|
||||
_(0xB0, 0x221E, "# INFINITY")
|
||||
_(0xB1, 0x00B1, "# PLUS-MINUS SIGN")
|
||||
_(0xB2, 0x2264, "# LESS-THAN OR EQUAL TO")
|
||||
_(0xB3, 0x2265, "# GREATER-THAN OR EQUAL TO")
|
||||
_(0xB4, 0x00A5, "# YEN SIGN")
|
||||
_(0xB5, 0x00B5, "# MICRO SIGN")
|
||||
_(0xB6, 0x2202, "# PARTIAL DIFFERENTIAL")
|
||||
_(0xB7, 0x2211, "# N-ARY SUMMATION")
|
||||
_(0xB8, 0x220F, "# N-ARY PRODUCT")
|
||||
_(0xB9, 0x03C0, "# GREEK SMALL LETTER PI")
|
||||
_(0xBA, 0x222B, "# INTEGRAL")
|
||||
_(0xBB, 0x00AA, "# FEMININE ORDINAL INDICATOR")
|
||||
_(0xBC, 0x00BA, "# MASCULINE ORDINAL INDICATOR")
|
||||
_(0xBD, 0x03A9, "# GREEK CAPITAL LETTER OMEGA")
|
||||
_(0xBE, 0x00E6, "# LATIN SMALL LETTER AE")
|
||||
_(0xBF, 0x00F8, "# LATIN SMALL LETTER O WITH STROKE")
|
||||
_(0xC0, 0x00BF, "# INVERTED QUESTION MARK")
|
||||
_(0xC1, 0x00A1, "# INVERTED EXCLAMATION MARK")
|
||||
_(0xC2, 0x00AC, "# NOT SIGN")
|
||||
_(0xC3, 0x221A, "# SQUARE ROOT")
|
||||
_(0xC4, 0x0192, "# LATIN SMALL LETTER F WITH HOOK")
|
||||
_(0xC5, 0x2248, "# ALMOST EQUAL TO")
|
||||
_(0xC6, 0x2206, "# INCREMENT")
|
||||
_(0xC7, 0x00AB, "# LEFT-POINTING DOUBLE ANGLE QUOTATION MARK")
|
||||
_(0xC8, 0x00BB, "# RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK")
|
||||
_(0xC9, 0x2026, "# HORIZONTAL ELLIPSIS")
|
||||
_(0xCA, 0x00A0, "# NO-BREAK SPACE")
|
||||
_(0xCB, 0x00C0, "# LATIN CAPITAL LETTER A WITH GRAVE")
|
||||
_(0xCC, 0x00C3, "# LATIN CAPITAL LETTER A WITH TILDE")
|
||||
_(0xCD, 0x00D5, "# LATIN CAPITAL LETTER O WITH TILDE")
|
||||
_(0xCE, 0x0152, "# LATIN CAPITAL LIGATURE OE")
|
||||
_(0xCF, 0x0153, "# LATIN SMALL LIGATURE OE")
|
||||
_(0xD0, 0x2013, "# EN DASH")
|
||||
_(0xD1, 0x2014, "# EM DASH")
|
||||
_(0xD2, 0x201C, "# LEFT DOUBLE QUOTATION MARK")
|
||||
_(0xD3, 0x201D, "# RIGHT DOUBLE QUOTATION MARK")
|
||||
_(0xD4, 0x2018, "# LEFT SINGLE QUOTATION MARK")
|
||||
_(0xD5, 0x2019, "# RIGHT SINGLE QUOTATION MARK")
|
||||
_(0xD6, 0x00F7, "# DIVISION SIGN")
|
||||
_(0xD7, 0x25CA, "# LOZENGE")
|
||||
_(0xD8, 0x00FF, "# LATIN SMALL LETTER Y WITH DIAERESIS")
|
||||
_(0xD9, 0x0178, "# LATIN CAPITAL LETTER Y WITH DIAERESIS")
|
||||
_(0xDA, 0x2044, "# FRACTION SLASH")
|
||||
_(0xDB, 0x20AC, "# EURO SIGN")
|
||||
_(0xDC, 0x2039, "# SINGLE LEFT-POINTING ANGLE QUOTATION MARK")
|
||||
_(0xDD, 0x203A, "# SINGLE RIGHT-POINTING ANGLE QUOTATION MARK")
|
||||
_(0xDE, 0xFB01, "# LATIN SMALL LIGATURE FI")
|
||||
_(0xDF, 0xFB02, "# LATIN SMALL LIGATURE FL")
|
||||
_(0xE0, 0x2021, "# DOUBLE DAGGER")
|
||||
_(0xE1, 0x00B7, "# MIDDLE DOT")
|
||||
_(0xE2, 0x201A, "# SINGLE LOW-9 QUOTATION MARK")
|
||||
_(0xE3, 0x201E, "# DOUBLE LOW-9 QUOTATION MARK")
|
||||
_(0xE4, 0x2030, "# PER MILLE SIGN")
|
||||
_(0xE5, 0x00C2, "# LATIN CAPITAL LETTER A WITH CIRCUMFLEX")
|
||||
_(0xE6, 0x00CA, "# LATIN CAPITAL LETTER E WITH CIRCUMFLEX")
|
||||
_(0xE7, 0x00C1, "# LATIN CAPITAL LETTER A WITH ACUTE")
|
||||
_(0xE8, 0x00CB, "# LATIN CAPITAL LETTER E WITH DIAERESIS")
|
||||
_(0xE9, 0x00C8, "# LATIN CAPITAL LETTER E WITH GRAVE")
|
||||
_(0xEA, 0x00CD, "# LATIN CAPITAL LETTER I WITH ACUTE")
|
||||
_(0xEB, 0x00CE, "# LATIN CAPITAL LETTER I WITH CIRCUMFLEX")
|
||||
_(0xEC, 0x00CF, "# LATIN CAPITAL LETTER I WITH DIAERESIS")
|
||||
_(0xED, 0x00CC, "# LATIN CAPITAL LETTER I WITH GRAVE")
|
||||
_(0xEE, 0x00D3, "# LATIN CAPITAL LETTER O WITH ACUTE")
|
||||
_(0xEF, 0x00D4, "# LATIN CAPITAL LETTER O WITH CIRCUMFLEX")
|
||||
_(0xF0, 0xF8FF, "# Apple logo")
|
||||
_(0xF1, 0x00D2, "# LATIN CAPITAL LETTER O WITH GRAVE")
|
||||
_(0xF2, 0x00DA, "# LATIN CAPITAL LETTER U WITH ACUTE")
|
||||
_(0xF3, 0x00DB, "# LATIN CAPITAL LETTER U WITH CIRCUMFLEX")
|
||||
_(0xF4, 0x00D9, "# LATIN CAPITAL LETTER U WITH GRAVE")
|
||||
_(0xF5, 0x0131, "# LATIN SMALL LETTER DOTLESS I")
|
||||
_(0xF6, 0x02C6, "# MODIFIER LETTER CIRCUMFLEX ACCENT")
|
||||
_(0xF7, 0x02DC, "# SMALL TILDE")
|
||||
_(0xF8, 0x00AF, "# MACRON")
|
||||
_(0xF9, 0x02D8, "# BREVE")
|
||||
_(0xFA, 0x02D9, "# DOT ABOVE")
|
||||
_(0xFB, 0x02DA, "# RING ABOVE")
|
||||
_(0xFC, 0x00B8, "# CEDILLA")
|
||||
_(0xFD, 0x02DD, "# DOUBLE ACUTE ACCENT")
|
||||
_(0xFE, 0x02DB, "# OGONEK")
|
||||
_(0xFF, 0x02C7, "# CARON")
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env ruby -w
|
||||
|
||||
# make-version version
|
||||
|
||||
def q(x)
|
||||
# quote a string
|
||||
'"' + x.gsub(/[\"]/, '\\\1' ) + '"'
|
||||
end
|
||||
|
||||
unless ARGV.length == 1
|
||||
puts("Usage: make-version version")
|
||||
exit(1)
|
||||
end
|
||||
|
||||
|
||||
VERSION = ARGV[0]
|
||||
|
||||
File.open("version.h", "w") {|f|
|
||||
|
||||
f.puts("#ifndef __version_h__")
|
||||
f.puts("#define __version_h__")
|
||||
f.puts("#define VERSION #{q(VERSION)}")
|
||||
f.puts("#define VERSION_DATE #{q(Time.new.ctime)}")
|
||||
f.puts("#endif")
|
||||
}
|
||||
|
||||
ok = system(*%w(cmake --build build))
|
||||
ok = system(*%w(git add version.h))
|
||||
ok = system(*%w(git commit -m), "Bump Version: #{VERSION}")
|
||||
ok = system(*%w(git tag), "r#{VERSION}")
|
||||
exit 0
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
|
||||
#include "mpw-regex.h"
|
||||
#include "environment.h"
|
||||
|
||||
typedef std::string::const_iterator iterator;
|
||||
|
||||
namespace {
|
||||
bool ecma_special(unsigned char c) {
|
||||
//
|
||||
switch(c) {
|
||||
case '|':
|
||||
case '{':
|
||||
case '}':
|
||||
case '(':
|
||||
case ')':
|
||||
case '[':
|
||||
case ']':
|
||||
case '*':
|
||||
case '+':
|
||||
case '^':
|
||||
case '$':
|
||||
case '.':
|
||||
case '\\':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mpw_regex::mpw_regex(const std::string &s, bool slash) {
|
||||
convert_re(s, slash);
|
||||
}
|
||||
|
||||
bool mpw_regex::is_glob(const std::string &s) {
|
||||
bool esc = false;
|
||||
for (unsigned char c : s) {
|
||||
if (esc) {
|
||||
esc = false;
|
||||
continue;
|
||||
}
|
||||
switch(c) {
|
||||
case 0xb6:
|
||||
esc = true;
|
||||
break;
|
||||
case '[':
|
||||
case '?':
|
||||
case '*':
|
||||
case '+':
|
||||
case 0xc7:
|
||||
case 0xc5:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mpw_regex::match(const std::string &s, Environment &e) {
|
||||
std::smatch m;
|
||||
bool ok = std::regex_match(s, m, re);
|
||||
if (!ok) return false;
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
int index = capture_map[i];
|
||||
|
||||
if (index && index < m.size() && m[index].matched) {
|
||||
std::string v(m[index].first, m[index].second);
|
||||
std::string k("\xa8");
|
||||
k += (i + '0');
|
||||
e.set(k, std::move(v));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mpw_regex::match(const std::string &s) {
|
||||
return std::regex_match(s, re);
|
||||
}
|
||||
|
||||
|
||||
// convert a mpw-flavor regex to std::regex flavor regex.
|
||||
void mpw_regex::convert_re(const std::string &s, bool slash) {
|
||||
|
||||
|
||||
std::string accumulator;
|
||||
auto iter = s.begin();
|
||||
auto end = s.end();
|
||||
|
||||
if (slash) {
|
||||
if (iter == end || *iter++ != '/')
|
||||
throw std::regex_error(std::regex_constants::error_space);
|
||||
}
|
||||
|
||||
iter = convert_re(iter, end, accumulator, slash ? '/' : 0);
|
||||
|
||||
if (iter != end) throw std::regex_error(std::regex_constants::error_space);
|
||||
|
||||
|
||||
re = std::regex(accumulator);
|
||||
if (slash) key = s;
|
||||
else key = "/" + s + "/";
|
||||
}
|
||||
|
||||
|
||||
iterator mpw_regex::convert_re(iterator iter, iterator end, std::string &accumulator, unsigned char term) {
|
||||
|
||||
while (iter != end) {
|
||||
unsigned char c = *iter++;
|
||||
if (c == 0xb6) {
|
||||
// escape
|
||||
if (iter == end) throw std::regex_error(std::regex_constants::error_escape);
|
||||
c = *iter++;
|
||||
if (ecma_special(c))
|
||||
accumulator += '\\';
|
||||
accumulator += c;
|
||||
continue;
|
||||
}
|
||||
if (term && c == term) {
|
||||
return iter;
|
||||
}
|
||||
if (c == '?') {
|
||||
// match any char
|
||||
accumulator += '.';
|
||||
continue;
|
||||
}
|
||||
if (c == 0xc5) {
|
||||
// match any string
|
||||
accumulator += ".*";
|
||||
continue;
|
||||
}
|
||||
if (c == '[') {
|
||||
// begin a set
|
||||
iter = convert_re_set(iter, end, accumulator);
|
||||
continue;
|
||||
}
|
||||
if (c == '(') {
|
||||
// begin a capture
|
||||
iter = convert_re_capture(iter, end, accumulator);
|
||||
continue;
|
||||
}
|
||||
if (c == 0xc7) {
|
||||
// repeat
|
||||
iter = convert_re_repeat(iter, end, accumulator);
|
||||
continue;
|
||||
}
|
||||
if (c == '+' || c == '*') {
|
||||
// same meaning
|
||||
accumulator += c;
|
||||
continue;
|
||||
}
|
||||
if (ecma_special(c)) {
|
||||
accumulator += '\\';
|
||||
}
|
||||
accumulator += c;
|
||||
}
|
||||
|
||||
if (term) throw std::regex_error(std::regex_constants::error_paren);
|
||||
return iter;
|
||||
}
|
||||
|
||||
iterator mpw_regex::convert_re_repeat(iterator iter, iterator end, std::string &accumulator) {
|
||||
int min = -1;
|
||||
int max = -1;
|
||||
|
||||
accumulator += "{";
|
||||
|
||||
while (iter != end) {
|
||||
unsigned char c = *iter++;
|
||||
if (c == 0xc8) {
|
||||
accumulator += "}";
|
||||
return iter;
|
||||
}
|
||||
if (c != ',' && !isdigit(c)) break;
|
||||
accumulator += c;
|
||||
}
|
||||
throw std::regex_error(std::regex_constants::error_brace);
|
||||
}
|
||||
|
||||
iterator mpw_regex::convert_re_set(iterator iter, iterator end, std::string &accumulator) {
|
||||
// need extra logic to block character classes.
|
||||
|
||||
unsigned char c;
|
||||
accumulator += "[";
|
||||
|
||||
if (iter != end && static_cast<unsigned char>(*iter) == 0xc2) {
|
||||
accumulator += "^";
|
||||
++iter;
|
||||
} else if (iter != end && *iter == '^') {
|
||||
// leading ^ needs to be escaped.
|
||||
accumulator += "\\^";
|
||||
++iter;
|
||||
}
|
||||
while (iter != end) {
|
||||
c = *iter++;
|
||||
|
||||
if (c == 0xb6) {
|
||||
// escape
|
||||
if (iter == end) throw std::regex_error(std::regex_constants::error_escape);
|
||||
c = *iter++;
|
||||
accumulator += '\\';
|
||||
accumulator += c;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (c == ']') {
|
||||
accumulator += "]";
|
||||
return iter;
|
||||
}
|
||||
if (c == '\\') {
|
||||
accumulator += "\\\\";
|
||||
continue;
|
||||
}
|
||||
accumulator += c;
|
||||
}
|
||||
|
||||
throw std::regex_error(std::regex_constants::error_brack);
|
||||
}
|
||||
|
||||
iterator mpw_regex::convert_re_capture(iterator iter, iterator end, std::string &accumulator) {
|
||||
|
||||
|
||||
/*
|
||||
* consider: (abc(abc)®1(xyz))®2
|
||||
* m[1] = (abcabcxyz)
|
||||
* m[2] = (abc)
|
||||
* BUT we don't know if it's captured until the ® is parsed.
|
||||
*/
|
||||
|
||||
std::string scratch;
|
||||
bool capture = false;
|
||||
int n = -1;
|
||||
|
||||
int ecma_index = ++num_captures;
|
||||
|
||||
if (iter != end && *iter == '?') {
|
||||
// leading ? needs to be escaped.
|
||||
scratch += "\\?";
|
||||
++iter;
|
||||
}
|
||||
iter = convert_re(iter, end, scratch, ')');
|
||||
|
||||
// check for capture?
|
||||
if (iter != end && static_cast<unsigned char>(*iter) == 0xa8) {
|
||||
++iter;
|
||||
if (iter == end || !isdigit(*iter))
|
||||
throw std::regex_error(std::regex_constants::error_badbrace); // eh
|
||||
n = *iter++ - '0';
|
||||
capture = true;
|
||||
}
|
||||
|
||||
accumulator += '(';
|
||||
if (capture) {
|
||||
/// ummm capture within a capture? backwards?
|
||||
capture_map[n] = ecma_index;
|
||||
} else {
|
||||
accumulator += "?:";
|
||||
// re-number all sub-captures.
|
||||
--num_captures;
|
||||
for (int &index : capture_map) {
|
||||
if (index >= ecma_index) --index;
|
||||
}
|
||||
}
|
||||
accumulator += scratch;
|
||||
accumulator += ')';
|
||||
return iter;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef __mpw_regex_h__
|
||||
#define __mpw_regex_h__
|
||||
|
||||
#include "environment.h"
|
||||
|
||||
#include <string>
|
||||
#include <regex>
|
||||
|
||||
class mpw_regex {
|
||||
|
||||
public:
|
||||
|
||||
mpw_regex(const std::string &s, bool slash);
|
||||
|
||||
mpw_regex(const mpw_regex &) = default;
|
||||
// mpw_regex(mpw_regex &&) = default;
|
||||
|
||||
~mpw_regex() = default;
|
||||
|
||||
|
||||
mpw_regex &operator=(const mpw_regex &) = default;
|
||||
// mpw_regex &operator=(mpw_regex &&) = default;
|
||||
|
||||
bool match(const std::string &, class Environment &);
|
||||
bool match(const std::string &);
|
||||
|
||||
static bool is_glob(const std::string &s);
|
||||
|
||||
private:
|
||||
typedef std::string::const_iterator iterator;
|
||||
|
||||
|
||||
void convert_re(const std::string &, bool slash);
|
||||
|
||||
iterator convert_re(iterator iter, iterator end, std::string &accumulator, unsigned char term);
|
||||
iterator convert_re_repeat(iterator iter, iterator end, std::string &accumulator);
|
||||
iterator convert_re_set(iterator iter, iterator end, std::string &accumulator);
|
||||
iterator convert_re_capture(iterator iter, iterator end, std::string &accumulator);
|
||||
|
||||
|
||||
std::regex re;
|
||||
std::string key;
|
||||
int capture_map[10] = {}; // map mpw capture number to ecma group
|
||||
int num_captures = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,123 +0,0 @@
|
|||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mpw-shell.h"
|
||||
|
||||
%%{
|
||||
machine classify;
|
||||
alphtype unsigned char;
|
||||
|
||||
ws = [ \t];
|
||||
|
||||
IF = /if/i;
|
||||
ELSE = /else/i;
|
||||
END = /end/i;
|
||||
EVALUATE = /evaluate/i;
|
||||
|
||||
|
||||
main := |*
|
||||
IF %eof{ return command_if; };
|
||||
IF ws => {return command_if; };
|
||||
|
||||
ELSE %eof{ return command_else;};
|
||||
ELSE ws => { return command_else; };
|
||||
|
||||
ELSE ws+ IF %eof{ return command_else_if; };
|
||||
ELSE ws+ IF ws => {return command_else_if; };
|
||||
|
||||
END %eof{ return command_end; };
|
||||
END ws => {return command_end; };
|
||||
|
||||
EVALUATE %eof{ return command_evaluate; };
|
||||
EVALUATE ws => {return command_evaluate; };
|
||||
|
||||
|
||||
*|;
|
||||
|
||||
}%%
|
||||
|
||||
|
||||
int classify(const std::string &line) {
|
||||
|
||||
%% write data;
|
||||
|
||||
int cs;
|
||||
int act;
|
||||
|
||||
const unsigned char *p = (const unsigned char *)line.data();
|
||||
const unsigned char *pe = (const unsigned char *)line.data() + line.size();
|
||||
const unsigned char *eof = pe;
|
||||
const unsigned char *te, *ts;
|
||||
|
||||
%%write init;
|
||||
|
||||
%%write exec;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generates a linked-list of commands. Why? Because it also checks
|
||||
* for shell-special syntax (currently if / else /end only) and
|
||||
* adds pointers to make executing them easier.
|
||||
*
|
||||
*/
|
||||
command_ptr build_command(const std::vector<std::string> &lines) {
|
||||
|
||||
std::vector<command_ptr> if_stack;
|
||||
|
||||
command_ptr head;
|
||||
command_ptr prev;
|
||||
|
||||
for (const auto &line : lines) {
|
||||
if (line.empty()) continue;
|
||||
|
||||
int type = classify(line);
|
||||
command_ptr c = std::make_shared<command>(type, line);
|
||||
|
||||
if (!head) head = c;
|
||||
if (!prev) prev = c;
|
||||
else {
|
||||
prev->next = c;
|
||||
prev = c;
|
||||
}
|
||||
|
||||
// if stack...
|
||||
switch (type) {
|
||||
case command_if:
|
||||
if_stack.push_back(c);
|
||||
break;
|
||||
|
||||
case command_else:
|
||||
case command_else_if:
|
||||
|
||||
if (if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Else must be within if ... end.");
|
||||
}
|
||||
|
||||
if_stack.back()->alternate = c;
|
||||
if_stack.back() = c;
|
||||
break;
|
||||
|
||||
case command_end:
|
||||
if (if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Extra end command.");
|
||||
}
|
||||
if_stack.back()->alternate = c;
|
||||
if_stack.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Unterminated if command.");
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
|
@ -1,301 +0,0 @@
|
|||
|
||||
#line 1 "mpw-shell-commands.rl"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
typedef std::shared_ptr<command> command_ptr;
|
||||
typedef std::weak_ptr<command> weak_command_ptr;
|
||||
|
||||
class command {
|
||||
enum type {
|
||||
command_if = 1,
|
||||
command_else,
|
||||
command_else_if,
|
||||
command_end
|
||||
} = 0;
|
||||
std::string line;
|
||||
command_ptr next;
|
||||
weak_command_ptr alternate; // if -> else -> end.
|
||||
};
|
||||
|
||||
|
||||
|
||||
#line 49 "mpw-shell-commands.rl"
|
||||
|
||||
|
||||
|
||||
int classify(const std::string &line) {
|
||||
|
||||
|
||||
#line 35 "mpw-shell-commands.c"
|
||||
static const int classify_start = 8;
|
||||
static const int classify_first_final = 8;
|
||||
static const int classify_error = 0;
|
||||
|
||||
static const int classify_en_main = 8;
|
||||
|
||||
|
||||
#line 55 "mpw-shell-commands.rl"
|
||||
|
||||
int cs;
|
||||
const unsigned char *p = (const unsigned char *)line.data();
|
||||
const unsigned char *pe = (const unsigned char *)line.data() + line.size();
|
||||
const unsigned char *eof = pe;
|
||||
const unsigned char *te, *ts;
|
||||
|
||||
|
||||
#line 52 "mpw-shell-commands.c"
|
||||
{
|
||||
cs = classify_start;
|
||||
ts = 0;
|
||||
te = 0;
|
||||
act = 0;
|
||||
}
|
||||
|
||||
#line 63 "mpw-shell-commands.rl"
|
||||
|
||||
|
||||
#line 63 "mpw-shell-commands.c"
|
||||
{
|
||||
if ( p == pe )
|
||||
goto _test_eof;
|
||||
switch ( cs )
|
||||
{
|
||||
tr5:
|
||||
#line 40 "mpw-shell-commands.rl"
|
||||
{{p = ((te))-1;}{ return command_else; }}
|
||||
goto st8;
|
||||
tr13:
|
||||
#line 39 "mpw-shell-commands.rl"
|
||||
{ return command_else;}
|
||||
#line 39 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr14:
|
||||
#line 39 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr16:
|
||||
#line 40 "mpw-shell-commands.rl"
|
||||
{te = p;p--;{ return command_else; }}
|
||||
goto st8;
|
||||
tr17:
|
||||
#line 42 "mpw-shell-commands.rl"
|
||||
{ return command_else_if; }
|
||||
#line 42 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr18:
|
||||
#line 42 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr19:
|
||||
#line 43 "mpw-shell-commands.rl"
|
||||
{te = p+1;{return command_else_if; }}
|
||||
goto st8;
|
||||
tr20:
|
||||
#line 45 "mpw-shell-commands.rl"
|
||||
{ return command_end; }
|
||||
#line 45 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr21:
|
||||
#line 45 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr22:
|
||||
#line 46 "mpw-shell-commands.rl"
|
||||
{te = p+1;{return command_end; }}
|
||||
goto st8;
|
||||
tr23:
|
||||
#line 36 "mpw-shell-commands.rl"
|
||||
{ return command_if; }
|
||||
#line 36 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr24:
|
||||
#line 36 "mpw-shell-commands.rl"
|
||||
{te = p;p--;}
|
||||
goto st8;
|
||||
tr25:
|
||||
#line 37 "mpw-shell-commands.rl"
|
||||
{te = p+1;{return command_if; }}
|
||||
goto st8;
|
||||
st8:
|
||||
#line 1 "NONE"
|
||||
{ts = 0;}
|
||||
if ( ++p == pe )
|
||||
goto _test_eof8;
|
||||
case 8:
|
||||
#line 1 "NONE"
|
||||
{ts = p;}
|
||||
#line 137 "mpw-shell-commands.c"
|
||||
switch( (*p) ) {
|
||||
case 69u: goto st1;
|
||||
case 73u: goto st7;
|
||||
case 101u: goto st1;
|
||||
case 105u: goto st7;
|
||||
}
|
||||
goto st0;
|
||||
st0:
|
||||
cs = 0;
|
||||
goto _out;
|
||||
st1:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof1;
|
||||
case 1:
|
||||
switch( (*p) ) {
|
||||
case 76u: goto st2;
|
||||
case 78u: goto st6;
|
||||
case 108u: goto st2;
|
||||
case 110u: goto st6;
|
||||
}
|
||||
goto st0;
|
||||
st2:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof2;
|
||||
case 2:
|
||||
switch( (*p) ) {
|
||||
case 83u: goto st3;
|
||||
case 115u: goto st3;
|
||||
}
|
||||
goto st0;
|
||||
st3:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof3;
|
||||
case 3:
|
||||
switch( (*p) ) {
|
||||
case 69u: goto st9;
|
||||
case 101u: goto st9;
|
||||
}
|
||||
goto st0;
|
||||
st9:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof9;
|
||||
case 9:
|
||||
switch( (*p) ) {
|
||||
case 9u: goto tr15;
|
||||
case 32u: goto tr15;
|
||||
}
|
||||
goto tr14;
|
||||
tr15:
|
||||
#line 1 "NONE"
|
||||
{te = p+1;}
|
||||
goto st10;
|
||||
st10:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof10;
|
||||
case 10:
|
||||
#line 194 "mpw-shell-commands.c"
|
||||
switch( (*p) ) {
|
||||
case 9u: goto st4;
|
||||
case 32u: goto st4;
|
||||
case 73u: goto st5;
|
||||
case 105u: goto st5;
|
||||
}
|
||||
goto tr16;
|
||||
st4:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof4;
|
||||
case 4:
|
||||
switch( (*p) ) {
|
||||
case 9u: goto st4;
|
||||
case 32u: goto st4;
|
||||
case 73u: goto st5;
|
||||
case 105u: goto st5;
|
||||
}
|
||||
goto tr5;
|
||||
st5:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof5;
|
||||
case 5:
|
||||
switch( (*p) ) {
|
||||
case 70u: goto st11;
|
||||
case 102u: goto st11;
|
||||
}
|
||||
goto tr5;
|
||||
st11:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof11;
|
||||
case 11:
|
||||
switch( (*p) ) {
|
||||
case 9u: goto tr19;
|
||||
case 32u: goto tr19;
|
||||
}
|
||||
goto tr18;
|
||||
st6:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof6;
|
||||
case 6:
|
||||
switch( (*p) ) {
|
||||
case 68u: goto st12;
|
||||
case 100u: goto st12;
|
||||
}
|
||||
goto st0;
|
||||
st12:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof12;
|
||||
case 12:
|
||||
switch( (*p) ) {
|
||||
case 9u: goto tr22;
|
||||
case 32u: goto tr22;
|
||||
}
|
||||
goto tr21;
|
||||
st7:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof7;
|
||||
case 7:
|
||||
switch( (*p) ) {
|
||||
case 70u: goto st13;
|
||||
case 102u: goto st13;
|
||||
}
|
||||
goto st0;
|
||||
st13:
|
||||
if ( ++p == pe )
|
||||
goto _test_eof13;
|
||||
case 13:
|
||||
switch( (*p) ) {
|
||||
case 9u: goto tr25;
|
||||
case 32u: goto tr25;
|
||||
}
|
||||
goto tr24;
|
||||
}
|
||||
_test_eof8: cs = 8; goto _test_eof;
|
||||
_test_eof1: cs = 1; goto _test_eof;
|
||||
_test_eof2: cs = 2; goto _test_eof;
|
||||
_test_eof3: cs = 3; goto _test_eof;
|
||||
_test_eof9: cs = 9; goto _test_eof;
|
||||
_test_eof10: cs = 10; goto _test_eof;
|
||||
_test_eof4: cs = 4; goto _test_eof;
|
||||
_test_eof5: cs = 5; goto _test_eof;
|
||||
_test_eof11: cs = 11; goto _test_eof;
|
||||
_test_eof6: cs = 6; goto _test_eof;
|
||||
_test_eof12: cs = 12; goto _test_eof;
|
||||
_test_eof7: cs = 7; goto _test_eof;
|
||||
_test_eof13: cs = 13; goto _test_eof;
|
||||
|
||||
_test_eof: {}
|
||||
if ( p == eof )
|
||||
{
|
||||
switch ( cs ) {
|
||||
case 9: goto tr13;
|
||||
case 10: goto tr16;
|
||||
case 4: goto tr5;
|
||||
case 5: goto tr5;
|
||||
case 11: goto tr17;
|
||||
case 12: goto tr20;
|
||||
case 13: goto tr23;
|
||||
}
|
||||
}
|
||||
|
||||
_out: {}
|
||||
}
|
||||
|
||||
#line 65 "mpw-shell-commands.rl"
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,297 +0,0 @@
|
|||
#include "mpw-shell.h"
|
||||
#include "fdset.h"
|
||||
#include "value.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <cassert>
|
||||
#include <cerrno>
|
||||
#include <cstring>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sysexits.h>
|
||||
|
||||
|
||||
/*
|
||||
* Relevant shell variables (not currently supported)
|
||||
*
|
||||
* Echo {Echo} # control the echoing of commands to diagnostic output
|
||||
* Echo {Exit} # control script termination based on {Status}
|
||||
* Echo {Test} # don't actually run anything.
|
||||
*
|
||||
*/
|
||||
|
||||
typedef std::vector<std::string> vs;
|
||||
|
||||
namespace {
|
||||
|
||||
std::string &lowercase(std::string &s) {
|
||||
std::transform(s.begin(), s.end(), s.begin(), [](char c){ return std::tolower(c); });
|
||||
return s;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, int (*)(const std::vector<std::string> &, const fdmask &)> builtins = {
|
||||
{"directory", builtin_directory},
|
||||
{"echo", builtin_echo},
|
||||
{"parameters", builtin_parameters},
|
||||
{"quote", builtin_quote},
|
||||
{"set", builtin_set},
|
||||
{"unset", builtin_unset},
|
||||
{"export", builtin_export},
|
||||
{"unexport", builtin_unexport},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
typedef std::pair<int, command_ptr> icp;
|
||||
|
||||
|
||||
|
||||
icp execute_all(command_ptr cmd);
|
||||
|
||||
// returns status and pointer to the next command to execute.
|
||||
icp execute_if(command_ptr cmd) {
|
||||
|
||||
assert(cmd && cmd->type == command_if);
|
||||
// evaluate condition...
|
||||
// skip to else or end.
|
||||
|
||||
command_ptr head(cmd);
|
||||
|
||||
int status = 0;
|
||||
|
||||
// find the end pointer.
|
||||
// if ... end > file.text
|
||||
// redirects all output within the block.
|
||||
|
||||
command_ptr end = head;
|
||||
while (end && end->type != command_end) {
|
||||
end = end->alternate.lock();
|
||||
}
|
||||
|
||||
|
||||
fdmask fds; // todo -- inherit from block, can be parsed from end line.
|
||||
|
||||
|
||||
|
||||
fprintf(stdout, " %s ... %s\n", cmd->string.c_str(), end ? end->string.c_str() : "");
|
||||
|
||||
// todo -- indent levels.
|
||||
while(cmd && cmd->type != command_end) {
|
||||
|
||||
int32_t e;
|
||||
|
||||
std::string s = cmd->string;
|
||||
s = expand_vars(s, Environment);
|
||||
|
||||
auto tokens = tokenize(s, true);
|
||||
|
||||
std::reverse(tokens.begin(), tokens.end());
|
||||
e = 0;
|
||||
status = 0;
|
||||
switch(cmd->type) {
|
||||
case command_else_if:
|
||||
tokens.pop_back();
|
||||
case command_if:
|
||||
tokens.pop_back();
|
||||
try {
|
||||
e = evaluate_expression("If", std::move(tokens));
|
||||
} catch (std::exception &ex) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
status = -5;
|
||||
}
|
||||
break;
|
||||
case command_else:
|
||||
e = 1;
|
||||
if (tokens.size() > 1) {
|
||||
fprintf(stderr, "### Else - Missing if keyword.\n");
|
||||
fprintf(stderr, "# Usage - Else [if expression...]\n");
|
||||
e = 0;
|
||||
status = -3;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (e) {
|
||||
command_ptr tmp;
|
||||
std::tie(status, tmp) = execute_all(cmd->next);
|
||||
break;
|
||||
}
|
||||
// skip to next condition.
|
||||
cmd = cmd->alternate.lock();
|
||||
}
|
||||
|
||||
// todo -- print but don't execute remaining alternates
|
||||
|
||||
// print the end tokens... [ doesn't include other tokens.]
|
||||
fprintf(stdout, " End\n");
|
||||
|
||||
return std::make_pair(status, end); // return end token -- will advance later.
|
||||
|
||||
}
|
||||
|
||||
int execute_evaluate(command_ptr cmd) {
|
||||
|
||||
fdmask fds; // todo -- inherit from block.
|
||||
|
||||
std::string s = cmd->string;
|
||||
s = expand_vars(s, Environment);
|
||||
|
||||
|
||||
fprintf(stdout, " %s\n", s.c_str());
|
||||
|
||||
auto tokens = tokenize(s, true);
|
||||
|
||||
return builtin_evaluate(std::move(tokens), fds);
|
||||
}
|
||||
|
||||
|
||||
int execute_external(const std::vector<std::string> &argv, const fdmask &fds) {
|
||||
|
||||
std::vector<char *> cargv;
|
||||
cargv.reserve(argv.size() + 3);
|
||||
|
||||
int status;
|
||||
int pid;
|
||||
|
||||
cargv.push_back((char *)"mpw");
|
||||
//cargv.push_back((char *)"--shell");
|
||||
|
||||
unsigned offset = cargv.size();
|
||||
|
||||
|
||||
std::transform(argv.begin(), argv.end(), std::back_inserter(cargv),
|
||||
[](const std::string &s) { return strdup(s.c_str()); }
|
||||
);
|
||||
|
||||
cargv.push_back(nullptr);
|
||||
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
perror("fork: ");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
|
||||
|
||||
if (pid == 0) {
|
||||
|
||||
// also export environment...
|
||||
|
||||
// handle any indirection...
|
||||
fds.dup();
|
||||
|
||||
execvp(cargv.front(), cargv.data());
|
||||
perror("execvp: ");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
|
||||
std::for_each(cargv.begin()+offset, cargv.end(), free);
|
||||
|
||||
for(;;) {
|
||||
int status;
|
||||
pid_t ok;
|
||||
ok = waitpid(pid, &status, 0);
|
||||
if (ok < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
perror("waitpid:");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
|
||||
if (WIFEXITED(status)) return WEXITSTATUS(status);
|
||||
if (WIFSIGNALED(status)) return -1;
|
||||
fprintf(stderr, "waitpid - unexpected result\n");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int execute_one(command_ptr cmd) {
|
||||
|
||||
if (!cmd) return 0;
|
||||
|
||||
assert(cmd && cmd->type == 0);
|
||||
|
||||
|
||||
// todo -- before variable expansion,
|
||||
// expand |, ||, && control structures.
|
||||
// (possibly when classifing.)
|
||||
|
||||
std::string s = cmd->string;
|
||||
s = expand_vars(s, Environment);
|
||||
|
||||
|
||||
fprintf(stdout, " %s\n", s.c_str());
|
||||
|
||||
auto tokens = tokenize(s);
|
||||
|
||||
process p;
|
||||
parse_tokens(std::move(tokens), p);
|
||||
|
||||
|
||||
|
||||
fdmask fds = p.fds.to_mask();
|
||||
|
||||
std::string name = p.arguments.front();
|
||||
lowercase(name);
|
||||
|
||||
auto iter = builtins.find(name);
|
||||
if (iter != builtins.end()) {
|
||||
int status = iter->second(p.arguments, fds);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
return execute_external(p.arguments, fds);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
icp execute_all(command_ptr cmd) {
|
||||
if (!cmd) return std::make_pair(0, cmd);
|
||||
|
||||
int status;
|
||||
|
||||
while(cmd) {
|
||||
|
||||
unsigned type = cmd->type;
|
||||
switch(type)
|
||||
{
|
||||
case command_evaluate:
|
||||
status = execute_evaluate(cmd);
|
||||
break;
|
||||
|
||||
default:
|
||||
status = execute_one(cmd);
|
||||
break;
|
||||
|
||||
case command_if:
|
||||
std::tie(status, cmd) = execute_if(cmd);
|
||||
break;
|
||||
|
||||
case command_end:
|
||||
case command_else:
|
||||
case command_else_if:
|
||||
return std::make_pair(status, cmd);
|
||||
}
|
||||
|
||||
Environment["status"] = std::to_string(status);
|
||||
|
||||
if (status != 0) {
|
||||
// only if Environment["Exit"] ?
|
||||
throw std::runtime_error("### MPW Shell - Execution of input terminated.");
|
||||
}
|
||||
cmd = cmd->next;
|
||||
}
|
||||
|
||||
return std::make_pair(status, cmd);
|
||||
}
|
||||
|
||||
int execute(command_ptr cmd) {
|
||||
int status;
|
||||
std::tie(status, cmd) = execute_all(cmd);
|
||||
return status;
|
||||
}
|
|
@ -3,132 +3,201 @@
|
|||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <algorithm>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "mpw-shell.h"
|
||||
|
||||
#include "error.h"
|
||||
|
||||
%%{
|
||||
machine line_parser;
|
||||
|
||||
machine expand;
|
||||
alphtype unsigned char;
|
||||
|
||||
escape = 0xb6;
|
||||
ws = [ \t];
|
||||
nl = '\n';
|
||||
|
||||
action push_back {
|
||||
line.push_back(fc);
|
||||
}
|
||||
action push_back_escape {
|
||||
line.push_back(escape);
|
||||
line.push_back(fc);
|
||||
}
|
||||
|
||||
|
||||
sstring =
|
||||
['] $push_back
|
||||
( (any-nl-[']) $push_back )*
|
||||
['] $push_back
|
||||
$err{
|
||||
fprintf(stderr, "### MPW Shell - 's must occur in pairs.\n");
|
||||
action push { scratch.push_back(fc); }
|
||||
action vinit { /* vinit */ ev.clear(); xcs = fcurs; fgoto vstring_state; }
|
||||
action vpush { /* vpush */ ev.push_back(fc); }
|
||||
action vfinish0 { /* vfinish0 */ fnext *xcs; }
|
||||
action vfinish1 {
|
||||
/* vfinish1 */
|
||||
auto iter = env.find(ev);
|
||||
if (iter != env.end()) {
|
||||
const std::string &s = iter->second;
|
||||
scratch.append(s);
|
||||
}
|
||||
;
|
||||
|
||||
# same quoting logic as ' string
|
||||
vstring =
|
||||
'{'
|
||||
( (any-nl-'}') ${var.push_back(fc); } )*
|
||||
'}'
|
||||
${
|
||||
if (!var.empty()) {
|
||||
|
||||
// flag to pass through vs "" ?
|
||||
auto iter = env.find(var);
|
||||
if (iter == env.end()) {
|
||||
if (env.passthrough()) {
|
||||
line.push_back('{');
|
||||
line.append(var);
|
||||
line.push_back('}');
|
||||
}
|
||||
}
|
||||
else {
|
||||
line.append((std::string)iter->second);
|
||||
}
|
||||
fgoto *xcs;
|
||||
}
|
||||
action vfinish2 {
|
||||
/* vfinish2 */
|
||||
auto iter = env.find(ev);
|
||||
if (iter != env.end()) {
|
||||
// quote special chars...
|
||||
const std::string &s = iter->second;
|
||||
for (auto c : s) {
|
||||
if (c == '\'' || c == '"' ) scratch.push_back(escape);
|
||||
scratch.push_back(c);
|
||||
}
|
||||
var.clear();
|
||||
}
|
||||
$err{
|
||||
fprintf(stderr, "### MPW Shell - {s must occur in pairs.\n");
|
||||
fgoto *xcs;
|
||||
}
|
||||
|
||||
action einit { /* einit */ ev.clear(); xcs = fcurs; fgoto estring_state; }
|
||||
action epush { /* epush */ ev.push_back(fc); }
|
||||
action efinish1 {
|
||||
/* efinish1 */
|
||||
|
||||
/*
|
||||
throw std::runtime_error("MPW Shell - `...` not yet supported.");
|
||||
*/
|
||||
|
||||
std::string s = subshell(ev, env, fds);
|
||||
scratch.append(s);
|
||||
|
||||
fgoto *xcs;
|
||||
}
|
||||
action efinish2 {
|
||||
/* efinish2 */
|
||||
|
||||
std::string s = subshell(ev, env, fds);
|
||||
for (auto c : s) {
|
||||
if (c == '\'' || c == '"' ) scratch.push_back(escape);
|
||||
scratch.push_back(c);
|
||||
}
|
||||
;
|
||||
|
||||
fgoto *xcs;
|
||||
}
|
||||
|
||||
action vstring_error{
|
||||
throw vstring_error();
|
||||
}
|
||||
|
||||
action estring_error{
|
||||
throw estring_error();
|
||||
}
|
||||
|
||||
|
||||
# double-quoted string.
|
||||
# escape \n is ignored. others do nothing.
|
||||
dstring =
|
||||
["] $push_back
|
||||
(
|
||||
escape (
|
||||
nl ${ /* esc newline */ }
|
||||
|
|
||||
(any-nl) $push_back_escape
|
||||
)
|
||||
|
|
||||
vstring
|
||||
|
|
||||
(any-escape-nl-["{]) $push_back
|
||||
)* ["] $push_back
|
||||
$err{
|
||||
fprintf(stderr, "### MPW Shell - \"s must occur in pairs.\n");
|
||||
}
|
||||
;
|
||||
escape = 0xb6;
|
||||
char = any - escape - ['"{`/\\];
|
||||
escape_seq = escape any;
|
||||
|
||||
schar = [^'];
|
||||
sstring = ['] schar** ['];
|
||||
|
||||
fchar = [^/];
|
||||
fstring = [/] fchar** [/];
|
||||
|
||||
bchar = [^\\];
|
||||
bstring = [\\] bchar** [\\];
|
||||
|
||||
|
||||
main :=
|
||||
(
|
||||
sstring
|
||||
|
|
||||
dstring
|
||||
|
|
||||
vstring
|
||||
|
|
||||
escape any $push_back_escape
|
||||
|
|
||||
(any-['"{]) $push_back
|
||||
)*
|
||||
;
|
||||
vchar = [^}] $vpush;
|
||||
vchar1 = [^{}] $vpush;
|
||||
|
||||
vstring0 = '}' @vfinish0;
|
||||
vstring1 = vchar1 vchar** '}' @vfinish1;
|
||||
vstring2 = '{' vchar** '}}' @vfinish2;
|
||||
|
||||
vstring_state := (vstring0 | vstring1 | vstring2) $err(vstring_error);
|
||||
vstring = '{' $vinit;
|
||||
|
||||
echar = (escape_seq | any - escape - [`]) $epush;
|
||||
|
||||
estring1 = echar+ '`' @efinish1;
|
||||
estring2 = '`' echar* '``' @efinish2;
|
||||
|
||||
estring_state := (estring1 | estring2) $err(estring_error);
|
||||
estring = '`' $einit;
|
||||
|
||||
|
||||
|
||||
dchar = escape_seq | estring | vstring | (any - escape - [`{"]);
|
||||
dstring = ["] dchar** ["] ;
|
||||
|
||||
|
||||
main := (
|
||||
escape_seq $push
|
||||
| sstring $push
|
||||
| fstring $push
|
||||
| bstring $push
|
||||
| dstring $push
|
||||
| vstring
|
||||
| estring
|
||||
| char $push
|
||||
)**;
|
||||
|
||||
|
||||
}%%
|
||||
|
||||
namespace {
|
||||
%% write data;
|
||||
|
||||
|
||||
%% write data;
|
||||
|
||||
|
||||
/*
|
||||
* has to be done separately since you can do dumb stuff like:
|
||||
* set q '"' ; echo {q} dsfsdf"
|
||||
*/
|
||||
|
||||
std::string expand_vars(const std::string &s, const Environment &env) {
|
||||
std::string subshell(const std::string &s, Environment &env, const fdmask &fds) {
|
||||
|
||||
if (s.find('{') == s.npos) return s;
|
||||
std::string var;
|
||||
std::string line;
|
||||
|
||||
int cs;
|
||||
const unsigned char *p = (const unsigned char *)s.data();
|
||||
const unsigned char *pe = (const unsigned char *)s.data() + s.size();
|
||||
const unsigned char *eof = pe;
|
||||
char temp[32] = "/tmp/mpw-shell-XXXXXXXX";
|
||||
|
||||
%%write init;
|
||||
int fd = mkstemp(temp);
|
||||
unlink(temp);
|
||||
|
||||
%%write exec;
|
||||
fdset new_fds;
|
||||
new_fds.set(1, fd);
|
||||
|
||||
return line;
|
||||
int rv = 0;
|
||||
env.indent_and([&](){
|
||||
|
||||
rv = read_string(env, s, new_fds | fds);
|
||||
|
||||
});
|
||||
|
||||
std::string tmp;
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
for(;;) {
|
||||
uint8_t buffer[1024];
|
||||
ssize_t len = read(fd, buffer, sizeof(buffer));
|
||||
if (len == 0) break;
|
||||
if (len < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
throw std::system_error(errno, std::system_category(), "read");
|
||||
}
|
||||
tmp.append(buffer, buffer + len);
|
||||
}
|
||||
|
||||
|
||||
/* if present, a trailing carriage return is stripped */
|
||||
|
||||
if (!tmp.empty()) {
|
||||
if (tmp.back() == '\r' || tmp.back() == '\n') tmp.pop_back();
|
||||
}
|
||||
|
||||
std::transform(tmp.begin(), tmp.end(), tmp.begin(), [](uint8_t x){
|
||||
if (x == '\r' || x == '\n') x = ' ';
|
||||
return x;
|
||||
});
|
||||
return tmp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::string expand_vars(const std::string &s, Environment &env, const fdmask &fds) {
|
||||
if (s.find_first_of("{`", 0, 2) == s.npos) return s;
|
||||
|
||||
int cs;
|
||||
int xcs;
|
||||
|
||||
const unsigned char *p = (const unsigned char *)s.data();
|
||||
const unsigned char *pe = p + s.size();
|
||||
const unsigned char *eof = pe;
|
||||
|
||||
std::string scratch;
|
||||
std::string ev;
|
||||
|
||||
scratch.reserve(s.size());
|
||||
%% write init;
|
||||
%% write exec;
|
||||
|
||||
return scratch;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "mpw-shell.h"
|
||||
#include "fdset.h"
|
||||
#include "value.h"
|
||||
#include "error.h"
|
||||
#include "mpw-regex.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
@ -13,7 +15,10 @@
|
|||
|
||||
|
||||
|
||||
|
||||
namespace ToolBox {
|
||||
std::string MacToUnix(const std::string path);
|
||||
std::string UnixToMac(const std::string path);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T pop(std::vector<T> &v) {
|
||||
|
@ -30,19 +35,21 @@ T pop(std::vector<T> &v) {
|
|||
|
||||
void open_error(const std::string &name) {
|
||||
|
||||
std::string error = "### MPW Shell - Unable to open ";
|
||||
std::string error = "MPW Shell - Unable to open ";
|
||||
error.push_back('"');
|
||||
error.append(name);
|
||||
error.push_back('"');
|
||||
error.push_back('.');
|
||||
throw std::runtime_error(error);
|
||||
throw mpw_error(-4, error);
|
||||
}
|
||||
|
||||
int open(const std::string &name, int flags) {
|
||||
|
||||
// dup2 does not copy the O_CLOEXEC flag so it's safe to use.
|
||||
|
||||
int fd = ::open(name.c_str(), flags | O_CLOEXEC, 0666);
|
||||
std::string uname = ToolBox::MacToUnix(name);
|
||||
|
||||
int fd = ::open(uname.c_str(), flags | O_CLOEXEC, 0666);
|
||||
if (fd < 0) {
|
||||
open_error(name);
|
||||
return -1;
|
||||
|
@ -99,7 +106,7 @@ void parse_tokens(std::vector<token> &&tokens, process &p) {
|
|||
fd_bits = (1 << 1) + (1 << 2);
|
||||
goto redir;
|
||||
|
||||
// ≥, ≥≥ -- redirect stdout.
|
||||
// ≥, ≥≥ -- redirect stderr.
|
||||
case 0xb3: // ≥
|
||||
flags = O_WRONLY | O_CREAT | O_TRUNC;
|
||||
fd_bits = 1 << 2;
|
||||
|
@ -114,7 +121,7 @@ void parse_tokens(std::vector<token> &&tokens, process &p) {
|
|||
|
||||
{
|
||||
if (tokens.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Missing file name.");
|
||||
throw mpw_error(-4, "MPW Shell - Missing file name.");
|
||||
}
|
||||
token name = pop(tokens);
|
||||
int fd = open(name.string, flags);
|
||||
|
@ -166,8 +173,8 @@ class expression_parser {
|
|||
|
||||
public:
|
||||
|
||||
expression_parser(const std::string &n, std::vector<token> &&t) :
|
||||
name(n), tokens(std::move(t))
|
||||
expression_parser(Environment &e, const std::string &n, std::vector<token> &&t) :
|
||||
environment(e), name(n), tokens(std::move(t))
|
||||
{}
|
||||
|
||||
expression_parser(const expression_parser &) = delete;
|
||||
|
@ -188,6 +195,7 @@ private:
|
|||
|
||||
|
||||
value eval(int op, value &lhs, value &rhs);
|
||||
value eval_regex(value &lhs, value &rhs);
|
||||
|
||||
[[noreturn]] void expect_binary_operator();
|
||||
[[noreturn]] void end_of_expression();
|
||||
|
@ -201,6 +209,7 @@ private:
|
|||
if (!tokens.empty()) tokens.pop_back();
|
||||
}
|
||||
|
||||
Environment &environment;
|
||||
const std::string &name;
|
||||
std::vector<token> tokens;
|
||||
};
|
||||
|
@ -219,23 +228,23 @@ void expression_parser::expect_binary_operator() {
|
|||
token t = next();
|
||||
|
||||
std::string error;
|
||||
error = "### " + name;
|
||||
error = name;
|
||||
error += " - Expected a binary operator when \"";
|
||||
error += t.string;
|
||||
error += "\" was encountered.";
|
||||
throw std::runtime_error(error);
|
||||
throw mpw_error(-5, error);
|
||||
}
|
||||
|
||||
void expression_parser::end_of_expression() {
|
||||
std::string error;
|
||||
error = "### " + name + " - Unexpected end of expression.";
|
||||
throw std::runtime_error(error);
|
||||
error = name + " - Unexpected end of expression.";
|
||||
throw mpw_error(-5, error);
|
||||
}
|
||||
|
||||
void expression_parser::divide_by_zero() {
|
||||
std::string error;
|
||||
error = "### " + name + " - Attempt to divide by zero.";
|
||||
throw std::runtime_error(error);
|
||||
error = name + " - Attempt to divide by zero.";
|
||||
throw mpw_error(-5, error);
|
||||
}
|
||||
|
||||
|
||||
|
@ -313,12 +322,14 @@ int expression_parser::precedence(int op) {
|
|||
case '<=':
|
||||
case '>':
|
||||
case '>=':
|
||||
case 0xb2:
|
||||
case 0xb3:
|
||||
return 6;
|
||||
|
||||
case '==':
|
||||
case '!=':
|
||||
case token::equivalent:
|
||||
case token::not_equivalent:
|
||||
case '=~':
|
||||
case '!~':
|
||||
return 7;
|
||||
case '&':
|
||||
return 8;
|
||||
|
@ -335,6 +346,25 @@ int expression_parser::precedence(int op) {
|
|||
//throw std::runtime_error("unimplemented op";);
|
||||
}
|
||||
|
||||
value expression_parser::eval_regex(value &lhs, value &rhs) {
|
||||
try {
|
||||
mpw_regex re(rhs.string, true);
|
||||
// todo -- need environment to store matches.
|
||||
bool ok = re.match(lhs.string, environment);
|
||||
return ok ? 1 : 0;
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
std::string error;
|
||||
error = name;
|
||||
if (rhs.string.empty() || rhs.string.front() != '/')
|
||||
error += " - Missing /s around regular expression: ";
|
||||
else
|
||||
error += " - Invalid regular expression encountered: ";
|
||||
error += rhs.string;
|
||||
throw mpw_error(-5, error);
|
||||
}
|
||||
}
|
||||
|
||||
value expression_parser::eval(int op, value &lhs, value &rhs) {
|
||||
switch (op) {
|
||||
|
||||
|
@ -360,16 +390,18 @@ value expression_parser::eval(int op, value &lhs, value &rhs) {
|
|||
return lhs.to_number() < rhs.to_number();
|
||||
|
||||
case '<=':
|
||||
case 0xb2:
|
||||
return lhs.to_number() <= rhs.to_number();
|
||||
|
||||
case '>=':
|
||||
case 0xb3:
|
||||
return lhs.to_number() >= rhs.to_number();
|
||||
|
||||
case '>>':
|
||||
return lhs.to_number() >> rhs.to_number();
|
||||
|
||||
case '<<':
|
||||
return lhs.to_number() >> rhs.to_number();
|
||||
return lhs.to_number() << rhs.to_number();
|
||||
|
||||
// logical || . NaN ok
|
||||
case '||':
|
||||
|
@ -401,6 +433,12 @@ value expression_parser::eval(int op, value &lhs, value &rhs) {
|
|||
return lhs.string != rhs.string;
|
||||
|
||||
|
||||
case '=~':
|
||||
return eval_regex(lhs, rhs);
|
||||
|
||||
case '!~':
|
||||
return !eval_regex(lhs, rhs).number;
|
||||
|
||||
}
|
||||
// todo...
|
||||
throw std::runtime_error("unimplemented op");
|
||||
|
@ -457,14 +495,14 @@ int32_t expression_parser::evaluate() {
|
|||
value v = binary();
|
||||
if (!tokens.empty()) {
|
||||
if (tokens.back().type == ')')
|
||||
throw std::runtime_error("### MPW Shell - Extra ) command.");
|
||||
throw mpw_error(-3, "MPW Shell - Extra ) command.");
|
||||
throw std::runtime_error("evaluation stack error."); // ?? should be caught above.
|
||||
}
|
||||
return v.to_number(1);
|
||||
}
|
||||
|
||||
int32_t evaluate_expression(const std::string &name, std::vector<token> &&tokens) {
|
||||
int32_t evaluate_expression(Environment &env, const std::string &name, std::vector<token> &&tokens) {
|
||||
|
||||
expression_parser p(name, std::move(tokens));
|
||||
expression_parser p(env, name, std::move(tokens));
|
||||
return p.evaluate();
|
||||
}
|
||||
|
|
|
@ -7,31 +7,19 @@ bool must_quote(const std::string &s){
|
|||
alphtype unsigned char;
|
||||
|
||||
quotable = (
|
||||
[ \t\r\n]
|
||||
|
|
||||
0x00
|
||||
|
|
||||
[0x80-0xff]
|
||||
|
|
||||
[+#;&|()'"/\\{}`?*<>]
|
||||
|
|
||||
'-'
|
||||
|
|
||||
'['
|
||||
|
|
||||
']'
|
||||
[ \t\r\n]
|
||||
| 0x00
|
||||
| [0x80-0xff]
|
||||
| [+#;&|()'"/\\{}`?*<>]
|
||||
| '-'
|
||||
| '['
|
||||
| ']'
|
||||
);
|
||||
|
||||
#simpler just to say what's ok.
|
||||
normal = [A-Za-z0-9_.:];
|
||||
|
||||
main :=
|
||||
(
|
||||
normal
|
||||
|
|
||||
(any-normal) ${return true;}
|
||||
)*
|
||||
;
|
||||
main := normal*;
|
||||
}%%
|
||||
|
||||
%%write data;
|
||||
|
@ -43,7 +31,8 @@ bool must_quote(const std::string &s){
|
|||
|
||||
%%write init;
|
||||
%%write exec;
|
||||
return false;
|
||||
|
||||
return cs == must_quote_error;
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
|
|
@ -1,379 +0,0 @@
|
|||
#include "mpw-shell.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <cerrno>
|
||||
#include <algorithm>
|
||||
|
||||
%%{
|
||||
machine classify;
|
||||
alphtype unsigned char;
|
||||
|
||||
ws = [ \t];
|
||||
|
||||
IF = /if/i;
|
||||
ELSE = /else/i;
|
||||
END = /end/i;
|
||||
BEGIN = /begin/i;
|
||||
EVALUATE = /evaluate/i;
|
||||
|
||||
|
||||
main := |*
|
||||
IF %eof{ return command_if; };
|
||||
IF ws => {return command_if; };
|
||||
|
||||
ELSE %eof{ return command_else;};
|
||||
ELSE ws => { return command_else; };
|
||||
|
||||
ELSE ws+ IF %eof{ return command_else_if; };
|
||||
ELSE ws+ IF ws => {return command_else_if; };
|
||||
|
||||
END %eof{ return command_end; };
|
||||
END ws => {return command_end; };
|
||||
|
||||
BEGIN %eof{ return command_begin; };
|
||||
BEGIN ws => {return command_begin; };
|
||||
|
||||
EVALUATE %eof{ return command_evaluate; };
|
||||
EVALUATE ws => {return command_evaluate; };
|
||||
|
||||
*|;
|
||||
|
||||
}%%
|
||||
|
||||
|
||||
static int classify(const std::string &line) {
|
||||
|
||||
%%machine classify;
|
||||
%% write data;
|
||||
|
||||
int cs;
|
||||
int act;
|
||||
|
||||
const unsigned char *p = (const unsigned char *)line.data();
|
||||
const unsigned char *pe = (const unsigned char *)line.data() + line.size();
|
||||
const unsigned char *eof = pe;
|
||||
const unsigned char *te, *ts;
|
||||
|
||||
%%write init;
|
||||
|
||||
%%write exec;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* this state machine splits input into lines.
|
||||
* only new-line escapes are removed.
|
||||
* "", '', and {} are also matched.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* from experimentation, mpw splits on ; after variable expansion;
|
||||
* this splits before. something stupid like:
|
||||
* set q '"'; echo {q} ; "
|
||||
* will not be handled correctly. oh well.
|
||||
* (should probably just drop that and we can then combine tokenizing w/
|
||||
* variable expansion)
|
||||
*/
|
||||
%%{
|
||||
machine line_parser;
|
||||
alphtype unsigned char;
|
||||
|
||||
|
||||
escape = 0xb6;
|
||||
ws = [ \t];
|
||||
nl = ('\n' | '\r');
|
||||
|
||||
action add_line {
|
||||
/* strip trailing ws */
|
||||
while (!scratch.empty() && isspace(scratch.back())) scratch.pop_back();
|
||||
if (!scratch.empty()) {
|
||||
command_ptr cmd = std::make_shared<command>(std::move(scratch));
|
||||
cmd->line = start_line;
|
||||
start_line = line;
|
||||
program.emplace_back(std::move(cmd));
|
||||
}
|
||||
scratch.clear();
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action push_back {
|
||||
scratch.push_back(fc);
|
||||
}
|
||||
|
||||
action push_back_escape {
|
||||
scratch.push_back(escape);
|
||||
scratch.push_back(fc);
|
||||
}
|
||||
|
||||
comment = '#' (any-nl)*;
|
||||
|
||||
escape_seq =
|
||||
escape
|
||||
(
|
||||
nl ${ /* esc newline */ line++; }
|
||||
|
|
||||
(any-nl) $push_back_escape
|
||||
)
|
||||
;
|
||||
|
||||
|
||||
# single-quoted string. only escape \n is special.
|
||||
# handling is so stupid I'm not going to support it.
|
||||
|
||||
sstring =
|
||||
['] $push_back
|
||||
( (any-nl-[']) $push_back )*
|
||||
['] $push_back
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - 's must occur in pairs.");
|
||||
}
|
||||
;
|
||||
|
||||
# same quoting logic as ' string
|
||||
vstring =
|
||||
'{' $push_back
|
||||
( (any-nl-'}') $push_back )*
|
||||
'}' $push_back
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - {s must occur in pairs.");
|
||||
}
|
||||
;
|
||||
|
||||
|
||||
# double-quoted string.
|
||||
# escape \n is ignored. others do nothing.
|
||||
dstring =
|
||||
["] $push_back
|
||||
(
|
||||
escape_seq
|
||||
|
|
||||
vstring
|
||||
|
|
||||
(any-escape-nl-["{]) $push_back
|
||||
)* ["] $push_back
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - \"s must occur in pairs.");
|
||||
}
|
||||
;
|
||||
|
||||
# this is a mess ...
|
||||
coalesce_ws =
|
||||
ws
|
||||
(
|
||||
ws
|
||||
|
|
||||
escape nl ${ line++; }
|
||||
)*
|
||||
<:
|
||||
any ${ scratch.push_back(' '); fhold; }
|
||||
;
|
||||
|
||||
line :=
|
||||
(
|
||||
sstring
|
||||
|
|
||||
dstring
|
||||
|
|
||||
vstring
|
||||
|
|
||||
[;] $add_line
|
||||
|
|
||||
escape_seq
|
||||
|
|
||||
coalesce_ws
|
||||
|
|
||||
(any-escape-nl-ws-[;#'"{]) $push_back
|
||||
)*
|
||||
comment?
|
||||
nl ${ line++; } $add_line
|
||||
;
|
||||
|
||||
main :=
|
||||
# strip leading whitespace.
|
||||
ws*
|
||||
<: # left guard -- higher priority to ws.
|
||||
any ${ fhold; fgoto line; }
|
||||
;
|
||||
|
||||
}%%
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class line_parser {
|
||||
|
||||
public:
|
||||
|
||||
void process(const void *data, size_t size) {
|
||||
process((const unsigned char *)data, size, false);
|
||||
}
|
||||
|
||||
command_ptr finish() {
|
||||
process((const unsigned char *)"\n\n", 2, true);
|
||||
return build_program();
|
||||
}
|
||||
|
||||
line_parser();
|
||||
|
||||
private:
|
||||
|
||||
%% machine line_parser;
|
||||
%% write data;
|
||||
|
||||
|
||||
std::vector<command_ptr> program;
|
||||
std::string scratch;
|
||||
int line = 1;
|
||||
int cs;
|
||||
|
||||
command_ptr build_program();
|
||||
void process(const unsigned char *data, size_t size, bool final);
|
||||
};
|
||||
|
||||
line_parser::line_parser() {
|
||||
%% machine line_parser;
|
||||
%% write init;
|
||||
}
|
||||
|
||||
void line_parser::process(const unsigned char *data, size_t size, bool final) {
|
||||
|
||||
int start_line;
|
||||
|
||||
const unsigned char *p = data;
|
||||
const unsigned char *pe = data + size;
|
||||
const unsigned char *eof = nullptr;
|
||||
|
||||
if (final)
|
||||
eof = pe;
|
||||
|
||||
start_line = line;
|
||||
%% machine line_parser;
|
||||
%% write exec;
|
||||
|
||||
if (cs == line_parser_error) {
|
||||
throw std::runtime_error("MPW Shell - Lexer error.");
|
||||
|
||||
}
|
||||
|
||||
if (cs != line_parser_start && final) {
|
||||
// will this happen?
|
||||
throw std::runtime_error("MPW Shell - Lexer error.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Generates a linked-list of commands. Why? Because it also checks
|
||||
* for shell-special syntax (currently if / else /end only) and
|
||||
* adds pointers to make executing them easier.
|
||||
*
|
||||
*/
|
||||
|
||||
// todo -- use recursive descent parser, support begin/end, (), ||, &&, etc.
|
||||
command_ptr line_parser::build_program() {
|
||||
|
||||
|
||||
std::vector<command_ptr> if_stack;
|
||||
|
||||
command_ptr head;
|
||||
command_ptr ptr;
|
||||
|
||||
if (program.empty()) return head;
|
||||
|
||||
std::reverse(program.begin(), program.end());
|
||||
|
||||
head = program.back();
|
||||
|
||||
while (!program.empty()) {
|
||||
|
||||
if (ptr) ptr->next = program.back();
|
||||
|
||||
ptr = std::move(program.back());
|
||||
program.pop_back();
|
||||
|
||||
int type = ptr->type = classify(ptr->string);
|
||||
|
||||
ptr->level = if_stack.size();
|
||||
|
||||
// if stack...
|
||||
switch (type) {
|
||||
default:
|
||||
break;
|
||||
|
||||
case command_if:
|
||||
if_stack.push_back(ptr);
|
||||
break;
|
||||
|
||||
case command_else:
|
||||
case command_else_if:
|
||||
|
||||
if (if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Else must be within if ... end.");
|
||||
}
|
||||
|
||||
ptr->level--;
|
||||
if_stack.back()->alternate = ptr;
|
||||
if_stack.back() = ptr;
|
||||
break;
|
||||
|
||||
case command_end:
|
||||
if (if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Extra end command.");
|
||||
}
|
||||
|
||||
ptr->level--;
|
||||
if_stack.back()->alternate = ptr;
|
||||
if_stack.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!if_stack.empty()) {
|
||||
throw std::runtime_error("### MPW Shell - Unterminated if command.");
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
|
||||
command_ptr read_fd(int fd) {
|
||||
unsigned char buffer[1024];
|
||||
|
||||
line_parser p;
|
||||
|
||||
for(;;) {
|
||||
ssize_t s = read(fd, buffer, sizeof(buffer));
|
||||
if (s < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
throw std::runtime_error("MPW Shell - Read error.");
|
||||
}
|
||||
if (s == 0) break;
|
||||
p.process(buffer, s);
|
||||
}
|
||||
return p.finish();
|
||||
}
|
||||
|
||||
command_ptr read_file(const std::string &name) {
|
||||
int fd;
|
||||
fd = open(name.c_str(), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
throw std::runtime_error("MPW Shell - Unable to open file " + name + ".");
|
||||
}
|
||||
|
||||
auto tmp = read_fd(fd);
|
||||
close(fd);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
command_ptr read_string(const std::string &s) {
|
||||
line_parser p;
|
||||
|
||||
p.process(s.data(), s.size());
|
||||
return p.finish();
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#include "mpw-shell.h"
|
||||
#include "error.h"
|
||||
|
||||
%%{
|
||||
machine tokenizer;
|
||||
|
@ -10,99 +11,48 @@
|
|||
|
||||
|
||||
escape = 0xb6;
|
||||
ws = [ \t];
|
||||
nl = '\n' | '\r';
|
||||
ws = [ \t\n\r];
|
||||
|
||||
action push_token {
|
||||
if (!scratch.empty() || quoted) {
|
||||
if (!scratch.empty()) {
|
||||
tokens.emplace_back(std::move(scratch));
|
||||
scratch.clear();
|
||||
quoted = false;
|
||||
}
|
||||
}
|
||||
|
||||
action push_back {
|
||||
action push {
|
||||
scratch.push_back(fc);
|
||||
}
|
||||
|
||||
# vstring_quoted =
|
||||
# [{]
|
||||
# ( (any-nl-[}]) ${ var.push_back(fc); } )*
|
||||
# [}]
|
||||
# %{
|
||||
# auto iter = Environment.find(var);
|
||||
# if (iter != Environment.end() {
|
||||
# scratch.append(iter->second);
|
||||
# })
|
||||
# var.clear();
|
||||
# }
|
||||
# $err{
|
||||
# throw std::runtime_error("### MPW Shell - '{ must occur in pairs.");
|
||||
# }
|
||||
# ;
|
||||
action push_string {
|
||||
scratch.append(ts, te);
|
||||
}
|
||||
|
||||
# vstring_unqoted =
|
||||
# [{]
|
||||
# ( (any-nl-[}]) ${ var.push_back(fc); } )*
|
||||
# [}]
|
||||
# %{
|
||||
# auto iter = Environment.find(var);
|
||||
# if (iter != Environment.end() {
|
||||
# // re-parse. ", ', { are not
|
||||
# // special. all others are treated normally.
|
||||
# })
|
||||
# var.clear();
|
||||
# }
|
||||
# $err{
|
||||
# throw std::runtime_error("### MPW Shell - '{ must occur in pairs.");
|
||||
# }
|
||||
# ;
|
||||
schar = [^'] ;
|
||||
sstring = ['] schar** ['] $err{ throw sstring_error(); } ;
|
||||
|
||||
sstring =
|
||||
[']
|
||||
( (any-nl-[']) $push_back )*
|
||||
[']
|
||||
${ quoted = true; }
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - 's must occur in pairs.");
|
||||
}
|
||||
;
|
||||
|
||||
escape_seq =
|
||||
escape
|
||||
(
|
||||
'f' ${scratch.push_back('\f'); }
|
||||
|
|
||||
'n' ${scratch.push_back('\n'); /* \r ? */ }
|
||||
|
|
||||
't' ${scratch.push_back('\t'); }
|
||||
|
|
||||
any-[fnt] $push_back
|
||||
)
|
||||
;
|
||||
escape_seq = escape any ;
|
||||
|
||||
# double-quoted string.
|
||||
dstring =
|
||||
["]
|
||||
(
|
||||
escape_seq
|
||||
|
|
||||
(any-escape-["]) $push_back
|
||||
)*
|
||||
["]
|
||||
${ quoted = true; }
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - \"s must occur in pairs.");
|
||||
}
|
||||
;
|
||||
dchar = escape_seq | (any - escape - ["]);
|
||||
dstring = ["] dchar** ["] $err{ throw dstring_error(); } ;
|
||||
|
||||
# search-forward string
|
||||
# fschar = escape_seq | (any - escape - [/]);
|
||||
fchar = [^/];
|
||||
fstring = [/] fchar** [/] $err{ throw fsstring_error(); } ;
|
||||
|
||||
# search-backward string
|
||||
# bschar = escape_seq | (any - escape - [\\]);
|
||||
bchar = [^\\];
|
||||
bstring = [\\] bchar** [\\] $err{ throw bsstring_error(); } ;
|
||||
|
||||
action eval { eval }
|
||||
|
||||
# > == start state (single char tokens or common prefix)
|
||||
# % == final state (multi char tokens w/ unique prefix)
|
||||
# $ == all states
|
||||
|
||||
char = any - ['"/\\];
|
||||
main := |*
|
||||
ws+ >push_token;
|
||||
'>>' %push_token => { tokens.emplace_back(">>", '>>'); };
|
||||
|
@ -110,20 +60,48 @@
|
|||
|
||||
'<' %push_token => { tokens.emplace_back("<", '<'); };
|
||||
|
||||
'||' %push_token => { tokens.emplace_back("||", '||'); };
|
||||
'|' %push_token => { tokens.emplace_back("|", '|'); };
|
||||
# macroman ∑, ∑∑
|
||||
0xb7 0xb7 %push_token => { tokens.emplace_back("\xb7\xb7", 0xb7b7); };
|
||||
0xb7 %push_token => { tokens.emplace_back("\xb7", 0xb7); };
|
||||
|
||||
'&&'
|
||||
%push_token => { tokens.emplace_back("&&", '&&'); };
|
||||
# macroman ≥, ≥≥
|
||||
0xb3 0xb3 %push_token => { tokens.emplace_back("\xb3\xb3", 0xb3b3); };
|
||||
0xb3 %push_token => { tokens.emplace_back("\xb3", 0xb3); };
|
||||
|
||||
# eval-only.
|
||||
|
||||
# macroman ≤
|
||||
0xb2 when eval
|
||||
%push_token => { tokens.emplace_back("\xb2", '<='); };
|
||||
|
||||
# macroman ≠
|
||||
0xad when eval
|
||||
%push_token => { tokens.emplace_back("\xad", '!='); };
|
||||
|
||||
# macroman ¬
|
||||
0xc2 when eval
|
||||
%push_token => { tokens.emplace_back("\xc2", '!'); };
|
||||
|
||||
# macroman ÷
|
||||
0xd6 when eval
|
||||
%push_token => { tokens.emplace_back("\xd6", '/'); };
|
||||
|
||||
'||' when eval
|
||||
%push_token => { tokens.emplace_back("||", '||'); };
|
||||
'|' when eval
|
||||
%push_token => { tokens.emplace_back("|", '|'); };
|
||||
|
||||
'&&' when eval
|
||||
%push_token => { tokens.emplace_back("&&", '&&'); };
|
||||
|
||||
'(' when eval
|
||||
%push_token => { tokens.emplace_back("(", '('); };
|
||||
|
||||
')' when eval
|
||||
%push_token => { tokens.emplace_back(")", ')'); };
|
||||
|
||||
'<>' when eval
|
||||
%push_token => { tokens.emplace_back("<>", '!='); };
|
||||
|
||||
'<<' when eval
|
||||
%push_token => { tokens.emplace_back("<<", '<<'); };
|
||||
|
@ -154,7 +132,7 @@
|
|||
|
||||
|
||||
'-' when eval
|
||||
%push_token => { tokens.emplace_back("+", '-'); };
|
||||
%push_token => { tokens.emplace_back("-", '-'); };
|
||||
|
||||
'!' when eval
|
||||
%push_token => { tokens.emplace_back("!", '!'); };
|
||||
|
@ -175,35 +153,37 @@
|
|||
'-=' when eval
|
||||
%push_token => { tokens.emplace_back("-=", '-='); };
|
||||
|
||||
'=~' when eval
|
||||
%push_token => { tokens.emplace_back("=~", '=~'); };
|
||||
|
||||
sstring ;
|
||||
dstring ;
|
||||
escape_seq;
|
||||
'!~' when eval
|
||||
%push_token => { tokens.emplace_back("!~", '!~'); };
|
||||
|
||||
(any-escape-['"]) => push_back; # { scratch.append(ts, te); };
|
||||
#(any-escape-ws-[>'"])+ => { scratch.append(ts, te); };
|
||||
sstring => push_string;
|
||||
dstring => push_string;
|
||||
fstring => push_string;
|
||||
bstring => push_string;
|
||||
escape_seq => push_string;
|
||||
|
||||
char => push;
|
||||
*|
|
||||
;
|
||||
}%%
|
||||
|
||||
|
||||
|
||||
inline void replace_eval_token(token &t) {
|
||||
void replace_eval_token(token &t) {
|
||||
|
||||
%%{
|
||||
|
||||
machine eval_keywords;
|
||||
|
||||
main :=
|
||||
/and/i %{ t.type = '&&'; }
|
||||
|
|
||||
/or/i %{ t.type = '||'; }
|
||||
|
|
||||
/not/i %{ t.type = '!'; }
|
||||
|
|
||||
/div/i %{ t.type = '/'; }
|
||||
|
|
||||
/mod/i %{ t.type = '%'; }
|
||||
'and'i %{ t.type = '&&'; }
|
||||
| 'or'i %{ t.type = '||'; }
|
||||
| 'not'i %{ t.type = '!'; }
|
||||
| 'div'i %{ t.type = '/'; }
|
||||
| 'mod'i %{ t.type = '%'; }
|
||||
;
|
||||
}%%
|
||||
|
||||
|
@ -216,15 +196,78 @@ inline void replace_eval_token(token &t) {
|
|||
const char *pe = t.string.data() + t.string.size();
|
||||
const char *eof = pe;
|
||||
int cs;
|
||||
|
||||
%%write init;
|
||||
|
||||
%%write exec;
|
||||
}
|
||||
std::vector<token> tokenize(const std::string &s, bool eval)
|
||||
|
||||
|
||||
void unquote(token &t) {
|
||||
|
||||
if (t.string.find_first_of("'\"\xb6", 0, 3) == t.string.npos) return;
|
||||
|
||||
int cs;
|
||||
const unsigned char *p = (const unsigned char *)t.string.data();
|
||||
const unsigned char *pe = p + t.string.length();
|
||||
const unsigned char *eof = pe;
|
||||
|
||||
std::string scratch;
|
||||
scratch.reserve(t.string.length());
|
||||
%%{
|
||||
|
||||
machine unquote;
|
||||
alphtype unsigned char;
|
||||
|
||||
action push { scratch.push_back(fc); }
|
||||
escape = 0xb6;
|
||||
char = any - escape - ['"/\\];
|
||||
|
||||
schar = [^'] $push;
|
||||
sstring = ['] schar** ['];
|
||||
|
||||
# // and \\ strings retain the delimiter.
|
||||
fchar = [^/];
|
||||
fstring = ([/] fchar** [/]) $push;
|
||||
|
||||
bchar = [^\\];
|
||||
bstring = ([\\] bchar** [\\]) $push;
|
||||
|
||||
|
||||
ecode =
|
||||
'f' ${ scratch.push_back('\f'); }
|
||||
| 'n' ${ scratch.push_back('\n'); }
|
||||
| 't' ${ scratch.push_back('\t'); }
|
||||
| [^fnt] ${ scratch.push_back(fc); }
|
||||
;
|
||||
|
||||
escape_seq = escape $err{ scratch.push_back(escape); } ecode;
|
||||
|
||||
dchar = escape ecode | (any - escape - ["]) $push;
|
||||
dstring = ["] dchar** ["];
|
||||
|
||||
main := (
|
||||
escape_seq
|
||||
| sstring
|
||||
| fstring
|
||||
| bstring
|
||||
| dstring
|
||||
| char $push
|
||||
)**;
|
||||
|
||||
write data;
|
||||
write init;
|
||||
write exec;
|
||||
}%%
|
||||
|
||||
t.string = std::move(scratch);
|
||||
}
|
||||
|
||||
|
||||
std::vector<token> tokenize(std::string &s, bool eval)
|
||||
{
|
||||
std::vector<token> tokens;
|
||||
std::string scratch;
|
||||
bool quoted = false; // found a quote character ("" creates a token)
|
||||
|
||||
|
||||
%%machine tokenizer;
|
||||
|
@ -241,15 +284,23 @@ std::vector<token> tokenize(const std::string &s, bool eval)
|
|||
|
||||
%%write exec;
|
||||
|
||||
if (cs == tokenizer_error) {
|
||||
throw std::runtime_error("MPW Shell - Lexer error.");
|
||||
}
|
||||
|
||||
if (!scratch.empty() || quoted) {
|
||||
if (!scratch.empty()) {
|
||||
tokens.emplace_back(std::move(scratch));
|
||||
scratch.clear();
|
||||
}
|
||||
|
||||
// re-build s.
|
||||
s.clear();
|
||||
for (const token &t : tokens) {
|
||||
s.append(t.string);
|
||||
s.push_back(' ');
|
||||
}
|
||||
if (!s.empty()) s.pop_back();
|
||||
|
||||
for (token &t : tokens) {
|
||||
if (t.type == token::text) unquote(t);
|
||||
}
|
||||
|
||||
// alternate operator tokens for eval
|
||||
if (eval) {
|
||||
|
||||
|
|
594
mpw-shell.cpp
594
mpw-shell.cpp
|
@ -2,49 +2,89 @@
|
|||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <cerrno>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "mpw-shell.h"
|
||||
#include "mpw_parser.h"
|
||||
|
||||
#include "fdset.h"
|
||||
|
||||
#include "phase1.h"
|
||||
#include "phase2.h"
|
||||
#include "command.h"
|
||||
#include "macroman.h"
|
||||
|
||||
#include "cxx/mapped_file.h"
|
||||
#include "cxx/filesystem.h"
|
||||
#include "cxx/string_splitter.h"
|
||||
|
||||
#include "mapped_file.h"
|
||||
#include "error.h"
|
||||
|
||||
//#include <histedit.h>
|
||||
#include <editline/readline.h>
|
||||
#include <readline/readline.h>
|
||||
#include <readline/history.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#include <sysexits.h>
|
||||
#include <paths.h>
|
||||
|
||||
//#include <uuid/uuid.h>
|
||||
#include "version.h"
|
||||
|
||||
std::string root() {
|
||||
namespace fs = filesystem;
|
||||
|
||||
static std::string root;
|
||||
bool utf8 = false;
|
||||
|
||||
if (root.empty()) {
|
||||
const char *cp = getenv("HOME");
|
||||
if (!cp || !*cp) {
|
||||
auto pw = getpwuid(getuid());
|
||||
if (!pw) {
|
||||
fprintf(stderr,"Unable to determine home directory\n.");
|
||||
exit(EX_NOUSER);
|
||||
fs::path home() {
|
||||
|
||||
const char *cp = getenv("HOME");
|
||||
if (cp && cp) {
|
||||
auto pw = getpwuid(getuid());
|
||||
if (pw) return fs::path(pw->pw_dir);
|
||||
|
||||
}
|
||||
return fs::path();
|
||||
}
|
||||
|
||||
|
||||
fs::path root() {
|
||||
|
||||
static fs::path root;
|
||||
bool init = false;
|
||||
|
||||
static std::array<filesystem::path, 2> locations = { {
|
||||
"/usr/share/mpw/",
|
||||
"/usr/local/share/mpw/"
|
||||
} };
|
||||
|
||||
if (!init) {
|
||||
init = true;
|
||||
std::error_code ec;
|
||||
fs::path p;
|
||||
|
||||
p = home();
|
||||
if (!p.empty()) {
|
||||
p /= "mpw/";
|
||||
if (fs::is_directory(p, ec)) {
|
||||
root = std::move(p);
|
||||
return root;
|
||||
}
|
||||
cp = pw->pw_dir;
|
||||
}
|
||||
root = cp;
|
||||
if (root.back() != '/') root.push_back('/');
|
||||
root += "mpw/";
|
||||
for (fs::path p : locations) {
|
||||
p /= "mpw/";
|
||||
if (fs::is_directory(p, ec)) {
|
||||
root = std::move(p);
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "### Warning: Unable to find mpw directory.\n");
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
@ -52,88 +92,209 @@ std::string root() {
|
|||
void init(Environment &env) {
|
||||
|
||||
env.set("mpw", root());
|
||||
env.set("status", std::string("0"));
|
||||
env.set("exit", std::string("1")); // terminate script on error.
|
||||
env.set("echo", std::string("1"));
|
||||
env.set("status", 0);
|
||||
env.set("exit", 1); // terminate script on error.
|
||||
env.set("echo", 1);
|
||||
}
|
||||
|
||||
int read_file(phase1 &p, const std::string &file) {
|
||||
const mapped_file mf(file, mapped_file::readonly);
|
||||
|
||||
p.process(mf.begin(), mf.end(), false);
|
||||
p.finish();
|
||||
return 0;
|
||||
|
||||
int read_file(Environment &e, const std::string &file, const fdmask &fds) {
|
||||
std::error_code ec;
|
||||
const mapped_file mf(file, mapped_file::readonly, ec);
|
||||
if (ec) {
|
||||
fprintf(stderr, "# Error reading %s: %s\n", file.c_str(), ec.message().c_str());
|
||||
return e.status(-1, false);
|
||||
}
|
||||
|
||||
|
||||
mpw_parser p(e, fds);
|
||||
e.status(0, false);
|
||||
|
||||
try {
|
||||
p.parse(mf.begin(), mf.end());
|
||||
p.finish();
|
||||
} catch(const execution_of_input_terminated &ex) {
|
||||
return ex.status();
|
||||
}
|
||||
return e.status();
|
||||
}
|
||||
|
||||
int read_fd(phase1 &p, int fd) {
|
||||
|
||||
int read_string(Environment &e, const std::string &s, const fdmask &fds) {
|
||||
mpw_parser p(e, fds);
|
||||
e.status(0, false);
|
||||
try {
|
||||
p.parse(s);
|
||||
p.finish();
|
||||
} catch(const execution_of_input_terminated &ex) {
|
||||
return ex.status();
|
||||
}
|
||||
return e.status();
|
||||
|
||||
}
|
||||
|
||||
|
||||
int read_fd(Environment &e, int fd, const fdmask &fds) {
|
||||
|
||||
unsigned char buffer[2048];
|
||||
ssize_t size;
|
||||
|
||||
for (;;) {
|
||||
size = read(fd, buffer, sizeof(buffer));
|
||||
if (size == 0) break;
|
||||
if (size < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
perror("read: ");
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
p.process(buffer, buffer + size);
|
||||
} catch(std::exception &ex) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
p.reset();
|
||||
}
|
||||
}
|
||||
mpw_parser p(e, fds);
|
||||
e.status(0, false);
|
||||
|
||||
try {
|
||||
for (;;) {
|
||||
size = read(fd, buffer, sizeof(buffer));
|
||||
if (size < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
perror("read");
|
||||
e.status(-1, false);
|
||||
}
|
||||
if (size == 0) break;
|
||||
p.parse(buffer, buffer + size);
|
||||
}
|
||||
p.finish();
|
||||
} catch(std::exception &ex) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
p.reset();
|
||||
} catch(const execution_of_input_terminated &ex) {
|
||||
return ex.status();
|
||||
}
|
||||
|
||||
return 0;
|
||||
return e.status();
|
||||
}
|
||||
|
||||
int interactive(phase1 &p) {
|
||||
void launch_mpw(const Environment &env, const std::vector<std::string> &argv, const fdmask &fds);
|
||||
fs::path which(const Environment &env, const std::string &name);
|
||||
|
||||
int read_make(Environment &env, const std::vector<std::string> &argv) {
|
||||
|
||||
int out[2];
|
||||
int ok;
|
||||
|
||||
|
||||
env.set("echo", 1);
|
||||
env.set("exit", 1);
|
||||
|
||||
ok = pipe(out);
|
||||
if (ok < 0) {
|
||||
perror("pipe");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
fcntl(out[0], F_SETFD, FD_CLOEXEC);
|
||||
fcntl(out[1], F_SETFD, FD_CLOEXEC);
|
||||
|
||||
int child = fork();
|
||||
if (child < 0) {
|
||||
perror("fork");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
if (child == 0) {
|
||||
// child.
|
||||
fdmask fds = {-1, out[1], -1};
|
||||
|
||||
launch_mpw(env, argv, fds);
|
||||
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
|
||||
close(out[1]);
|
||||
int rv = read_fd(env, out[0]);
|
||||
close(out[0]);
|
||||
|
||||
|
||||
// check for make errors.
|
||||
for(;;) {
|
||||
int status;
|
||||
int ok = waitpid(child, &status, 0);
|
||||
if (ok < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
perror("waitpid: ");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
|
||||
if (WIFEXITED(status)) {
|
||||
ok = WEXITSTATUS(status);
|
||||
env.status(ok, false);
|
||||
break;
|
||||
}
|
||||
if (WIFSIGNALED(status)) {
|
||||
env.status(-9, false);
|
||||
break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "waitpid - unexpected result\n");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
std::atomic<int> control_c{0};
|
||||
void control_c_handler(int signal, siginfo_t *sinfo, void *context) {
|
||||
|
||||
// libedit gobbles up the first control-C and doesn't return until the second.
|
||||
// GNU's readline may return on the first.
|
||||
if (control_c > 3) abort();
|
||||
++control_c;
|
||||
//fprintf(stderr, "interrupt!\n");
|
||||
}
|
||||
|
||||
|
||||
int interactive(Environment &env) {
|
||||
|
||||
std::string history_file = root();
|
||||
history_file += ".history";
|
||||
read_history(history_file.c_str());
|
||||
|
||||
|
||||
for(;;) {
|
||||
char *cp = readline("# ");
|
||||
if (!cp) break;
|
||||
struct sigaction act;
|
||||
struct sigaction old_act;
|
||||
memset(&act, 0, sizeof(struct sigaction));
|
||||
sigemptyset(&act.sa_mask);
|
||||
|
||||
act.sa_sigaction = control_c_handler;
|
||||
act.sa_flags = SA_SIGINFO;
|
||||
|
||||
sigaction(SIGINT, &act, &old_act);
|
||||
|
||||
mpw_parser p(env, true);
|
||||
|
||||
|
||||
for(;;) {
|
||||
const char *prompt = "# ";
|
||||
if (p.continuation()) prompt = "> ";
|
||||
char *cp = readline(prompt);
|
||||
if (!cp) {
|
||||
if (control_c) {
|
||||
control_c = 0;
|
||||
fprintf(stdout, "\n");
|
||||
p.abort();
|
||||
env.status(-9, false);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
control_c = 0;
|
||||
std::string s(cp);
|
||||
free(cp);
|
||||
|
||||
if (s.empty()) continue;
|
||||
//if (s.empty()) continue;
|
||||
|
||||
// don't add if same as previous entry.
|
||||
HIST_ENTRY *he = history_get(history_length);
|
||||
if (he == nullptr || s != he->line)
|
||||
add_history(s.c_str());
|
||||
if (!s.empty()) {
|
||||
HIST_ENTRY *he = history_get(history_length);
|
||||
if (he == nullptr || s != he->line)
|
||||
add_history(s.c_str());
|
||||
}
|
||||
if (utf8)
|
||||
s = utf8_to_macroman(s);
|
||||
|
||||
s.push_back('\n');
|
||||
try {
|
||||
p.process(s);
|
||||
|
||||
} catch(std::exception &ex) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
p.reset();
|
||||
}
|
||||
p.parse(s);
|
||||
|
||||
}
|
||||
p.finish();
|
||||
|
||||
try {
|
||||
p.finish();
|
||||
} catch(std::exception &ex) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
p.reset();
|
||||
}
|
||||
sigaction(SIGINT, &old_act, nullptr);
|
||||
|
||||
write_history(history_file.c_str());
|
||||
fprintf(stdout, "\n");
|
||||
|
@ -143,11 +304,24 @@ int interactive(phase1 &p) {
|
|||
|
||||
void help() {
|
||||
|
||||
#undef _
|
||||
#define _(x) puts(x)
|
||||
|
||||
_("MPW Shell " VERSION " (" VERSION_DATE ")");
|
||||
_("mpw-shell [option...]");
|
||||
_(" -c string # read commands from string");
|
||||
_(" -d name[=value] # define variable name");
|
||||
_(" -f # don't load MPW:Startup file");
|
||||
_(" -h # display help information");
|
||||
_(" -v # be verbose (echo = 1)");
|
||||
|
||||
#undef _
|
||||
}
|
||||
|
||||
void define(Environment &env, const std::string &s) {
|
||||
|
||||
auto pos = s.find('=');
|
||||
if (pos == s.npos) env.set(s, "1");
|
||||
if (pos == s.npos) env.set(s, 1);
|
||||
else {
|
||||
std::string k = s.substr(0, pos);
|
||||
std::string v = s.substr(pos+1);
|
||||
|
@ -156,15 +330,221 @@ void define(Environment &env, const std::string &s) {
|
|||
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* todo: prevent -r and -s (don't generate shell code)
|
||||
*/
|
||||
void make_help(void) {
|
||||
|
||||
#undef _
|
||||
#define _(x) puts(x)
|
||||
|
||||
_("Make # build up-to-date version of a program");
|
||||
_("Make [option...] [target...]");
|
||||
_(" -d name[=value] # define variable name (overrides makefile definition)");
|
||||
_(" -e # rebuild everything regardless of dates");
|
||||
_(" -f filename # read dependencies from specified file (default: MakeFile)");
|
||||
_(" -i dirname # additional directory to search for include files");
|
||||
#if 0
|
||||
_(" -[no]mf # [don't] use temporary memory (default: mf)");
|
||||
#endif
|
||||
_(" -p # write progress information to diagnostics");
|
||||
_(" -r # display the roots of the dependency graph");
|
||||
_(" -s # display the structure of the dependency graph");
|
||||
_(" -t # touch dates of targets and prerequisites");
|
||||
_(" -u # write list of unreachable targets to diagnostics");
|
||||
_(" -v # write verbose explanations to diagnostics (implies -p)");
|
||||
_(" -w # suppress warning messages");
|
||||
_(" -y # like -v, but omit announcing up-to-date targets");
|
||||
_("");
|
||||
_(" --help # display help");
|
||||
_(" --dry-run, --test # show what commands would run");
|
||||
#undef _
|
||||
}
|
||||
|
||||
int make(int argc, char **argv) {
|
||||
|
||||
Environment e;
|
||||
init(e);
|
||||
|
||||
std::vector<std::string> args;
|
||||
args.reserve(argc+1);
|
||||
int c;
|
||||
bool passthrough = false;
|
||||
|
||||
static struct option longopts[] = {
|
||||
{ "help", no_argument, nullptr, 'h' },
|
||||
{ "verbose", no_argument, nullptr, 'v' },
|
||||
{ "test", no_argument, nullptr, 1 },
|
||||
{ "dry-run", no_argument, nullptr, 2 },
|
||||
{ nullptr, 0, nullptr, 0},
|
||||
};
|
||||
|
||||
args.push_back(""); // place-holder.
|
||||
|
||||
while ((c = getopt_long(argc, argv, "d:ef:i:prstuvwy", longopts, nullptr)) != -1) {
|
||||
std::string flag = "-"; flag.push_back(c);
|
||||
switch(c) {
|
||||
default:
|
||||
make_help();
|
||||
return EX_USAGE;
|
||||
|
||||
case 'h':
|
||||
make_help();
|
||||
return 0;
|
||||
|
||||
case 1:
|
||||
e.set("test", 1);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
passthrough = true;
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
case 'f':
|
||||
case 'i':
|
||||
args.push_back(std::move(flag));
|
||||
args.push_back(optarg);
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
case 'p':
|
||||
case 't':
|
||||
case 'u':
|
||||
case 'v':
|
||||
case 'w':
|
||||
case 'y':
|
||||
args.push_back(std::move(flag));
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
case 's':
|
||||
args.push_back(std::move(flag));
|
||||
passthrough = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
std::transform(argv, argv+argc, std::back_inserter(args), [](const char *cp){
|
||||
return std::string(cp);
|
||||
});
|
||||
|
||||
|
||||
|
||||
e.startup(true);
|
||||
read_file(e, root() / "Startup");
|
||||
e.startup(false);
|
||||
|
||||
auto path = which(e, "Make");
|
||||
if (path.empty()) {
|
||||
fputs("### MPW Shell - Command \"Make\" was not found.\n", stderr);
|
||||
return -1;
|
||||
}
|
||||
e.set("command", path);
|
||||
args[0] = path;
|
||||
|
||||
if (passthrough) {
|
||||
|
||||
launch_mpw(e, args, fdmask());
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
|
||||
return read_make(e, args);
|
||||
|
||||
}
|
||||
|
||||
fs::path mpw_path() {
|
||||
|
||||
static fs::path path;
|
||||
|
||||
if (path.empty()) {
|
||||
std::error_code ec;
|
||||
const char *cp = getenv("PATH");
|
||||
if (!cp) cp = _PATH_DEFPATH;
|
||||
std::string s(cp);
|
||||
string_splitter ss(s, ':');
|
||||
for (; ss; ++ss) {
|
||||
if (ss->empty()) continue;
|
||||
fs::path p(*ss);
|
||||
p /= "mpw";
|
||||
|
||||
if (fs::is_regular_file(p, ec)) {
|
||||
path = std::move(p);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//also check /usr/local/bin
|
||||
if (path.empty()) {
|
||||
fs::path p = "/usr/local/bin/mpw";
|
||||
if (fs::is_regular_file(p, ec)) {
|
||||
path = std::move(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (path.empty()) {
|
||||
fs::path p = root() / "bin/mpw";
|
||||
if (fs::is_regular_file(p, ec)) {
|
||||
path = std::move(p);
|
||||
}
|
||||
}
|
||||
|
||||
if (path.empty()) {
|
||||
fprintf(stderr, "Unable to find mpw executable\n");
|
||||
fprintf(stderr, "PATH = %s\n", s.c_str());
|
||||
path = "mpw";
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
void init_locale() {
|
||||
|
||||
/*
|
||||
* libedit assumes utf-8 if locale is C.
|
||||
* MacRoman is en_US. utf-8 is en_US.UTF8.
|
||||
*/
|
||||
|
||||
|
||||
const char *lang = getenv("LANG");
|
||||
/*
|
||||
if (lang && !strcmp(lang, "en_US")) {
|
||||
setlocale(LC_ALL, "POSIX");
|
||||
}
|
||||
*/
|
||||
utf8 = false;
|
||||
if (lang && strcasestr(lang, ".UTF-8")) {
|
||||
utf8 = true;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
|
||||
init_locale();
|
||||
|
||||
mpw_path();
|
||||
|
||||
fs::path self = fs::path(argv[0]).filename();
|
||||
if (self == "mpw-make") return make(argc, argv);
|
||||
if (self == "mpw-shell" && argc > 1 && !strcmp(argv[1],"make")) {
|
||||
argv[1] = (char *)"mpw-make";
|
||||
return make(argc - 1, argv + 1);
|
||||
}
|
||||
|
||||
Environment e;
|
||||
init(e);
|
||||
|
||||
const char *cflag = nullptr;
|
||||
bool fflag = false;
|
||||
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "c:D:v:h")) != -1) {
|
||||
while ((c = getopt(argc, argv, "c:D:vhf")) != -1) {
|
||||
switch (c) {
|
||||
case 'c':
|
||||
// -c command
|
||||
|
@ -178,6 +558,9 @@ int main(int argc, char **argv) {
|
|||
// -v verbose
|
||||
e.set("echo", "1");
|
||||
break;
|
||||
case 'f':
|
||||
fflag = true;
|
||||
break;
|
||||
case 'h':
|
||||
help();
|
||||
exit(0);
|
||||
|
@ -192,51 +575,38 @@ int main(int argc, char **argv) {
|
|||
|
||||
|
||||
|
||||
if (!cflag) fprintf(stdout, "MPW Shell " VERSION "\n");
|
||||
if (!fflag) {
|
||||
fs::path startup = root() / "Startup";
|
||||
e.startup(true);
|
||||
mpw_parser p(e);
|
||||
|
||||
|
||||
phase1 p1;
|
||||
phase2 p2;
|
||||
|
||||
p1 >>= [&p2](std::string &&s) {
|
||||
if (s.empty()) p2.finish();
|
||||
else p2(std::move(s));
|
||||
};
|
||||
|
||||
p2 >>= [&e](command_ptr_vector &&v) {
|
||||
|
||||
for (auto iter = v.begin(); iter != v.end(); ++iter) {
|
||||
auto &ptr = *iter;
|
||||
fdmask fds;
|
||||
try {
|
||||
ptr->execute(e, fds);
|
||||
} catch (execution_of_input_terminated &ex) {
|
||||
if (!(ptr->terminal() && ++iter == v.end())) {
|
||||
fprintf(stderr, "%s\n", ex.what());
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
read_file(e, startup);
|
||||
} catch (const std::system_error &ex) {
|
||||
fprintf(stderr, "### %s: %s\n", startup.c_str(), ex.what());
|
||||
} catch (const quit_command_t &) {
|
||||
}
|
||||
};
|
||||
|
||||
if (!cflag) fprintf(stdout, "MPW Shell 0.0\n");
|
||||
e.startup(true);
|
||||
read_file(p1, "/Users/kelvin/mpw/Startup");
|
||||
//p2.finish();
|
||||
e.startup(false);
|
||||
|
||||
if (cflag) {
|
||||
std::string s(cflag);
|
||||
s.push_back('\n');
|
||||
p1.process(s, true);
|
||||
p2.finish();
|
||||
exit(e.status());
|
||||
e.startup(false);
|
||||
}
|
||||
|
||||
if (isatty(STDIN_FILENO))
|
||||
interactive(p1);
|
||||
else
|
||||
read_fd(p1, STDIN_FILENO);
|
||||
p2.finish();
|
||||
try {
|
||||
|
||||
exit(e.status());
|
||||
int rv = 0;
|
||||
if (cflag) {
|
||||
rv = read_string(e, cflag);
|
||||
exit(rv);
|
||||
}
|
||||
|
||||
if (isatty(STDIN_FILENO))
|
||||
rv = interactive(e);
|
||||
else
|
||||
rv = read_fd(e, STDIN_FILENO);
|
||||
|
||||
exit(rv);
|
||||
}
|
||||
catch (const quit_command_t &) {
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
|
12
mpw-shell.h
12
mpw-shell.h
|
@ -8,6 +8,7 @@
|
|||
#include <cstdint>
|
||||
|
||||
#include "environment.h"
|
||||
#include "fdset.h"
|
||||
|
||||
const unsigned char escape = 0xb6;
|
||||
|
||||
|
@ -48,8 +49,8 @@ public:
|
|||
|
||||
|
||||
|
||||
std::vector<token> tokenize(const std::string &s, bool eval = false);
|
||||
std::string expand_vars(const std::string &s, const class Environment &);
|
||||
std::vector<token> tokenize(std::string &s, bool eval = false);
|
||||
std::string expand_vars(const std::string &s, class Environment &, const fdmask &fds = fdmask());
|
||||
|
||||
//std::string quote(std::string &&s);
|
||||
std::string quote(const std::string &s);
|
||||
|
@ -63,8 +64,13 @@ void parse_tokens(std::vector<token> &&tokens, process &p);
|
|||
|
||||
|
||||
|
||||
int32_t evaluate_expression(const std::string &name, std::vector<token> &&tokens);
|
||||
int32_t evaluate_expression(Environment &e, const std::string &name, std::vector<token> &&tokens);
|
||||
|
||||
|
||||
|
||||
int read_file(Environment &e, const std::string &file, const fdmask &fds = fdmask());
|
||||
int read_string(Environment &e, const std::string &s, const fdmask &fds = fdmask());
|
||||
int read_fd(Environment &e, int fd, const fdmask &fds = fdmask());
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include "mpw_parser.h"
|
||||
#include "phase3_parser.h"
|
||||
|
||||
#include "command.h"
|
||||
#include "error.h"
|
||||
|
||||
mpw_parser::mpw_parser(Environment &e, fdmask fds, bool interactive) : _env(e), _fds(fds), _interactive(interactive)
|
||||
{
|
||||
|
||||
_p3 = phase3::make();
|
||||
_p2.set_next([this](int type, std::string &&s){
|
||||
_p3->parse(type, std::move(s));
|
||||
});
|
||||
|
||||
_p1.set_next([this](std::string &&s){
|
||||
_p2.parse(std::move(s));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
mpw_parser::~mpw_parser() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
bool mpw_parser::continuation() const {
|
||||
if (_p1.continuation()) return true;
|
||||
if (_p2.continuation()) return true;
|
||||
if (_p3->continuation()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void mpw_parser::finish() {
|
||||
_p1.finish();
|
||||
_p2.finish();
|
||||
_p3->parse(0, "");
|
||||
|
||||
// and now execute the commands...
|
||||
execute();
|
||||
}
|
||||
|
||||
|
||||
void mpw_parser::reset() {
|
||||
_p1.reset();
|
||||
_p2.reset();
|
||||
_p3->reset();
|
||||
_abort = false;
|
||||
}
|
||||
|
||||
void mpw_parser::abort() {
|
||||
_p1.abort();
|
||||
_p2.abort();
|
||||
//_p3->abort();
|
||||
_p3->reset();
|
||||
_abort = true;
|
||||
}
|
||||
|
||||
|
||||
void mpw_parser::parse(const void *begin, const void *end) {
|
||||
if (_abort) return;
|
||||
_p1.parse((const unsigned char *)begin, (const unsigned char *)end);
|
||||
|
||||
// and execute...
|
||||
execute();
|
||||
}
|
||||
|
||||
|
||||
void mpw_parser::execute() {
|
||||
if (_abort) {
|
||||
_p3->command_queue.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
auto commands = std::move(_p3->command_queue);
|
||||
_p3->command_queue.clear();
|
||||
|
||||
|
||||
std::reverse(commands.begin(), commands.end());
|
||||
|
||||
command_ptr cmd;
|
||||
|
||||
|
||||
try {
|
||||
while (!commands.empty()) {
|
||||
cmd = std::move(commands.back());
|
||||
commands.pop_back();
|
||||
cmd->execute(_env, _fds);
|
||||
}
|
||||
|
||||
} catch (execution_of_input_terminated &ex) {
|
||||
|
||||
_env.status(ex.status(), false);
|
||||
|
||||
if (_interactive) {
|
||||
if (!cmd->terminal() || !commands.empty()) {
|
||||
if (ex.status()) fprintf(stderr, "### %s\n", ex.what());
|
||||
}
|
||||
return;
|
||||
}
|
||||
_abort = true;
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef __mpw_parser__
|
||||
#define __mpw_parser__
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "environment.h"
|
||||
#include "fdset.h"
|
||||
|
||||
#include "phase1.h"
|
||||
#include "phase2.h"
|
||||
|
||||
class mpw_parser {
|
||||
|
||||
public:
|
||||
|
||||
mpw_parser(Environment &e, fdmask fds = fdmask(), bool interactive = false);
|
||||
mpw_parser(Environment &e, bool interactive) : mpw_parser(e, fdmask(), interactive)
|
||||
{}
|
||||
|
||||
~mpw_parser();
|
||||
|
||||
void parse(const void *begin, const void *end);
|
||||
void parse(const std::string &s) {
|
||||
parse(s.data(), s.data() + s.size());
|
||||
}
|
||||
void parse(const void *begin, size_t length) {
|
||||
parse(begin, (const char *)begin + length);
|
||||
}
|
||||
|
||||
void reset();
|
||||
void abort();
|
||||
void finish();
|
||||
|
||||
bool continuation() const;
|
||||
|
||||
private:
|
||||
|
||||
mpw_parser& operator=(const mpw_parser &) = delete;
|
||||
mpw_parser& operator=(mpw_parser &&) = delete;
|
||||
|
||||
mpw_parser(const mpw_parser &) = delete;
|
||||
mpw_parser(mpw_parser &&) = delete;
|
||||
|
||||
void execute();
|
||||
|
||||
Environment &_env;
|
||||
fdmask _fds;
|
||||
bool _interactive = false;
|
||||
bool _abort = false;
|
||||
|
||||
phase1 _p1;
|
||||
phase2 _p2;
|
||||
std::unique_ptr<class phase3> _p3;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,303 @@
|
|||
/*
|
||||
* Copyright (c) 2013, Kelvin W Sherlock
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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 OWNER 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.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <string>
|
||||
|
||||
//#include "toolbox.h"
|
||||
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
/*
|
||||
* To Mac:
|
||||
* file -> file
|
||||
* :directory:file -> directory/file
|
||||
* volume:directory -> /volume/directory
|
||||
* : -> ./
|
||||
* :: -> ../
|
||||
* ::: -> ../../
|
||||
*
|
||||
* To Unix:
|
||||
* file -> file
|
||||
* directory/file -> :directory:file
|
||||
* /volume/file -> volume:file
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
%%{
|
||||
machine mac;
|
||||
|
||||
main := |*
|
||||
|
||||
':' {2,} {
|
||||
|
||||
// :: = ..
|
||||
// ::: = ../..
|
||||
// etc
|
||||
unsigned count = te - ts;
|
||||
|
||||
if (ts != begin)
|
||||
{
|
||||
rv.push_back('/');
|
||||
}
|
||||
for (unsigned i = 1; i < count; ++i)
|
||||
{
|
||||
rv.append("../");
|
||||
}
|
||||
};
|
||||
|
||||
':' {
|
||||
/*
|
||||
if (ts == begin)
|
||||
rv.append("./");
|
||||
else
|
||||
rv.push_back('/');
|
||||
*/
|
||||
if (ts != begin)
|
||||
rv.push_back('/');
|
||||
};
|
||||
|
||||
|
||||
any {
|
||||
rv.push_back(*ts);
|
||||
};
|
||||
|
||||
*|;
|
||||
|
||||
write data;
|
||||
}%%
|
||||
|
||||
%%{
|
||||
machine unix;
|
||||
|
||||
main := |*
|
||||
|
||||
'/' + {
|
||||
// collapse multiple /s to a single ':'
|
||||
if (ts != begin)
|
||||
{
|
||||
rv.push_back(':');
|
||||
slash = true;
|
||||
}
|
||||
};
|
||||
|
||||
any {
|
||||
rv.push_back(*ts);
|
||||
};
|
||||
|
||||
*|;
|
||||
|
||||
write data;
|
||||
}%%
|
||||
|
||||
/*
|
||||
* this is not quite right. dev:std* is a reference to the current output device.
|
||||
* need to move this logic elsewhere.
|
||||
*
|
||||
*/
|
||||
|
||||
%%{
|
||||
machine dev;
|
||||
|
||||
main :=
|
||||
'/dev/null'i $eof{ return "/dev/null"; }
|
||||
|
|
||||
'/dev/stderr'i $eof{ return "/dev/stderr"; }
|
||||
|
|
||||
'/dev/stdout'i $eof{ return "/dev/stdout"; }
|
||||
;
|
||||
write data;
|
||||
}%%
|
||||
|
||||
}
|
||||
|
||||
namespace ToolBox
|
||||
{
|
||||
|
||||
std::string check_dev(std::string &&str) {
|
||||
|
||||
const char *p = str.c_str();
|
||||
const char *pe = p + str.length();
|
||||
const char *eof = pe;
|
||||
|
||||
int cs;
|
||||
%%{
|
||||
machine dev;
|
||||
write init;
|
||||
write exec;
|
||||
|
||||
}%%
|
||||
|
||||
return std::move(str);
|
||||
|
||||
}
|
||||
|
||||
std::string MacToUnix(const std::string path)
|
||||
{
|
||||
|
||||
// todo -- Dev:Null -> lowercase it?
|
||||
|
||||
int sep = 0;
|
||||
bool colon = false;
|
||||
|
||||
for (char c : path)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case ':':
|
||||
colon = true;
|
||||
case '/':
|
||||
if (!sep) sep = c;
|
||||
break;
|
||||
|
||||
}
|
||||
if (colon) break;
|
||||
}
|
||||
|
||||
// no colon - no problem.
|
||||
if (!colon) return path;
|
||||
|
||||
// special case ":" -> "."
|
||||
if (colon && path.length() == 1) return ".";
|
||||
|
||||
const char *p = path.c_str();
|
||||
const char *pe = p + path.length();
|
||||
const char *eof = pe;
|
||||
const char *ts;
|
||||
const char *te;
|
||||
|
||||
const char *begin = p;
|
||||
int cs;
|
||||
int act;
|
||||
|
||||
std::string rv;
|
||||
|
||||
// volume:directory -> /volume
|
||||
if (sep == ':' && path.front() != ':')
|
||||
rv.push_back('/');
|
||||
|
||||
%%{
|
||||
machine mac;
|
||||
|
||||
write init;
|
||||
write exec;
|
||||
}%%
|
||||
|
||||
return check_dev(std::move(rv));
|
||||
}
|
||||
|
||||
|
||||
std::string UnixToMac(const std::string path)
|
||||
{
|
||||
// /volume/directory -> volume:directory
|
||||
// // -> /
|
||||
|
||||
|
||||
bool slash = false;
|
||||
bool sep = 0;
|
||||
|
||||
for (char c : path)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case '/':
|
||||
slash = true;
|
||||
case ':':
|
||||
if (!sep) sep = c;
|
||||
break;
|
||||
}
|
||||
if (slash) break;
|
||||
}
|
||||
|
||||
if (!slash) return path;
|
||||
|
||||
const char *p = path.c_str();
|
||||
const char *pe = p + path.length();
|
||||
const char *eof = pe;
|
||||
const char *ts;
|
||||
const char *te;
|
||||
|
||||
const char *begin = p;
|
||||
int act;
|
||||
int cs;
|
||||
|
||||
std::string rv;
|
||||
|
||||
|
||||
slash = false;
|
||||
// path/file -> :path:file
|
||||
if (path.front() != '/')
|
||||
{
|
||||
rv.push_back(':');
|
||||
slash = true;
|
||||
}
|
||||
%%{
|
||||
machine unix;
|
||||
|
||||
write init;
|
||||
write exec;
|
||||
}%%
|
||||
|
||||
// special check /dir -> dir:
|
||||
if (path.front() == '/' && !slash) rv.push_back(':');
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
#ifdef TESTING
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
std::string s(argv[i]);
|
||||
std::string a = ToolBox::MacToUnix(s);
|
||||
std::string b = ToolBox::UnixToMac(s);
|
||||
|
||||
if (s != a)
|
||||
printf("ToUnix: %s -> %s\n", s.c_str(), a.c_str());
|
||||
|
||||
if (s != b)
|
||||
printf("ToMac: %s -> %s\n", s.c_str(), b.c_str());
|
||||
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,207 @@
|
|||
#include "phase1.h"
|
||||
#include <cassert>
|
||||
|
||||
enum {
|
||||
st_text,
|
||||
st_text_esc,
|
||||
|
||||
st_comment,
|
||||
st_comment_esc,
|
||||
|
||||
st_vstring,
|
||||
st_vstring_esc,
|
||||
|
||||
st_dstring,
|
||||
st_dstring_esc,
|
||||
|
||||
st_sstring,
|
||||
st_sstring_esc,
|
||||
|
||||
st_estring,
|
||||
st_estring1,
|
||||
st_estring1_esc,
|
||||
|
||||
st_estring2,
|
||||
st_estring2_esc,
|
||||
|
||||
st_estring3,
|
||||
|
||||
st_fstring,
|
||||
st_fstring_esc,
|
||||
|
||||
st_bstring,
|
||||
st_bstring_esc,
|
||||
|
||||
};
|
||||
|
||||
|
||||
int phase1::process(unsigned char c, int st) {
|
||||
|
||||
const unsigned char esc = 0xb6;
|
||||
|
||||
|
||||
if (c == '\r' || c == '\n') {
|
||||
switch (st) {
|
||||
case st_text:
|
||||
case st_comment:
|
||||
default: // will error later.
|
||||
flush();
|
||||
multiline = false;
|
||||
line++;
|
||||
return st_text;
|
||||
|
||||
case st_comment_esc:
|
||||
multiline = true;
|
||||
line++;
|
||||
return st_text;
|
||||
|
||||
case st_text_esc:
|
||||
case st_vstring_esc:
|
||||
case st_dstring_esc:
|
||||
case st_sstring_esc:
|
||||
case st_estring1_esc:
|
||||
case st_estring2_esc:
|
||||
case st_fstring_esc:
|
||||
case st_bstring_esc:
|
||||
multiline = true;
|
||||
scratch.pop_back();
|
||||
line++;
|
||||
return st - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (st != st_comment) scratch.push_back(c);
|
||||
|
||||
switch(st) {
|
||||
|
||||
case st_text:
|
||||
text:
|
||||
switch(c) {
|
||||
case '#':
|
||||
scratch.pop_back();
|
||||
return st_comment;
|
||||
case esc:
|
||||
return st_text_esc;
|
||||
case '{':
|
||||
return st_vstring;
|
||||
case '"':
|
||||
return st_dstring;
|
||||
case '\'':
|
||||
return st_sstring;
|
||||
case '`':
|
||||
return st_estring;
|
||||
case '/':
|
||||
return st_fstring;
|
||||
case '\\':
|
||||
return st_bstring;
|
||||
|
||||
default:
|
||||
return st_text;
|
||||
}
|
||||
break;
|
||||
|
||||
case st_comment:
|
||||
if (c == esc) return st_comment_esc;
|
||||
return st_comment;
|
||||
break;
|
||||
|
||||
case st_comment_esc:
|
||||
case st_text_esc:
|
||||
case st_dstring_esc:
|
||||
case st_estring1_esc:
|
||||
case st_estring2_esc:
|
||||
return st-1;
|
||||
break;
|
||||
|
||||
|
||||
case st_sstring_esc:
|
||||
// fall through
|
||||
case st_sstring:
|
||||
if (c == '\'') return st_text;
|
||||
if (c == esc) return st_sstring_esc;
|
||||
return st_sstring;
|
||||
break;
|
||||
|
||||
|
||||
case st_fstring_esc:
|
||||
// fall through
|
||||
case st_fstring:
|
||||
if (c == '/') return st_text;
|
||||
if (c == esc) return st_fstring_esc;
|
||||
return st_fstring;
|
||||
break;
|
||||
|
||||
case st_bstring_esc:
|
||||
// fall through
|
||||
case st_bstring:
|
||||
if (c == '\\') return st_text;
|
||||
if (c == esc) return st_bstring_esc;
|
||||
return st_bstring;
|
||||
break;
|
||||
|
||||
case st_dstring:
|
||||
if (c == '\"') return st_text;
|
||||
if (c == esc) return st_dstring_esc;
|
||||
return st_dstring;
|
||||
break;
|
||||
|
||||
case st_vstring_esc:
|
||||
// fall through
|
||||
case st_vstring:
|
||||
// '{' var '}' or '{{' var '}}'
|
||||
// don't care if {{ or { at this point. A single } terminates.
|
||||
if (c == '}') return st_text;
|
||||
if (c == esc) return st_vstring_esc;
|
||||
return st_vstring;
|
||||
|
||||
case st_estring:
|
||||
// ``...`` or `...`
|
||||
if (c == '`') return st_estring2;
|
||||
// fall through.
|
||||
case st_estring1:
|
||||
if (c == '`') return st_text;
|
||||
if (c == esc) return st_estring1_esc;
|
||||
return st_estring1;
|
||||
|
||||
case st_estring2:
|
||||
if (c == '`') return st_estring3;
|
||||
if (c == esc) return st_estring2_esc;
|
||||
return st_estring2;
|
||||
|
||||
case st_estring3:
|
||||
if (c == '`') return st_text;
|
||||
// error! handled later.
|
||||
goto text;
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
assert(!"unknown state");
|
||||
}
|
||||
|
||||
|
||||
void phase1::parse(const unsigned char *begin, const unsigned char *end) {
|
||||
while (begin != end) {
|
||||
cs = process(*begin++, cs);
|
||||
}
|
||||
}
|
||||
|
||||
void phase1::finish() {
|
||||
|
||||
cs = process('\n', cs);
|
||||
flush();
|
||||
}
|
||||
|
||||
void phase1::reset() {
|
||||
cs = st_text;
|
||||
multiline = false;
|
||||
line = 1;
|
||||
scratch.clear();
|
||||
}
|
||||
|
||||
void phase1::flush() {
|
||||
multiline = false;
|
||||
if (scratch.empty()) return;
|
||||
if (_then) _then(std::move(scratch));
|
||||
scratch.clear();
|
||||
}
|
37
phase1.h
37
phase1.h
|
@ -8,40 +8,33 @@
|
|||
class phase1 {
|
||||
|
||||
public:
|
||||
typedef std::function<void(std::string &&)> pipe_function;
|
||||
|
||||
phase1();
|
||||
typedef std::function<void(std::string &&)> next_function_type;
|
||||
|
||||
void process(const unsigned char *begin, const unsigned char *end, bool final = false);
|
||||
phase1() = default;
|
||||
|
||||
void process(const char *begin, const char *end, bool final = false) {
|
||||
process((const unsigned char *)begin, (const unsigned char *)end, final);
|
||||
}
|
||||
|
||||
void process(const std::string &s, bool final = false) { process(s.data(), s.data() + s.size(), final); }
|
||||
|
||||
void finish() { const char *tmp = "\n"; process(tmp, tmp+1, true); }
|
||||
void parse(const unsigned char *begin, const unsigned char *end);
|
||||
void parse(const std::string &s) { parse((const unsigned char *)s.data(), (const unsigned char *)s.data() + s.size()); }
|
||||
void finish();
|
||||
|
||||
void reset();
|
||||
void abort() { reset(); }
|
||||
|
||||
bool continuation() const { return multiline; }
|
||||
|
||||
phase1 &operator >>= (pipe_function f) { pipe_to = f; return *this; }
|
||||
|
||||
|
||||
template<class F>
|
||||
phase1 &operator >>= (F &f) {
|
||||
using std::placeholders::_1;
|
||||
pipe_to = std::bind(&F::operator(), &f, _1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
void set_next(next_function_type &&fx) { _then = std::move(fx); }
|
||||
|
||||
private:
|
||||
|
||||
int process(unsigned char, int);
|
||||
void flush();
|
||||
|
||||
std::string scratch;
|
||||
pipe_function pipe_to;
|
||||
int line = 1;
|
||||
int cs = 0;
|
||||
bool multiline = false;
|
||||
|
||||
next_function_type _then;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
15
phase1.rl
15
phase1.rl
|
@ -72,6 +72,7 @@ const unsigned char escape = 0xb6;
|
|||
}
|
||||
;
|
||||
|
||||
# todo -- {{variables}}
|
||||
# same quoting logic as ' string
|
||||
vstring =
|
||||
'{' $push_back
|
||||
|
@ -99,17 +100,13 @@ const unsigned char escape = 0xb6;
|
|||
}
|
||||
;
|
||||
|
||||
# this is a mess ...
|
||||
# gobble up all the white space...
|
||||
coalesce_ws =
|
||||
ws
|
||||
(
|
||||
ws
|
||||
|
|
||||
escape nl ${ line++; }
|
||||
)*
|
||||
ws+
|
||||
<:
|
||||
any ${ scratch.push_back(' '); fhold; }
|
||||
;
|
||||
''
|
||||
%{ if (!scratch.empty() && scratch.back() != ' ') scratch.push_back(' '); }
|
||||
;
|
||||
|
||||
line :=
|
||||
(
|
||||
|
|
|
@ -1,218 +0,0 @@
|
|||
|
||||
/*
|
||||
>, < redirection is handled later, after environment expansion.
|
||||
(also, redirection can be in the middle of a command.)
|
||||
*/
|
||||
|
||||
%include {
|
||||
|
||||
#include "phase2.h"
|
||||
#include "command.h"
|
||||
#define LEMON_SUPER phase2_parser
|
||||
|
||||
}
|
||||
|
||||
%code {
|
||||
|
||||
std::unique_ptr<phase2_parser> phase2_parser::make() {
|
||||
return std::make_unique<yypParser>();
|
||||
}
|
||||
}
|
||||
|
||||
%left PIPE_PIPE AMP_AMP.
|
||||
%left PIPE.
|
||||
|
||||
%token_type {std::string}
|
||||
%default_type {command_ptr}
|
||||
|
||||
%type start {void}
|
||||
%type opt_command_list {void}
|
||||
%type command_list {void}
|
||||
%type opt_command_sep {void}
|
||||
|
||||
/* these are put into a queue for immmediate execution */
|
||||
|
||||
|
||||
|
||||
start ::= opt_command_list.
|
||||
opt_command_list ::= .
|
||||
opt_command_list ::= command_list.
|
||||
|
||||
command_list ::= command_list opt_command(C) sep . {
|
||||
if (C) command_queue.emplace_back(std::move(C));
|
||||
}
|
||||
|
||||
command_list ::= opt_command(C) sep. {
|
||||
if (C) command_queue.emplace_back(std::move(C));
|
||||
}
|
||||
|
||||
/*
|
||||
command_list ::= opt_command_sep.
|
||||
command_list ::= command_list opt_command_sep.
|
||||
|
||||
|
||||
opt_command_sep ::= opt_command(C) sep. {
|
||||
if (C) command_queue.emplace_back(std::move(C));
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
compound_list is identical to command_list, but it is not executed immediately.
|
||||
*/
|
||||
|
||||
%type opt_compound_list { command_ptr_vector }
|
||||
%type compound_list { command_ptr_vector }
|
||||
|
||||
//%type opt_paren_list { command_ptr_vector }
|
||||
//%type paren_list { command_ptr_vector }
|
||||
|
||||
|
||||
opt_compound_list ::= .
|
||||
opt_compound_list(RV) ::= compound_list(L). { RV = std::move(L); }
|
||||
|
||||
compound_list(RV) ::= compound_list(L) opt_command(C) sep . {
|
||||
RV = std::move(L);
|
||||
if (C) RV.emplace_back(std::move(C));
|
||||
}
|
||||
|
||||
compound_list(RV) ::= opt_command(C) sep. {
|
||||
if (C) RV.emplace_back(std::move(C));
|
||||
}
|
||||
|
||||
/*
|
||||
opt_paren_list ::= .
|
||||
opt_paren_list(RV) ::= paren_list(L). { RV = std::move(L); }
|
||||
|
||||
paren_list(RV) ::= opt_command(C). {
|
||||
if (C) RV.emplace_back(std::move(C));
|
||||
}
|
||||
|
||||
paren_list(RV) ::= paren_list(L) sep opt_command(C). {
|
||||
RV = std::move(L);
|
||||
if (C) RV.emplace_back(std::move(C));
|
||||
}
|
||||
*/
|
||||
|
||||
sep ::= SEMI.
|
||||
sep ::= NL.
|
||||
|
||||
%type opt_command { command_ptr }
|
||||
opt_command(RV) ::= command(C). { RV = std::move(C); }
|
||||
opt_command ::= .
|
||||
|
||||
%type command { command_ptr }
|
||||
|
||||
/* nb -- ||, &&, | -- both sides are optional. This does not. */
|
||||
|
||||
command(RV) ::= command(L) PIPE_PIPE opt_nl command(R). {
|
||||
RV = std::make_unique<or_command>(std::move(L), std::move(R));
|
||||
}
|
||||
|
||||
command(RV) ::= command(L) AMP_AMP opt_nl command(R). {
|
||||
RV = std::make_unique<and_command>(std::move(L), std::move(R));
|
||||
}
|
||||
|
||||
/*
|
||||
command(RV) ::= command PIPE opt_nl command. {
|
||||
RV = std::make_unique<pipe_command>(std::move(L), std::move(R));
|
||||
}
|
||||
*/
|
||||
|
||||
command(RV) ::= term(T). { RV = std::move(T); }
|
||||
|
||||
term(RV) ::= COMMAND(C). { RV = std::make_unique<simple_command>(std::move(C)); }
|
||||
term(RV) ::= EVALUATE(C). { RV = std::make_unique<evaluate_command>(std::move(C)); }
|
||||
term(RV) ::= if_command(C). { RV = std::move(C); }
|
||||
term(RV) ::= begin_command(C). { RV = std::move(C); }
|
||||
term(RV) ::= paren_command(C). { RV = std::move(C); }
|
||||
|
||||
/*
|
||||
* fall back to an end error. w/o fallback, it will cause a parse conflict.
|
||||
*/
|
||||
/*
|
||||
%fallback ERROR END RPAREN ELSE ELSE_IF.
|
||||
|
||||
term(RV) ::= ERROR(C). {
|
||||
RV = std::make_unique<error_command>(@C, std::move(C));
|
||||
}
|
||||
*/
|
||||
|
||||
/* opt_compound_list requires a sep after every item -- not ok for paren command! */
|
||||
/*
|
||||
paren_command(RV) ::= LPAREN(T) opt_paren_list(L) RPAREN(E). {
|
||||
RV = std::make_unique<begin_command>(@T, std::move(L), std::move(T), std::move(E));
|
||||
}
|
||||
*/
|
||||
|
||||
/* compound list ends with a separator. paren command does not need the final separator */
|
||||
paren_command(RV) ::= LPAREN(T) opt_command(C) RPAREN(E). {
|
||||
command_ptr_vector L;
|
||||
if (C) L.emplace_back(std::move(C));
|
||||
RV = std::make_unique<begin_command>(@T, std::move(L), std::move(T), std::move(E));
|
||||
}
|
||||
|
||||
paren_command(RV) ::= LPAREN(T) compound_list(L) RPAREN(E). {
|
||||
RV = std::make_unique<begin_command>(@T, std::move(L), std::move(T), std::move(E));
|
||||
}
|
||||
|
||||
paren_command(RV) ::= LPAREN(T) compound_list(L) command(C) RPAREN(E). {
|
||||
if (C) L.emplace_back(std::move(C));
|
||||
RV = std::make_unique<begin_command>(@T, std::move(L), std::move(T), std::move(E));
|
||||
}
|
||||
|
||||
|
||||
begin_command(RV) ::= BEGIN(T) sep opt_compound_list(L) END(E). {
|
||||
RV = std::make_unique<begin_command>(@T, std::move(L), std::move(T), std::move(E));
|
||||
}
|
||||
|
||||
if_command(RV) ::= IF(I) sep opt_compound_list(L) END(E). {
|
||||
|
||||
if_command::clause_vector_type v;
|
||||
v.emplace_back(std::make_unique<if_else_clause>(IF, std::move(L), std::move(I)));
|
||||
|
||||
RV = std::make_unique<if_command>(
|
||||
std::move(v),
|
||||
std::move(E)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if_command(RV) ::= IF(I) sep opt_compound_list(L) else_command(EC) END(E). {
|
||||
|
||||
if_command::clause_vector_type v;
|
||||
v.emplace_back(std::make_unique<if_else_clause>(IF, std::move(L), std::move(I)));
|
||||
for(auto &c : EC) { v.emplace_back(std::move(c)); }
|
||||
|
||||
RV = std::make_unique<if_command>(
|
||||
std::move(v), std::move(E));
|
||||
}
|
||||
|
||||
%token_class else ELSE_IF ELSE.
|
||||
|
||||
%type else_command { if_command::clause_vector_type }
|
||||
else_command(RV) ::= else(E) sep opt_compound_list(L). {
|
||||
RV.emplace_back(std::make_unique<if_else_clause>(@E, std::move(L), std::move(E)));
|
||||
}
|
||||
/*
|
||||
else_command(RV) ::= ELSE_IF(E) sep opt_compound_list(L). {
|
||||
RV.emplace_back(std::make_unique<if_else_clause>(@E, std::move(L), std::move(E)));
|
||||
}
|
||||
*/
|
||||
|
||||
else_command(RV) ::= else_command(EC) else(E) sep opt_compound_list(L). {
|
||||
RV = std::move(EC);
|
||||
RV.emplace_back(std::make_unique<if_else_clause>(@E, std::move(L), std::move(E)));
|
||||
}
|
||||
|
||||
/*
|
||||
else_command(RV) ::= else_command(EC) ELSE_IF(E) sep opt_compound_list(L). {
|
||||
RV = std::move(EC);
|
||||
RV.emplace_back(std::make_unique<else_command>(std::move(E), std::move(L)));
|
||||
}
|
||||
*/
|
||||
|
||||
opt_nl ::= .
|
||||
opt_nl ::= nl.
|
||||
nl ::= NL.
|
||||
nl ::= nl NL.
|
||||
|
69
phase2.h
69
phase2.h
|
@ -3,86 +3,41 @@
|
|||
#define __phase2_h__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "command.h"
|
||||
#include "lemon_base.h"
|
||||
|
||||
//typedef std::unique_ptr<struct command> command_ptr;
|
||||
//typedef std::vector<command_ptr> command_ptr_vector;
|
||||
|
||||
class phase2_parser : public lemon_base<std::string> {
|
||||
|
||||
public:
|
||||
static std::unique_ptr<phase2_parser> make();
|
||||
|
||||
virtual void syntax_error(int yymajor, std::string &yyminor) override final;
|
||||
virtual void parse_accept() override final;
|
||||
virtual void parse_failure() override final;
|
||||
|
||||
private:
|
||||
friend class phase2;
|
||||
|
||||
phase2_parser(const phase2_parser &) = delete;
|
||||
phase2_parser(phase2_parser &&) = delete;
|
||||
|
||||
phase2_parser& operator=(const phase2_parser &) = delete;
|
||||
phase2_parser& operator=(phase2_parser &&) = delete;
|
||||
|
||||
protected:
|
||||
// these need to be accessible to the lemon-generated parser.
|
||||
phase2_parser() = default;
|
||||
|
||||
command_ptr_vector command_queue;
|
||||
bool error = false;
|
||||
};
|
||||
|
||||
|
||||
class phase2 {
|
||||
|
||||
public:
|
||||
typedef std::function<void(command_ptr_vector &&)> pipe_function;
|
||||
|
||||
phase2();
|
||||
phase2(const phase2 &) = delete;
|
||||
phase2(phase2 &&) = default;
|
||||
typedef std::function<void(int, std::string &&)> next_function_type;
|
||||
|
||||
phase2 & operator=(const phase2 &) = delete;
|
||||
phase2 & operator=(phase2 &&) = default;
|
||||
phase2() = default;
|
||||
|
||||
void operator()(const std::string &line) { process(line); }
|
||||
void process(const std::string &line);
|
||||
void parse(std::string &&);
|
||||
void finish();
|
||||
|
||||
phase2 &operator >>=(pipe_function f) { pipe_to = f; return *this; }
|
||||
void reset();
|
||||
void abort() { reset(); }
|
||||
|
||||
template<class F>
|
||||
phase2 &operator >>= (F &f) {
|
||||
using std::placeholders::_1;
|
||||
pipe_to = std::bind(&F::operator(), &f, _1);
|
||||
return *this;
|
||||
}
|
||||
bool continuation() const { return false; }
|
||||
|
||||
void set_next(next_function_type &&fx) { _then = std::move(fx); }
|
||||
|
||||
private:
|
||||
|
||||
void parse(int, std::string &&);
|
||||
|
||||
|
||||
std::unique_ptr<phase2_parser> parser;
|
||||
void parse(int type, std::string &&s);
|
||||
|
||||
std::string scratch;
|
||||
int type = 0;
|
||||
int pcount = 0;
|
||||
|
||||
pipe_function pipe_to;
|
||||
|
||||
void flush();
|
||||
bool special();
|
||||
void classify();
|
||||
int classify();
|
||||
void exec();
|
||||
|
||||
|
||||
next_function_type _then;
|
||||
};
|
||||
|
||||
|
||||
|
|
399
phase2.rl
399
phase2.rl
|
@ -4,9 +4,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#include "phase2-parser.h"
|
||||
#include "phase2.h"
|
||||
#include "command.h"
|
||||
#include "phase3.h"
|
||||
|
||||
%%{
|
||||
machine main;
|
||||
|
@ -14,282 +13,284 @@
|
|||
|
||||
action not_special { !special() }
|
||||
|
||||
action parse_ws {
|
||||
if (scratch.empty()) fgoto main;
|
||||
}
|
||||
action parse_semi {
|
||||
flush();
|
||||
parse(SEMI, ";");
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
|
||||
action parse_amp_amp {
|
||||
if (!special()) {
|
||||
scratch.pop_back();
|
||||
flush();
|
||||
parse(AMP_AMP, "&&");
|
||||
fgoto main;
|
||||
}
|
||||
}
|
||||
|
||||
action parse_pipe_pipe {
|
||||
if (!special()) {
|
||||
scratch.pop_back();
|
||||
flush();
|
||||
parse(PIPE_PIPE, "||");
|
||||
fgoto main;
|
||||
}
|
||||
}
|
||||
|
||||
action parse_pipe_any {
|
||||
if (!special()) {
|
||||
scratch.pop_back();
|
||||
flush();
|
||||
parse(PIPE, "|");
|
||||
}
|
||||
fhold;
|
||||
fgoto main;
|
||||
}
|
||||
|
||||
action parse_pipe_eof {
|
||||
|
||||
if (!special()) {
|
||||
scratch.pop_back();
|
||||
flush();
|
||||
parse(PIPE, "|");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
action parse_lparen {
|
||||
if (scratch.empty()) {
|
||||
parse(LPAREN, "(");
|
||||
fgoto main;
|
||||
}
|
||||
pcount++;
|
||||
}
|
||||
|
||||
action parse_rparen {
|
||||
if (pcount <= 0) {
|
||||
flush();
|
||||
// parse(RPAREN, ")");
|
||||
scratch.push_back(fc);
|
||||
fgoto main;
|
||||
}
|
||||
--pcount;
|
||||
}
|
||||
|
||||
escape = 0xb6;
|
||||
ws = [ \t];
|
||||
|
||||
|
||||
escape_seq =
|
||||
escape any
|
||||
;
|
||||
escape_seq = escape any ;
|
||||
|
||||
sstring =
|
||||
[']
|
||||
( (any-[']) )*
|
||||
[']
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - 's must occur in pairs.");
|
||||
}
|
||||
;
|
||||
schar = [^'];
|
||||
sstring = ['] schar** ['] ;
|
||||
|
||||
vstring =
|
||||
[{]
|
||||
( (any-[}]) )*
|
||||
[}]
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - {s must occur in pairs.");
|
||||
}
|
||||
;
|
||||
fchar = [^/];
|
||||
fstring = [/] fchar** [/] ;
|
||||
|
||||
bchar = [^\\];
|
||||
bstring = [\\] bchar** [\\] ;
|
||||
|
||||
|
||||
vchar = [^}];
|
||||
vstring = [{] vchar** [}] ;
|
||||
|
||||
# double-quoted string.
|
||||
dstring =
|
||||
["]
|
||||
(
|
||||
escape_seq
|
||||
|
|
||||
(any-escape-["])
|
||||
)*
|
||||
["]
|
||||
$err{
|
||||
throw std::runtime_error("### MPW Shell - \"s must occur in pairs.");
|
||||
}
|
||||
;
|
||||
dchar = escape_seq | (any - escape - ["]) ;
|
||||
dstring = ["] dchar** ["];
|
||||
|
||||
|
||||
echar = escape_seq | (any - escape - [`]) ;
|
||||
estring1 = '`' echar** '`';
|
||||
estring2 = '``' echar** '``';
|
||||
estring = estring1 | estring2 ;
|
||||
|
||||
main := |*
|
||||
# default action is to push character into scratch.
|
||||
# fgoto main inhibits.
|
||||
main := (
|
||||
ws $parse_ws
|
||||
| ';' $parse_semi
|
||||
| '(' $parse_lparen
|
||||
| ')' $parse_rparen
|
||||
| '|' <eof(parse_pipe_eof)
|
||||
| '|' [^|] $parse_pipe_any
|
||||
| '|' '|' $parse_pipe_pipe
|
||||
| '&' '&' $parse_amp_amp
|
||||
| escape_seq
|
||||
| sstring
|
||||
| fstring
|
||||
| bstring
|
||||
| dstring
|
||||
| vstring
|
||||
| estring
|
||||
| any
|
||||
)** ${ scratch.push_back(fc); };
|
||||
|
||||
'||' when not_special => {
|
||||
flush();
|
||||
parse(PIPE_PIPE, std::string(ts, te));
|
||||
};
|
||||
|
||||
'&&' when not_special => {
|
||||
flush();
|
||||
parse(AMP_AMP, std::string(ts, te));
|
||||
};
|
||||
|
||||
|
||||
# ( evaluate (1+2) ) is lparen, eval, rparen.
|
||||
# need to balance parens here and terminate a special token when it goes negative.
|
||||
|
||||
|
||||
'(' => {
|
||||
if (special()) {
|
||||
pcount++;
|
||||
scratch.push_back(fc);
|
||||
} else {
|
||||
flush();
|
||||
parse(LPAREN, std::string(ts, te));
|
||||
}
|
||||
};
|
||||
|
||||
')' => {
|
||||
if (special() && pcount-- > 0) scratch.push_back(fc);
|
||||
else {
|
||||
flush();
|
||||
scratch.push_back(fc);
|
||||
type = RPAREN;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
';' => { flush(); parse(SEMI, ";"); };
|
||||
|
||||
ws => { if (!scratch.empty()) scratch.push_back(fc); };
|
||||
|
||||
sstring => { scratch.append(ts, te); };
|
||||
dstring => { scratch.append(ts, te); };
|
||||
vstring => { scratch.append(ts, te); };
|
||||
escape_seq => { scratch.append(ts, te); };
|
||||
|
||||
|
||||
(any-escape-['"{]) => { scratch.push_back(fc); };
|
||||
*|;
|
||||
}%%
|
||||
|
||||
|
||||
%%{
|
||||
machine classify;
|
||||
machine argv0;
|
||||
alphtype unsigned char;
|
||||
|
||||
action push { argv0.push_back(tolower(fc)); }
|
||||
action break { fbreak; }
|
||||
|
||||
escape = 0xb6;
|
||||
ws = [ \t];
|
||||
|
||||
IF = /if/i;
|
||||
ELSE = /else/i;
|
||||
END = /end/i;
|
||||
BEGIN = /begin/i;
|
||||
EVALUATE = /evaluate/i;
|
||||
|
||||
# ` and { not supported here.
|
||||
|
||||
|
||||
main := |*
|
||||
IF %eof{ type = IF; return; };
|
||||
IF ws => { type = IF; return; };
|
||||
# hmmm ... only push the converted char - escape n = \n, for example.
|
||||
esc_seq =
|
||||
escape (
|
||||
'f' ${argv0.push_back('\f'); } |
|
||||
'n' ${argv0.push_back('\n'); } |
|
||||
't' ${argv0.push_back('\t'); } |
|
||||
[^fnt] $push
|
||||
);
|
||||
|
||||
ELSE %eof{ type = ELSE; return; };
|
||||
ELSE ws => { type = ELSE; return; };
|
||||
schar = [^'] $push;
|
||||
sstring = ['] schar** ['];
|
||||
|
||||
ELSE ws+ IF %eof{ type = ELSE_IF; return; };
|
||||
ELSE ws+ IF ws => { type = ELSE_IF; return; };
|
||||
dchar = esc_seq | (any-escape-["]) $push;
|
||||
dstring = ["] dchar** ["];
|
||||
|
||||
EVALUATE %eof{ type = EVALUATE; return; };
|
||||
EVALUATE ws => { type = EVALUATE; return; };
|
||||
|
||||
END %eof{ type = END; return; };
|
||||
END ws => { type = END; return; };
|
||||
|
||||
BEGIN %eof{ type = BEGIN; return; };
|
||||
BEGIN ws => { type = BEGIN; return; };
|
||||
|
||||
'(' => { type = LPAREN; return; };
|
||||
*|;
|
||||
# mpw doesn't handle quotes at this point,
|
||||
# so simplify and stop if we see anything invalid.
|
||||
main := (
|
||||
ws $break
|
||||
| [|<>] $break
|
||||
| 0xb7 $break
|
||||
| 0xb3 $break
|
||||
| [^a-zA-Z] ${ return COMMAND; }
|
||||
| any $push
|
||||
)**;
|
||||
|
||||
}%%
|
||||
|
||||
|
||||
int phase2::classify() {
|
||||
|
||||
%%machine argv0;
|
||||
%%write data;
|
||||
|
||||
if (type) return type;
|
||||
if (scratch.front() == ')') {
|
||||
type = RPAREN;
|
||||
return type;
|
||||
}
|
||||
std::string argv0;
|
||||
|
||||
const unsigned char *p = (const unsigned char *)scratch.data();
|
||||
const unsigned char *pe = p + scratch.size();
|
||||
int cs;
|
||||
|
||||
type = COMMAND;
|
||||
|
||||
%%write init;
|
||||
%%write exec;
|
||||
|
||||
// fprintf(stderr, "%s -> %s\n", scratch.c_str(), argv0.c_str());
|
||||
#undef _
|
||||
#define _(a,b) if (argv0 == a) { type = b; return type; }
|
||||
|
||||
// expand aliases?
|
||||
|
||||
_("begin", BEGIN)
|
||||
_("break", BREAK)
|
||||
_("continue", CONTINUE)
|
||||
_("else", ELSE)
|
||||
_("end", END)
|
||||
_("evaluate", EVALUATE)
|
||||
_("exit", EXIT)
|
||||
_("for", FOR)
|
||||
_("if", IF)
|
||||
_("loop", LOOP)
|
||||
|
||||
#undef _
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
%% machine classify;
|
||||
%% machine argv0;
|
||||
%% write data;
|
||||
|
||||
%% machine main;
|
||||
%% write data;
|
||||
}
|
||||
|
||||
void phase2::flush() {
|
||||
//fprintf(stderr, "flush: %s\n", scratch.c_str());
|
||||
// remove white space...
|
||||
while (!scratch.empty() && isspace(scratch.back())) scratch.pop_back();
|
||||
|
||||
|
||||
if (!scratch.empty()) {
|
||||
if (!type) classify();
|
||||
parse(type, std::move(scratch));
|
||||
}
|
||||
if (!scratch.empty()) parse(classify(), std::move(scratch));
|
||||
|
||||
type = 0;
|
||||
pcount = 0;
|
||||
scratch.clear();
|
||||
}
|
||||
|
||||
/* slightly wrong since whitespace is needed for it to be special. */
|
||||
bool phase2::special() {
|
||||
if (!type) classify();
|
||||
|
||||
switch (type) {
|
||||
switch (classify()) {
|
||||
case IF:
|
||||
case ELSE:
|
||||
case ELSE_IF:
|
||||
case EVALUATE:
|
||||
case BREAK:
|
||||
case CONTINUE:
|
||||
case EXIT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void phase2::classify() {
|
||||
if (type) return;
|
||||
if (scratch.empty()) return;
|
||||
|
||||
int cs;
|
||||
int act;
|
||||
const unsigned char *p = (const unsigned char *)scratch.data();
|
||||
const unsigned char *pe = p + scratch.size();
|
||||
const unsigned char *eof = pe;
|
||||
const unsigned char *te, *ts;
|
||||
|
||||
type = COMMAND;
|
||||
|
||||
%% machine classify;
|
||||
%% write init;
|
||||
%% write exec;
|
||||
void phase2::parse(int type, std::string &&s) {
|
||||
if (_then) _then(type, std::move(s));
|
||||
}
|
||||
|
||||
void phase2::process(const std::string &line) {
|
||||
void phase2::parse(std::string &&line) {
|
||||
|
||||
if (line.empty()) { finish(); return; }
|
||||
|
||||
int pcount = 0; // special form parens cannot cross lines.
|
||||
//fprintf(stderr, "-> %s\n", line.c_str());
|
||||
|
||||
int cs;
|
||||
int act;
|
||||
const unsigned char *p = (const unsigned char *)line.data();
|
||||
const unsigned char *pe = p + line.size();
|
||||
const unsigned char *eof = pe;
|
||||
const unsigned char *te, *ts;
|
||||
|
||||
scratch.clear();
|
||||
type = 0;
|
||||
pcount = 0; // parenthesis balancing within command only.
|
||||
|
||||
%% machine main;
|
||||
%% write init;
|
||||
%% write exec;
|
||||
|
||||
flush();
|
||||
// 2 NLs to make the stack reduce. harmless if in a multi-line constuct.
|
||||
parse(NL, "");
|
||||
parse(NL, "");
|
||||
|
||||
exec();
|
||||
if (_then) {
|
||||
_then(NL, "");
|
||||
_then(NL, "");
|
||||
}
|
||||
}
|
||||
|
||||
void phase2::finish() {
|
||||
parse(0, "");
|
||||
exec();
|
||||
}
|
||||
|
||||
void phase2::parse(int token, std::string &&s) {
|
||||
if (parser) parser->parse(token, std::move(s));
|
||||
void phase2::reset() {
|
||||
|
||||
type = 0;
|
||||
pcount = 0;
|
||||
scratch.clear();
|
||||
}
|
||||
|
||||
void phase2::exec() {
|
||||
|
||||
if (pipe_to && parser) {
|
||||
command_ptr_vector tmp;
|
||||
for (auto &p : parser->command_queue) {
|
||||
if (p) tmp.emplace_back(std::move(p));
|
||||
}
|
||||
parser->command_queue.clear();
|
||||
|
||||
if (!tmp.empty()) pipe_to(std::move(tmp));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
phase2::phase2() {
|
||||
parser = std::move(phase2_parser::make());
|
||||
//parser->trace(stdout, " ] ");
|
||||
|
||||
}
|
||||
|
||||
#pragma mark - phase2_parser
|
||||
|
||||
void phase2_parser::parse_accept() {
|
||||
error = false;
|
||||
}
|
||||
|
||||
void phase2_parser::parse_failure() {
|
||||
error = false;
|
||||
}
|
||||
|
||||
void phase2_parser::syntax_error(int yymajor, std::string &yyminor) {
|
||||
/*
|
||||
switch (yymajor) {
|
||||
case END:
|
||||
fprintf(stderr, "### MPW Shell - Extra END command.\n");
|
||||
break;
|
||||
|
||||
case RPAREN:
|
||||
fprintf(stderr, "### MPW Shell - Extra ) command.\n");
|
||||
break;
|
||||
|
||||
case ELSE:
|
||||
case ELSE_IF:
|
||||
fprintf(stderr, "### MPW Shell - ELSE must be within IF ... END.\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "### Parse error near %s\n", yyminor.c_str());
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
fprintf(stderr, "### MPW Shell - Parse error near %s\n", yymajor ? yyminor.c_str() : "EOF");
|
||||
error = true;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,20 @@
|
|||
#define PIPE_PIPE 1
|
||||
#define AMP_AMP 2
|
||||
#define PIPE 3
|
||||
#define SEMI 4
|
||||
#define NL 5
|
||||
#define COMMAND 6
|
||||
#define EVALUATE 7
|
||||
#define BREAK 8
|
||||
#define CONTINUE 9
|
||||
#define EXIT 10
|
||||
#define ERROR 11
|
||||
#define LPAREN 12
|
||||
#define RPAREN 13
|
||||
#define BEGIN 14
|
||||
#define END 15
|
||||
#define LOOP 16
|
||||
#define FOR 17
|
||||
#define IF 18
|
||||
#define ELSE_IF 19
|
||||
#define ELSE 20
|
|
@ -0,0 +1,238 @@
|
|||
|
||||
/*
|
||||
>, < redirection is handled later, after environment expansion.
|
||||
(also, redirection can be in the middle of a command.)
|
||||
*/
|
||||
|
||||
%include {
|
||||
|
||||
#include "phase3_parser.h"
|
||||
#include "command.h"
|
||||
#define LEMON_SUPER phase3
|
||||
}
|
||||
|
||||
|
||||
%code {
|
||||
|
||||
std::unique_ptr<phase3> phase3::make() {
|
||||
return std::make_unique<yypParser>();
|
||||
}
|
||||
|
||||
bool phase3::continuation() const {
|
||||
yypParser *self = (yypParser *)this;
|
||||
|
||||
for (const auto &e : *self) {
|
||||
if (e.major == BEGIN) return true;
|
||||
if (e.major == LPAREN) return true;
|
||||
if (e.major == IF) return true;
|
||||
if (e.major == AMP_AMP) return true;
|
||||
if (e.major == PIPE_PIPE) return true;
|
||||
if (e.major == LOOP) return true;
|
||||
if (e.major == FOR) return true;
|
||||
if (e.major == PIPE) return true;
|
||||
if (e.major == PIPE_PIPE) return true;
|
||||
if (e.major == AMP_AMP) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void phase3::parse_accept() {
|
||||
error = false;
|
||||
}
|
||||
|
||||
void phase3::parse_failure() {
|
||||
error = false;
|
||||
}
|
||||
|
||||
void phase3::syntax_error(int yymajor, std::string &yyminor) {
|
||||
/*
|
||||
switch (yymajor) {
|
||||
case END:
|
||||
fprintf(stderr, "### MPW Shell - Extra END command.\n");
|
||||
break;
|
||||
|
||||
case RPAREN:
|
||||
fprintf(stderr, "### MPW Shell - Extra ) command.\n");
|
||||
break;
|
||||
|
||||
case ELSE:
|
||||
case ELSE_IF:
|
||||
fprintf(stderr, "### MPW Shell - ELSE must be within IF ... END.\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "### Parse error near %s\n", yyminor.c_str());
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
fprintf(stderr, "### MPW Shell - Parse error near %s\n", yymajor ? yyminor.c_str() : "EOF");
|
||||
error = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
%left PIPE_PIPE AMP_AMP.
|
||||
%left PIPE.
|
||||
|
||||
%token_type {std::string}
|
||||
%default_type {command_ptr}
|
||||
|
||||
%type start {void}
|
||||
%type command_list {void}
|
||||
|
||||
/* these are put into a queue for immmediate execution */
|
||||
|
||||
|
||||
start ::= command_list.
|
||||
|
||||
|
||||
command_list ::= .
|
||||
command_list ::= command_list sep .
|
||||
command_list ::= command_list command(C) sep . {
|
||||
if (C) command_queue.emplace_back(std::move(C));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
compound_list is identical to command_list, but it is not executed immediately.
|
||||
*/
|
||||
|
||||
%type compound_list { command_ptr_vector }
|
||||
|
||||
|
||||
compound_list ::= .
|
||||
compound_list(L) ::= compound_list(L) sep.
|
||||
|
||||
compound_list(L) ::= compound_list(L) command(C) sep . {
|
||||
if (C) L.emplace_back(std::move(C));
|
||||
}
|
||||
|
||||
|
||||
sep ::= SEMI.
|
||||
sep ::= NL.
|
||||
|
||||
|
||||
%type command { command_ptr }
|
||||
|
||||
/* nb -- ||, &&, | -- both sides are optional. This does not. */
|
||||
|
||||
command(RV) ::= command(L) PIPE_PIPE opt_nl command(R). {
|
||||
RV = std::make_unique<or_command>(std::move(L), std::move(R));
|
||||
}
|
||||
|
||||
command(RV) ::= command(L) AMP_AMP opt_nl command(R). {
|
||||
RV = std::make_unique<and_command>(std::move(L), std::move(R));
|
||||
}
|
||||
|
||||
command(RV) ::= command(L) PIPE opt_nl command(R). {
|
||||
RV = std::make_unique<pipe_command>(std::move(L), std::move(R));
|
||||
}
|
||||
|
||||
command(C) ::= term(C).
|
||||
|
||||
term(RV) ::= COMMAND(C). { RV = std::make_unique<simple_command>(std::move(C)); }
|
||||
term(RV) ::= EVALUATE(C). { RV = std::make_unique<evaluate_command>(std::move(C)); }
|
||||
term(RV) ::= BREAK(C). { RV = std::make_unique<break_command>(std::move(C)); }
|
||||
term(RV) ::= CONTINUE(C). { RV = std::make_unique<continue_command>(std::move(C)); }
|
||||
term(RV) ::= EXIT(C). { RV = std::make_unique<exit_command>(std::move(C)); }
|
||||
term(C) ::= if_command(C).
|
||||
term(C) ::= begin_command(C).
|
||||
term(C) ::= paren_command(C).
|
||||
term(C) ::= loop_command(C).
|
||||
term(C) ::= for_command(C).
|
||||
|
||||
|
||||
/* lexer error (mismatched quotes, etc) */
|
||||
term(RV) ::= ERROR(C). {
|
||||
RV = std::make_unique<error_command>(@C, std::move(C));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* fall back to an end error. w/o fallback, it will cause a parse conflict.
|
||||
*/
|
||||
/*
|
||||
%fallback ERROR END RPAREN ELSE ELSE_IF.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
term(RV) ::= error RPAREN.
|
||||
term(RV) ::= error END.
|
||||
term(RV) ::= LPAREN error RPAREN.
|
||||
term(RV) ::= BEGIN error END.
|
||||
term(RV) ::= IF error END.
|
||||
term(RV) ::= LOOP error END.
|
||||
term(RV) ::= FOR error END.
|
||||
*/
|
||||
|
||||
/* compound list ends with a separator. paren command does not need the final separator */
|
||||
%type paren_list { command_ptr_vector }
|
||||
|
||||
paren_list(L) ::= compound_list(L) .
|
||||
|
||||
paren_list(L) ::= compound_list(L) command(C) . {
|
||||
L.emplace_back(std::move(C));
|
||||
}
|
||||
|
||||
paren_command(RV) ::= LPAREN(T) paren_list(L) RPAREN(E). {
|
||||
RV = std::make_unique<begin_command>(@T, std::move(L), std::move(T), std::move(E));
|
||||
}
|
||||
|
||||
|
||||
begin_command(RV) ::= BEGIN(T) sep compound_list(L) END(E). {
|
||||
RV = std::make_unique<begin_command>(@T, std::move(L), std::move(T), std::move(E));
|
||||
}
|
||||
|
||||
|
||||
loop_command(RV) ::= LOOP(T) sep compound_list(L) END(E). {
|
||||
RV = std::make_unique<loop_command>(@T, std::move(L), std::move(T), std::move(E));
|
||||
}
|
||||
|
||||
for_command(RV) ::= FOR(T) sep compound_list(L) END(E). {
|
||||
RV = std::make_unique<for_command>(@T, std::move(L), std::move(T), std::move(E));
|
||||
}
|
||||
|
||||
if_command(RV) ::= IF(I) sep compound_list(L) END(E). {
|
||||
|
||||
if_command::clause_vector_type v;
|
||||
v.emplace_back(std::make_unique<if_else_clause>(IF, std::move(L), std::move(I)));
|
||||
|
||||
RV = std::make_unique<if_command>(
|
||||
std::move(v),
|
||||
std::move(E)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
if_command(RV) ::= IF(I) sep compound_list(L) else_command(EC) END(E). {
|
||||
|
||||
if_command::clause_vector_type v;
|
||||
v.emplace_back(std::make_unique<if_else_clause>(IF, std::move(L), std::move(I)));
|
||||
for(auto &c : EC) { v.emplace_back(std::move(c)); }
|
||||
|
||||
RV = std::make_unique<if_command>(
|
||||
std::move(v), std::move(E));
|
||||
}
|
||||
|
||||
%token_class else ELSE_IF ELSE.
|
||||
|
||||
%type else_command { if_command::clause_vector_type }
|
||||
else_command(RV) ::= else(E) sep compound_list(L). {
|
||||
RV.emplace_back(std::make_unique<if_else_clause>(@E, std::move(L), std::move(E)));
|
||||
}
|
||||
|
||||
|
||||
else_command(EC) ::= else_command(EC) else(E) sep compound_list(L). {
|
||||
EC.emplace_back(std::make_unique<if_else_clause>(@E, std::move(L), std::move(E)));
|
||||
}
|
||||
|
||||
|
||||
opt_nl ::= .
|
||||
opt_nl ::= opt_nl NL .
|
|
@ -0,0 +1,822 @@
|
|||
State 0:
|
||||
start ::= * command_list
|
||||
(21) command_list ::= *
|
||||
command_list ::= * command_list sep
|
||||
command_list ::= * command_list command sep
|
||||
|
||||
start accept
|
||||
command_list shift 5
|
||||
{default} reduce 21 command_list ::=
|
||||
|
||||
State 1:
|
||||
compound_list ::= compound_list * sep
|
||||
compound_list ::= compound_list * command sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= IF sep compound_list * END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
if_command ::= IF sep compound_list * else_command END
|
||||
else_command ::= * ELSE_IF|ELSE sep compound_list
|
||||
else_command ::= * else_command ELSE_IF|ELSE sep compound_list
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
END shift-reduce 16 if_command ::= IF sep compound_list END
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
ELSE_IF shift 16
|
||||
ELSE shift 16
|
||||
sep shift-reduce 24 compound_list ::= compound_list sep
|
||||
command shift 13
|
||||
term shift 13 /* because term==command */
|
||||
if_command shift 13 /* because if_command==term */
|
||||
begin_command shift 13 /* because begin_command==term */
|
||||
paren_command shift 13 /* because paren_command==term */
|
||||
loop_command shift 13 /* because loop_command==term */
|
||||
for_command shift 13 /* because for_command==term */
|
||||
else_command shift 31
|
||||
|
||||
State 2:
|
||||
compound_list ::= compound_list * sep
|
||||
compound_list ::= compound_list * command sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
for_command ::= FOR sep compound_list * END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
END shift-reduce 15 for_command ::= FOR sep compound_list END
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
sep shift-reduce 24 compound_list ::= compound_list sep
|
||||
command shift 13
|
||||
term shift 13 /* because term==command */
|
||||
if_command shift 13 /* because if_command==term */
|
||||
begin_command shift 13 /* because begin_command==term */
|
||||
paren_command shift 13 /* because paren_command==term */
|
||||
loop_command shift 13 /* because loop_command==term */
|
||||
for_command shift 13 /* because for_command==term */
|
||||
|
||||
State 3:
|
||||
compound_list ::= compound_list * sep
|
||||
compound_list ::= compound_list * command sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
loop_command ::= LOOP sep compound_list * END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
END shift-reduce 14 loop_command ::= LOOP sep compound_list END
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
sep shift-reduce 24 compound_list ::= compound_list sep
|
||||
command shift 13
|
||||
term shift 13 /* because term==command */
|
||||
if_command shift 13 /* because if_command==term */
|
||||
begin_command shift 13 /* because begin_command==term */
|
||||
paren_command shift 13 /* because paren_command==term */
|
||||
loop_command shift 13 /* because loop_command==term */
|
||||
for_command shift 13 /* because for_command==term */
|
||||
|
||||
State 4:
|
||||
compound_list ::= compound_list * sep
|
||||
compound_list ::= compound_list * command sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
begin_command ::= BEGIN sep compound_list * END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
END shift-reduce 13 begin_command ::= BEGIN sep compound_list END
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
sep shift-reduce 24 compound_list ::= compound_list sep
|
||||
command shift 13
|
||||
term shift 13 /* because term==command */
|
||||
if_command shift 13 /* because if_command==term */
|
||||
begin_command shift 13 /* because begin_command==term */
|
||||
paren_command shift 13 /* because paren_command==term */
|
||||
loop_command shift 13 /* because loop_command==term */
|
||||
for_command shift 13 /* because for_command==term */
|
||||
|
||||
State 5:
|
||||
(20) start ::= command_list *
|
||||
command_list ::= command_list * sep
|
||||
command_list ::= command_list * command sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
|
||||
$ reduce 20 start ::= command_list
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
sep shift-reduce 22 command_list ::= command_list sep
|
||||
command shift 15
|
||||
term shift 15 /* because term==command */
|
||||
if_command shift 15 /* because if_command==term */
|
||||
begin_command shift 15 /* because begin_command==term */
|
||||
paren_command shift 15 /* because paren_command==term */
|
||||
loop_command shift 15 /* because loop_command==term */
|
||||
for_command shift 15 /* because for_command==term */
|
||||
|
||||
State 6:
|
||||
compound_list ::= compound_list * sep
|
||||
compound_list ::= compound_list * command sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
(18) else_command ::= ELSE_IF|ELSE sep compound_list *
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
sep shift-reduce 24 compound_list ::= compound_list sep
|
||||
command shift 13
|
||||
term shift 13 /* because term==command */
|
||||
if_command shift 13 /* because if_command==term */
|
||||
begin_command shift 13 /* because begin_command==term */
|
||||
paren_command shift 13 /* because paren_command==term */
|
||||
loop_command shift 13 /* because loop_command==term */
|
||||
for_command shift 13 /* because for_command==term */
|
||||
{default} reduce 18 else_command ::= ELSE_IF|ELSE sep compound_list
|
||||
|
||||
State 7:
|
||||
compound_list ::= compound_list * sep
|
||||
compound_list ::= compound_list * command sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
(19) else_command ::= else_command ELSE_IF|ELSE sep compound_list *
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
sep shift-reduce 24 compound_list ::= compound_list sep
|
||||
command shift 13
|
||||
term shift 13 /* because term==command */
|
||||
if_command shift 13 /* because if_command==term */
|
||||
begin_command shift 13 /* because begin_command==term */
|
||||
paren_command shift 13 /* because paren_command==term */
|
||||
loop_command shift 13 /* because loop_command==term */
|
||||
for_command shift 13 /* because for_command==term */
|
||||
{default} reduce 19 else_command ::= else_command ELSE_IF|ELSE sep compound_list
|
||||
|
||||
State 8:
|
||||
compound_list ::= compound_list * sep
|
||||
compound_list ::= compound_list * command sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
(33) paren_list ::= compound_list *
|
||||
paren_list ::= compound_list * command
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
sep shift-reduce 24 compound_list ::= compound_list sep
|
||||
command shift 14
|
||||
term shift 14 /* because term==command */
|
||||
if_command shift 14 /* because if_command==term */
|
||||
begin_command shift 14 /* because begin_command==term */
|
||||
paren_command shift 14 /* because paren_command==term */
|
||||
loop_command shift 14 /* because loop_command==term */
|
||||
for_command shift 14 /* because for_command==term */
|
||||
{default} reduce 33 paren_list ::= compound_list
|
||||
|
||||
State 9:
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= command PIPE opt_nl * command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
opt_nl ::= opt_nl * NL
|
||||
|
||||
NL shift-reduce 35 opt_nl ::= opt_nl NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
command shift-reduce 4 command ::= command PIPE opt_nl command
|
||||
term shift-reduce 4 command ::= command PIPE opt_nl command /* because term==command */
|
||||
if_command shift-reduce 4 command ::= command PIPE opt_nl command /* because if_command==term */
|
||||
begin_command shift-reduce 4 command ::= command PIPE opt_nl command /* because begin_command==term */
|
||||
paren_command shift-reduce 4 command ::= command PIPE opt_nl command /* because paren_command==term */
|
||||
loop_command shift-reduce 4 command ::= command PIPE opt_nl command /* because loop_command==term */
|
||||
for_command shift-reduce 4 command ::= command PIPE opt_nl command /* because for_command==term */
|
||||
|
||||
State 10:
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= command AMP_AMP opt_nl * command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
opt_nl ::= opt_nl * NL
|
||||
|
||||
NL shift-reduce 35 opt_nl ::= opt_nl NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
command shift 33
|
||||
term shift 33 /* because term==command */
|
||||
if_command shift 33 /* because if_command==term */
|
||||
begin_command shift 33 /* because begin_command==term */
|
||||
paren_command shift 33 /* because paren_command==term */
|
||||
loop_command shift 33 /* because loop_command==term */
|
||||
for_command shift 33 /* because for_command==term */
|
||||
|
||||
State 11:
|
||||
command ::= * command PIPE_PIPE opt_nl command
|
||||
command ::= command PIPE_PIPE opt_nl * command
|
||||
command ::= * command AMP_AMP opt_nl command
|
||||
command ::= * command PIPE opt_nl command
|
||||
command ::= * term
|
||||
term ::= * COMMAND
|
||||
term ::= * EVALUATE
|
||||
term ::= * BREAK
|
||||
term ::= * CONTINUE
|
||||
term ::= * EXIT
|
||||
term ::= * if_command
|
||||
term ::= * begin_command
|
||||
term ::= * paren_command
|
||||
term ::= * loop_command
|
||||
term ::= * for_command
|
||||
term ::= * ERROR
|
||||
paren_command ::= * LPAREN paren_list RPAREN
|
||||
begin_command ::= * BEGIN sep compound_list END
|
||||
loop_command ::= * LOOP sep compound_list END
|
||||
for_command ::= * FOR sep compound_list END
|
||||
if_command ::= * IF sep compound_list END
|
||||
if_command ::= * IF sep compound_list else_command END
|
||||
opt_nl ::= opt_nl * NL
|
||||
|
||||
NL shift-reduce 35 opt_nl ::= opt_nl NL
|
||||
COMMAND shift-reduce 5 term ::= COMMAND
|
||||
EVALUATE shift-reduce 6 term ::= EVALUATE
|
||||
BREAK shift-reduce 7 term ::= BREAK
|
||||
CONTINUE shift-reduce 8 term ::= CONTINUE
|
||||
EXIT shift-reduce 9 term ::= EXIT
|
||||
ERROR shift-reduce 10 term ::= ERROR
|
||||
LPAREN shift 12
|
||||
BEGIN shift 21
|
||||
LOOP shift 20
|
||||
FOR shift 19
|
||||
IF shift 18
|
||||
command shift 34
|
||||
term shift 34 /* because term==command */
|
||||
if_command shift 34 /* because if_command==term */
|
||||
begin_command shift 34 /* because begin_command==term */
|
||||
paren_command shift 34 /* because paren_command==term */
|
||||
loop_command shift 34 /* because loop_command==term */
|
||||
for_command shift 34 /* because for_command==term */
|
||||
|
||||
State 12:
|
||||
(23) compound_list ::= *
|
||||
compound_list ::= * compound_list sep
|
||||
compound_list ::= * compound_list command sep
|
||||
paren_list ::= * compound_list
|
||||
paren_list ::= * compound_list command
|
||||
paren_command ::= LPAREN * paren_list RPAREN
|
||||
|
||||
compound_list shift 8
|
||||
paren_list shift 32
|
||||
{default} reduce 23 compound_list ::=
|
||||
|
||||
State 13:
|
||||
compound_list ::= compound_list command * sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= command * PIPE_PIPE opt_nl command
|
||||
command ::= command * AMP_AMP opt_nl command
|
||||
command ::= command * PIPE opt_nl command
|
||||
|
||||
PIPE_PIPE shift 30
|
||||
AMP_AMP shift 29
|
||||
PIPE shift 28
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
sep shift-reduce 1 compound_list ::= compound_list command sep
|
||||
|
||||
State 14:
|
||||
compound_list ::= compound_list command * sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= command * PIPE_PIPE opt_nl command
|
||||
command ::= command * AMP_AMP opt_nl command
|
||||
command ::= command * PIPE opt_nl command
|
||||
(11) paren_list ::= compound_list command *
|
||||
|
||||
PIPE_PIPE shift 30
|
||||
AMP_AMP shift 29
|
||||
PIPE shift 28
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
sep shift-reduce 1 compound_list ::= compound_list command sep
|
||||
{default} reduce 11 paren_list ::= compound_list command
|
||||
|
||||
State 15:
|
||||
command_list ::= command_list command * sep
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
command ::= command * PIPE_PIPE opt_nl command
|
||||
command ::= command * AMP_AMP opt_nl command
|
||||
command ::= command * PIPE opt_nl command
|
||||
|
||||
PIPE_PIPE shift 30
|
||||
AMP_AMP shift 29
|
||||
PIPE shift 28
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
sep shift-reduce 0 command_list ::= command_list command sep
|
||||
|
||||
State 16:
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
else_command ::= ELSE_IF|ELSE * sep compound_list
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
sep shift 22
|
||||
|
||||
State 17:
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
else_command ::= else_command ELSE_IF|ELSE * sep compound_list
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
sep shift 23
|
||||
|
||||
State 18:
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
if_command ::= IF * sep compound_list END
|
||||
if_command ::= IF * sep compound_list else_command END
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
sep shift 24
|
||||
|
||||
State 19:
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
for_command ::= FOR * sep compound_list END
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
sep shift 25
|
||||
|
||||
State 20:
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
loop_command ::= LOOP * sep compound_list END
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
sep shift 26
|
||||
|
||||
State 21:
|
||||
sep ::= * SEMI
|
||||
sep ::= * NL
|
||||
begin_command ::= BEGIN * sep compound_list END
|
||||
|
||||
SEMI shift-reduce 25 sep ::= SEMI
|
||||
NL shift-reduce 26 sep ::= NL
|
||||
sep shift 27
|
||||
|
||||
State 22:
|
||||
(23) compound_list ::= *
|
||||
compound_list ::= * compound_list sep
|
||||
compound_list ::= * compound_list command sep
|
||||
else_command ::= ELSE_IF|ELSE sep * compound_list
|
||||
|
||||
compound_list shift 6
|
||||
{default} reduce 23 compound_list ::=
|
||||
|
||||
State 23:
|
||||
(23) compound_list ::= *
|
||||
compound_list ::= * compound_list sep
|
||||
compound_list ::= * compound_list command sep
|
||||
else_command ::= else_command ELSE_IF|ELSE sep * compound_list
|
||||
|
||||
compound_list shift 7
|
||||
{default} reduce 23 compound_list ::=
|
||||
|
||||
State 24:
|
||||
(23) compound_list ::= *
|
||||
compound_list ::= * compound_list sep
|
||||
compound_list ::= * compound_list command sep
|
||||
if_command ::= IF sep * compound_list END
|
||||
if_command ::= IF sep * compound_list else_command END
|
||||
|
||||
compound_list shift 1
|
||||
{default} reduce 23 compound_list ::=
|
||||
|
||||
State 25:
|
||||
(23) compound_list ::= *
|
||||
compound_list ::= * compound_list sep
|
||||
compound_list ::= * compound_list command sep
|
||||
for_command ::= FOR sep * compound_list END
|
||||
|
||||
compound_list shift 2
|
||||
{default} reduce 23 compound_list ::=
|
||||
|
||||
State 26:
|
||||
(23) compound_list ::= *
|
||||
compound_list ::= * compound_list sep
|
||||
compound_list ::= * compound_list command sep
|
||||
loop_command ::= LOOP sep * compound_list END
|
||||
|
||||
compound_list shift 3
|
||||
{default} reduce 23 compound_list ::=
|
||||
|
||||
State 27:
|
||||
(23) compound_list ::= *
|
||||
compound_list ::= * compound_list sep
|
||||
compound_list ::= * compound_list command sep
|
||||
begin_command ::= BEGIN sep * compound_list END
|
||||
|
||||
compound_list shift 4
|
||||
{default} reduce 23 compound_list ::=
|
||||
|
||||
State 28:
|
||||
command ::= command PIPE * opt_nl command
|
||||
(34) opt_nl ::= *
|
||||
opt_nl ::= * opt_nl NL
|
||||
|
||||
opt_nl shift 9
|
||||
{default} reduce 34 opt_nl ::=
|
||||
|
||||
State 29:
|
||||
command ::= command AMP_AMP * opt_nl command
|
||||
(34) opt_nl ::= *
|
||||
opt_nl ::= * opt_nl NL
|
||||
|
||||
opt_nl shift 10
|
||||
{default} reduce 34 opt_nl ::=
|
||||
|
||||
State 30:
|
||||
command ::= command PIPE_PIPE * opt_nl command
|
||||
(34) opt_nl ::= *
|
||||
opt_nl ::= * opt_nl NL
|
||||
|
||||
opt_nl shift 11
|
||||
{default} reduce 34 opt_nl ::=
|
||||
|
||||
State 31:
|
||||
if_command ::= IF sep compound_list else_command * END
|
||||
else_command ::= else_command * ELSE_IF|ELSE sep compound_list
|
||||
|
||||
END shift-reduce 17 if_command ::= IF sep compound_list else_command END
|
||||
ELSE_IF shift 17
|
||||
ELSE shift 17
|
||||
|
||||
State 32:
|
||||
paren_command ::= LPAREN paren_list * RPAREN
|
||||
|
||||
RPAREN shift-reduce 12 paren_command ::= LPAREN paren_list RPAREN
|
||||
|
||||
State 33:
|
||||
command ::= command * PIPE_PIPE opt_nl command
|
||||
command ::= command * AMP_AMP opt_nl command
|
||||
(3) command ::= command AMP_AMP opt_nl command *
|
||||
command ::= command * PIPE opt_nl command
|
||||
|
||||
PIPE shift 28
|
||||
{default} reduce 3 command ::= command AMP_AMP opt_nl command
|
||||
|
||||
State 34:
|
||||
command ::= command * PIPE_PIPE opt_nl command
|
||||
(2) command ::= command PIPE_PIPE opt_nl command *
|
||||
command ::= command * AMP_AMP opt_nl command
|
||||
command ::= command * PIPE opt_nl command
|
||||
|
||||
PIPE shift 28
|
||||
{default} reduce 2 command ::= command PIPE_PIPE opt_nl command
|
||||
|
||||
----------------------------------------------------
|
||||
Symbols:
|
||||
0: $:
|
||||
1: PIPE_PIPE
|
||||
2: AMP_AMP
|
||||
3: PIPE
|
||||
4: SEMI
|
||||
5: NL
|
||||
6: COMMAND
|
||||
7: EVALUATE
|
||||
8: BREAK
|
||||
9: CONTINUE
|
||||
10: EXIT
|
||||
11: ERROR
|
||||
12: LPAREN
|
||||
13: RPAREN
|
||||
14: BEGIN
|
||||
15: END
|
||||
16: LOOP
|
||||
17: FOR
|
||||
18: IF
|
||||
19: ELSE_IF
|
||||
20: ELSE
|
||||
21: error:
|
||||
22: start: <lambda> SEMI NL COMMAND EVALUATE BREAK CONTINUE EXIT ERROR LPAREN BEGIN LOOP FOR IF
|
||||
23: command_list: <lambda> SEMI NL COMMAND EVALUATE BREAK CONTINUE EXIT ERROR LPAREN BEGIN LOOP FOR IF
|
||||
24: sep: SEMI NL
|
||||
25: command: COMMAND EVALUATE BREAK CONTINUE EXIT ERROR LPAREN BEGIN LOOP FOR IF
|
||||
26: compound_list: <lambda> SEMI NL COMMAND EVALUATE BREAK CONTINUE EXIT ERROR LPAREN BEGIN LOOP FOR IF
|
||||
27: opt_nl: <lambda> NL
|
||||
28: term: COMMAND EVALUATE BREAK CONTINUE EXIT ERROR LPAREN BEGIN LOOP FOR IF
|
||||
29: if_command: IF
|
||||
30: begin_command: BEGIN
|
||||
31: paren_command: LPAREN
|
||||
32: loop_command: LOOP
|
||||
33: for_command: FOR
|
||||
34: paren_list: <lambda> SEMI NL COMMAND EVALUATE BREAK CONTINUE EXIT ERROR LPAREN BEGIN LOOP FOR IF
|
||||
35: else_command: ELSE_IF ELSE
|
|
@ -0,0 +1,43 @@
|
|||
|
||||
#ifndef __phase3_h__
|
||||
#define __phase3_h__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "command.h"
|
||||
#include <lemon_base.h>
|
||||
|
||||
class phase3 : public lemon_base<std::string> {
|
||||
|
||||
public:
|
||||
static std::unique_ptr<phase3> make();
|
||||
|
||||
virtual void syntax_error(int yymajor, std::string &yyminor) override final;
|
||||
virtual void parse_accept() override final;
|
||||
virtual void parse_failure() override final;
|
||||
|
||||
bool continuation() const;
|
||||
|
||||
//void parse(int type, std::string &&s) override final;
|
||||
|
||||
private:
|
||||
|
||||
phase3(const phase3 &) = delete;
|
||||
phase3(phase3 &&) = delete;
|
||||
|
||||
phase3& operator=(const phase3 &) = delete;
|
||||
phase3& operator=(phase3 &&) = delete;
|
||||
|
||||
protected:
|
||||
// these need to be accessible to the lemon-generated parser.
|
||||
phase3() = default;
|
||||
|
||||
command_ptr_vector command_queue;
|
||||
bool error = false;
|
||||
|
||||
friend class mpw_parser;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue