m68kmac: Add a port to m68k Macintosh.

This is lightly tested with umac emulating a 4MB Macintosh Plus.
This commit is contained in:
Jeff Epler 2025-06-25 20:51:45 +02:00
parent 536f6fbfcc
commit ee591eb58b
55 changed files with 3531 additions and 1250 deletions

View file

@ -1,16 +0,0 @@
name: JavaScript code lint and formatting with Biome
on: [push, pull_request]
jobs:
eslint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: 1.5.3
- name: Run Biome
run: biome ci --indent-style=space --indent-width=4 tests/ ports/webassembly

View file

@ -1,50 +0,0 @@
name: Check code size
on:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'ports/bare-arm/**'
- 'ports/mimxrt/**'
- 'ports/minimal/**'
- 'ports/rp2/**'
- 'ports/samd/**'
- 'ports/stm32/**'
- 'ports/unix/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 100
- name: Install packages
run: source tools/ci.sh && ci_code_size_setup
- name: Build
run: source tools/ci.sh && ci_code_size_build
- name: Compute code size difference
run: tools/metrics.py diff ~/size0 ~/size1 | tee diff
- name: Save PR number
if: github.event_name == 'pull_request'
env:
PR_NUMBER: ${{ github.event.number }}
run: echo $PR_NUMBER > pr_number
- name: Upload diff
if: github.event_name == 'pull_request'
uses: actions/upload-artifact@v4
with:
name: code-size-report
path: |
diff
pr_number
retention-days: 1

View file

@ -1,105 +0,0 @@
name: Code size comment
on:
workflow_run:
workflows: [Check code size]
types: [completed]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
comment:
runs-on: ubuntu-22.04
steps:
- name: 'Download artifact'
id: download-artifact
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const fs = require('fs');
const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
const matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
return artifact.name == "code-size-report"
});
if (matchArtifact.length === 0) {
console.log('no matching artifact found');
console.log('result: "skip"');
return 'skip';
}
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: matchArtifact[0].id,
archive_format: 'zip',
});
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/code-size-report.zip`, Buffer.from(download.data));
console.log('artifact downloaded to `code-size-report.zip`');
console.log('result: "ok"');
return 'ok';
- name: 'Unzip artifact'
if: steps.download-artifact.outputs.result == 'ok'
run: unzip code-size-report.zip
- name: Post comment to pull request
if: steps.download-artifact.outputs.result == 'ok'
uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const fs = require('fs');
const prNumber = Number(fs.readFileSync('pr_number'));
const codeSizeReport = `Code size report:
\`\`\`
${fs.readFileSync('diff')}
\`\`\`
`;
const comments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
}
);
comments.reverse();
const previousComment = comments.find(comment =>
comment.user.login === 'github-actions[bot]'
)
// if github-actions[bot] already made a comment, update it,
// otherwise create a new comment.
if (previousComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: previousComment.id,
body: codeSizeReport,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: codeSizeReport,
});
}

View file

