mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2024-12-04 15:29:58 +00:00
Merge branch 'mc-1.16.x' into mc-1.18.x
This commit is contained in:
commit
edf372a695
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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" );
|
||||
|
@ -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() );
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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() )
|
||||
|
@ -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;
|
||||
|
@ -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" );
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
--------------------------------------------------------------------------------
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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])
|
||||
|
@ -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 )
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user