Compare commits

...

149 Commits
r0.0 ... master

Author SHA1 Message Date
Kelvin Sherlock 91590e92aa add missing (mostly macroman) operators 2022-11-25 16:02:27 -05:00
Kelvin Sherlock 6199944350 script parameters, move shift to the environment 2022-11-23 21:22:00 -05:00
Kelvin Sherlock f799eb6d81 allow break within loop ... end 2022-11-23 18:26:30 -05:00
Kelvin Sherlock 298c601300 include trailing text with the ) token so redirection works correctly 2022-11-23 18:18:32 -05:00
Kelvin Sherlock ce1a36eba5 add regular expression support for evaluate, if, etc.
eg:

evaluate abc =~ /(abc)®4/ # sets the '@4' environment variable.
evaluate abc !~ /[aA]bc/

MPW regular expressions are converted to c++11 std::regex regular expressions and evaluated.
Sadly, the // regular expression syntax interferes with unix-paths (if the / count is odd).  quoting or ∂-escaping the /s is therefore necessary.

file globbing is not yet implemented.
2022-11-22 17:22:49 -05:00
Kelvin Sherlock a90ca3c849 use classic macos file type to identify scripts. 2022-11-13 22:47:23 -05:00
Kelvin Sherlock fdf33c69b7 token support for regular expression strings /.../ and \...\. Also tokenizer support for =~ and !~ operators. 2022-11-02 21:42:25 -04:00
Kelvin Sherlock 6f2b59c4d6 script support. scripts run with an independent copy of the environment and aliases. local variables are not imported.
Currently, it for a ".text" extension to check if it's a script; this is a placeholder.
2022-11-02 21:42:25 -04:00
ksherlock fd94247aec
Merge pull request #6 from uliwitness/feature/docs-fixes
Add build instructions to Readme.
2022-10-16 12:12:08 -04:00
Uli Kusterer 8e54c6519c Add build instructions to Readme. 2022-10-16 14:39:56 +02:00
ksherlock 0c1419ec98
Update cmake-macos.yml 2022-09-25 13:19:26 -04:00
Kelvin Sherlock 225c3b8ebd open(O_CREATE) needs 3rd parameter. 2020-12-01 23:31:28 -05:00
ksherlock da24b85f68
Create cmake-ubuntu.yml 2020-12-01 23:21:37 -05:00
ksherlock f25a30edc7
Update cmake.yml 2020-12-01 23:08:34 -05:00
ksherlock e8c0080f77
Create cmake.yml 2020-12-01 23:05:43 -05:00
Kelvin Sherlock b8cada73e8 Merge branch 'master' of github.com:ksherlock/mpw-shell 2020-12-01 23:01:48 -05:00
Kelvin Sherlock be1c6c14fa std::move warning. 2020-12-01 23:01:00 -05:00
Kelvin Sherlock a15d2bf257 include generated lemon++ parser code so lemon++ isn't needed. 2020-12-01 23:00:41 -05:00
ksherlock 87b2cc0902
Update .travis.yml 2017-11-26 14:40:30 -05:00
Kelvin Sherlock 5d95f10dd8 remove trailing newline from sub-shell strings. 2017-11-26 14:02:59 -05:00
ksherlock 9d4340b3ac Merge pull request #4 from MaddTheSane/vsprintfFix
Fix messed up varargs call in environment
2017-05-01 18:06:04 -04:00
C.W. Betts 45df4524ea Fix messed up varargs calls. 2017-05-01 15:44:42 -06:00
Kelvin Sherlock 474b10ccaa look for ~/mpw, /usr/share/mpw, /usr/local/mpw 2016-10-08 09:55:31 -04:00
Kelvin Sherlock ed96470e18 builtin_help 2016-10-08 09:54:06 -04:00
Kelvin Sherlock 7b99997f28 Bump Version: 0.4 2016-09-24 12:51:24 -04:00
Kelvin Sherlock 42f9552352 nl @ end. 2016-09-24 12:49:10 -04:00
Kelvin Sherlock fa804e91f0 clean up cmakefile a little bit more. 2016-08-31 14:43:29 -04:00
Kelvin Sherlock 0ec15bcd63 lunix headers. 2016-08-30 20:59:44 -04:00
Kelvin Sherlock 06e2e1f309 improved option parsing for mpw-make. 2016-08-30 20:57:37 -04:00
Kelvin Sherlock 32c72cb89a update readme. 2016-08-30 20:56:54 -04:00
Kelvin Sherlock f125b533f7 Squashed commit of the following:
commit f0944a89f27e44b1764988806e655f09764e80df
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Tue Aug 30 12:24:08 2016 -0400

    exit throws execution of input error w/ possible 0 value.  catch it.