@ -1,18 +0,0 @@
name: Check commit message formatting
on: [pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 100
- uses: actions/setup-python@v5
- name: Check commit message formatting
run: source tools/ci.sh && ci_commit_formatting_run

View file

@ -1,23 +0,0 @@
name: Build docs
on:
push:
pull_request:
paths:
- docs/**
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- name: Install Python packages
run: pip install -r docs/requirements.txt
- name: Build docs
run: make -C docs/ html

View file

@ -1,25 +0,0 @@
name: Check examples
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'examples/**'
- 'ports/unix/**'
- 'py/**'
- 'shared/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
embedding:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: make -C examples/embedding -f micropython_embed.mk && make -C examples/embedding
- name: Run
run: ./examples/embedding/embed | grep "hello world"

View file

@ -1,29 +0,0 @@
name: Package mpremote
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Setting this to zero means fetch all history and tags,
# which hatch-vcs can use to discover the version tag.
fetch-depth: 0
- uses: actions/setup-python@v5
- name: Install build tools
run: pip install build
- name: Build mpremote wheel
run: cd tools/mpremote && python -m build --wheel
- name: Archive mpremote wheel
uses: actions/upload-artifact@v4
with:
name: mpremote
path: |
tools/mpremote/dist/mpremote*.whl

View file

@ -1,24 +0,0 @@
name: .mpy file format and tools
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'examples/**'
- 'tests/**'
- 'tools/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-22.04 # use 22.04 to get python2
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_mpy_format_setup
- name: Test mpy-tool.py
run: source tools/ci.sh && ci_mpy_format_test

View file

@ -1,22 +0,0 @@
name: Build ports metadata
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- ports/**
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build ports download metadata
run: mkdir boards && ./tools/autobuild/build-downloads.py . ./boards

View file

@ -1,33 +0,0 @@
name: alif port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/alif/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build_alif:
strategy:
fail-fast: false
matrix:
ci_func: # names are functions in ci.sh
- alif_ae3_build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_alif_setup
- name: Build ci_${{matrix.ci_func }}
run: source tools/ci.sh && ci_${{ matrix.ci_func }}

View file

@ -1,28 +0,0 @@
name: cc3200 port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/cc3200/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_cc3200_setup
- name: Build
run: source tools/ci.sh && ci_cc3200_build

View file

@ -1,57 +0,0 @@
name: esp32 port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/esp32/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build_idf:
strategy:
fail-fast: false
matrix:
ci_func: # names are functions in ci.sh
- esp32_build_cmod_spiram_s2
- esp32_build_s3_c3
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: idf_ver
name: Read the ESP-IDF version (including Python version)
run: source tools/ci.sh && echo "IDF_VER=${IDF_VER}-py${PYTHON_VER}" | tee "$GITHUB_OUTPUT"
- name: Cached ESP-IDF install
id: cache_esp_idf
uses: actions/cache@v4
with:
path: |
./esp-idf/
~/.espressif/
!~/.espressif/dist/
~/.cache/pip/
key: esp-idf-${{ steps.idf_ver.outputs.IDF_VER }}
- name: Install ESP-IDF packages
if: steps.cache_esp_idf.outputs.cache-hit != 'true'
run: source tools/ci.sh && ci_esp32_idf_setup
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: esp32-${{ matrix.ci_func }}
- name: Build ci_${{matrix.ci_func }}
run: source tools/ci.sh && ci_${{ matrix.ci_func }}

View file

@ -1,28 +0,0 @@
name: esp8266 port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/esp8266/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_esp8266_setup && ci_esp8266_path >> $GITHUB_PATH
- name: Build
run: source tools/ci.sh && ci_esp8266_build

27
.github/workflows/ports_m68kmac.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: m68k macintosh port
on:
push:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
container: ghcr.io/autc04/retro68
steps:
- uses: actions/checkout@v4
- name: Build
run: |
git config --global --add safe.directory $(pwd)
make -C mpy-cross -j$(nproc)
make -C ports/m68kmac submodules
make -C ports/m68kmac -j$(nproc)
- name: Upload disk image
uses: actions/upload-artifact@v4
with:
path: ports/m68kmac/build/micropython.dsk

View file

@ -1,33 +0,0 @@
name: mimxrt port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/mimxrt/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: 'micropython repo' # test build with space in path
steps:
- uses: actions/checkout@v4
with:
path: 'micropython repo'
- name: Install packages
run: source tools/ci.sh && ci_mimxrt_setup
- name: Build
run: source tools/ci.sh && ci_mimxrt_build

View file

@ -1,28 +0,0 @@
name: nrf port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/nrf/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_nrf_setup
- name: Build
run: source tools/ci.sh && ci_nrf_build

View file

@ -1,28 +0,0 @@
name: powerpc port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/powerpc/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_powerpc_setup
- name: Build
run: source tools/ci.sh && ci_powerpc_build

View file

@ -1,51 +0,0 @@
name: qemu port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/qemu/**'
- 'tests/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build_and_test_arm:
strategy:
fail-fast: false
matrix:
ci_func: # names are functions in ci.sh
- bigendian
- sabrelite
- thumb
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_qemu_setup_arm
- name: Build and run test suite ci_qemu_build_arm_${{ matrix.ci_func }}
run: source tools/ci.sh && ci_qemu_build_arm_${{ matrix.ci_func }}
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
build_and_test_rv32:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_qemu_setup_rv32
- name: Build and run test suite
run: source tools/ci.sh && ci_qemu_build_rv32
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures

View file

@ -1,29 +0,0 @@
name: renesas-ra port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/renesas-ra/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build_renesas_ra_board:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_renesas_ra_setup
- name: Build
run: source tools/ci.sh && ci_renesas_ra_board_build

View file

@ -1,33 +0,0 @@
name: rp2 port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/rp2/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: 'micropython repo' # test build with space in path
steps:
- uses: actions/checkout@v4
with:
path: 'micropython repo'
- name: Install packages
run: source tools/ci.sh && ci_rp2_setup
- name: Build
run: source tools/ci.sh && ci_rp2_build

View file

@ -1,28 +0,0 @@
name: samd port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/samd/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_samd_setup
- name: Build
run: source tools/ci.sh && ci_samd_build

View file

@ -1,36 +0,0 @@
name: stm32 port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'drivers/**'
- 'ports/stm32/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build_stm32:
strategy:
fail-fast: false
matrix:
ci_func: # names are functions in ci.sh
- stm32_pyb_build
- stm32_nucleo_build
- stm32_misc_build
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_stm32_setup
- name: Build ci_${{matrix.ci_func }}
run: source tools/ci.sh && ci_${{ matrix.ci_func }}

View file

@ -1,284 +0,0 @@
name: unix port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'examples/**'
- 'mpy-cross/**'
- 'ports/unix/**'
- 'tests/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
minimal:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: source tools/ci.sh && ci_unix_minimal_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_minimal_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
reproducible:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build with reproducible date
run: source tools/ci.sh && ci_unix_minimal_build
env:
SOURCE_DATE_EPOCH: 1234567890
- name: Check reproducible build date
run: echo | ports/unix/build-minimal/micropython -i | grep 'on 2009-02-13;'
standard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: source tools/ci.sh && ci_unix_standard_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_standard_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
standard_v2:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: source tools/ci.sh && ci_unix_standard_v2_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_standard_v2_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_coverage_setup
- name: Build
run: source tools/ci.sh && ci_unix_coverage_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_coverage_run_tests
- name: Test merging .mpy files
run: source tools/ci.sh && ci_unix_coverage_run_mpy_merge_tests
- name: Build native mpy modules
run: source tools/ci.sh && ci_native_mpy_modules_build
- name: Test importing .mpy generated by mpy_ld.py
run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests
- name: Run gcov coverage analysis
run: |
(cd ports/unix && gcov -o build-coverage/py ../../py/*.c || true)
(cd ports/unix && gcov -o build-coverage/extmod ../../extmod/*.c || true)
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
coverage_32bit:
runs-on: ubuntu-22.04 # use 22.04 to get libffi-dev:i386
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_32bit_setup
- name: Build
run: source tools/ci.sh && ci_unix_coverage_32bit_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_coverage_32bit_run_tests
- name: Build native mpy modules
run: source tools/ci.sh && ci_native_mpy_modules_32bit_build
- name: Test importing .mpy generated by mpy_ld.py
run: source tools/ci.sh && ci_unix_coverage_32bit_run_native_mpy_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
nanbox:
runs-on: ubuntu-22.04 # use 22.04 to get python2, and libffi-dev:i386
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_32bit_setup
- name: Build
run: source tools/ci.sh && ci_unix_nanbox_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_nanbox_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
float:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build
run: source tools/ci.sh && ci_unix_float_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_float_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
stackless_clang:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_clang_setup
- name: Build
run: source tools/ci.sh && ci_unix_stackless_clang_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_stackless_clang_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
float_clang:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_clang_setup
- name: Build
run: source tools/ci.sh && ci_unix_float_clang_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_float_clang_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
settrace:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
# Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests.
# Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default.
with:
python-version: '3.11'
- name: Build
run: source tools/ci.sh && ci_unix_settrace_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_settrace_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
settrace_stackless:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
# Python 3.12 is the default for ubuntu-24.04, but that has compatibility issues with settrace tests.
# Can remove this step when ubuntu-latest uses a more recent Python 3.x as the default.
with:
python-version: '3.11'
- name: Build
run: source tools/ci.sh && ci_unix_settrace_stackless_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_settrace_stackless_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.8'
- name: Build
run: source tools/ci.sh && ci_unix_macos_build
- name: Run tests
run: source tools/ci.sh && ci_unix_macos_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
qemu_mips:
# ubuntu-22.04 is needed for older libffi.
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_qemu_mips_setup
- name: Build
run: source tools/ci.sh && ci_unix_qemu_mips_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_qemu_mips_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
qemu_arm:
# ubuntu-22.04 is needed for older libffi.
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_qemu_arm_setup
- name: Build
run: source tools/ci.sh && ci_unix_qemu_arm_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_qemu_arm_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
qemu_riscv64:
# ubuntu-22.04 is needed for older libffi.
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_qemu_riscv64_setup
- name: Build
run: source tools/ci.sh && ci_unix_qemu_riscv64_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_qemu_riscv64_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures
sanitize_undefined:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_unix_coverage_setup
- name: Build
run: source tools/ci.sh && ci_unix_sanitize_undefined_build
- name: Run main test suite
run: source tools/ci.sh && ci_unix_sanitize_undefined_run_tests
- name: Test merging .mpy files
run: source tools/ci.sh && ci_unix_coverage_run_mpy_merge_tests
- name: Build native mpy modules
run: source tools/ci.sh && ci_native_mpy_modules_build
- name: Test importing .mpy generated by mpy_ld.py
run: source tools/ci.sh && ci_unix_coverage_run_native_mpy_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures

View file

@ -1,32 +0,0 @@
name: webassembly port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'ports/webassembly/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_webassembly_setup
- name: Build
run: source tools/ci.sh && ci_webassembly_build
- name: Run tests
run: source tools/ci.sh && ci_webassembly_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures

View file

@ -1,150 +0,0 @@
name: windows port
on:
push:
pull_request:
paths:
- '.github/workflows/*.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'ports/unix/**'
- 'ports/windows/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-vs:
strategy:
fail-fast: false
matrix:
platform: [x86, x64]
configuration: [Debug, Release]
variant: [dev, standard]
visualstudio: ['2017', '2019', '2022']
include:
- visualstudio: '2017'
vs_version: '[15, 16)'
- visualstudio: '2019'
vs_version: '[16, 17)'
- visualstudio: '2022'
vs_version: '[17, 18)'
# trim down the number of jobs in the matrix
exclude:
- variant: standard
configuration: Debug
- visualstudio: '2019'
configuration: Debug
runs-on: windows-latest
env:
CI_BUILD_CONFIGURATION: ${{ matrix.configuration }}
steps:
- name: Install Visual Studio 2017
if: matrix.visualstudio == '2017'
run: |
choco install visualstudio2017buildtools
choco install visualstudio2017-workload-vctools
choco install windows-sdk-8.1
- name: Install Visual Studio 2019
if: matrix.visualstudio == '2019'
run: |
choco install visualstudio2019buildtools
choco install visualstudio2019-workload-vctools
choco install windows-sdk-8.1
- uses: microsoft/setup-msbuild@v2
with:
vs-version: ${{ matrix.vs_version }}
- uses: actions/checkout@v4
- name: Build mpy-cross.exe
run: msbuild mpy-cross\mpy-cross.vcxproj -maxcpucount -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }}
- name: Update submodules
run: git submodule update --init lib/micropython-lib
- name: Build micropython.exe
run: msbuild ports\windows\micropython.vcxproj -maxcpucount -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} -property:PyVariant=${{ matrix.variant }}
- name: Get micropython.exe path
id: get_path
run: |
$exePath="$(msbuild ports\windows\micropython.vcxproj -nologo -v:m -t:ShowTargetPath -property:Configuration=${{ matrix.configuration }} -property:Platform=${{ matrix.platform }} -property:PyVariant=${{ matrix.variant }})"
echo ("micropython=" + $exePath.Trim()) >> $env:GITHUB_OUTPUT
- name: Run tests
id: test
env:
MICROPY_MICROPYTHON: ${{ steps.get_path.outputs.micropython }}
working-directory: tests
run: python run-tests.py
- name: Print failures
if: failure() && steps.test.conclusion == 'failure'
working-directory: tests
run: python run-tests.py --print-failures
- name: Run mpy tests
id: test_mpy
env:
MICROPY_MICROPYTHON: ${{ steps.get_path.outputs.micropython }}
working-directory: tests
run: python run-tests.py --via-mpy -d basics float micropython
- name: Print mpy failures
if: failure() && steps.test_mpy.conclusion == 'failure'
working-directory: tests
run: python run-tests.py --print-failures
build-mingw:
strategy:
fail-fast: false
matrix:
variant: [dev, standard]
sys: [mingw32, mingw64]
include:
- sys: mingw32
env: i686
- sys: mingw64
env: x86_64
runs-on: windows-latest
env:
CHERE_INVOKING: enabled_from_arguments
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/setup-python@v5
# note: can go back to installing mingw-w64-${{ matrix.env }}-python after
# MSYS2 updates to Python >3.12 (due to settrace compatibility issue)
with:
python-version: '3.11'
- uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.sys }}
update: true
install: >-
make
mingw-w64-${{ matrix.env }}-gcc
pkg-config
git
diffutils
path-type: inherit # Remove when setup-python is removed
- uses: actions/checkout@v4
- name: Build mpy-cross.exe
run: make -C mpy-cross -j2
- name: Update submodules
run: make -C ports/windows VARIANT=${{ matrix.variant }} submodules
- name: Build micropython.exe
run: make -C ports/windows -j2 VARIANT=${{ matrix.variant }}
- name: Run tests
id: test
run: make -C ports/windows test_full VARIANT=${{ matrix.variant }}
- name: Print failures
if: failure() && steps.test.conclusion == 'failure'
working-directory: tests
run: python run-tests.py --print-failures
cross-build-on-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install packages
run: source tools/ci.sh && ci_windows_setup
- name: Build
run: source tools/ci.sh && ci_windows_build

View file

@ -1,60 +0,0 @@
name: zephyr port
on:
push:
pull_request:
paths:
- '.github/workflows/ports_zephyr.yml'
- 'tools/**'
- 'py/**'
- 'extmod/**'
- 'shared/**'
- 'lib/**'
- 'ports/zephyr/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: jlumbroso/free-disk-space@main
with:
# Only free up a few things so this step runs quickly.
android: false
dotnet: true
haskell: true
large-packages: false
docker-images: false
swap-storage: false
- uses: actions/checkout@v4
- id: versions
name: Read Zephyr version
run: source tools/ci.sh && echo "ZEPHYR=$ZEPHYR_VERSION" | tee "$GITHUB_OUTPUT"
- name: Cached Zephyr Workspace
id: cache_workspace
uses: actions/cache@v4
with:
# note that the Zephyr CI docker image is 15GB. At time of writing
# GitHub caches are limited to 10GB total for a project. So we only
# cache the "workspace"
path: ./zephyrproject
key: zephyr-workspace-${{ steps.versions.outputs.ZEPHYR }}
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: zephyr
- name: Install packages
run: source tools/ci.sh && ci_zephyr_setup
- name: Install Zephyr
if: steps.cache_workspace.outputs.cache-hit != 'true'
run: source tools/ci.sh && ci_zephyr_install
- name: Build
run: source tools/ci.sh && ci_zephyr_build
- name: Run main test suite
run: source tools/ci.sh && ci_zephyr_run_tests
- name: Print failures
if: failure()
run: tests/run-tests.py --print-failures

1
esp-idf Submodule

@ -0,0 +1 @@
Subproject commit 8153bfe4125e6a608abccf1561fd10285016c90a

107
ports/m68kmac/Makefile Normal file
View file

@ -0,0 +1,107 @@
include ../../py/mkenv.mk
CROSS_COMPILE=m68k-apple-macos-
# qstr definitions (must come before including py.mk)
QSTR_DEFS = qstrdefsport.h
# MicroPython feature configurations
MICROPY_ROM_TEXT_COMPRESSION ?= 1
FROZEN_MANIFEST = manifest.py
# include py core make definitions
include $(TOP)/py/py.mk
include $(TOP)/extmod/extmod.mk
INC += -I.
INC += -I$(TOP)
INC += -I$(BUILD)
UNAME_S := $(shell uname -s)
LD = $(CXX)
CFLAGS += $(INC) -Wall -Wdouble-promotion -Wfloat-conversion -std=c99 $(COPT)
CFLAGS += --param=min-pagesize=0
CSUPEROPT = -Os # save some code space
# Tune for Debugging or Optimization
#CFLAGS += -g # always include debug info in the ELF
ifeq ($(DEBUG), 1)
CFLAGS += -O0
else
CFLAGS += -Os -DNDEBUG
CFLAGS += -fdata-sections -ffunction-sections
endif
# Flags for optional C++ source code
CXXFLAGS += $(filter-out -std=c99,$(CFLAGS)) -fno-rtti -fno-exceptions
LIBS =
SRC_C = \
main.c \
vfs_mac.c \
macutil.c \
SRC_C += \
shared/readline/readline.c \
shared/runtime/gchelper_native.c \
shared/runtime/interrupt_char.c \
shared/runtime/pyexec.c \
shared/runtime/stdout_helpers.c \
SRC_CXX += \
uart_core.cpp \
retro/Console.cpp \
retro/ConsoleWindow.cpp \
retro/InitConsole.cpp \
SRC_S += \
shared/runtime/gchelper_m68k.s \
SRC_QSTR += \
vfs_mac.c \
shared/readline/readline.c \
shared/runtime/pyexec.c \
OBJ += $(PY_CORE_O) $(PY_O)
OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
OBJ += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o))
OBJ += $(addprefix $(BUILD)/, $(SRC_S:.s=.o))
all: $(BUILD)/micropython.bin
$(BUILD)/micropython.code.bin: $(OBJ)
$(ECHO) "LINK $@"
$(Q)$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)
$(SIZE) $@.gdb
REZ=Rez
RINCLUDES=/Retro68-build/toolchain/RIncludes
REZFLAGS=-I$(RINCLUDES)
$(BUILD)/micropython.bin $(BUILD)/micropython.APPL $(BUILD)/micropython.dsk: $(BUILD)/micropython.code.bin
$(REZ) $(REZFLAGS) \
--copy "$(BUILD)/micropython.code.bin" \
"$(RINCLUDES)/Retro68APPL.r" \
-t "APPL" -c "mupy" \
-o $(BUILD)/micropython.bin --cc $(BUILD)/micropython.APPL --cc $(BUILD)/micropython.dsk
.PHONY: docker-build
docker-build:
docker run --rm --mount type=bind,source=$(abspath $(TOP)),destination=/work ghcr.io/autc04/retro68 make -C /work/ports/m68kmac -j$(shell nproc)
.PHONY: docker-build-%
docker-build-%:
docker run --rm --mount type=bind,source=$(abspath $(TOP)),destination=/work ghcr.io/autc04/retro68 make -C /work/ports/m68kmac $*
BASE_IMG ?= base.img
UMAC=$(HOME)/src/umac/main -w -r ~/src/umac/rom.bin
.PHONY: run
run:
cp $(BASE_IMG) $(BUILD)/run.dsk
hmount $(BUILD)/run.dsk
hcopy $(BUILD)/micropython.bin ":Desktop Folder"
$(UMAC) -d build/run.dsk
include $(TOP)/py/mkrules.mk

47
ports/m68kmac/README.md Normal file
View file

@ -0,0 +1,47 @@
# The m68k Mac port
This port runs on m68k macs. The author tests it in a modified umac emulating a
4MB "mac plus" with System 7, though it may also run on System 6.
## Building and running
The build assumes it will occur inside the Retro68 docker image:
$ docker run --rm --mount type=bind,source=.,destination=/work -it ghcr.io/autc04/retro68 make -C /work/ports/m68kmac
A modified version of umac with multi disc image support is required.
(It hopefully works in other emulators but this is what I use.)
To run the executable and get a basic working REPL do:
$ /path/to/umac/main -r rom.bin -d HyperCardBootSystem7.img -d build/micropython.dsk
.. then when the micropython disk is mounted, double click it and then the
micropython application icon. It will open up with a repl window that supports
minimal ANSI-style escape codes.
## Built in editor
There's a built in editor:
```py
>>> import editor
>>> editor.edit() ## OR
>>> editor.edit("filename.py")
```
umac doesn't support arrow keys. Emacs-style ctrl+p/n f/b work to move cursor instead.
Probably doesn't work right now on a keyboard with no control key.
## Key TODOs
* Finish `VfsMac`
* a few more methods
* chdir may affect a lot
* handling `.` and `..` in paths
* Add sys.stdin.readable & polling of sys.stdin for editor
* auto-launch `code.py` (with bypass via shift key or something?)
* OR double-clickable ".py" files
* "Mac Roman" encoding support in terminal, text I/O & filenames
* Mac API support (e.g., quickdraw, maybe arbitrary traps)
* Support larger heap (via split heap? how does heap allocation work?)
* Decide whether RetroConsole needs to be replaced (is GPL a problem for upstream??)
* ctrl-c interrupt char handling
* Any other issues that might ease upstream inclusion.

View file

@ -0,0 +1,14 @@
#pragma once
#if defined(__cplusplus)
extern "C" {
#endif
typedef struct _mp_print_t mp_print_t;
extern mp_print_t debug_print;
int mp_printf(const mp_print_t *print, const char *fmt, ...);
#define DPRINTF(fmt, ...) mp_printf(&debug_print, fmt "\n",##__VA_ARGS__)
#if defined(__cplusplus)
}
#endif

4
ports/m68kmac/docker-run.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
HERE="$(dirname "$0")"
TOP="$(readlink -f "${HERE}/../..")"
docker run -w /work/ports/m68kmac --rm --mount type=bind,source=${TOP},destination=/work -it ghcr.io/autc04/retro68 "$@"

118
ports/m68kmac/macutil.c Normal file
View file

@ -0,0 +1,118 @@
#include <py/runtime.h>
#include <py/objstr.h>
#include <py/mperrno.h>
#include "macutil.h"
mp_obj_t new_bytes_from_pstr(Byte *pStr) {
return mp_obj_new_bytes(pStr + 1, *pStr);
}
mp_obj_t new_str_from_pstr(Byte *pStr) {
return mp_obj_new_str((const char *)pStr + 1, *pStr);
}
Byte *pstr_from_data(Byte *pStr, size_t pStr_size, const char *str_data, size_t str_len) {
pStr[0] = 0;
return pstr_cat_data(pStr, pStr_size, str_data, str_len);
}
Byte *pstr_cat_data(Byte *pStr, size_t pStr_size, const char *str_data, size_t str_len) {
int orig_len = pStr[0];
if (str_len + 1 + orig_len >= pStr_size) {
mp_raise_ValueError(MP_ERROR_TEXT("buffer too long"));
}
*pStr += str_len;
memcpy(pStr + 1 + orig_len, str_data, str_len);
return pStr;
}
Byte *pstr_from_str(Byte *pStr, size_t pStr_size, mp_obj_t obj) {
GET_STR_DATA_LEN(obj, str_data, str_len);
return pstr_from_data(pStr, pStr_size, (const char *)str_data, str_len);
}
Byte *pstr_from_cstr(Byte *pStr, size_t pStr_size, const char *str_data) {
return pstr_from_data(pStr, pStr_size, str_data, strlen(str_data));
}
Byte *pstr_cat_str(Byte *pStr, size_t pStr_size, mp_obj_t obj) {
GET_STR_DATA_LEN(obj, str_data, str_len);
return pstr_cat_data(pStr, pStr_size, (const char *)str_data, str_len);
}
Byte *pstr_cat_cstr(Byte *pStr, size_t pStr_size, const char *str_data) {
return pstr_cat_data(pStr, pStr_size, str_data, strlen(str_data));
}
int convert_mac_err(OSErr e) {
int err = MP_EINVAL;
switch (e) {
case nsvErr: // "No such volume"
case noMacDskErr:
case extFSErr:
case badMDBErr:
err = MP_ENODEV;
break;
case gfpErr: // "Error during GetFPos"
case fsRnErr: // "Problem during rename"
case posErr:
case bdNamErr:
case paramErr:
case rfNumErr:
err = MP_EINVAL;
break;
case dirFulErr:
err = MP_ENFILE;
break;
case dskFulErr:
err = MP_EPIPE;
break;
case dupFNErr:
err = MP_EEXIST;
break;
case ioErr:
case eofErr:
err = MP_EIO;
break;
case fBsyErr:
err = MP_EBUSY;
break;
case volOffLinErr:
err = MP_EISCONN;
break;
case fLckdErr:
err = MP_EWOULDBLOCK;
break;
case dirNFErr:
case fnfErr:
err = MP_ENOENT;
break;
case fnOpnErr:
err = MP_EBADF;
break;
case wPrErr:
case wrPermErr:
case vLckdErr:
case permErr:
case opWrErr:
err = MP_EPERM;
break;
case tmwdoErr:
case tmfoErr:
err = MP_EMFILE;
break;
case wrgVolTypErr:
err = MP_EINVAL;
break;
}
return err;
}
MP_NORETURN void raise_mac_err(OSErr e) {
mp_raise_OSError(convert_mac_err(e));
}
void check_mac_err(OSErr e) {
if (e != noErr) {
raise_mac_err(e);
}
}

21
ports/m68kmac/macutil.h Normal file
View file

@ -0,0 +1,21 @@
#include <py/obj.h>
#include "Multiverse.h"
void check_mac_err(OSErr e);
MP_NORETURN void raise_mac_err(OSErr e);
int convert_mac_err(OSErr e);
mp_obj_t new_str_from_pstr(Byte *pStr);
mp_obj_t new_bytes_from_pstr(Byte *pStr);
Byte *pstr_from_str(Byte *pStr, size_t pStr_size, mp_obj_t obj);
Byte *pstr_from_data(Byte *pStr, size_t pStr_size, const char *str_data, size_t str_len);
Byte *pstr_from_cstr(Byte *pStr, size_t pStr_size, const char *str_data);
Byte *pstr_cat_str(Byte *pStr, size_t pStr_size, mp_obj_t obj);
Byte *pstr_cat_cstr(Byte *pStr, size_t pStr_size, const char *str_data);
Byte *pstr_cat_data(Byte *pStr, size_t pStr_size, const char *str_data, size_t str_len);
#define PSTR_FROM_STR(pStr, obj) pstr_from_str(pStr, MP_ARRAY_SIZE(pStr), obj)
#define PSTR_FROM_CSTR(pStr, str) pstr_from_cstr(pStr, MP_ARRAY_SIZE(pStr), str)
#define PSTR_FROM_DATA(pStr, ptr, len) pstr_from_data(pStr, MP_ARRAY_SIZE(pStr), ptr, len)
#define PSTR_LEN(p) (*(p))
#define PSTR_DATA(p) ((char *)((p) + 1))

177
ports/m68kmac/main.c Normal file
View file

@ -0,0 +1,177 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "Multiverse.h"
#include "macutil.h"
#include "uart_core.h"
#include "py/builtin.h"
#include "py/compile.h"
#include "py/runtime.h"
#include "py/repl.h"
#include "py/gc.h"
#include "py/mperrno.h"
#include "py/stackctrl.h"
#include "shared/runtime/pyexec.h"
#include "shared/runtime/gchelper.h"
#include "shared/readline/readline.h"
#include "shared/runtime/interrupt_char.h"
#include "extmod/vfs.h"
#include "extmod/vfs_posix.h"
#if MICROPY_ENABLE_COMPILER
static int execute_from_lexer(mp_obj_t source, mp_parse_input_kind_t input_kind, bool is_repl) {
mp_hal_set_interrupt_char(CHAR_CTRL_C);
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
// create lexer based on source kind
mp_lexer_t *lex;
lex = mp_lexer_new_from_file(mp_obj_str_get_qstr(source));
qstr source_name = lex->source_name;
mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
mp_obj_t module_fun = mp_compile(&parse_tree, source_name, is_repl);
mp_call_function_0(module_fun);
mp_hal_set_interrupt_char(-1);
mp_handle_pending(true);
nlr_pop();
return 0;
} else {
// uncaught exception
mp_hal_set_interrupt_char(-1);
mp_handle_pending(false);
mp_obj_base_t *exc = nlr.ret_val;
mp_obj_print_exception(&mp_plat_print, MP_OBJ_FROM_PTR(exc));
(void)mp_hal_stdin_rx_chr();
return 1;
}
mp_printf(&mp_plat_print, "Press any key to exit");
}
#endif
void LMSetApplLimit_checked(Ptr val) {
if (val > LMGetHeapEnd()) {
LMSetApplLimit(val);
}
}
int main(int argc, char **argv) {
// Enlarge stack to at least 32kB
LMSetApplLimit_checked(LMGetCurStackBase() - 32768);
// Set MP stack limits, reserving 1kB stack for internal use
mp_stack_set_top(LMGetCurStackBase());
mp_stack_set_limit(LMGetApplLimit() + 1024);
// Use half the largest possible block as MicroPython heap
Size growmax;
(void)MaxMem(&growmax);
size_t heap_size = growmax / 2;
unsigned char *heap = (unsigned char *)NewPtr(growmax / 2);
// Check if we were given a file ...
short message, count;
CountAppFiles(&message, &count);
AppFile file = {};
if (count) {
GetAppFiles(1, &file);
}
gc_init(heap, heap + heap_size);
mp_init();
{
// Mount the host FS at the root of our internal VFS
mp_obj_t args[2] = {
MP_OBJ_TYPE_GET_SLOT(&mp_type_vfs_mac, make_new)(&mp_type_vfs_mac, 0, 0, NULL),
MP_OBJ_NEW_QSTR(MP_QSTR__slash_),
};
mp_vfs_mount(2, args, (mp_map_t *)&mp_const_empty_map);
// Make sure the root that was just mounted is the current VFS (it's always at
// the end of the linked list). Can't use chdir('/') because that will change
// the current path within the VfsPosix object.
MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_mount_table);
while (MP_STATE_VM(vfs_cur)->next != NULL) {
MP_STATE_VM(vfs_cur) = MP_STATE_VM(vfs_cur)->next;
}
}
if (count) {
mp_obj_t filename = new_str_from_pstr(file.fName);
execute_from_lexer(filename, MP_PARSE_FILE_INPUT, false);
} else {
#if MICROPY_REPL_EVENT_DRIVEN
pyexec_event_repl_init();
for (;;) {
int c = mp_hal_stdin_rx_chr();
if (pyexec_event_repl_process_char(c)) {
break;
}
}
#else
pyexec_friendly_repl();
#endif
}
mp_deinit();
return 0;
}
void MP_NORETURN __fatal_error(const char *msg);
void gc_collect(void) {
// WARNING: This gc_collect implementation doesn't try to get root
// pointers from CPU registers, and thus may function incorrectly.
gc_collect_start();
gc_helper_collect_regs_and_stack();
#if MICROPY_PY_THREAD
mp_thread_gc_others();
#endif
gc_collect_end();
}
void nlr_jump_fail(void *val) {
__fatal_error("nlr jump fail");
}
void MP_NORETURN __fatal_error(const char *msg) {
mp_printf(&mp_plat_print, "Fatal: %s\n", msg);
while (1) {
;
}
}
#ifndef NDEBUG
void MP_WEAK __assert_func(const char *file, int line, const char *func, const char *expr) {
printf("Assertion '%s' failed, at file %s:%d\n", expr, file, line);
__fatal_error("Assertion failed");
}
#endif
#define TICK_TO_MS(x) ((x + 2) * 50 / 3)
#define TICK_TO_US(x) ((x + 2) * 50000 / 3)
#define MS_TO_TICK(x) ((x + 25) * 3 / 50)
#define US_TO_TICK(x) ((x + 25000) * 3 / 50000)
void mp_hal_delay_ms(mp_uint_t delay) {
long unused;
Delay(MS_TO_TICK(delay), &unused);
}
void mp_hal_delay_us(mp_uint_t delay) {
long unused;
Delay(US_TO_TICK(delay), &unused);
}
mp_uint_t mp_hal_ticks_ms(void) {
return TICK_TO_MS(TickCount());
}
mp_uint_t mp_hal_ticks_us(void) {
return TICK_TO_US(TickCount());
}
mp_uint_t mp_hal_ticks_cpu(void) {
return 0;
}

View file

@ -0,0 +1,2 @@
freeze("$(PORT_DIR)/modules")
include("$(MPY_DIR)/extmod/asyncio")

View file

@ -0,0 +1,8 @@
def edit(filename=None):
if filename is None:
from . import picker
filename = picker.pick_file()
from . import editor
editor.edit(filename)

View file

@ -0,0 +1,151 @@
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
#
# SPDX-License-Identifier: MIT
try:
import select
except:
select = None
import sys
# pylint: disable=no-self-use
try:
import termios
_orig_attr = None # pylint: disable=invalid-name
def _nonblocking():
global _orig_attr # pylint: disable=global-statement
_orig_attr = termios.tcgetattr(sys.stdin)
attr = termios.tcgetattr(sys.stdin)
attr[3] &= ~(termios.ECHO | termios.ICANON)
attr[6][termios.VMIN] = 1
attr[6][termios.VTIME] = 0
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, attr)
def _blocking():
if _orig_attr is not None:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, _orig_attr)
except ImportError:
def _nonblocking():
pass
def _blocking():
pass
LINES = 24
COLS = 80
def CTRL(c):
return chr(ord(c) & 0x1F)
special_keys = {
# Some emacs style bindings for Mac w/o arrow keys
CTRL('a'): "KEY_HOME",
CTRL('e'): "KEY_END",
CTRL('p'): "KEY_UP",
CTRL('n'): "KEY_DOWN",
CTRL('b'): "KEY_LEFT",
CTRL('f'): "KEY_RIGHT",
CTRL('v'): "KEY_PGDN",
"\x1bv": "KEY_PGUP",
CTRL('d'): "KEY_DELETE",
"\x1b": ..., # all prefixes of special keys must be entered as Ellipsis
"\x1b[": ...,
"\x1b[5": ...,
"\x1b[6": ...,
"\x1b[A": "KEY_UP",
"\x1b[B": "KEY_DOWN",
"\x1b[C": "KEY_RIGHT",
"\x1b[D": "KEY_LEFT",
"\x1b[H": "KEY_HOME",
"\x1b[F": "KEY_END",
"\x1b[5~": "KEY_PGUP",
"\x1b[6~": "KEY_PGDN",
"\x1b[3~": "KEY_DELETE",
}
class FakePoll:
def poll(self, timeout):
return True
class Screen:
def __init__(self):
if select is None:
self._poll = select.poll()
self._poll.register(sys.stdin, select.POLLIN)
else:
self._poll = FakePoll()
self._pending = ""
def _sys_stdin_readable(self):
return hasattr(sys.stdin, "readable") and sys.stdin.readable()
def _sys_stdout_flush(self):
if hasattr(sys, 'stdout') and hasattr(sys.stdout, "flush"):
sys.stdout.flush()
def _terminal_read_blocking(self):
return sys.stdin.read(1)
def _terminal_read_timeout(self, timeout):
if self._sys_stdin_readable() or self._poll.poll(timeout):
r = sys.stdin.read(1)
return r
return None
def move(self, y, x):
print(end=f"\033[{y + 1};{x + 1}H")
def erase(self):
print(end="\033H\033[2J")
def addstr(self, y, x, text):
self.move(y, x)
print(end=text)
def getkey(self):
self._sys_stdout_flush()
pending = self._pending
if pending and (code := special_keys.get(pending)) is None:
self._pending = pending[1:]
return pending[0]
while True:
if pending:
c = self._terminal_read_timeout(50)
if c is None:
self._pending = pending[1:]
return pending[0]
else:
c = self._terminal_read_blocking()
c = pending + c
code = special_keys.get(c)
if code is None:
self._pending = c[1:]
return c[0]
if code is not Ellipsis:
return code
pending = c
def wrapper(func, *args, **kwds):
stdscr = Screen()
try:
_nonblocking()
return func(stdscr, *args, **kwds)
finally:
_blocking()
stdscr.move(LINES - 1, 0)
print("\n")

View file

@ -0,0 +1,284 @@
# SPDX-FileCopyrightText: 2020 Wasim Lorgat
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import gc
import os
from . import dang as curses
# pylint: disable=redefined-builtin
class MaybeDisableReload:
def __enter__(self):
try:
from supervisor import runtime # pylint: disable=import-outside-toplevel
except ImportError:
return
self._old_autoreload = ( # pylint: disable=attribute-defined-outside-init
runtime.autoreload
)
runtime.autoreload = False
def __exit__(self, exc_type, exc_value, traceback):
try:
from supervisor import runtime # pylint: disable=import-outside-toplevel
except ImportError:
return
runtime.autoreload = self._old_autoreload
def os_exists(filename):
try:
with open(filename, "r"):
return True
except OSError:
return False
def gc_mem_free_hint():
if hasattr(gc, "mem_free"):
gc.collect()
return f" | free: {gc.mem_free()}"
return ""
class Buffer:
def __init__(self, lines):
self.lines = lines
def __len__(self):
return len(self.lines)
def __getitem__(self, index):
return self.lines[index]
@property
def bottom(self):
return len(self) - 1
def insert(self, cursor, string):
row, col = cursor.row, cursor.col
try:
current = self.lines.pop(row)
except IndexError:
current = ""
new = current[:col] + string + current[col:]
self.lines.insert(row, new)
def split(self, cursor):
row, col = cursor.row, cursor.col
current = self.lines.pop(row)
self.lines.insert(row, current[:col])
self.lines.insert(row + 1, current[col:])
def delete(self, cursor):
row, col = cursor.row, cursor.col
if (row, col) < (self.bottom, len(self[row])):
current = self.lines.pop(row)
if col < len(current):
new = current[:col] + current[col + 1 :]
self.lines.insert(row, new)
else:
nextline = self.lines.pop(row)
new = current + nextline
self.lines.insert(row, new)
def clamp(x, lower, upper):
if x < lower:
return lower
if x > upper:
return upper
return x
class Cursor:
def __init__(self, row=0, col=0, col_hint=None):
self.row = row
self._col = col
self._col_hint = col if col_hint is None else col_hint
@property
def col(self):
return self._col
@col.setter
def col(self, col):
self._col = col
self._col_hint = col
def _clamp_col(self, buffer):
self._col = min(self._col_hint, len(buffer[self.row]))
def up(self, buffer): # pylint: disable=invalid-name
if self.row > 0:
self.row -= 1
self._clamp_col(buffer)
def down(self, buffer):
if self.row < len(buffer) - 1:
self.row += 1
self._clamp_col(buffer)
def left(self, buffer):
if self.col > 0:
self.col -= 1
elif self.row > 0:
self.row -= 1
self.col = len(buffer[self.row])
def right(self, buffer):
if len(buffer) > 0 and self.col < len(buffer[self.row]):
self.col += 1
elif self.row < len(buffer) - 1:
self.row += 1
self.col = 0
def end(self, buffer):
self.col = len(buffer[self.row])
class Window:
def __init__(self, n_rows, n_cols, row=0, col=0):
self.n_rows = n_rows
self.n_cols = n_cols
self.row = row
self.col = col
@property
def bottom(self):
return self.row + self.n_rows - 1
def up(self, cursor): # pylint: disable=invalid-name
if cursor.row == self.row - 1 and self.row > 0:
self.row -= 1
def down(self, buffer, cursor):
if cursor.row == self.bottom + 1 and self.bottom < len(buffer) - 1:
self.row += 1
def horizontal_scroll(self, cursor, left_margin=5, right_margin=2):
n_pages = cursor.col // (self.n_cols - right_margin)
self.col = max(n_pages * self.n_cols - right_margin - left_margin, 0)
def translate(self, cursor):
return cursor.row - self.row, cursor.col - self.col
def left(window, buffer, cursor):
cursor.left(buffer)
window.up(cursor)
window.horizontal_scroll(cursor)
def right(window, buffer, cursor):
cursor.right(buffer)
window.down(buffer, cursor)
window.horizontal_scroll(cursor)
def home(window, buffer, cursor): # pylint: disable=unused-argument
cursor.col = 0
window.horizontal_scroll(cursor)
def end(window, buffer, cursor):
cursor.end(buffer)
window.horizontal_scroll(cursor)
def editor(stdscr, filename): # pylint: disable=too-many-branches,too-many-statements
if os_exists(filename):
with open(filename, "r", encoding="utf-8") as f:
buffer = Buffer(f.read().splitlines())
else:
buffer = Buffer([""])
window = Window(curses.LINES - 1, curses.COLS - 1)
cursor = Cursor()
stdscr.erase()
img = [None] * curses.LINES
def setline(row, line):
if img[row] == line:
return
img[row] = line
line += " " * (window.n_cols - len(line))
stdscr.addstr(row, 0, line)
while True:
lastrow = 0
for row, line in enumerate(buffer[window.row : window.row + window.n_rows]):
lastrow = row
if row == cursor.row - window.row and window.col > 0:
line = "«" + line[window.col + 1 :]
if len(line) > window.n_cols:
line = line[: window.n_cols - 1] + "»"
setline(row, line)
for row in range(lastrow + 1, window.n_rows):
setline(row, "~~ EOF ~~")
row = curses.LINES - 1
line = f"{filename:12} | ^X: write & exit | ^C: quit w/o save{gc_mem_free_hint()}"
setline(row, line)
stdscr.move(*window.translate(cursor))
k = stdscr.getkey()
if len(k) == 1 and " " <= k <= "~":
buffer.insert(cursor, k)
for _ in k:
right(window, buffer, cursor)
elif k == "\x18": # ctrl-x
with open(filename, "w", encoding="utf-8") as f:
for row in buffer:
f.write(f"{row}\n")
return
elif k == "\x11": # Ctrl-Q
for row in buffer:
print(row)
elif k == "KEY_HOME":
home(window, buffer, cursor)
elif k == "KEY_END":
end(window, buffer, cursor)
elif k == "KEY_LEFT":
left(window, buffer, cursor)
elif k == "KEY_DOWN":
cursor.down(buffer)
window.down(buffer, cursor)
window.horizontal_scroll(cursor)
elif k == "KEY_PGDN":
for _ in range(window.n_rows):
cursor.down(buffer)
window.down(buffer, cursor)
window.horizontal_scroll(cursor)
elif k == "KEY_UP":
cursor.up(buffer)
window.up(cursor)
window.horizontal_scroll(cursor)
elif k == "KEY_PGUP":
for _ in range(window.n_rows):
cursor.up(buffer)
window.up(cursor)
window.horizontal_scroll(cursor)
elif k == "KEY_RIGHT":
right(window, buffer, cursor)
elif k == "\n" or k == "\r":
buffer.split(cursor)
right(window, buffer, cursor)
elif k in ("KEY_DELETE", "\x04"):
buffer.delete(cursor)
elif k in ("KEY_BACKSPACE", "\x7f", "\x08"):
if (cursor.row, cursor.col) > (0, 0):
left(window, buffer, cursor)
buffer.delete(cursor)
def edit(filename):
return curses.wrapper(editor, filename)

View file

@ -0,0 +1,72 @@
# SPDX-FileCopyrightText: 2023 Jeff Epler for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import os
from . import dang as curses
always = ["code.py", "boot.py", "settings.toml", "boot_out.txt"]
good_extensions = [".py", ".toml", ".txt", ".json"]
def os_exists(filename):
try:
os.stat(filename)
return True
except OSError:
return False
def isdir(filename):
st_mode = os.stat(filename)[0]
r = st_mode & 0o40_000
print(f"isdir {filename} {st_mode=:o} {r=}")
return r
def has_good_extension(filename):
for g in good_extensions:
if filename.endswith(g):
return True
return False
def picker(stdscr, options, notes=(), start_idx=0):
stdscr.erase()
stdscr.addstr(curses.LINES - 1, 0, "Enter: select | ^C: quit")
del options[curses.LINES - 1 :]
for row, option in enumerate(options):
if row < len(notes) and (note := notes[row]):
option = f"{option} {note}"
stdscr.addstr(row, 3, option)
old_idx = None
idx = start_idx
while True:
if idx != old_idx:
if old_idx is not None:
stdscr.addstr(old_idx, 0, " ")
stdscr.addstr(idx, 0, "=>")
old_idx = idx
k = stdscr.getkey()
if k == "KEY_DOWN":
idx = min(idx + 1, len(options) - 1)
elif k == "KEY_UP":
idx = max(idx - 1, 0)
elif k in ("\r", "\n"):
return options[idx]
elif k == "\3":
raise SystemExit
def pick_file():
options = always[:] + sorted(
(g for g in os.listdir("") if g not in always and not isdir(g) and not g.startswith(".")),
key=lambda filename: (not has_good_extension(filename), filename),
)
notes = [None if os_exists(filename) else "(NEW)" for filename in options]
return curses.wrapper(picker, options, notes)

View file

@ -0,0 +1,55 @@
#include <stdint.h>
// options to control how MicroPython is built
#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_B)
// Use the minimal starting configuration (disables all optional features).
#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_FULL_FEATURES)
// You can disable the built-in MicroPython compiler by setting the following
// config option to 0. If you do this then you won't get a REPL prompt, but you
// will still be able to execute pre-compiled scripts, compiled with mpy-cross.
#define MICROPY_ENABLE_COMPILER (1)
#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool
#define MICROPY_ENABLE_GC (1)
#define MICROPY_HELPER_REPL (1)
#define MICROPY_ENABLE_EXTERNAL_IMPORT (1)
#define MICROPY_PY_UCTYPES (0)
#define MICROPY_PY_SYS_STDFILES (1)
#define MICROPY_STACK_CHECK (0)
#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ)
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
#define MICROPY_READER_POSIX (0)
#define MICROPY_READER_VFS (1)
#define MICROPY_VFS (1)
#define MICROPY_PY_OS_STATVFS (0)
#define MP_SSIZE_MAX LONG_MAX
#define MICROPY_ALLOC_PATH_MAX (256)
// Use the minimum headroom in the chunk allocator for parse nodes.
#define MICROPY_ALLOC_PARSE_CHUNK_INIT (16)
// type definitions for the specific machine
typedef intptr_t mp_int_t; // must be pointer size
typedef uintptr_t mp_uint_t; // must be pointer size
typedef long mp_off_t;
// We need to provide a declaration/definition of alloca()
#include <alloca.h>
#define MICROPY_HW_BOARD_NAME "macplus"
#define MICROPY_HW_MCU_NAME "m68000"
#define MICROPY_MIN_USE_STDOUT (0)
#define MICROPY_HEAP_SIZE (100 * 1024)
#define MP_STATE_PORT MP_STATE_VM
typedef struct _mp_obj_type_t mp_obj_type_t;
extern const mp_obj_type_t mp_type_vfs_mac;
#define MICROPY_VFS_PORT { MP_ROM_QSTR(MP_QSTR_VfsMac), MP_ROM_PTR(&mp_type_vfs_mac) }

20
ports/m68kmac/mphalport.h Normal file
View file

@ -0,0 +1,20 @@
static inline void mp_hal_set_interrupt_char(char c) {
}
// This macro is used to implement PEP 475 to retry specified syscalls on EINTR
#define MP_HAL_RETRY_SYSCALL(ret, syscall, raise) { \
for (;;) { \
MP_THREAD_GIL_EXIT(); \
ret = syscall; \
MP_THREAD_GIL_ENTER(); \
if (ret == -1) { \
int err = errno; \
if (err == EINTR) { \
mp_handle_pending(true); \
continue; \
} \
raise; \
} \
break; \
} \
}

View file

@ -0,0 +1,2 @@
// qstrs specific to this port
// *FORMAT-OFF*

View file

@ -0,0 +1,900 @@
/*
Copyright 2012-2020 Wolfgang Thaller, Davide Bucci
This file is part of Retro68.
Retro68 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Retro68 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License
along with Retro68. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Console.h"
#include "MacUtils.h"
#include "Fonts.h"
#include "Processes.h"
#include <cctype>
#include <algorithm>
#include <stack>
#include <sstream>
using namespace retro;
const char BEL = 7;
const char MAX_LEN = 250;
Console *Console::currentInstance = NULL;
Attributes::Attributes(void)
{
reset();
}
void Attributes::reset(void)
{
cBold=false;
cUnderline=false;
cItalic=false;
}
bool Attributes::isBold(void) const
{
return cBold;
}
bool Attributes::isUnderline(void) const
{
return cUnderline;
}
bool Attributes::isItalic(void) const
{
return cItalic;
}
void Attributes::setBold(const bool v)
{
cBold=v;
}
void Attributes::setItalic(const bool v)
{
cItalic=v;
}
void Attributes::setUnderline(const bool v)
{
cUnderline=v;
}
inline bool operator==(const Attributes& lhs, const Attributes& rhs)
{
return lhs.isBold()==rhs.isBold() && lhs.isUnderline()==rhs.isUnderline() && lhs.isItalic()==rhs.isItalic();
}
inline bool operator!=(const Attributes& lhs, const Attributes& rhs)
{
return !(lhs == rhs);
}
inline bool operator==(const AttributedChar& lhs, const AttributedChar& rhs)
{
return lhs.c==rhs.c && lhs.attrs==rhs.attrs;
}
inline bool operator!=(const AttributedChar& lhs, const AttributedChar& rhs)
{
return !(lhs == rhs);
}
namespace
{
class FontSetup
{
short saveFont, saveSize, saveFace;
public:
FontSetup()
{
#if TARGET_API_MAC_CARBON
GrafPtr port;
GetPort(&port);
saveFont = GetPortTextFont(port);
saveSize = GetPortTextSize(port);
#else
saveFont = qd.thePort->txFont;
saveSize = qd.thePort->txSize;
saveFace = qd.thePort->txFace;
#endif
TextFont(kFontIDMonaco);
TextSize(9);
TextFace(normal);
}
~FontSetup()
{
TextFont(saveFont);
TextSize(saveSize);
TextFace(saveFace);
}
};
}
Console::Console()
{
InitEscapeSequenceMap();
}
Console::Console(GrafPtr port, Rect r)
{
Init(port, r);
InitEscapeSequenceMap();
}
Console::~Console()
{
if(currentInstance == this)
currentInstance = NULL;
}
void Console::Init(GrafPtr port, Rect r)
{
consolePort = port;
bounds = r;
PortSetter setport(consolePort);
FontSetup fontSetup;
InsetRect(&bounds, 2,2);
cellSizeY = 12;
cellSizeX = CharWidth('M');
rows = (bounds.bottom - bounds.top) / cellSizeY;
cols = (bounds.right - bounds.left) / cellSizeX;
chars = std::vector<AttributedChar>(rows*cols, AttributedChar(' ',currentAttr));
onscreen = chars;
cursorX = cursorY = 0;
sequenceState=State::noSequence;
}
void Console::SetAttributes(Attributes aa)
{
TextFace(aa.isBold()?bold+condense:0 + aa.isUnderline()?underline:0 + aa.isItalic()?italic:0);
}
Rect Console::CellRect(short x, short y)
{
return { (short) (bounds.top + y * cellSizeY), (short) (bounds.left + x * cellSizeX),
(short) (bounds.top + (y+1) * cellSizeY), (short) (bounds.left + (x+1) * cellSizeX) };
}
void Console::DrawCell(short x, short y, bool erase)
{
Rect r = CellRect(x,y);
if(cursorDrawn)
{
if(y == cursorY && x == cursorX)
{
erase = true;
cursorDrawn = false;
}
}
Attributes a=chars[y * cols + x].attrs;
SetAttributes(a);
if(erase)
EraseRect(&r);
MoveTo(r.left, r.bottom - 2);
DrawChar(chars[y * cols + x].c);
}
void Console::DrawCells(short x1, short x2, short y, bool erase)
{
Rect r = { (short) (bounds.top + y * cellSizeY), (short) (bounds.left + x1 * cellSizeX),
(short) (bounds.top + (y+1) * cellSizeY), (short) (bounds.left + x2 * cellSizeX) };
if(cursorDrawn)
{
if(y == cursorY && x1 <= cursorX && x2 > cursorX)
{
erase = true;
cursorDrawn = false;
}
}
if(erase)
EraseRect(&r);
MoveTo(r.left, r.bottom - 2);
Attributes a=chars[y * cols + x1].attrs;
SetAttributes(a);
for(int i=x1; i<x2; ++i)
{
if(a!=chars[y * cols + i].attrs) {
a=chars[y * cols + i].attrs;
SetAttributes(a);
}
DrawChar(chars[y * cols + i].c);
}
}
void Console::Draw(Rect r)
{
if(!consolePort)
return;
PortSetter setport(consolePort);
FontSetup fontSetup;
SectRect(&r, &bounds, &r);
short minRow = std::max(0, (r.top - bounds.top) / cellSizeY);
short maxRow = std::min((int)rows, (r.bottom - bounds.top + cellSizeY - 1) / cellSizeY);
short minCol = std::max(0, (r.left - bounds.left) / cellSizeX);
short maxCol = std::min((int)cols, (r.right - bounds.left + cellSizeX - 1) / cellSizeX);
EraseRect(&r);
for(short row = minRow; row < maxRow; ++row)
{
DrawCells(minCol, maxCol, row, false);
}
if(cursorDrawn)
{
Rect cursor = CellRect(cursorX, cursorY);
InvertRect(&cursor);
}
onscreen = chars;
}
void Console::ScrollUp(short n)
{
PortSetter setport(consolePort);
cursorY--;
std::copy(chars.begin() + cols, chars.end(), chars.begin());
std::fill(chars.end() - cols, chars.end(), AttributedChar(' ', currentAttr));
std::copy(onscreen.begin() + cols, onscreen.end(), onscreen.begin());
std::fill(onscreen.end() - cols, onscreen.end(), AttributedChar(' ', currentAttr));
RgnHandle rgn = NewRgn();
ScrollRect(&bounds, 0, -cellSizeY, rgn);
DisposeRgn(rgn);
dirtyRect.top = dirtyRect.top > 0 ? dirtyRect.top - 1 : 0;
dirtyRect.bottom = dirtyRect.bottom > 0 ? dirtyRect.bottom - 1 : 0;
}
bool Console::ProcessEscSequence(char c)
{
switch(sequenceState)
{
case State::noSequence:
return false; // Break is not needed there.
case State::waitingForSequenceStart:
if(c=='[') {
sequenceState=State::waitingForControlSequence;
got_something = false;
cur_arg = 0;
} else if(c==']')
sequenceState=State::waitingForOSCStart;
else
sequenceState=State::noSequence; // Unrecognized sequence
break;
case State::waitingForControlSequence:
HandleControlSequence(c);
break;
case State::waitingForOSCStart:
if(c=='0')
sequenceState=State::waitingForSemicolon;
else
sequenceState=State::noSequence; // Normal end of sequence
break;
case State::waitingForSemicolon:
if(c==';')
{
sequenceState=State::inWindowName;
title.clear();
}
else
sequenceState=State::noSequence; // Normal end of sequence
break;
case State::inWindowName:
if(c==BEL)
{
title.push_back(0);
setWindowName(&title[0]);
sequenceState=State::noSequence; // Normal end of sequence
}
else
{
if(title.size() < (unsigned)MAX_LEN) // Ignore subsequent characters
title.push_back(c);
}
break;
default:
sequenceState=State::noSequence;
break;
}
return true;
}
void Console::PutCharNoUpdate(char c)
{
if(ProcessEscSequence(c))
return;
InvalidateCursor();
switch(c)
{
case '\033': // Begin of an ANSI escape sequence
sequenceState=State::waitingForSequenceStart;
break;
case '\b':
cursorX--;
break;
case '\r':
cursorX = 0;
break;
case '\n':
cursorY++;
cursorX = 0;
if(cursorY >= rows)
ScrollUp();
break;
default:
chars[cursorY * cols + cursorX].c = c;
chars[cursorY * cols + cursorX].attrs = currentAttr;
if(dirtyRect.right == 0)
{
dirtyRect.right = (dirtyRect.left = cursorX) + 1;
dirtyRect.bottom = (dirtyRect.top = cursorY) + 1;
}
else
{
dirtyRect.left = std::min(dirtyRect.left, cursorX);
dirtyRect.top = std::min(dirtyRect.top, cursorY);
dirtyRect.right = std::max(dirtyRect.right, short(cursorX + 1));
dirtyRect.bottom = std::max(dirtyRect.bottom, short(cursorY + 1));
}
cursorX++;
if(cursorX >= cols)
PutCharNoUpdate('\n');
}
}
void Console::Update()
{
PortSetter setport(consolePort);
FontSetup fontSetup;
for(short row = dirtyRect.top; row < dirtyRect.bottom; ++row)
{
short start = -1;
bool needclear = false;
for(short col = dirtyRect.left; col < dirtyRect.right; ++col)
{
AttributedChar old = onscreen[row * cols + col];
if(chars[row * cols + col] != old)
{
if(start == -1)
start = col;
if(old.c != ' ')
needclear = true;
onscreen[row * cols + col] = chars[row * cols + col];
}
else
{
if(start != -1)
DrawCells(start, col, row, needclear);
start = -1;
needclear = false;
}
}
if(start != -1)
DrawCells(start, dirtyRect.right, row, needclear);
}
dirtyRect = Rect();
if(cursorVisible != cursorDrawn)
{
Rect r = CellRect(cursorX, cursorY);
if(cursorDrawn)
DrawCell(cursorX, cursorY, true);
else if(cursorRequestedHidden == false)
InvertRect(&r);
cursorDrawn = !cursorDrawn;
}
#if TARGET_API_MAC_CARBON
QDFlushPortBuffer(consolePort,NULL);
#endif
}
void Console::putch(char c)
{
if(!rows)
return;
PutCharNoUpdate(c);
Update();
}
void Console::write(const char *p, int n)
{
if(!rows)
return;
for(int i = 0; i < n; i++)
Console::currentInstance->PutCharNoUpdate(*p++);
Update();
}
void Console::InvalidateCursor()
{
if(cursorDrawn)
{
PortSetter setport(consolePort);
FontSetup fontSetup;
DrawCell(cursorX, cursorY, true);
cursorDrawn = false;
}
}
void Console::Idle()
{
long ticks = TickCount();
if(ticks - blinkTicks > 60)
{
cursorVisible = !cursorVisible;
blinkTicks = ticks;
Update();
}
}
void Console::Reshape(Rect newBounds)
{
if(!consolePort)
return;
bounds = newBounds;
InsetRect(&bounds, 2,2);
short newRows = (bounds.bottom - bounds.top) / cellSizeY;
short newCols = (bounds.right - bounds.left) / cellSizeX;
short upshift = 0;
if(cursorY >= newRows)
{
upshift = cursorY - (newRows - 1);
InvalidateCursor();
cursorY = std::max(newRows - 1, 0) ;
}
std::vector<AttributedChar> newChars(newRows*newCols, AttributedChar(' ', currentAttr));
for(short row = 0; row < newRows && row + upshift < rows; row++)
{
AttributedChar *src = &chars[(row+upshift) * cols];
AttributedChar *dst = &newChars[row * newCols];
std::copy(src, src + std::min(cols, newCols), dst);
}
chars.swap(newChars);
onscreen = newChars;
rows = newRows;
cols = newCols;
dirtyRect = Rect { 0, 0, rows, cols };
EraseRect(&newBounds);
Update();
Draw(newBounds);
}
int Console::WaitNextChar(unsigned long timeout)
{
return EOF;
}
bool Console::Available(unsigned long timeout)
{
return false;
}
// Map a letter to a function
void Console::InitEscapeSequenceMap()
{
escapeSequenceMap['A'] = &Console::MoveCursorUp;
escapeSequenceMap['B'] = &Console::MoveCursorDown;
escapeSequenceMap['C'] = &Console::MoveCursorForward;
escapeSequenceMap['D'] = &Console::MoveCursorBack;
escapeSequenceMap['E'] = &Console::MoveCursorNextLine;
escapeSequenceMap['F'] = &Console::MoveCursorPreviousLine;
escapeSequenceMap['G'] = &Console::MoveCursorHorizonalAbsolute;
escapeSequenceMap['H'] = &Console::SetCursorPosition;
escapeSequenceMap['J'] = &Console::EraseInDisplay;
escapeSequenceMap['K'] = &Console::EraseInLine;
escapeSequenceMap['h'] = &Console::ShowCursor;
escapeSequenceMap['l'] = &Console::HideCursor;
escapeSequenceMap['m'] = &Console::SetDisplayAttributes;
escapeSequenceMap['s'] = &Console::SaveCursorPosition;
escapeSequenceMap['u'] = &Console::RestoreCursorPosition;
}
// Bound to ANSI escape code H
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Control Sequence Introducer commands
// Name: Cursor Position
void Console::SetCursorPosition()
{
// possible formats the arguments can be in:
// n -> (1,n)
// n;m. -> (m,n)
// n;m; -> (m,n)
// n; -> (1,n)
// ;m -> (m,1)
// -> (1,1)
SetCursorX(getArgDefault(1, 1));
SetCursorY(getArgDefault(0, 1));
Update();
}
// Bound to ANSI escape code J
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Control Sequence Introducer commands
// Name: Erase in Display
void Console::EraseInDisplay()
{
int n = getArgDefault(0, 1);
switch(n) {
case 0: // clear from cursor to end of window
ClearFromCursorToEndOfWindow();
break;
case 1: // clear from cursor to beginning of the window
ClearFromTopOfWindowToCursor();
break;
case 2: // clear entire screen
ClearWindow();
break;
case 3: // clear entire screen and delete all lines saved in the scrollback buffer
ClearWindow();
break;
}
}
// Sets attributes of characters
// Note: only a few attributes are currently implemented
// Bound to ANSI escape code m
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Select Graphic Rendition parameters
void Console::SetDisplayAttributes()
{
int c = getArgDefault(0, 0);
switch(c)
{
case '0': // Normal character
currentAttr.reset();
break;
case '1': // Bold
currentAttr.setBold(true);
break;
case '3': // Italic
currentAttr.setItalic(true);
break;
case '4': // Underline
currentAttr.setUnderline(true);
break;
}
}
// Clears the window of all text
void Console::ClearWindow()
{
// Fill the buffer with blank spaces
std::fill(chars.begin(), chars.end(), AttributedChar(' ', currentAttr));
std::fill(onscreen.begin(), onscreen.end(), AttributedChar(' ', currentAttr));
// Erase the window
EraseRect(&bounds);
Update();
Draw(bounds);
}
// Clears the window of text from the current cursor position to the bottom of the window
void Console::ClearFromCursorToEndOfWindow()
{
int newPosition = GetCursorY() * cols + GetCursorX() - 1;
// Fill the buffer with blank spaces
std::fill(chars.begin() + newPosition, chars.end(), AttributedChar(' ', currentAttr));
std::fill(onscreen.begin() + newPosition, onscreen.end(), AttributedChar(' ', currentAttr));
// Erase the window
EraseRect(&bounds);
Update();
Draw(bounds);
}
// Clears the window from the top to the current cursor position
void Console::ClearFromTopOfWindowToCursor()
{
int newPosition = GetCursorY() * cols + GetCursorX();
// Fill the buffer with blank spaces
std::fill(chars.begin(), chars.begin() + newPosition, AttributedChar(' ', currentAttr));
std::fill(onscreen.begin(), onscreen.begin() + newPosition, AttributedChar(' ', currentAttr));
// Erase the window
EraseRect(&bounds);
Update();
Draw(bounds);
}
// handles the waitingForControlSequence state
void Console::HandleControlSequence(char c)
{
if (isalpha(c))
{
if(got_something) args.push_back(cur_arg);
EscapeSequenceFunction escFunc = escapeSequenceMap[(unsigned char)c];
if (escFunc)
(this->*(escFunc))();
sequenceState=State::noSequence;
args.clear();
}
else
{
if (c >= '0' && c <= '9') {
got_something = true;
cur_arg = cur_arg * 10 + (c - '0');
} else if(c == ';') {
args.push_back(cur_arg);
got_something = false;
cur_arg = 0;
}
}
}
// Bound to ANSI escape code A
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some ANSI control sequences
// Name: Cursor Up
void Console::MoveCursorUp() {
int lines = getArgDefault(2, 1);
SetCursorY(GetCursorY() - lines);
Update();
}
// Bound to ANSI escape code B
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some ANSI control sequences
// Name: Cursor Down
void Console::MoveCursorDown()
{
int lines = getArgDefault(0, 1);
SetCursorY(GetCursorY() + lines);
Update();
}
// Bound to ANSI escape code C
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some ANSI control sequences
// Name: Cursor Forward
void Console::MoveCursorForward()
{
int columns = getArgDefault(0, 1);
SetCursorX(GetCursorX() + columns);
Update();
}
// Bound to ANSI escape code D
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some ANSI control sequences
// Name: Cursor Back
void Console::MoveCursorBack()
{
int columns = getArgDefault(0, 1);
SetCursorX(GetCursorX() - columns);
Update();
}
// Bound to ANSI escape code E
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some ANSI control sequences
// Name: Cursor Next Line
void Console::MoveCursorNextLine()
{
int lines = getArgDefault(0, 1);
SetCursorX(1);
SetCursorY(GetCursorY() + lines);
Update();
}
// Bound to ANSI escape code F
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some ANSI control sequences
// Name: Cursor Previous Line
void Console::MoveCursorPreviousLine()
{
int lines = getArgDefault(0, 1);
SetCursorX(1);
SetCursorY(GetCursorY() - lines);
Update();
}
// Bound to ANSI escape code G
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some ANSI control sequences
// Name: Cursor Horizontal Absolute
void Console::MoveCursorHorizonalAbsolute()
{
int newPosition = getArgDefault(0, 1);
SetCursorX(newPosition);
Update();
}
// Bound to ANSI escape code K
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some ANSI control sequences
// Name: Erase in Line
void Console::EraseInLine()
{
int newPosition = getArgDefault(0, 0);
switch(newPosition)
{
case 0:
ClearFromCursorToEndOfLine();
break;
case 1:
ClearFromBeginningOfLineToCursor();
break;
case 2:
ClearEntireLine();
break;
}
}
// Erases from the current cursor position to the end of the line
void Console::ClearFromCursorToEndOfLine()
{
int currentPosition = (GetCursorY() - 1) * cols + GetCursorX() - 1;
int endOfLinePosition = GetCursorY() * cols;
// Fill part of the buffer with blank spaces
std::fill(chars.begin() + currentPosition, chars.begin() + endOfLinePosition, AttributedChar(' ', currentAttr));
std::fill(onscreen.begin() + currentPosition, onscreen.begin() + endOfLinePosition, AttributedChar(' ', currentAttr));
Update();
// Erase only on the line the cursor is on
PortSetter setport(consolePort);
Rect rect;
rect = CellRect(cursorX, cursorY);
rect.right = cols * cellSizeX;
EraseRect(&rect);
}
// Erases from the beginning of the line to the cursor's position
void Console::ClearFromBeginningOfLineToCursor()
{
int currentPosition = (GetCursorY() - 1) * cols + GetCursorX();
int beginningOfLinePosition = (GetCursorY() - 1) * cols;
// Fill part of the buffer with blank spaces
std::fill(chars.begin() + beginningOfLinePosition, chars.begin() + currentPosition, AttributedChar(' ', currentAttr));
std::fill(onscreen.begin() + beginningOfLinePosition, onscreen.begin() + currentPosition, AttributedChar(' ', currentAttr));
Update();
// Erase only on the line the cursor is on
Rect rect;
rect = CellRect(0, cursorY);
rect.right = GetCursorX() * cellSizeX;
EraseRect(&rect);
}
// Erases the entire line the cursor is on
void Console::ClearEntireLine()
{
int beginningOfLinePosition = (GetCursorY() - 1) * cols;
int endOfLinePosition = GetCursorY() * cols;
// Fill part of the buffer with blank spaces
std::fill(chars.begin() + beginningOfLinePosition, chars.begin() + endOfLinePosition, AttributedChar(' ', currentAttr));
std::fill(onscreen.begin() + beginningOfLinePosition, onscreen.begin() + endOfLinePosition, AttributedChar(' ', currentAttr));
Update();
// Erase only the line the cursor is on
Rect rect;
rect = CellRect(0, cursorY);
rect.right = cols * cellSizeX;
EraseRect(&rect);
}
// Bound to ANSI escape code h
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some popular private sequences
// Description: Sets a variable to indicate the cursor should be shown
void Console::ShowCursor()
{
cursorRequestedHidden = false;
}
// Bound to ANSI escape code l
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some popular private sequences
// Description: Sets a variable to indicate the cursor should be hidden
void Console::HideCursor()
{
cursorRequestedHidden = true;
}
// Bound to ANSI escape code s
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some popular private sequences
// Description: Saves the current cursor position
void Console::SaveCursorPosition()
{
savedCursorX = cursorX;
savedCursorY = cursorY;
}
// Bound to ANSI escape code u
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Some popular private sequences
// Description: Restores the cursor's position to the saved value
void Console::RestoreCursorPosition()
{
cursorX = savedCursorX;
cursorY = savedCursorY;
}
/*
These setter and getter functions fix a problem where ANSI escape codes expect
an origin of (1,1) while the Console class expects an origin of (0,0).
Only functions that work with ANSI escape codes should use these functions.
*/
void Console::SetCursorX(int newX)
{
cursorX = newX - 1;
cursorX = cursorX < 0 ? 0 : cursorX; // Terminal.app does this so we will too
}
int Console::GetCursorX()
{
return cursorX + 1;
}
void Console::SetCursorY(int newY)
{
cursorY = newY - 1;
cursorY = cursorY < 0 ? 0 : cursorY; // Terminal.app does this so we will too
}
int Console::GetCursorY()
{
return cursorY + 1;
}

View file

@ -0,0 +1,205 @@
/*
Copyright 2012-2020 Wolfgang Thaller, Davide Bucci
This file is part of Retro68.
Retro68 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Retro68 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License
along with Retro68. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RETRO68_CONSOLE_H_
#define RETRO68_CONSOLE_H_
#include <Quickdraw.h>
#include <vector>
namespace retro
{
class Console;
typedef void (Console::*EscapeSequenceFunction)();
class Attributes
{
public:
bool isBold(void) const;
bool isUnderline(void) const;
bool isItalic(void) const;
void setBold(const bool v);
void setUnderline(const bool v);
void setItalic(const bool v);
Attributes(void);
void reset(void);
private:
bool cBold;
bool cUnderline;
bool cItalic;
};
class AttributedChar
{
public:
char c;
Attributes attrs;
AttributedChar(char cc, Attributes aa) {
c = cc;
attrs = aa;
}
};
enum class State
{
noSequence,
waitingForSequenceStart,
waitingForControlSequence,
waitingForM,
waitingForOSCStart,
waitingForSemicolon,
inWindowName
};
class Console
{
public:
Console();
Console(GrafPtr port, Rect r);
~Console();
void Reshape(Rect newBounds);
void Draw(Rect r);
void Draw() {
Draw(bounds);
}
void putch(char c);
void write(const char *s, int n);
static Console *currentInstance;
short GetRows() const {
return rows;
}
short GetCols() const {
return cols;
}
virtual void setWindowName(const char *newName) {
};
void Idle();
bool IsEOF() const {
return eof;
}
private:
State sequenceState;
GrafPtr consolePort = nullptr;
Rect bounds;
Attributes currentAttr;
EscapeSequenceFunction escapeSequenceMap[256] = {};
std::vector < AttributedChar > chars, onscreen;
short cellSizeX;
short cellSizeY;
short rows = 0, cols = 0;
short cursorX, cursorY;
short savedCursorX, savedCursorY;
Rect dirtyRect = {};
long blinkTicks = 0;
bool cursorDrawn = false;
bool cursorVisible = true;
bool cursorRequestedHidden = false;
bool eof = false;
public:
void PutCharNoUpdate(char c);
void Update();
private:
short CalcStartX(short x, short y);
Rect CellRect(short x, short y);
void DrawCell(short x, short y, bool erase = true);
void DrawCells(short x1, short x2, short y, bool erase = true);
void ScrollUp(short n = 1);
bool ProcessEscSequence(char c);
void SetAttributes(Attributes aa);
void InvalidateCursor();
public:
virtual int WaitNextChar(unsigned long timeout = ~-0UL);
virtual bool Available(unsigned long timeout = ~-0UL);
private:
void InitEscapeSequenceMap();
void SetCursorPosition();
void EraseInDisplay();
void SetDisplayAttributes();
void ClearWindow();
void ClearFromCursorToEndOfWindow();
void ClearFromTopOfWindowToCursor();
void HandleControlSequence(char);
void MoveCursorUp();
void MoveCursorDown();
void MoveCursorForward();
void MoveCursorBack();
void MoveCursorNextLine();
void MoveCursorPreviousLine();
void MoveCursorHorizonalAbsolute();
void EraseInLine();
void ClearFromCursorToEndOfLine();
void ClearFromBeginningOfLineToCursor();
void ClearEntireLine();
void ShowCursor();
void HideCursor();
void SaveCursorPosition();
void RestoreCursorPosition();
void SetCursorX(int newX);
int GetCursorX();
void SetCursorY(int newY);
int GetCursorY();
int getArgDefault(size_t i, int defval) {
if (i < args.size()) {
return args[i];
}
return defval;
}
std::vector < char > title;
std::vector < int > args;
int cur_arg;
bool got_something;
protected:
void Init(GrafPtr port, Rect r);
};
}
#endif /* RETRO68_CONSOLE_H_ */

View file

@ -0,0 +1,274 @@
/*
Copyright 2012-2020 Wolfgang Thaller, Davide Bucci
This file is part of Retro68.
Retro68 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Retro68 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License
along with Retro68. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ConsoleWindow.h"
#include "Events.h"
#include <unordered_map>
#include <cstring>
#include <TextUtils.h>
#include <functional>
#include <OSUtils.h>
#include <Traps.h>
using namespace std;
using namespace retro;
namespace
{
std::unordered_map<WindowPtr, ConsoleWindow*> *windows = NULL;
function<bool(EventRecord *)> getEvent;
}
static void setupEventFunction();
ConsoleWindow::ConsoleWindow(Rect r, ConstStr255Param title)
{
GrafPtr port;
win = NewWindow(NULL, &r, title, true, 0, (WindowPtr)-1, true, 0);
#if !TARGET_API_MAC_CARBON
port = win;
Rect portRect = port->portRect;
#else
port = GetWindowPort(win);
Rect portRect;
GetPortBounds(port, &portRect);
#endif
SetPort(port);
EraseRect(&portRect);
if(!windows)
windows = new std::unordered_map<WindowPtr, ConsoleWindow*>();
(*windows)[win] = this;
Init(port, portRect);
setupEventFunction();
}
ConsoleWindow::~ConsoleWindow()
{
windows->erase(win);
DisposeWindow(win);
}
void ConsoleWindow::setWindowName(std::string newName)
{
Str255 pname;
#if TARGET_API_MAC_CARBON
// Carbon has the new, sane version.
c2pstrcpy(pname,newName.c_str());
#else
// It is also available in various glue code libraries and
// in some versions of InterfaceLib, but it's confusing.
// Using the inplace variant, c2pstr, isn't much better than
// doing things by hand:
strncpy((char *)&pname[1],newName.c_str(),255);
pname[0] = newName.length();
#endif
SetWTitle(win, pname);
}
int ConsoleWindow::WaitNextChar(unsigned long timeout) {
if (pending.empty()) {
if (!Available(timeout) || pending.empty()) return EOF;
}
int c = pending.front();
pending.pop_front();
return c;
}
bool ConsoleWindow::Available(unsigned long timeout)
{
if(!pending.empty()) return true;
unsigned long start = TickCount();
EventRecord event;
WindowPtr eventWin;
ConsoleWindow *realConsole;
#if TARGET_API_MAC_CARBON
Rect *boundsPtr = NULL;
#else
Rect *boundsPtr = &qd.screenBits.bounds;
#endif
do
{
#if TARGET_API_MAC_CARBON
#define SystemTask()
#endif
SystemTask();
Idle();
while(!getEvent(&event))
{
SystemTask();
Idle();
unsigned long now = TickCount();
if((now-start) > timeout) {
return false;
}
}
switch(event.what)
{
case updateEvt:
eventWin = (WindowPtr)event.message;
realConsole = (*windows)[(WindowPtr)event.message];
if(realConsole)
{
Rect updateRect;
BeginUpdate(eventWin);
#if TARGET_API_MAC_CARBON
RgnHandle rgn = NewRgn();
GetPortVisibleRegion(GetWindowPort(eventWin), rgn);
GetRegionBounds(rgn, &updateRect);
DisposeRgn(rgn);
#else
updateRect = (*qd.thePort->visRgn)->rgnBBox; // Life was simple back then.
#endif
realConsole->Draw(updateRect);
EndUpdate(eventWin);
}
break;
case mouseDown:
switch(FindWindow(event.where, &eventWin))
{
case inDrag:
DragWindow(eventWin, event.where, boundsPtr);
break;
case inGrow:
{
long growResult = GrowWindow(eventWin, event.where, boundsPtr);
SizeWindow(eventWin, growResult & 0xFFFF, growResult >> 16, false);
Reshape(Rect {0, 0, (short) (growResult >> 16), (short) (growResult & 0xFFFF) });
}
break;
case inGoAway:
{
if (TrackGoAway(eventWin,event.where))
exit(0);
}
break;
}
break;
}
} while(event.what != keyDown && event.what != autoKey);
int c = event.message & charCodeMask;
if (event.modifiers & ControlKey) {
// treat option like ctrl
c = c & 31;
}
if (event.modifiers & optionKey) {
// treat cmd like alt
pending.push_back(c);
c = 27;
}
// umac doesn't seem to ever generate these, so this code is untested....
int keycode = (event.message & keyCodeMask) >> 8;
static const struct { int keycode; const char *str; } keycodes[] = {
#define MKC_Left /* 0x46 */ 0x7B
#define MKC_Right /* 0x42 */ 0x7C
#define MKC_Down /* 0x48 */ 0x7D
#define MKC_Up /* 0x4D */ 0x7E
{MKC_Left, "\x1b[D"},
{MKC_Right, "\x1b[C"},
{MKC_Up, "\x1b[A"},
{MKC_Down, "\x1b[B"},
};
for(auto &i : keycodes) {
if(keycode == i.keycode) {
for(auto p = i.str; *p; p++) {
pending.push_back(*p);
}
return true;
}
}
// if keycode not found in map...
pending.push_back(c);
return true;
}
// Wrapper for the WaitNextEvent() function
static bool waitNextEventWrapper(EventRecord *event)
{
const int sleepValue = 5;
const RgnHandle mouseRegion = nil;
return WaitNextEvent(everyEvent, event, sleepValue, mouseRegion);
}
#if !(TARGET_API_MAC_CARBON)
// Wrapper for the GetNextEvent() function
static bool getNextEventWrapper(EventRecord *event)
{
return GetNextEvent(everyEvent, event);
}
// Determines if a Toolbox routine is available
static bool routineAvailable(int trapWord) {
TrapType trType;
int OSTrap = 0;
int ToolTrap = 1;
// Determine whether it is an Operating System or Toolbox routine
if ((trapWord & 0x0800) == 0) {
trType = OSTrap;
}
else {
trType = ToolTrap;
}
// Filter cases where older systems mask with 0x1FF rather than 0x3FF
if ((trType == ToolTrap) &&
((trapWord & 0x03FF) >= 0x200) &&
(GetToolboxTrapAddress(0xA86E) == GetToolboxTrapAddress(0xAA6E))) {
return false;
}
else {
return (NGetTrapAddress(trapWord, trType) != GetToolboxTrapAddress(_Unimplemented));
}
}
#endif /* TARGET_API_MAC_CARBON */
// Decides which event retrieving function to use
static void setupEventFunction()
{
#if TARGET_API_MAC_CARBON
getEvent = waitNextEventWrapper;
#else
if (routineAvailable(_WaitNextEvent) == true) {
getEvent = waitNextEventWrapper;
}
else {
getEvent = getNextEventWrapper;
}
#endif
}

