Merge branch 'mc-1.16.x' into mc-1.18.x

This commit is contained in:
Jonathan Coates 2023-07-07 00:02:42 +01:00
commit edf372a695
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
24 changed files with 368 additions and 160 deletions

View File

@ -5,7 +5,7 @@ kotlin.stdlib.default.dependency=false
kotlin.jvm.target.validation.mode=error
# Mod properties
modVersion=1.101.2
modVersion=1.101.3
# Minecraft properties: We want to configure this here so we can read it in settings.gradle
mcVersion=1.18.2

View File

@ -8,9 +8,13 @@
import com.google.common.net.InetAddresses;
import dan200.computercraft.ComputerCraft;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* A predicate on an address. Matches against a domain and an ip address.
@ -135,13 +139,36 @@ final class PrivatePattern implements AddressPredicate
{
static final PrivatePattern INSTANCE = new PrivatePattern();
private static final Set<InetAddress> additionalAddresses = Arrays.stream( new String[] {
// Block various cloud providers internal IPs.
"100.100.100.200", // Alibaba
"192.0.0.192", // Oracle
} ).map( InetAddresses::forString ).collect( Collectors.toSet() );
@Override
public boolean matches( InetAddress socketAddress )
{
return socketAddress.isAnyLocalAddress()
|| socketAddress.isLoopbackAddress()
|| socketAddress.isLinkLocalAddress()
|| socketAddress.isSiteLocalAddress();
return socketAddress.isAnyLocalAddress() // 0.0.0.0, ::0
|| socketAddress.isLoopbackAddress() // 127.0.0.0/8, ::1
|| socketAddress.isLinkLocalAddress() // 169.254.0.0/16, fe80::/10
|| socketAddress.isSiteLocalAddress() // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fec0::/10
|| socketAddress.isMulticastAddress() // 224.0.0.0/4, ff00::/8
|| isUniqueLocalAddress( socketAddress ) // fd00::/8
|| additionalAddresses.contains( socketAddress );
}
/**
* Determine if an IP address lives inside the ULA address range.
*
* @param address The IP address to test.
* @return Whether this address sits in the ULA address range.
* @see <a href="https://en.wikipedia.org/wiki/Unique_local_address">Unique local address on Wikipedia</a>
*/
private boolean isUniqueLocalAddress( InetAddress address )
{
// ULA is actually defined as fc00::/7 (so both fc00::/8 and fd00::/8). However, only the latter is actually
// defined right now, so let's be conservative.
return address instanceof Inet6Address && (address.getAddress()[0] & 0xff) == 0xfd;
}
}

View File

