1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-10-15 14:07:38 +00:00

Compare commits

...

13 Commits

Author SHA1 Message Date
Jonathan Coates
edf372a695 Merge branch 'mc-1.16.x' into mc-1.18.x 2023-07-07 00:02:42 +01:00
Jonathan Coates
aa89e51639 Bump CC:T to 1.101.3
0/10, would not recommend.
2023-07-06 23:54:44 +01:00
Jonathan Coates
7436447a6e Several command permission fixes
- Attach permission checks to the first argument (so the literal
   command name) rather than the last argument. This fixes commands
   showing up when they shouldn't.

 - HelpingArgumentBuilder now inherits permissions of its leaf nodes.
   This only really impacts the "track" subcommand.

 - Don't autocomplete the computer selector for the "queue" subcommand.
   As everyone has permission for this command, it's possible to find
   all computer ids and labels in the world.

   I'm in mixed minds about this, but don't think this is an exploit -
   computer ids/labels are sent to in-range players so shouldn't be
   considered secret - but worth patching none-the-less.
2023-07-06 23:41:23 +01:00
Jonathan Coates
f629831b12 Tighten up the $private HTTP rule
- Block multicast and the fd00::/8 address ranges.
 - Block several cloud metadata providers which sit outside the
   standard address ranges.
2023-07-06 23:27:17 +01:00
Jonathan Coates
f7fdb6e729 Backport a couple of ROM commits
- Improve REPL's handling of expressions
   (655d5aeca8)

 - Some tiny optimisations to the window API
   (4accda6b8e)

 - Be lazy in reporting errors in the lexer
   (54ab98473f)

 - Update lua.lua require logic.
   (88f0c44152)
2023-07-06 23:15:57 +01:00
Jonathan Coates
db2616d1c0 Don't (metaphorically) explode on null explosions
Closes #1423.
2023-05-03 23:38:12 +01:00
Jonathan Coates
c0f982dc97 Use correct model for the turtle modem
Introduced in 0c3de1087e, so should only
affect 1.16.5 and 1.18.2.

Fixes #1426
2023-05-03 23:34:22 +01:00
Jonathan Coates
2a9f35de5e Backport several ROM/Lua commits
- Fix GPS returning nan on duplicate positions.
 - Distinguish between all parsers passing and failing.
 - Improve several comma related parse errors.
 - Ignore metatables in textutils.serialize.
 - Detect common audio containers in "speaker".

Co-authored-by: Wojbie <Wojbie@gmail.com>
2023-05-03 23:26:44 +01:00
Jonathan Coates
0fce3212a3 Distinguish between all parsers passing and failing
Given an input like f(x), which is both a valid statement and
expression, both parsers would accept the whole input. However, this was
treated the same as both parsers rejecting the input, resulting in a
crash when trying to print the error.

We now return immediately when any parser accepts the input.

Fixes #1354
2023-03-11 22:45:50 +00:00
Jonathan Coates
652f954886 Merge pull request #1352 from MCJack123/lets-encrypt
Add certificate workaround for Let's Encrypt on 1.16
2023-03-04 11:11:33 +00:00
JackMacWindows
6f65bad9af Fixed CI 2023-03-04 02:02:05 -05:00
JackMacWindows
e4dd4dbef0 Added version check to certificate addition 2023-03-02 18:30:54 -05:00
MCJack123
e1dffaa334 Add certificate workaround for Let's Encrypt 2023-02-28 20:52:14 -05:00
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 @@ package dan200.computercraft.core.apis.http.options;
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 @@ interface 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 @@ package dan200.computercraft.shared.command;
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 @@ public final class CommandComputerCraft
.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 enum UserLevel implements Predicate<CommandSourceStack>
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 class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
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 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
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 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
// Chain all of them together!
tail.then( moreArg );
return link( tail );
return buildTail( tail );
};
}
@@ -106,22 +111,18 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>>
@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.builder.LiteralArgumentBuilder;
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 net.minecraft.network.chat.TextComponent;
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 final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
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 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command
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 class BlockTurtle extends BlockComputerBase<TileTurtle> implements Simple
@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 class TurtleModem extends AbstractTurtleUpgrade
}
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 class AddressRuleTest
@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