View file

@ -0,0 +1,46 @@
/*
Copyright 2012-2020 Wolfgang Thaller, Davide Bucci
This file is part of Retro68.
Retro68 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Retro68 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License
along with Retro68. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Windows.h>
#include <deque>
#include <string>
#include "Console.h"
namespace retro
{
class ConsoleWindow: public Console
{
public:
ConsoleWindow(Rect r, ConstStr255Param title);
~ConsoleWindow();
void setWindowName(std::string newName);
private:
WindowPtr win;
int WaitNextChar(unsigned long timeout = 0UL) override;
bool Available(unsigned long timeout = 0UL) override;
std::deque < char > pending;
};
}

View file

@ -0,0 +1,115 @@
/*
Copyright 2014 Wolfgang Thaller.
This file is part of Retro68.
Retro68 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Retro68 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License
along with Retro68. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string>
#include <Quickdraw.h>
#include <MacMemory.h>
#include <Sound.h>
#include <Events.h>
#include <Fonts.h>
#include <sys/types.h>
#include <string.h>
#include "MacUtils.h"
#include "Console.h"
#include "ConsoleWindow.h"
namespace retro
{
void InitConsole();
}
using namespace retro;
void retro::InitConsole()
{
if(Console::currentInstance)
return;
Console::currentInstance = (Console*) -1;
#if !TARGET_API_MAC_CARBON
InitGraf(&qd.thePort);
InitFonts();
InitWindows();
InitMenus();
Rect r = qd.screenBits.bounds;
#else
Rect r = (*GetMainDevice())->gdRect;
#endif
{
// give MultiFinder a chance to bring the App to front
// see Technote TB 35 - MultiFinder Miscellanea
// "If your application [...] has the canBackground bit set in the
// size resource, then it should call _EventAvail several times
// (or _WaitNextEvent or _GetNextEvent) before putting up the splash
// screen, or the splash screen will come up behind the frontmost
// layer. If the canBackground bit is set, MultiFinder will not move
// your layer to the front until you call _GetNextEvent,
// _WaitNextEvent, or _EventAvail."
EventRecord event;
for(int i = 0; i < 5; i++)
EventAvail(everyEvent, &event);
}
r.top += 40;
InsetRect(&r, 5,5);
Console::currentInstance = new ConsoleWindow(r, "\pRetro68 Console");
InitCursor();
}
extern "C" ssize_t _consolewrite(int fd, const void *buf, size_t count)
{
if(!Console::currentInstance)
InitConsole();
if(Console::currentInstance == (Console*)-1)
return 0;
Console::currentInstance->write((const char*)buf, count);
return count;
}
extern "C" ssize_t _consoleread(int fd, void *buf, size_t count)
{
#if 0
if(!Console::currentInstance)
InitConsole();
if(Console::currentInstance == (Console*)-1)
return 0;
static std::string consoleBuf;
if(consoleBuf.size() == 0)
consoleBuf = Console::currentInstance->ReadLine();
if(count > consoleBuf.size())
count = consoleBuf.size();
memcpy(buf, consoleBuf.data(), count);
consoleBuf = consoleBuf.substr(count);
return count;
#endif
return 0;
}

View file

@ -0,0 +1,44 @@
/*
Copyright 2012 Wolfgang Thaller.
This file is part of Retro68.
Retro68 is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Retro68 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License
along with Retro68. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Quickdraw.h>
#include <stdlib.h>
namespace retro
{
class PortSetter
{
GrafPtr save;
public:
PortSetter(GrafPtr port)
{
::GetPort(&save);
::SetPort(port);
}
~PortSetter()
{
::SetPort(save);
}
};
}

View file

@ -0,0 +1,80 @@
#include <unistd.h>
#include "py/mpconfig.h"
extern "C" {
#include "uart_core.h"
}
#include "retro/Console.h"
#include "retro/ConsoleWindow.h"
/*
* Core UART functions to implement for a port
*/
using namespace retro;
namespace retro
{
void InitConsole();
}
#define USE_CONSOLE (1)
// Receive single character
extern "C"
int mp_hal_stdin_rx_chr(void);
int mp_hal_stdin_rx_chr(void) {
#if USE_CONSOLE
if(!Console::currentInstance)
InitConsole();
if(Console::currentInstance == (Console*)-1)
return EOF;
int c = Console::currentInstance->WaitNextChar();
#else
int c = *(char*)0xc0006a;
#endif
return c;
}
bool mp_hal_stdin_available(void) {
if(!Console::currentInstance)
InitConsole();
if(Console::currentInstance == (Console*)-1)
return false;
return Console::currentInstance->Available(1);
}
extern "C"
mp_uint_t debug_uart_tx_strn(const char *str, mp_uint_t len);
mp_uint_t debug_uart_tx_strn(const char *str, mp_uint_t len) {
mp_uint_t result = len;
// debug hack, needs patched umac
while(len--) {
*(char*)0xc0006a = *str++;
}
return result;
}
void debug_print_fn(void *data, const char *str, size_t len) {
debug_uart_tx_strn(str, len);
}
mp_print_t debug_print = { NULL, debug_print_fn };
// Send string of given length
extern "C"
mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len);
mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) {
debug_uart_tx_strn(str, len);
#if USE_CONSOLE
if(!Console::currentInstance)
InitConsole();
if(Console::currentInstance == (Console*)-1)
return 0;
Console::currentInstance->write(str, (size_t)len);
#endif
return len;
}