@ -7,10 +7,13 @@
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.shared.command.arguments.ComputersArgumentType;
import dan200.computercraft.shared.command.text.TableBuilder;
import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.computer.core.ServerComputer;
@ -200,7 +203,10 @@ else if( b.getLevel() == world )
.then( command( "queue" )
.requires( UserLevel.ANYONE )
.arg( "computer", manyComputers() )
.arg(
RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument( "computer", manyComputers() )
.suggests( ( context, builder ) -> Suggestions.empty() )
)
.argManyValue( "args", StringArgumentType.string(), Collections.emptyList() )
.executes( ( ctx, args ) -> {
Collection<ServerComputer> computers = getComputersArgument( ctx, "computer" );

View File

@ -61,12 +61,36 @@ public boolean test( CommandSourceStack source )
return source.hasPermission( toLevel() );
}
/**
* Take the union of two {@link UserLevel}s.
* <p>
* This satisfies the property that for all sources {@code s}, {@code a.test(s) || b.test(s) == (a b).test(s)}.
*
* @param left The first user level to take the union of.
* @param right The second user level to take the union of.
* @return The union of two levels.
*/
public static UserLevel union( UserLevel left, UserLevel right )
{
if( left == right ) return left;
// x ANYONE = ANYONE
if( left == ANYONE || right == ANYONE ) return ANYONE;
// x OWNER = OWNER
if( left == OWNER ) return right;
if( right == OWNER ) return left;
// At this point, we have x != y and x, y { OP, OWNER_OP }.
return OWNER_OP;
}
private static boolean isOwner( CommandSourceStack source )
{
MinecraftServer server = source.getServer();
Entity sender = source.getEntity();
return server.isDedicatedServer()
? source.getEntity() == null && source.hasPermission( 4 ) && source.getTextName().equals( "Server" )
: sender instanceof Player player && player.getGameProfile().getName().equalsIgnoreCase( server.getServerModName() );
: sender instanceof Player player && server.isSingleplayerOwner( player.getGameProfile() );
}
}

View File

@ -52,10 +52,15 @@ public CommandBuilder<S> requires( Predicate<S> predicate )
return this;
}
public CommandBuilder<S> arg( ArgumentBuilder<S, ?> arg )
{
args.add( arg );
return this;
}
public CommandBuilder<S> arg( String name, ArgumentType<?> type )
{
args.add( RequiredArgumentBuilder.argument( name, type ) );
return this;
return arg( RequiredArgumentBuilder.argument( name, type ) );
}
public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue( String name, ArgumentType<T> type, List<T> empty )
@ -84,7 +89,7 @@ private <T, U> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argMany( String nam
return command -> {
// The node for no arguments
ArgumentBuilder<S, ?> tail = tail( ctx -> command.run( ctx, empty.get() ) );
ArgumentBuilder<S, ?> tail = setupTail( ctx -> command.run( ctx, empty.get() ) );
// The node for one or more arguments
ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder
@ -93,7 +98,7 @@ private <T, U> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argMany( String nam
// Chain all of them together!
tail.then( moreArg );
return link( tail );
return buildTail( tail );
};
}
@ -106,22 +111,18 @@ private static <T> List<T> getList( CommandContext<?> context, String name )
@Override
public CommandNode<S> executes( Command<S> command )
{
if( args.isEmpty() ) throw new IllegalStateException( "Cannot have empty arg chain builder" );
return link( tail( command ) );
return buildTail( setupTail( command ) );
}
private ArgumentBuilder<S, ?> tail( Command<S> command )
private ArgumentBuilder<S, ?> setupTail( Command<S> command )
{
ArgumentBuilder<S, ?> defaultTail = args.get( args.size() - 1 );
defaultTail.executes( command );
if( requires != null ) defaultTail.requires( requires );
return defaultTail;
return args.get( args.size() - 1 ).executes( command );
}
private CommandNode<S> link( ArgumentBuilder<S, ?> tail )
private CommandNode<S> buildTail( ArgumentBuilder<S, ?> tail )
{
for( int i = args.size() - 2; i >= 0; i-- ) tail = args.get( i ).then( tail );
if( requires != null ) tail.requires( requires );
return tail.build();
}
}

View File