commit 9e7f9c1ae049aa26513413f4767268b47ee22e98
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Tue Aug 30 12:23:21 2016 -0400

    builtins - more consistent argument handling.

commit be4c1c902f5a3a3f01e92ae52c7d6cc5d8731b65
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Tue Aug 30 12:23:01 2016 -0400

    .

commit 68d0c29fec112c6e7bc3a672b41eb7eb758a8941
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Tue Aug 30 12:22:51 2016 -0400

    exit command.

commit 25b0a7f7da9220b03026123bb5072c2da1d73fde
Author: Kelvin Sherlock <ksherlock@gmail.com>
Date:   Tue Aug 30 12:21:16 2016 -0400

    builtin quit command.
2016-08-30 12:25:43 -04:00
Kelvin Sherlock f6c5478063 gcc/case sensitive fixes. 2016-08-17 13:14:38 -04:00
Kelvin Sherlock efd51bcb48 load UserStartup file. 2016-08-17 13:14:16 -04:00
Kelvin Sherlock 33dd3228f8 update the parser/lexer to make it more reusable. For example, subshells are now handled. 2016-08-16 16:47:20 -04:00
Kelvin Sherlock 548880a517 support -ledit or -lreadline 2016-08-12 14:50:18 -04:00
Kelvin Sherlock 1da6c3e9da fix + / - confusion. 2016-08-11 16:09:39 -04:00
Kelvin Sherlock bdba86249e prevent pipe command from running commands multiple times. 2016-08-11 12:30:01 -04:00
Kelvin Sherlock 87f5398649 mpw-make -- find the Make program. 2016-08-10 20:08:28 -04:00
Kelvin Sherlock 811c8b976a ... and use the config.h 2016-08-09 15:29:28 -04:00
Kelvin Sherlock 0422976719 generate config.h via CMake. 2016-08-09 15:29:10 -04:00
Kelvin Sherlock 2893f7fe79 PATH_MAX + 1 2016-08-09 14:47:11 -04:00
Kelvin Sherlock 34a4f431c0 noexcept mapped_file constructors. 2016-08-09 14:40:27 -04:00
Kelvin Sherlock f3db9b7cc0 shut up warning 2016-08-09 14:40:00 -04:00
Kelvin Sherlock a4e724a1a6 Bump Version: 0.3 2016-08-07 15:38:04 -04:00
Kelvin Sherlock fbcbfffcb5 mapped file -- if length is 0, return an unopened mapped file. Posix would EINVAL on a length of 0. 2016-08-07 15:37:02 -04:00
Kelvin Sherlock 50ac7355bd solaris / PATH_MAX 2016-08-07 15:35:54 -04:00
Kelvin Sherlock 827f49c48b solaris... 2016-08-07 14:43:18 -04:00
Kelvin Sherlock e2affa1bdd be more helpful if mpw / Startup file does not exist. 2016-08-06 17:55:26 -04:00
Kelvin Sherlock 76980a6e06 headers 2016-08-05 22:38:06 -04:00
Kelvin Sherlock ba0fe6268f headers 2016-08-05 22:35:52 -04:00
Kelvin Sherlock 47b734a5fa headers. 2016-08-05 22:32:00 -04:00
Kelvin Sherlock f9bbf7a2f8 gcc/lunix 2016-08-05 22:14:36 -04:00
Kelvin Sherlock 8c8a768530 gcc/lunix 2016-08-05 22:07:22 -04:00
Kelvin Sherlock 27c4eadf93 gcc/lunix 2016-08-05 22:06:36 -04:00
Kelvin Sherlock ca54485061 gcc/lunix 2016-08-05 22:05:50 -04:00
Kelvin Sherlock f8c596668f lunix/gcc updates. 2016-08-05 22:00:48 -04:00
Kelvin Sherlock 469f0a23c1 fix for false || true 2016-08-05 11:56:38 -04:00
Kelvin Sherlock 9d5d3ca9e8 lemon left-hand-side optimizations 2016-08-05 10:34:05 -04:00
Kelvin Sherlock 5b343cc7dd use updated lemon++. 2016-08-05 10:25:17 -04:00
Kelvin Sherlock 56f945ce29 simplify argv0 lookup a little bit. 2016-07-28 16:30:17 -04:00
Kelvin Sherlock b9782a0926 phase 1 -- escape nl in comment joins the line. 2016-07-28 15:26:37 -04:00
Kelvin Sherlock ed341db9fa clean up error reporting. 2016-07-28 14:03:52 -04:00
Kelvin Sherlock edcb832c13 fix up execution a little more 2016-07-28 13:58:13 -04:00
Kelvin Sherlock c2c41f3a52 rewrite command execution to be more consistent with real mpw. 2016-07-28 13:44:00 -04:00
Kelvin Sherlock 413b9a805b tokenizer -- remove quotes as separate step, return clean command line. 2016-07-28 13:43:33 -04:00
Kelvin Sherlock a2a48fcba7 echo is const. 2016-07-28 13:42:29 -04:00
Kelvin Sherlock ad523f258e clean up errors a little bit. 2016-07-28 13:42:18 -04:00
Kelvin Sherlock 544f3a994c fix << evaluation. 2016-07-27 16:28:48 -04:00
Kelvin Sherlock 683b06b3b5 fix shell expansion -- retain "s 2016-07-27 16:28:28 -04:00
Kelvin Sherlock 9b16e98133 fix up tokenizer a little bit 2016-07-27 16:28:11 -04:00
Kelvin Sherlock fe76877693 rewrite variable expansion, add initial support for `...` command expansion. 2016-07-27 14:06:57 -04:00
Kelvin Sherlock 47af010ba5 mpw errors 2016-07-27 14:04:57 -04:00
Kelvin Sherlock 1a98acb756 update readme a little more. 2016-07-26 20:22:38 -04:00
Kelvin Sherlock 933f23de91 update readme. 2016-07-26 19:50:04 -04:00
Kelvin Sherlock 62612a10d3 white space. 2016-07-26 16:07:51 -04:00
Kelvin Sherlock 05f48c6a3c implement pipe. 2016-07-26 16:07:44 -04:00
Kelvin Sherlock 0524d10590 parse | pipe lines. 2016-07-26 14:20:11 -04:00
Kelvin Sherlock 2fdca6ea9d builtin_which -- also check builtin commands. 2016-07-24 21:21:15 -04:00
Kelvin Sherlock 9b577bdbb1 builtin catenate (builtin so cr/lf conversion blocked) 2016-07-23 23:50:28 -04:00
Kelvin Sherlock 84737e1cf7 use binary search for unicode -> macron. 2016-07-23 16:05:05 -04:00
Kelvin Sherlock f97625eba7 fix comment. 2016-07-23 16:04:13 -04:00
Kelvin Sherlock a366bbf2cb mpw-make - exit on error if {exit} 2016-07-23 15:29:53 -04:00
Kelvin Sherlock 76d5c9a474 smarter utf8/macroman 2016-07-23 15:29:22 -04:00
Kelvin Sherlock db8e9af504 macroman / utf 8 conversions. 2016-07-23 15:21:13 -04:00
Kelvin Sherlock fac76f1b54 shell tokenizer -- support extended characters. 2016-07-23 15:20:56 -04:00
Kelvin Sherlock 5c333cbedd -v doesn't take a parameter. 2016-07-23 15:20:24 -04:00
Kelvin Sherlock 34900a00b8 clean up must_quote a little bit, 2016-07-23 12:58:01 -04:00
Kelvin Sherlock da92eb4e36 clean up tokenizer. 2016-07-23 12:50:38 -04:00
Kelvin Sherlock c28cfb0710 rewrite phase-2 lexical analysis. It splits on ; ( ) || && and I've deferred the invalid string checks until later. 2016-07-23 11:54:46 -04:00
Kelvin Sherlock 31f33096cb ERROR terminal for parser. 2016-07-23 11:40:40 -04:00
Kelvin Sherlock 6d929aa87f convert utf8 input to macroman. 2016-07-21 15:14:27 -04:00
Kelvin Sherlock 80aaaa208d -f flag (don't load Startup file) 2016-07-21 11:48:41 -04:00
Kelvin Sherlock fed90b3753 rewrite phase1 in C -- slightly strange processing is ugly in ragel.
Also move error checking for strings/variables until later.
2016-07-21 11:46:39 -04:00
Kelvin Sherlock 0ba9574d2d adjust cmakelist 2016-07-19 13:40:57 -04:00
Kelvin Sherlock 40a92c7976 remove old file 2016-07-19 13:40:46 -04:00
Kelvin Sherlock 5f9293a9e2 remove old files no longer in use. 2016-07-19 12:58:27 -04:00
Kelvin Sherlock 7984ccca54 travis - make no longer needs to be installed. 2016-07-19 12:47:38 -04:00
Kelvin Sherlock 42806cdd9b . 2016-07-19 12:37:25 -04:00
Kelvin Sherlock 686bee2578 proper -h help. 2016-07-19 12:37:12 -04:00
Kelvin Sherlock adbf776d31 --shell support in MPW 2016-07-19 12:37:02 -04:00
Kelvin Sherlock b4db751cbe fix parenthesis processing. left-paren is only a command when it's argv0. Otherwise it's a normal character. (but must be balanced with right-paren to know when right-paren is a separator. ). 2016-06-26 13:04:32 -04:00
Kelvin Sherlock 84b24e6379 more child signal stuff. 2016-06-24 14:37:33 -04:00
Kelvin Sherlock e51e757556 re-set child signal handlers to default. 2016-06-24 14:30:27 -04:00
Kelvin Sherlock bc2381a360 fix whitespace 2016-06-24 10:31:34 -04:00
Kelvin Sherlock a44d2d3e4d user map for environment variables so they print in alphabetical order. 2016-06-22 13:48:26 -04:00
Kelvin Sherlock 45eade7af5 builtin_alias / builtin_unalias. 2016-06-22 13:48:05 -04:00
Kelvin Sherlock 6bfad57a35 offset_range 2016-06-22 13:46:19 -04:00
Kelvin Sherlock ca6d8a453e Bump Version: 0.2 2016-06-17 22:22:43 -04:00
Kelvin Sherlock a15c6fbd65 clean up env set via number. 2016-06-16 22:00:03 -04:00
Kelvin Sherlock ef99bb40de version bump 2016-06-16 21:55:39 -04:00
Kelvin Sherlock d56d689f98 Bump Version: 0.2 2016-06-16 21:54:19 -04:00
Kelvin Sherlock 8a2b9ec3cd shift builtin. 2016-06-16 16:48:04 -04:00
Kelvin Sherlock c8f1e370dc update readme 2016-06-16 09:39:43 -04:00
Kelvin Sherlock 92ddf18766 FOR var IN ... ; END support. 2016-06-16 00:04:29 -04:00
Kelvin Sherlock 701786277b . 2016-06-15 23:19:34 -04:00
Kelvin Sherlock 97bcf8259d updated readme 2016-06-15 23:04:14 -04:00
Kelvin Sherlock 322a32af65 support for Loop ... End, Break, and Continue. 2016-06-15 23:01:03 -04:00
Kelvin Sherlock edd80fc3c5 version builtin 2016-06-15 13:25:41 -04:00
Kelvin Sherlock 7724ca0d7c add Exists builtin. 2016-06-15 11:25:25 -04:00
Kelvin Sherlock 6ff7b50a7d use atomic variable for the control-c flag. 2016-02-22 12:03:58 -05:00
Kelvin Sherlock fafb08b90a use _PATH_DEFPATH if $PATH is null. 2016-02-22 12:02:27 -05:00
Kelvin Sherlock 56b0c93fd7 extra stuff 2016-02-22 09:59:50 -05:00
Kelvin Sherlock 0d0367ca08 builtin about box, bump version number. 2016-02-11 21:50:25 -05:00
Kelvin Sherlock ca4fafb62c filesystem::canonical 2016-02-11 20:50:38 -05:00
Kelvin Sherlock 7d18720162 use filesystem::current_path for builtin_directoery 2016-02-11 20:50:24 -05:00
Kelvin Sherlock 034321830f command updates 2016-02-11 15:51:56 -05:00
Kelvin Sherlock beb3e3813a builtin_which 2016-02-11 15:51:39 -05:00
Kelvin Sherlock a6913f7c46 Environment::get 2016-02-11 15:49:19 -05:00
Kelvin Sherlock a7dce37fe6 shell -- use filesystem::path stuff 2016-02-11 15:49:05 -05:00
Kelvin Sherlock 50f171d5c8 which command 2016-02-11 15:48:46 -05:00
Kelvin Sherlock 446e3e5e1e move cxx stuff into cxx directory 2016-02-11 15:46:40 -05:00
Kelvin Sherlock 9316311a1d mow-make -- load startup. also, don't hardcode path 2016-02-10 23:54:12 -05:00
Kelvin Sherlock 4131616420 clean up whitespace coalesce a bit 2016-02-10 23:53:41 -05:00
Kelvin Sherlock 2485f08172 commentary 2016-02-10 23:51:45 -05:00
Kelvin Sherlock a8920e2f53 pathname conversion -- handle dev:null, dev:stdout, dev:stdin, dev:stderr 2016-02-10 23:51:37 -05:00
Kelvin Sherlock 1339c0891f adjust indents 2016-02-10 21:58:00 -05:00
Kelvin Sherlock 6f53faeae7 fix directory return value 2016-02-10 21:57:35 -05:00
Kelvin Sherlock 3fea71d1e8 directory -- support for MacOS pathnames. 2016-02-09 23:50:33 -05:00
Kelvin Sherlock acf616845e add make help 2016-02-06 12:55:00 -05:00
Kelvin Sherlock b52565bc37 fix install 2016-02-06 12:54:52 -05:00
Kelvin Sherlock a65cdfead0 make install 2016-02-06 12:47:42 -05:00
Kelvin Sherlock d98247bd12 mpw-make support 2016-02-05 23:00:42 -05:00
Kelvin Sherlock 41e1424644 fix control-c 2016-02-05 13:47:34 -05:00
Kelvin Sherlock 0dfecbd520 control-c support 2016-02-05 13:19:20 -05:00
Kelvin Sherlock f58f6d4115 fixes for travis ci / abort support. 2016-02-05 12:42:22 -05:00
Kelvin Sherlock 353832ca3a travis ci 2016-02-05 12:25:51 -05:00
Kelvin Sherlock e2b1306b30 travis ci lemon++ 2016-02-05 12:20:04 -05:00
Kelvin Sherlock 371ce08ef6 continuation prompt. 2016-02-04 21:57:17 -05:00
Kelvin Sherlock 449595c56b simplify grammar a little bit. 2016-02-04 21:45:04 -05:00
57 changed files with 9677 additions and 3484 deletions

36
.github/workflows/cmake-macos.yml vendored Normal file
View File

@ -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

29
.github/workflows/cmake-ubuntu.yml vendored Normal file
View File

@ -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

View File

@ -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"

View File

@ -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
)