16
ports/m68kmac/uart_core.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#if defined(__cplusplus)
extern "C" {
#endif
#include "py/mpprint.h"
bool mp_hal_stdin_available(void);
int mp_hal_stdin_rx_chr(void);
extern mp_print_t debug_print;
#define DEBUG_PRINT(...) mp_printf(&debug_print, __VA_ARGS__)
#if defined(__cplusplus)
}
#endif

677
ports/m68kmac/vfs_mac.c Normal file
View file

@ -0,0 +1,677 @@
#include "py/mpconfig.h"
#include "py/obj.h"
#include "py/objstr.h"
#include "py/runtime.h"
#include "py/mperrno.h"
#include "py/mphal.h"
#include "py/stream.h"
#include "extmod/vfs.h"
#include "macutil.h"
#include "vfs_mac.h"
#include "debug_print.h"
#include "uart_core.h"
#define MAC_O_RDONLY (1)
#define MAC_O_WRONLY (2)
#define MAC_O_RDWR (3)
#define MAC_O_TRUNC (4)
#define MAC_O_APPEND (8)
#define MAC_O_CREAT (16)
// I have no idea whether mac is guaranteed not to generate these as file handles...
#define STDIN_FILENO (0)
#define STDOUT_FILENO (1)
#define STDERR_FILENO (2)
typedef struct _mp_obj_vfs_mac_t {
mp_obj_base_t base;
INTEGER volRefNum;
size_t root_len;
bool readonly;
} mp_obj_vfs_mac_t;
typedef struct _mp_obj_vfs_mac_file_t {
mp_obj_base_t base;
INTEGER fd;
INTEGER volRefNum;
int mode;
} mp_obj_vfs_mac_file_t;
static Byte *vfs_mac_get_path_cstr(Byte *pName, size_t pName_size, const char *path) {
while (*path == '/') {
path++;
}
pstr_from_cstr(pName, pName_size, "/");
pstr_cat_cstr(pName, pName_size, path);
for (int i = 1; i <= pName[0]; i++) {
if (pName[i] == '/') {
pName[i] = ':';
}
}
return pName;
}
static Byte *vfs_mac_get_path_str(Byte *pName, size_t pName_size, mp_obj_t obj) {
return vfs_mac_get_path_cstr(pName, pName_size, mp_obj_str_get_str(obj));
}
static VCB *getVolumeByName(mp_obj_t name) {
size_t str_len;
const char *str_data = mp_obj_str_get_data(name, &str_len);
VCB *vol = (VCB *)LMGetVCBQHdr().qHead;
while (vol) {
if (PSTR_LEN(vol->vcbVN) == str_len &&
memcmp(PSTR_DATA(vol->vcbVRefNum), str_data, str_len) == 0) {
return vol;
}
vol = (VCB *)vol->qLink;
}
mp_raise_ValueError(MP_ERROR_TEXT("volume not found (by name)"));
}
static VCB *getVolumeByVolumeReference(INTEGER vn) {
VCB *vol = (VCB *)LMGetVCBQHdr().qHead;
while (vol) {
if (vol->vcbVRefNum == vn) {
return vol;
}
vol = (VCB *)vol->qLink;
}
mp_raise_ValueError(MP_ERROR_TEXT("volume not found (by ref)"));
}
static void check_fd_is_open(const mp_obj_vfs_mac_file_t *o) {
if (o->fd < 0) {
mp_raise_ValueError(MP_ERROR_TEXT("I/O operation on closed file"));
}
}
static void vfs_mac_file_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
mp_obj_vfs_mac_file_t *self = MP_OBJ_TO_PTR(self_in);
if (self->fd <= STDERR_FILENO) {
mp_printf(print, "<io.%s %d>",
mp_obj_get_type_str(self_in), self->fd);
} else {
VCB *vol = getVolumeByVolumeReference(self->volRefNum);
mp_printf(print, "<io.%s %d on %.*s>",
mp_obj_get_type_str(self_in), self->fd,
(int)PSTR_LEN(vol->vcbVN), PSTR_DATA(vol->vcbVN));
}
}
mp_obj_t mp_vfs_mac_file_open(const mp_obj_vfs_mac_t *fs, const mp_obj_type_t *type, mp_obj_t file_in, mp_obj_t mode_in) {
const char *mode_s = mp_obj_str_get_str(mode_in);
int mode_rw = 0, mode_x = 0;
while (*mode_s) {
switch (*mode_s++) {
case 'r':
mode_rw = MAC_O_RDONLY;
break;
case 'w':
mode_rw = MAC_O_WRONLY;
mode_x = MAC_O_CREAT | MAC_O_TRUNC;
break;
case 'a':
mode_rw = MAC_O_WRONLY;
mode_x = MAC_O_CREAT | MAC_O_APPEND;
break;
case '+':
mode_rw = MAC_O_RDWR;
break;
case 'b':
type = &mp_type_vfs_mac_fileio;
break;
case 't':
type = &mp_type_vfs_mac_textio;
break;
}
}
mp_obj_vfs_mac_file_t *o = mp_obj_malloc_with_finaliser(mp_obj_vfs_mac_file_t, type);
o->fd = -1; // In case open() fails below, initialise this as a "closed" file object.
mp_obj_t fid = file_in;
if (mp_obj_is_small_int(fid)) {
o->fd = MP_OBJ_SMALL_INT_VALUE(fid);
return MP_OBJ_FROM_PTR(o);
}
Str255 pName;
vfs_mac_get_path_str(pName, sizeof(pName), fid);
INTEGER fd;
OSErr err = noErr;
while (true) {
OSErr err = FSOpen(pName, fs->volRefNum, &fd);
if (err == fnfErr && (mode_x & MAC_O_CREAT)) {
err = Create(pName, fs->volRefNum, 'mupy', type == &mp_type_vfs_mac_textio ? 'text' : 'bin ');
check_mac_err(err);
mode_x &= ~MAC_O_CREAT;
continue;
}
check_mac_err(err);
break;
}
if (mode_x & MAC_O_TRUNC) {
err = SetEOF(fd, 0);
} else if (mode_x & MAC_O_APPEND) {
err = SetFPos(fd, fsFromLEOF, 0);
}
if (err != noErr) {
FSClose(fd);
raise_mac_err(err);
}
o->fd = fd;
o->mode = mode_rw;
o->volRefNum = fs->volRefNum;
return MP_OBJ_FROM_PTR(o);
}
static mp_obj_t vfs_mac_file_fileno(mp_obj_t self_in) {
mp_obj_vfs_mac_file_t *self = MP_OBJ_TO_PTR(self_in);
return MP_OBJ_NEW_SMALL_INT(self->fd);
}
static MP_DEFINE_CONST_FUN_OBJ_1(vfs_mac_file_fileno_obj, vfs_mac_file_fileno);
static mp_uint_t vfs_mac_file_read(mp_obj_t o_in, void *buf, mp_uint_t size, int *errcode) {
mp_obj_vfs_mac_file_t *o = MP_OBJ_TO_PTR(o_in);
check_fd_is_open(o);
long count = (long)size;
if (o->fd == STDIN_FILENO) {
mp_uint_t result = size;
char *data = buf;
while (size--) {
*data++ = mp_hal_stdin_rx_chr();
}
return result;
}
if (o->fd <= STDERR_FILENO) {
return 0;
}
// As far as I could tell, reading past EOF is an error. It leaves the file advanced
// to the EOF position so "try again if EOF is signaled, only using the right length"
// doesn't work. Instead, check the current position & EOF position, limiting the size
// argument every time.
LONGINT pos_cur, pos_eof;
OSErr err = GetFPos(o->fd, &pos_cur);
if (err != noErr) {
goto out_err;
}
err = GetEOF(o->fd, &pos_eof);
if (err != noErr) {
goto out_err;
}
LONGINT max_count = pos_eof - pos_cur;
if (max_count < count) {
count = max_count;
}
err = FSRead(o->fd, &count, buf);
if (err == noErr) {
return count;
}
out_err:
*errcode = convert_mac_err(err);
return MP_STREAM_ERROR;
}
static mp_uint_t vfs_mac_file_write(mp_obj_t o_in, const void *buf, mp_uint_t size, int *errcode) {
mp_obj_vfs_mac_file_t *o = MP_OBJ_TO_PTR(o_in);
if (o->fd <= STDERR_FILENO) {
mp_hal_stdout_tx_strn(buf, size);
return size;
}
check_fd_is_open(o);
long count = (long)size;
OSErr err = FSWrite(o->fd, &count, buf);
if (err == noErr) {
return count;
}
*errcode = convert_mac_err(err);
return MP_STREAM_ERROR;
}
static mp_uint_t vfs_mac_file_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) {
mp_obj_vfs_mac_file_t *o = MP_OBJ_TO_PTR(o_in);
if (request != MP_STREAM_CLOSE) {
check_fd_is_open(o);
}
switch (request) {
case MP_STREAM_POLL: {
if (o->fd != STDIN_FILENO) {
*errcode = EINVAL;
return MP_STREAM_ERROR;
}
mp_uint_t ret = 0;
if (mp_hal_stdin_available()) {
ret |= arg & MP_STREAM_POLL_RD;
}
return ret;
}
case MP_STREAM_FLUSH: {
if (o->fd <= STDERR_FILENO) {
return 0;
}
INTEGER err = FlushVol(NULL, o->volRefNum);
if (err == noErr) {
*errcode = convert_mac_err(err);
break;
}
return 0;
}
case MP_STREAM_SEEK: {
struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)arg;
INTEGER posMode =
s->whence == MP_SEEK_CUR ? fsFromMark :
s->whence == MP_SEEK_SET ? fsFromStart : fsFromMark;
OSErr err = SetFPos(o->fd, posMode, (long)s->offset);
// TODO: seek that enlarges a file [need to call Allocate()?]
if (err == noErr) {
*errcode = convert_mac_err(err);
break;
}
return 0;
}
case MP_STREAM_CLOSE:
if (o->fd >= 0) {
int fd = o->fd;
o->fd = -1;
MP_THREAD_GIL_EXIT();
OSErr err = FSClose(fd);
OSErr err1 = FlushVol(NULL, o->volRefNum);
if (err != noErr) {
*errcode = convert_mac_err(err);
break;
}
if (err1 != noErr) {
*errcode = convert_mac_err(err1);
break;
}
MP_THREAD_GIL_ENTER();
}
return 0;
case MP_STREAM_GET_FILENO:
return o->fd;
default:
*errcode = EINVAL;
}
return MP_STREAM_ERROR;
}
static const mp_rom_map_elem_t vfs_mac_rawfile_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_fileno), MP_ROM_PTR(&vfs_mac_file_fileno_obj) },
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) },
{ MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) },
{ MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) },
{ MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) },
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) },
{ MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) },
{ MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) },
{ MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) },
{ MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) },
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj) },
{ MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) },
{ MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mp_stream___exit___obj) },
};
static MP_DEFINE_CONST_DICT(vfs_mac_rawfile_locals_dict, vfs_mac_rawfile_locals_dict_table);
static const mp_stream_p_t vfs_mac_fileio_stream_p = {
.read = vfs_mac_file_read,
.write = vfs_mac_file_write,
.ioctl = vfs_mac_file_ioctl,
};
MP_DEFINE_CONST_OBJ_TYPE(
mp_type_vfs_mac_fileio,
MP_QSTR_FileIO,
MP_TYPE_FLAG_ITER_IS_STREAM,
print, vfs_mac_file_print,
protocol, &vfs_mac_fileio_stream_p,
locals_dict, &vfs_mac_rawfile_locals_dict
);
static const mp_stream_p_t vfs_mac_textio_stream_p = {
.read = vfs_mac_file_read,
.write = vfs_mac_file_write,
.ioctl = vfs_mac_file_ioctl,
.is_text = true,
};
MP_DEFINE_CONST_OBJ_TYPE(
mp_type_vfs_mac_textio,
MP_QSTR_TextIOWrapper,
MP_TYPE_FLAG_ITER_IS_STREAM,
print, &vfs_mac_file_print,
protocol, &vfs_mac_textio_stream_p,
locals_dict, &vfs_mac_rawfile_locals_dict
);
static mp_obj_t vfs_mac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 0, 1, false);
INTEGER volRefNum;
if (n_args == 0) {
GetVol(NULL, &volRefNum);
} else if (mp_obj_is_small_int(args[0])) {
volRefNum = MP_OBJ_SMALL_INT_VALUE(args[0]);
} else {
volRefNum = getVolumeByName(args[0])->vcbVRefNum;
}
// create new object
mp_obj_vfs_mac_t *vfs = mp_obj_malloc(mp_obj_vfs_mac_t, type);
vfs->volRefNum = volRefNum;
return MP_OBJ_FROM_PTR(vfs);
}
static mp_obj_t volumes(mp_obj_t self_in) {
mp_obj_t result = mp_obj_list_make_new(&mp_type_list, 0, 0, NULL);
VCB *vol = (VCB *)LMGetVCBQHdr().qHead;
mp_obj_t args[3];
mp_load_method(result, MP_QSTR_append, args);
while (vol) {
args[2] = new_str_from_pstr(vol->vcbVN);
mp_call_method_n_kw(1, 0, args);
vol = (VCB *)vol->qLink;
}
return result;
}
MP_DEFINE_CONST_FUN_OBJ_1(volumes_obj, volumes);
static mp_obj_t vfs_mac_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) {
mp_obj_vfs_mac_t *self = MP_OBJ_TO_PTR(self_in);
if (mp_obj_is_true(readonly)) {
self->readonly = true;
}
if (mp_obj_is_true(mkfs)) {
mp_raise_OSError(MP_EPERM);
}
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_3(vfs_mac_mount_obj, vfs_mac_mount);
static mp_obj_t vfs_mac_umount(mp_obj_t self_in) {
(void)self_in;
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_1(vfs_mac_umount_obj, vfs_mac_umount);
static mp_obj_t vfs_mac_open(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mode_in) {
mp_obj_vfs_mac_t *self = MP_OBJ_TO_PTR(self_in);
const char *mode = mp_obj_str_get_str(mode_in);
if (self->readonly
&& (strchr(mode, 'w') != NULL || strchr(mode, 'a') != NULL || strchr(mode, '+') != NULL)) {
mp_raise_OSError(MP_EROFS);
}
return mp_vfs_mac_file_open(self, &mp_type_vfs_mac_textio, path_in, mode_in);
}
static MP_DEFINE_CONST_FUN_OBJ_3(vfs_mac_open_obj, vfs_mac_open);
static mp_import_stat_t mp_vfs_mac_import_stat(void *self_in, const char *path) {
mp_obj_vfs_mac_t *self = MP_OBJ_TO_PTR(self_in);
Str255 pName;
vfs_mac_get_path_cstr(pName, sizeof(pName), path);
CInfoPBRec pb = {
.hFileInfo.ioVRefNum = self->volRefNum,
.hFileInfo.ioNamePtr = (StringPtr)pName,
};
OSErr err;
err = PBGetCatInfoSync(&pb);
MP_THREAD_GIL_ENTER();
if (err != noErr) {
return MP_IMPORT_STAT_NO_EXIST;
}
bool is_dir = pb.hFileInfo.ioFlAttrib & (1 << 4);
if (is_dir) {
return MP_IMPORT_STAT_DIR;
} else {
return MP_IMPORT_STAT_FILE;
}
}
static mp_obj_t vfs_mac_chdir(mp_obj_t self_in, mp_obj_t path_in) {
mp_raise_NotImplementedError(NULL); // TODO
}
static MP_DEFINE_CONST_FUN_OBJ_2(vfs_mac_chdir_obj, vfs_mac_chdir);
static mp_obj_t vfs_mac_getcwd(mp_obj_t self_in) {
mp_raise_NotImplementedError(NULL); // TODO
}
static MP_DEFINE_CONST_FUN_OBJ_1(vfs_mac_getcwd_obj, vfs_mac_getcwd);
typedef struct _vfs_mac_ilistdir_it_t {
mp_obj_base_t base;
mp_fun_1_t iternext;
bool is_str;
INTEGER volRefNum;
INTEGER index;
INTEGER dirId;
} vfs_mac_ilistdir_it_t;
INTEGER get_dirid(INTEGER volRefNum, Byte *dirname) {
MP_THREAD_GIL_EXIT();
CInfoPBRec pb = {
.hFileInfo.ioVRefNum = volRefNum,
.hFileInfo.ioNamePtr = (StringPtr)dirname,
};
OSErr err;
err = PBGetCatInfoSync(&pb);
MP_THREAD_GIL_ENTER();
check_mac_err(err);
bool is_dir = pb.hFileInfo.ioFlAttrib & (1 << 4);
if (is_dir) {
return pb.hFileInfo.ioDirID;
}
mp_raise_OSError(MP_ENOTDIR);
}
static mp_obj_t vfs_mac_ilistdir_it_iternext(mp_obj_t self_in) {
vfs_mac_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in);
while (self->index != 0) {
int index = self->index++;
MP_THREAD_GIL_EXIT();
Str255 pName;
CInfoPBRec pb = {
.hFileInfo.ioVRefNum = self->volRefNum,
.hFileInfo.ioNamePtr = (StringPtr)pName,
.hFileInfo.ioFVersNum = 0,
.hFileInfo.ioFDirIndex = index,
.hFileInfo.ioDirID = self->dirId,
};
OSErr err;
err = PBGetCatInfoSync(&pb);
MP_THREAD_GIL_ENTER();
DPRINTF("PBGetCatInfoSync(%d) -> %d", index, err);
if (err == fnfErr) {
self->index = 0;
break;
}
if (err != noErr) {
continue;
}
bool is_dir = pb.hFileInfo.ioFlAttrib & (1 << 4);
// make 3-tuple with info about this entry
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(3, NULL));
if (self->is_str) {
t->items[0] = new_str_from_pstr(pName);
} else {
t->items[0] = new_bytes_from_pstr(pName);
}
if (is_dir) {
t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR);
} else {
t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFREG);
}
t->items[2] = MP_OBJ_NEW_SMALL_INT(index);
return MP_OBJ_FROM_PTR(t);
}
return MP_OBJ_STOP_ITERATION;
}
static mp_obj_t vfs_mac_ilistdir(mp_obj_t self_in, mp_obj_t path_in) {
mp_obj_vfs_mac_t *self = MP_OBJ_TO_PTR(self_in);
vfs_mac_ilistdir_it_t *iter = mp_obj_malloc(vfs_mac_ilistdir_it_t, &mp_type_polymorph_iter);
iter->iternext = vfs_mac_ilistdir_it_iternext;
iter->is_str = mp_obj_get_type(path_in) == &mp_type_str;
iter->volRefNum = self->volRefNum;
iter->index = 1;
Str255 pName;
vfs_mac_get_path_str(pName, sizeof(pName), path_in);
iter->dirId = get_dirid(self->volRefNum, pName);
return MP_OBJ_FROM_PTR(iter);
}
static MP_DEFINE_CONST_FUN_OBJ_2(vfs_mac_ilistdir_obj, vfs_mac_ilistdir);
static mp_obj_t vfs_mac_mkdir(mp_obj_t self_in, mp_obj_t path_in) {
mp_raise_NotImplementedError(NULL); // TODO
}
static MP_DEFINE_CONST_FUN_OBJ_2(vfs_mac_mkdir_obj, vfs_mac_mkdir);
static mp_obj_t vfs_mac_remove(mp_obj_t self_in, mp_obj_t path_in) {
mp_raise_NotImplementedError(NULL); // TODO
}
static MP_DEFINE_CONST_FUN_OBJ_2(vfs_mac_remove_obj, vfs_mac_remove);
static mp_obj_t vfs_mac_rename(mp_obj_t self_in, mp_obj_t old_path_in, mp_obj_t new_path_in) {
mp_raise_NotImplementedError(NULL); // TODO
}
static MP_DEFINE_CONST_FUN_OBJ_3(vfs_mac_rename_obj, vfs_mac_rename);
static mp_obj_t vfs_mac_rmdir(mp_obj_t self_in, mp_obj_t path_in) {
mp_raise_NotImplementedError(NULL); // TODO
}
static MP_DEFINE_CONST_FUN_OBJ_2(vfs_mac_rmdir_obj, vfs_mac_rmdir);
static mp_obj_t vfs_mac_stat(mp_obj_t self_in, mp_obj_t path_in) {
mp_obj_vfs_mac_t *self = MP_OBJ_TO_PTR(self_in);
Str255 pName;
vfs_mac_get_path_str(pName, sizeof(pName), path_in);
MP_THREAD_GIL_EXIT();
CInfoPBRec pb = {
.hFileInfo.ioVRefNum = self->volRefNum,
.hFileInfo.ioNamePtr = (StringPtr)pName,
};
OSErr err;
err = PBGetCatInfoSync(&pb);
MP_THREAD_GIL_ENTER();
check_mac_err(err);
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
bool is_dir = pb.hFileInfo.ioFlAttrib & (1 << 4);
t->items[0] = MP_OBJ_NEW_SMALL_INT(is_dir ? MP_S_IFDIR | 0777 : MP_S_IFREG | 0666);
t->items[1] = mp_obj_new_int_from_uint(pb.hFileInfo.ioFDirIndex);
t->items[2] = mp_obj_new_int_from_uint(self->volRefNum);
t->items[3] = mp_obj_new_int_from_uint(is_dir ? 2 : 1);
t->items[4] = mp_obj_new_int_from_uint(0); // uid
t->items[5] = mp_obj_new_int_from_uint(0); // gid
t->items[6] = mp_obj_new_int_from_uint(is_dir ? 512 : pb.hFileInfo.ioFlPyLen); // data fork only
t->items[7] = mp_obj_new_int_from_uint(0); // atime
t->items[8] = mp_obj_new_int_from_uint(0); // mtime
t->items[9] = mp_obj_new_int_from_uint(0); // ctime
return MP_OBJ_FROM_PTR(t);
}
static MP_DEFINE_CONST_FUN_OBJ_2(vfs_mac_stat_obj, vfs_mac_stat);
static mp_obj_t vfs_mac_statvfs(mp_obj_t self_in, mp_obj_t path_in) {
mp_raise_NotImplementedError(NULL); // TODO
#if 0
mp_obj_vfs_mac_t *self = MP_OBJ_TO_PTR(self_in);
const char *path = vfs_mac_get_path_cstr(self, path_in);
STRUCT_STATVFS sb;
int ret;
MP_HAL_RETRY_SYSCALL(ret, STATVFS(path, &sb), mp_raise_OSError(err));
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
t->items[0] = MP_OBJ_NEW_SMALL_INT(sb.f_bsize);
t->items[1] = MP_OBJ_NEW_SMALL_INT(sb.f_frsize);
t->items[2] = MP_OBJ_NEW_SMALL_INT(sb.f_blocks);
t->items[3] = MP_OBJ_NEW_SMALL_INT(sb.f_bfree);
t->items[4] = MP_OBJ_NEW_SMALL_INT(sb.f_bavail);
t->items[5] = MP_OBJ_NEW_SMALL_INT(sb.f_files);
t->items[6] = MP_OBJ_NEW_SMALL_INT(sb.f_ffree);
t->items[7] = MP_OBJ_NEW_SMALL_INT(F_FAVAIL);
t->items[8] = MP_OBJ_NEW_SMALL_INT(F_FLAG);
t->items[9] = MP_OBJ_NEW_SMALL_INT(F_NAMEMAX);
return MP_OBJ_FROM_PTR(t);
#endif
}
static MP_DEFINE_CONST_FUN_OBJ_2(vfs_mac_statvfs_obj, vfs_mac_statvfs);
static const mp_rom_map_elem_t vfs_mac_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_volumes), MP_ROM_PTR(&volumes_obj) },
{ MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&vfs_mac_mount_obj) },
{ MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&vfs_mac_umount_obj) },
{ MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&vfs_mac_open_obj) },
{ MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&vfs_mac_chdir_obj) },
{ MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&vfs_mac_getcwd_obj) },
{ MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&vfs_mac_ilistdir_obj) },
{ MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&vfs_mac_mkdir_obj) },
{ MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&vfs_mac_remove_obj) },
{ MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&vfs_mac_rename_obj) },
{ MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&vfs_mac_rmdir_obj) },
{ MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&vfs_mac_stat_obj) },
{ MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&vfs_mac_statvfs_obj) },
};
static MP_DEFINE_CONST_DICT(vfs_mac_locals_dict, vfs_mac_locals_dict_table);
static const mp_vfs_proto_t vfs_mac_proto = {
.import_stat = mp_vfs_mac_import_stat,
};
static void vfs_mac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
mp_obj_vfs_mac_t *self = MP_OBJ_TO_PTR(self_in);
VCB *vol = getVolumeByVolumeReference(self->volRefNum);
mp_printf(print, "<VfsMac %d %.*s>",
self->volRefNum, (int)PSTR_LEN(vol->vcbVN), PSTR_DATA(vol->vcbVN));
}
MP_DEFINE_CONST_OBJ_TYPE(
mp_type_vfs_mac,
MP_QSTR_VfsMac,
MP_TYPE_FLAG_NONE,
make_new, vfs_mac_make_new,
protocol, &vfs_mac_proto,
locals_dict, &vfs_mac_locals_dict,
print, &vfs_mac_print
);
mp_obj_vfs_mac_file_t mp_sys_stdin_obj = {{&mp_type_vfs_mac_textio}, STDIN_FILENO};
mp_obj_vfs_mac_file_t mp_sys_stdout_obj = {{&mp_type_vfs_mac_textio}, STDOUT_FILENO};
mp_obj_vfs_mac_file_t mp_sys_stderr_obj = {{&mp_type_vfs_mac_textio}, STDERR_FILENO};