@ -12,6 +12,7 @@
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import dan200.computercraft.shared.command.UserLevel;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.ClickEvent;
@ -22,6 +23,10 @@
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static dan200.computercraft.shared.command.text.ChatHelpers.coloured;
import static dan200.computercraft.shared.command.text.ChatHelpers.translate;
@ -44,6 +49,33 @@ public static HelpingArgumentBuilder choice( String literal )
return new HelpingArgumentBuilder( literal );
}
@Override
public LiteralArgumentBuilder<CommandSourceStack> requires( Predicate<CommandSourceStack> requirement )
{
throw new IllegalStateException( "Cannot use requires on a HelpingArgumentBuilder" );
}
@Override
public Predicate<CommandSourceStack> getRequirement()
{
// The requirement of this node is the union of all child's requirements.
List<Predicate<CommandSourceStack>> requirements = Stream.concat(
children.stream().map( ArgumentBuilder::getRequirement ),
getArguments().stream().map( CommandNode::getRequirement )
).collect( Collectors.toList() );
// If all requirements are a UserLevel, take the union of those instead.
UserLevel userLevel = UserLevel.OWNER;
for( Predicate<CommandSourceStack> requirement : requirements )
{
if( !(requirement instanceof UserLevel level) ) return x -> requirements.stream().anyMatch( y -> y.test( x ) );
userLevel = UserLevel.union( userLevel, level );
}
return userLevel;
}
@Override
public LiteralArgumentBuilder<CommandSourceStack> executes( final Command<CommandSourceStack> command )
{
@ -99,9 +131,7 @@ private LiteralCommandNode<CommandSourceStack> buildImpl( String id, String comm
helpCommand.node = node;
// Set up a /... help command
LiteralArgumentBuilder<CommandSourceStack> helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal( "help" )
.requires( x -> getArguments().stream().anyMatch( y -> y.getRequirement().test( x ) ) )
.executes( helpCommand );
LiteralArgumentBuilder<CommandSourceStack> helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal( "help" ).executes( helpCommand );
// Add all normal command children to this and the help node
for( CommandNode<CommandSourceStack> child : getArguments() )

View File

@ -167,7 +167,7 @@ public void setPlacedBy( @Nonnull Level world, @Nonnull BlockPos pos, @Nonnull B
@Override
public float getExplosionResistance( BlockState state, BlockGetter world, BlockPos pos, Explosion explosion )
{
Entity exploder = explosion.getExploder();
Entity exploder = explosion == null ? null : explosion.getExploder();
if( getFamily() == ComputerFamily.ADVANCED || exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile )
{
return 2000;

View File

@ -82,7 +82,7 @@ public TurtleModem( ResourceLocation id, ItemStack stack, boolean advanced )
}
else
{
leftOffModel = new ResourceLocation( ComputerCraft.MOD_ID, "turtle_modem_normal_off_left" );
leftOffModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_off_left" );
rightOffModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_off_right" );
leftOnModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_on_left" );
rightOnModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_on_right" );

View File

@ -153,12 +153,22 @@ function locate(_nTimeout, _bDebug)
if tFix.nDistance == 0 then
pos1, pos2 = tFix.vPosition, nil
else
table.insert(tFixes, tFix)
-- Insert our new position in our table, with a maximum of three items. If this is close to a
-- previous position, replace that instead of inserting.
local insIndex = math.min(3, #tFixes + 1)
for i, older in pairs(tFixes) do
if (older.vPosition - tFix.vPosition):length() < 1 then
insIndex = i
break
end
end
tFixes[insIndex] = tFix
if #tFixes >= 3 then
if not pos1 then
pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[#tFixes])
pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[3])
else
pos1, pos2 = narrow(pos1, pos2, tFixes[#tFixes])
pos1, pos2 = narrow(pos1, pos2, tFixes[3])
end
end
end

View File

@ -74,8 +74,7 @@ function undefine(name)
details[name] = nil
end
local function set_value(name, value)
local new = reserialize(value)
local function set_value(name, new)
local old = values[name]
if old == nil then
local opt = details[name]
@ -103,7 +102,7 @@ function set(name, value)
local opt = details[name]
if opt and opt.type then expect(2, value, opt.type) end
set_value(name, value)
set_value(name, reserialize(value))
end
--- Get the value of a setting.
@ -214,7 +213,9 @@ function load(sPath)
if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then
local opt = details[k]
if not opt or not opt.type or ty_v == opt.type then
set_value(k, v)
-- This may fail if the table is recursive (or otherwise cannot be serialized).
local ok, v = pcall(reserialize, v)
if ok then set_value(k, v) end
end
end
end

View File

@ -292,6 +292,13 @@ local g_tLuaKeywords = {
["while"] = true,
}
--- A version of the ipairs iterator which ignores metamethods
local function inext(tbl, i)
i = (i or 0) + 1
local v = rawget(tbl, i)
if v == nil then return nil else return i, v end
end
local serialize_infinity = math.huge
local function serialize_impl(t, tracking, indent, opts)
local sType = type(t)
@ -318,11 +325,11 @@ local function serialize_impl(t, tracking, indent, opts)
result = open
local seen_keys = {}
for k, v in ipairs(t) do
for k, v in inext, t do
seen_keys[k] = true
result = result .. sub_indent .. serialize_impl(v, tracking, sub_indent, opts) .. comma
end
for k, v in pairs(t) do
for k, v in next, t do
if not seen_keys[k] then
local sEntry
if type(k) == "string" and not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then

View File

@ -127,11 +127,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, nHeight do
tLines[y] = {
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
tLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
end
for i = 0, 15 do
@ -161,7 +157,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local function redrawLine(n)
local tLine = tLines[n]
parent.setCursorPos(nX, nY + n - 1)
parent.blit(tLine.text, tLine.textColor, tLine.backgroundColor)
parent.blit(tLine[1], tLine[2], tLine[3])
end
local function redraw()
@ -184,9 +180,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
-- Modify line
local tLine = tLines[nCursorY]
if nStart == 1 and nEnd == nWidth then
tLine.text = sText
tLine.textColor = sTextColor
tLine.backgroundColor = sBackgroundColor
tLine[1] = sText
tLine[2] = sTextColor
tLine[3] = sBackgroundColor
else
local sClippedText, sClippedTextColor, sClippedBackgroundColor
if nStart < 1 then
@ -206,9 +202,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
sClippedBackgroundColor = sBackgroundColor
end
local sOldText = tLine.text
local sOldTextColor = tLine.textColor
local sOldBackgroundColor = tLine.backgroundColor
local sOldText = tLine[1]
local sOldTextColor = tLine[2]
local sOldBackgroundColor = tLine[3]
local sNewText, sNewTextColor, sNewBackgroundColor
if nStart > 1 then
local nOldEnd = nStart - 1
@ -227,9 +223,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth)
end
tLine.text = sNewText
tLine.textColor = sNewTextColor
tLine.backgroundColor = sNewBackgroundColor
tLine[1] = sNewText
tLine[2] = sNewTextColor
tLine[3] = sNewBackgroundColor
end
-- Redraw line
@ -276,11 +272,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, nHeight do
tLines[y] = {
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
local line = tLines[y]
line[1] = sEmptyText
line[2] = sEmptyTextColor
line[3] = sEmptyBackgroundColor
end
if bVisible then
redraw()
@ -291,14 +286,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
function window.clearLine()
if nCursorY >= 1 and nCursorY <= nHeight then
local sEmptyText = sEmptySpaceLine
local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
tLines[nCursorY] = {
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
local line = tLines[nCursorY]
line[1] = sEmptySpaceLine
line[2] = tEmptyColorLines[nTextColor]
line[3] = tEmptyColorLines[nBackgroundColor]
if bVisible then
redrawLine(nCursorY)
updateCursorColor()
@ -427,11 +418,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
if y >= 1 and y <= nHeight then
tNewLines[newY] = tLines[y]
else
tNewLines[newY] = {
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
tNewLines[newY] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
end
end
tLines = tNewLines
@ -474,7 +461,8 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
error("Line is out of range.", 2)
end
return tLines[y].text, tLines[y].textColor, tLines[y].backgroundColor
local line = tLines[y]
return line[1], line[2], line[3]
end
-- Other functions
@ -570,26 +558,22 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, new_height do
if y > nHeight then
tNewLines[y] = {
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
tNewLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
else
local tOldLine = tLines[y]
if new_width == nWidth then
tNewLines[y] = tOldLine
elseif new_width < nWidth then
tNewLines[y] = {
text = string_sub(tOldLine.text, 1, new_width),
textColor = string_sub(tOldLine.textColor, 1, new_width),
backgroundColor = string_sub(tOldLine.backgroundColor, 1, new_width),
string_sub(tOldLine[1], 1, new_width),
string_sub(tOldLine[2], 1, new_width),
string_sub(tOldLine[3], 1, new_width),
}
else
tNewLines[y] = {
text = tOldLine.text .. string_sub(sEmptyText, nWidth + 1, new_width),
textColor = tOldLine.textColor .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
backgroundColor = tOldLine.backgroundColor .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
tOldLine[1] .. string_sub(sEmptyText, nWidth + 1, new_width),
tOldLine[2] .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
tOldLine[3] .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
}
end
end

View File

@ -1,3 +1,21 @@
# New features in CC: Tweaked 1.101.3
* Improve syntax errors when missing commas in tables, and on trailing commas in parameter lists.
* `speaker` program now reports an error on common unsupported audio formats.
* Small optimisations to the `window` API.
Several bug fixes:
* Fix the REPL syntax reporting crashing on valid parses.
* Ignore metatables in `textutils.serialize`.
* Fix `gps.locate` returning `nan` when receiving a duplicate location (Wojbie).
* Ignore metatables in `textutils.serialize`.
* Fix wireless turtles having an invalid model.
* Fix crash when turtles are exploded by a null explosion.
* Lua REPL no longer accepts `)(` as a valid expression.
* Fix several inconsistencies with `require`/`package.path` in the Lua REPL (Wojbie).
* Fix private several IP address ranges not being blocked by the `$private` rule.
* Improve permission checks in the `/computercraft` command.
# New features in CC: Tweaked 1.101.2
* Error messages in `edit` are now displayed in red on advanced computers.

View File

@ -1,16 +1,19 @@
New features in CC: Tweaked 1.101.2
New features in CC: Tweaked 1.101.3
* Error messages in `edit` are now displayed in red on advanced computers.
* Improvements to the display of errors in the shell and REPL.
* Improve syntax errors when missing commas in tables, and on trailing commas in parameter lists.
* `speaker` program now reports an error on common unsupported audio formats.
* Small optimisations to the `window` API.
Several bug fixes:
* Fix `import.lua` failing to upload a file.
* Fix several issues with sparse Lua tables (Shiranuit).
* Computer upgrades now accept normal computers, rather than uselessly allowing you to upgrade an advanced computer to an advanced computer!
* Correctly clamp speaker volume.
* Fix rednet queueing the wrong message when sending a message to the current computer.
* Fix the Lua VM crashing when a `__len` metamethod yields.
* Trim spaces from filesystem paths.
* Correctly format 12AM/PM with `%I`.
* Fix the REPL syntax reporting crashing on valid parses.
* Ignore metatables in `textutils.serialize`.
* Fix `gps.locate` returning `nan` when receiving a duplicate location (Wojbie).
* Ignore metatables in `textutils.serialize`.
* Fix wireless turtles having an invalid model.
* Fix crash when turtles are exploded by a null explosion.
* Lua REPL no longer accepts `)(` as a valid expression.
* Fix several inconsistencies with `require`/`package.path` in the Lua REPL (Wojbie).
* Fix private several IP address ranges not being blocked by the `$private` rule.
* Improve permission checks in the `/computercraft` command.
Type "help changelog" to see the full version history.

View File

@ -364,6 +364,48 @@ function errors.table_key_equals(start_pos, end_pos)
}
end
--[[- There is a trailing comma in this list of function arguments.
@tparam number token The token id.
@tparam number token_start The start position of the token.
@tparam number token_end The end position of the token.
@tparam number prev The start position of the previous entry.
@treturn table The resulting parse error.
]]
function errors.missing_table_comma(token, token_start, token_end, prev)
expect(1, token, "number")
expect(2, token_start, "number")
expect(3, token_end, "number")
expect(4, prev, "number")
return {
"Unexpected " .. token_names[token] .. " in table.",
annotate(token_start, token_end),
annotate(prev + 1, prev + 1, "Are you missing a comma here?"),
}
end
--[[- There is a trailing comma in this list of function arguments.
@tparam number comma_start The start position of the `,` token.
@tparam number comma_end The end position of the `,` token.
@tparam number paren_start The start position of the `)` token.
@tparam number paren_end The end position of the `)` token.
@treturn table The resulting parse error.
]]
function errors.trailing_call_comma(comma_start, comma_end, paren_start, paren_end)
expect(1, comma_start, "number")
expect(2, comma_end, "number")
expect(3, paren_start, "number")
expect(4, paren_end, "number")
return {
"Unexpected " .. code(")") .. " in function call.",
annotate(paren_start, paren_end),
annotate(comma_start, comma_end, "Tip: Try removing this " .. code(",") .. "."),
}
end
--------------------------------------------------------------------------------
-- Statement parsing errors
--------------------------------------------------------------------------------

View File

@ -17,6 +17,8 @@ local error_printer = require "cc.internal.error_printer"
local error_sentinel = {}
local function make_context(input)
expect(1, input, "string")
local context = {}
local lines = { 1 }
@ -69,8 +71,9 @@ local function parse(input, start_symbol)
expect(2, start_symbol, "number")
local context = make_context(input)
function context.report(msg)
expect(1, msg, "table")
function context.report(msg, ...)
expect(1, msg, "table", "function")
if type(msg) == "function" then msg = msg(...) end
error_printer(context, msg)
error(error_sentinel)
end
@ -106,8 +109,9 @@ local function parse_repl(input)
local context = make_context(input)
local last_error = nil
function context.report(msg)
expect(1, msg, "table")
function context.report(msg, ...)
expect(1, msg, "table", "function")
if type(msg) == "function" then msg = msg(...) end
last_error = msg
error(error_sentinel)
end
@ -120,22 +124,35 @@ local function parse_repl(input)
assert(coroutine.resume(parsers[i], context, coroutine.yield, start_code))
end
-- Run all parsers together in parallel, feeding them one token at a time.
-- Once all parsers have failed, report the last failure (corresponding to
-- the longest parse).
local ok, err = pcall(function()
local parsers_n = #parsers
while true do
local token, start, finish = lexer()
local stop = true
local all_failed = true
for i = 1, parsers_n do
local parser = parsers[i]
if coroutine.status(parser) ~= "dead" then
stop = false
if parser then
local ok, err = coroutine.resume(parser, token, start, finish)
if not ok and err ~= error_sentinel then error(err, 0) end
if ok then
-- This parser accepted our input, succeed immediately.
if coroutine.status(parser) == "dead" then return end
all_failed = false -- Otherwise continue parsing.
elseif err ~= error_sentinel then
-- An internal error occurred: propagate it.
error(err, 0)
else
-- The parser failed, stub it out so we don't try to continue using it.
parsers[i] = false
end
end
end
if stop then error(error_sentinel) end
if all_failed then error(error_sentinel) end
end
end)

View File

@ -92,7 +92,7 @@ local function lex_number(context, str, start)
local contents = sub(str, start, pos - 1)
if not tonumber(contents) then
-- TODO: Separate error for "2..3"?
context.report(errors.malformed_number(start, pos - 1))
context.report(errors.malformed_number, start, pos - 1)
end
return tokens.NUMBER, pos - 1
@ -114,14 +114,14 @@ local function lex_string(context, str, start_pos, quote)
return tokens.STRING, pos
elseif c == "\n" or c == "\r" or c == "" then
-- We don't call newline here, as that's done for the next token.
context.report(errors.unfinished_string(start_pos, pos, quote))
context.report(errors.unfinished_string, start_pos, pos, quote)
return tokens.STRING, pos - 1
elseif c == "\\" then
c = sub(str, pos + 1, pos + 1)
if c == "\n" or c == "\r" then
pos = newline(context, str, pos + 1, c)
elseif c == "" then
context.report(errors.unfinished_string_escape(start_pos, pos, quote))
context.report(errors.unfinished_string_escape, start_pos, pos, quote)
return tokens.STRING, pos
elseif c == "z" then
pos = pos + 2
@ -129,7 +129,7 @@ local function lex_string(context, str, start_pos, quote)
local next_pos, _, c = find(str, "([%S\r\n])", pos)
if not next_pos then
context.report(errors.unfinished_string(start_pos, #str, quote))
context.report(errors.unfinished_string, start_pos, #str, quote)
return tokens.STRING, #str
end
@ -192,7 +192,7 @@ local function lex_long_str(context, str, start, len)
elseif c == "[" then
local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[")
if ok and boundary_pos - pos == len and len == 1 then
context.report(errors.nested_long_str(pos, boundary_pos))
context.report(errors.nested_long_str, pos, boundary_pos)
end
pos = boundary_pos
@ -234,12 +234,12 @@ local function lex_token(context, str, pos)
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - pos)
if end_pos then return tokens.STRING, end_pos end
context.report(errors.unfinished_long_string(pos, boundary_pos, boundary_pos - pos))
context.report(errors.unfinished_long_string, pos, boundary_pos, boundary_pos - pos)
return tokens.ERROR, #str
elseif pos + 1 == boundary_pos then -- Just a "["
return tokens.OSQUARE, pos
else -- Malformed long string, for instance "[="
context.report(errors.malformed_long_string(pos, boundary_pos, boundary_pos - pos))
context.report(errors.malformed_long_string, pos, boundary_pos, boundary_pos - pos)
return tokens.ERROR, boundary_pos
end
@ -256,7 +256,7 @@ local function lex_token(context, str, pos)
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - comment_pos)
if end_pos then return tokens.COMMENT, end_pos end
context.report(errors.unfinished_long_comment(pos, boundary_pos, boundary_pos - comment_pos))
context.report(errors.unfinished_long_comment, pos, boundary_pos, boundary_pos - comment_pos)
return tokens.ERROR, #str
end
end
@ -313,18 +313,18 @@ local function lex_token(context, str, pos)
if end_pos - pos <= 3 then
local contents = sub(str, pos, end_pos)
if contents == "&&" then
context.report(errors.wrong_and(pos, end_pos))
context.report(errors.wrong_and, pos, end_pos)
return tokens.AND, end_pos
elseif contents == "||" then
context.report(errors.wrong_or(pos, end_pos))
context.report(errors.wrong_or, pos, end_pos)
return tokens.OR, end_pos
elseif contents == "!=" or contents == "<>" then
context.report(errors.wrong_ne(pos, end_pos))
context.report(errors.wrong_ne, pos, end_pos)
return tokens.NE, end_pos
end
end
context.report(errors.unexpected_character(pos))
context.report(errors.unexpected_character, pos)
return tokens.ERROR, end_pos
end
end

File diff suppressed because one or more lines are too long

View File

@ -107,7 +107,9 @@ local function set_status(text, ok)
status_text = text
end
if not bReadOnly and fs.getFreeSpace(sPath) < 1024 then
if bReadOnly then
set_status("File is read only", false)
elseif fs.getFreeSpace(sPath) < 1024 then
set_status("Disk is low on space", false)
else
local message

View File

@ -26,6 +26,12 @@ local function pcm_decoder(chunk)
return buffer
end
local function report_invalid_format(format)
printError(("speaker cannot play %s files."):format(format))
local pp = require "cc.pretty"
pp.print("Run '" .. pp.text("help speaker", colours.lightGrey) .. "' for information on supported formats.")
end
local cmd = ...
if cmd == "stop" then
@ -93,6 +99,11 @@ elseif cmd == "play" then
handle.read(4)
start = nil
-- Detect several other common audio files.
elseif start == "OggS" then return report_invalid_format("Ogg")
elseif start == "fLaC" then return report_invalid_format("FLAC")
elseif start:sub(1, 3) == "ID3" then return report_invalid_format("MP3")
elseif start == "<!DO" --[[<!DOCTYPE]] then return report_invalid_format("HTML")
end
print("Playing " .. file)

View File

@ -21,21 +21,13 @@ local tEnv = {
}
setmetatable(tEnv, { __index = _ENV })
-- Replace our package.path, so that it loads from the current directory, rather
-- than from /rom/programs. This makes it a little more friendly to use and
-- closer to what you'd expect.
-- Replace our require with new instance that loads from the current directory
-- rather than from /rom/programs. This makes it more friendly to use and closer
-- to what you'd expect.
do
local make_package = require "cc.require".make
local dir = shell.dir()
if dir:sub(1, 1) ~= "/" then dir = "/" .. dir end
if dir:sub(-1) ~= "/" then dir = dir .. "/" end
local strip_path = "?;?.lua;?/init.lua;"
local path = package.path
if path:sub(1, #strip_path) == strip_path then
path = path:sub(#strip_path + 1)
end
package.path = dir .. "?;" .. dir .. "?.lua;" .. dir .. "?/init.lua;" .. path
_ENV.require, _ENV.package = make_package(_ENV, dir)
end
if term.isColour() then
@ -78,18 +70,13 @@ while running do
local name, offset = "=lua[" .. chunk_idx .. "]", 0
local force_print = 0
local func, err = load(input, name, "t", tEnv)
local expr_func = load("return _echo(" .. input .. ");", name, "t", tEnv)
if not func then
if expr_func then
func = expr_func
offset = 13
force_print = 1
end
elseif expr_func then
func = expr_func
if load("return " .. input) then
-- We wrap the expression with a call to _echo(...), which prevents tail
-- calls (and thus confusing errors). Note we check this is a valid
-- expression separately, to avoid accepting inputs like `)--` (which are
-- parsed as `_echo()--)`.
func = load("return _echo(" .. input .. "\n)", name, "t", tEnv)
offset = 13
end
@ -99,9 +86,8 @@ while running do
local results = table.pack(exception.try(func))
if results[1] then
local n = 1
while n < results.n or n <= force_print do
local value = results[n + 1]
for i = 2, results.n do
local value = results[i]
local ok, serialised = pcall(pretty.pretty, value, {
function_args = settings.get("lua.function_args"),
function_source = settings.get("lua.function_source"),
@ -111,7 +97,6 @@ while running do
else
print(tostring(value))
end
n = n + 1
end
else
printError(results[2])

View File

@ -34,7 +34,14 @@ public void matchesPort()
@ValueSource( strings = {
"0.0.0.0", "[::]",
"localhost", "127.0.0.1.nip.io", "127.0.0.1", "[::1]",
"172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1"
"172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1",
// Multicast
"224.0.0.1", "ff02::1",
// Cloud metadata providers
"100.100.100.200", // Alibaba
"192.0.0.192", // Oracle
"fd00:ec2::254", // AWS
"169.254.169.254" // AWS, Digital Ocean, GCP, etc..
} )
public void blocksLocalDomains( String domain )
{

View File

@ -49,4 +49,18 @@ describe("cc.internal.syntax", function()
describe_golden("the lexer", "lexer_spec.md", true)
describe_golden("the parser", "parser_spec.md", false)
describe_golden("the parser (all states)", "parser_exhaustive_spec.md", false)
describe("the REPL input parser", function()
it("returns true when accepted by both parsers", function()
helpers.with_window(50, 10, function()
expect(syntax.parse_repl("print(x)")):eq(true)
end)
end)
it("returns true when accepted by one parser", function()
helpers.with_window(50, 10, function()
expect(syntax.parse_repl("x")):eq(true)
end)
end)
end)
end)

View File

@ -45,8 +45,9 @@ local function capture_parser(input, print_tokens, start)
end
local context = make_context(input)
function context.report(message)
expect(3, message, "table")
function context.report(message, ...)
expect(3, message, "table", "function")
if type(message) == "function" then message = message(...) end
for _, msg in ipairs(message) do
if type(msg) == "table" and msg.tag == "annotate" then