90
README.md Normal file
View File

@ -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`.

File diff suppressed because it is too large Load Diff

View File

@ -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 &);

File diff suppressed because it is too large Load Diff

View File

@ -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

7
config.h.in Normal file
View File

@ -0,0 +1,7 @@
#ifndef __mpw_shell_config_h__
#define __mpw_shell_config_h__
#cmakedefine HAVE_DPRINTF
#endif

193
cxx/directory_iterator.cpp Normal file
View File

@ -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;
}
}

337
cxx/filesystem.cpp Normal file
View File

@ -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();
}
}

849
cxx/filesystem.h Normal file
View File

@ -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

View File

@ -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();

View File

@ -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);
}

285
cxx/path.cpp Normal file
View File

@ -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);
}
}

81
cxx/string_splitter.h Normal file
View File

@ -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

95
cxx/unique_resource.h Normal file
View File

@ -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

View File

@ -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();
}

View File

@ -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
View File

@ -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

74
etc/MPW Shell.terminal Normal file
View File

@ -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>

57
etc/Startup Normal file
View File

@ -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

View File

@ -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() {

View File

@ -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

File diff suppressed because it is too large Load Diff

163
macroman.cpp Normal file
View File

@ -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;
}

8
macroman.h Normal file
View File

@ -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

130
macroman.x Normal file
View File

@ -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")

32
make-version.rb Normal file
View File

@ -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

269
mpw-regex.cpp Normal file
View File

@ -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;
}

48
mpw-regex.h Normal file
View File

@ -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

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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

View File

@ -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();
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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

106
mpw_parser.cpp Normal file
View File

@ -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;
}
}

59
mpw_parser.h Normal file
View File

@ -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

303
pathnames.rl Normal file
View File

@ -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

207
phase1.cpp Normal file
View File

@ -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();
}

View File

@ -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

View File

@ -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 :=
(

View File

@ -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.

View File

@ -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
View File

@ -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;
}

1862
phase3.cpp Normal file

File diff suppressed because it is too large Load Diff

20
phase3.h Normal file
View File

@ -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

238
phase3.lemon Normal file
View File

@ -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 .

822
phase3.out Normal file
View File

@ -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

43
phase3_parser.h Normal file
View File

@ -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

5
version.h Normal file
View File

@ -0,0 +1,5 @@
#ifndef __version_h__
#define __version_h__
#define VERSION "0.4"
#define VERSION_DATE "Sat Sep 24 12:51:22 2016"
#endif