10
ports/m68kmac/vfs_mac.h Normal file
View file

@ -0,0 +1,10 @@
#pragma once
typedef struct _mp_obj_vfs_mac_t mp_obj_vfs_mac_t;
typedef struct _mp_obj_vfs_mac_file_t mp_obj_vfs_mac_file_t;
extern const mp_obj_type_t mp_type_vfs_mac;
extern const mp_obj_type_t mp_type_vfs_mac_fileio;
extern const mp_obj_type_t mp_type_vfs_mac_textio;
mp_obj_t mp_vfs_mac_file_open(const mp_obj_vfs_mac_t *fs, const mp_obj_type_t *type, mp_obj_t file_in, mp_obj_t mode_in);

View file

@ -420,6 +420,9 @@ static void gc_collect_start_common(void) {
}
void gc_collect_root(void **ptrs, size_t len) {
#if __m68k__
len *= 2;
#endif
#if !MICROPY_GC_SPLIT_HEAP
mp_state_mem_area_t *area = &MP_STATE_MEM(area);
#endif
@ -676,6 +679,12 @@ static void gc_sweep_free_blocks(void) {
__attribute__((no_sanitize_address))
#endif
static void *gc_get_ptr(void **ptrs, int i) {
#if __m68k__
char *ptr = (char *)ptrs;
ptr += i * 2;
ptrs = (void **)ptr;
i = 0;
#endif
#if MICROPY_DEBUG_VALGRIND
if (!VALGRIND_CHECK_MEM_IS_ADDRESSABLE(&ptrs[i], sizeof(*ptrs))) {
return NULL;

View file

@ -43,6 +43,8 @@ typedef uintptr_t gc_helper_regs_t[10];
typedef uintptr_t gc_helper_regs_t[11]; // x19-x29
#elif defined(__riscv) && (__riscv_xlen <= 64)
typedef uintptr_t gc_helper_regs_t[12]; // S0-S11
#elif defined(__m68k__)
typedef uintptr_t gc_helper_regs_t[8]; // a0-a4/d3-d7
#endif
#endif

View file

@ -0,0 +1,43 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2024 Alessandro Gatti
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
.global gc_helper_get_regs_and_sp
.type gc_helper_get_regs_and_sp, @function
gc_helper_get_regs_and_sp:
/* Store registers into the given array (address on stack). */
move.l 4(%sp),%a0
lea.l 32(%a0), %a0
movem.l %a2-%a4/%d3-%d7, -(%a0)
/* Return the stack pointer in d0. */
move.l %sp, %d0
rts
.size gc_helper_get_regs_and_sp, .-gc_helper_get_regs_and_sp